zoukankan      html  css  js  c++  java
  • 8大排序算法图文讲解

    排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

    我们这里说说八大排序就是内部排序。

        

        当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

       快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

    算法一:插入排序

    插入排序示意图

    插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

    算法步骤:

    1)将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

    2)从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

    代码实现:

     1 public class InsertSort {
     2     
     3     public static void sort(int[] num){
     4         int i,j,min,temp;
     5         for(i=0;i<num.length;i++){
     6             min=i;//将当前下标定义为最小值下标
     7             for(j=i+1;j<num.length;j++){
     8                 if (num[min]>num[j]) {
     9                     min=j;//如果有小于当前最小值的关键字,将此关键字的下标赋值给min
    10                 }
    11             }
    12             if (i!=min) {//若min不等于i,说明上面相互比较的为true,即有最小值,交换
    13                 temp=num[i];
    14                 num[i]=num[min];
    15                 num[min]=temp;
    16             }
    17         }
    18         for (int k : num) {
    19             System.out.println(k);
    20         }
    21         
    22     }
    23     
    24     
    25     public static void main(String[] args) {
    26         // TODO 自动生成的方法存根
    27         int[] num={5,2,4,6,8,9,7,1,3,0};
    28         sort(num);
    29     }
    30 }

     算法二:希尔排序

    希尔排序示意图

     

    给定实例的shell排序的排序过程
    假设待排序文件有10个记录,其关键字分别是:
    49,38,65,97,76,13,27,49,55,04。
    增量序列的取值依次为:
    5,3,1

    希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

    希尔排序是基于插入排序的以下两点性质而提出改进方法的:

    • 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
    • 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位

    希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

    算法步骤:

    1)选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

    2)按增量序列个数k,对序列进行k 趟排序;

    3)每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

    代码实现:

     1 public class ShellSort {
     2     //希尔排序
     3     public static void sort(int[] a) {
     4         // 希尔排序
     5         int d = a.length;
     6         while (true) {
     7             d = d / 2;
     8             for (int x = 0; x < d; x++) {
     9                 for (int i = x + d; i < a.length; i = i + d) {
    10                     int temp = a[i];
    11                     int j;
    12                     for (j = i - d; j >= 0 && a[j] > temp; j = j - d) {
    13                         a[j + d] = a[j];
    14                     }
    15                     a[j + d] = temp;
    16                 }
    17             }
    18             if (d == 1) {
    19                 break;
    20             }
    21         }
    22         
    23         for (int k : a) {
    24             System.out.println(k);
    25         }
    26     }
    27 
    28     public static void main(String[] args) {
    29         // TODO Auto-generated method stub
    30         int[] a = { 5, 2, 4, 6, 8, 9, 7, 1, 3, 0 };
    31         // int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1};
    32         sort(a);
    33     }
    34 
    35 }

    算法三:选择排序

    选择排序示意图

    选择排序(Selection sort)也是一种简单直观的排序算法。

    算法步骤:

    1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

    2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

    3)重复第二步,直到所有元素均排序完毕。

    代码实现:

     1 /**
     2  * 选择一个min做基准和其他的数据相互比较,如果比较的数大则把当前的数的赋值给min
     3  * 以此类推
     4  * @author Administrator
     5  *
     6  */
     7 public class SelectSort {
     8     //简单选择排序,选择一个min做基准和其他的数据相互比较
     9     public static void sort(int[] num){
    10         int i,j,min,temp;
    11         for(i=0;i<num.length;i++){
    12             min=i;//将当前下标定义为最小值下标
    13             for(j=i+1;j<num.length;j++){
    14                 if (num[min]>num[j]) {
    15                     min=j;//如果有小于当前最小值的关键字,将此关键字的下标赋值给min
    16                 }
    17             }
    18             if (i!=min) {//若min不等于i,说明上面相互比较的为true,即有最小值,交换
    19                 temp=num[i];
    20                 num[i]=num[min];
    21                 num[min]=temp;
    22             }
    23         }
    24         for (int k : num) {
    25             System.out.println(k);
    26         }
    27         
    28     }
    29     
    30     public static void main(String args[]){
    31         int[] num={5,2,4,6,8,9,7,1,3,0};
    32         sort(num);
    33 
    34     }
    35 }

     算法四:冒泡排序

    冒泡排序示意图

     冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

    算法步骤:

    1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。

    2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

    3)针对所有的元素重复以上的步骤,除了最后一个。

    4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

    代码实现:

     1 /**
     2  * 相邻数据两两比较,大的排上面,小的排下面 第一次可排出最小的值
     3  * 第二次排出第二小的值
     4  * 第三次排出第三小的值
     5  * 以此类推排出顺序
     6  * @author Administrator
     7  *
     8  */
     9 public class BubbleSort {
    10     
    11     //初级版
    12     public static void sort1(int[] num){
    13         int i,j,temp;
    14         for(i=0;i<num.length;i++){
    15             for(j=i+1;j<num.length;j++){
    16                 if (num[i]>num[j]) {
    17                     temp=num[i];
    18                     num[i]=num[j];
    19                     num[j]=temp;
    20                 }
    21             }
    22         }
    23         for (int k : num) {
    24             System.out.println(k);
    25         }
    26         
    27     }
    28     //中级版
    29     public static void sort2(int[] num){
    30         int i,j,temp;
    31         for(i=0;i<num.length;i++){
    32             for(j=num.length-1;j>i;j--){
    33                 if (num[j-1]>num[j]) {
    34                     temp=num[j-1];
    35                     num[j-1]=num[j];
    36                     num[j]=temp;
    37                 }
    38             }
    39         }
    40         for (int k : num) {
    41             System.out.println(k);
    42         }
    43         
    44     }
    45     //终极版
    46     public static void sort3(int[] num){
    47         int i,j,temp;
    48         boolean flag=true;
    49         for(i=0;i<num.length&&flag;i++){
    50             flag=false;
    51             for(j=num.length-1;j>i;j--){
    52                 if (num[j-1]>num[j]) {
    53                     temp=num[j-1];
    54                     num[j-1]=num[j];
    55                     num[j]=temp;
    56                     flag=true;
    57                 }
    58             }
    59         }
    60         for (int k : num) {
    61             System.out.println(k);
    62         }
    63         
    64     }
    65     
    66     
    67     public static void main(String args[]){
    68         int[] num={5,2,4,6,8,9,7,1,3,0};
    69 //        sort1(num);
    70 //        sort2(num);
    71         sort3(num);
    72     }
    73     
    74 }

     算法五:归并排序

    归并排序示意图

    归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

    分而治之

       可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

    合并相邻有序子序列

      再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

    代码实现

    复制代码
    package sortdemo;
    
    import java.util.Arrays;
    
    /**
     * Created by chengxiao on 2016/12/8.
     */
    public class MergeSort {
        public static void main(String []args){
            int []arr = {9,8,7,6,5,4,3,2,1};
            sort(arr);
            System.out.println(Arrays.toString(arr));
        }
        public static void sort(int []arr){
            int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
            sort(arr,0,arr.length-1,temp);
        }
        private static void sort(int[] arr,int left,int right,int []temp){
            if(left<right){
                int mid = (left+right)/2;
                sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
                sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
                merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
            }
        }
        private static void merge(int[] arr,int left,int mid,int right,int[] temp){
            int i = left;//左序列指针
            int j = mid+1;//右序列指针
            int t = 0;//临时数组指针
            while (i<=mid && j<=right){
                if(arr[i]<=arr[j]){
                    temp[t++] = arr[i++];
                }else {
                    temp[t++] = arr[j++];
                }
            }
            while(i<=mid){//将左边剩余元素填充进temp中
                temp[t++] = arr[i++];
            }
            while(j<=right){//将右序列剩余元素填充进temp中
                temp[t++] = arr[j++];
            }
            t = 0;
            //将temp中的元素全部拷贝到原数组中
            while(left <= right){
                arr[left++] = temp[t++];
            }
        }
    }
    复制代码

    执行结果

    [1, 2, 3, 4, 5, 6, 7, 8, 9]

    最后

      归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

     算法六:快速排序

    假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法呢?那就是“快速排序”啦!光听这个名字是不是就觉得很高端呢。
     
            假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥的了)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列。
           3  1  2 5  4  6  9 7  10  8
     
            在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以做到这点吗?
     
            给你一个提示吧。请回忆一下冒泡排序,是如何通过“交换”,一步步让每个数归位的。此时你也可以通过“交换”的方法来达到目的。具体是如何一步步交换呢?怎样交换才既方便又节省时间呢?先别急着往下看,拿出笔来,在纸上画画看。我高中时第一次学习冒泡排序算法的时候,就觉得冒泡排序很浪费时间,每次都只能对相邻的两个数进行比较,这显然太不合理了。于是我就想了一个办法,后来才知道原来这就是“快速排序”,请允许我小小的自恋一下(^o^)。
     

            方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

     
           首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
     
     
     

           现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

            6  1  2  5  9 3  4  7  10  8
     
     
     
            到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
            6  1  2 5  4  3  9  7 10  8
     
            第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。
            3  1 2  5  4  6  9 7  10  8
     
     
            到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。
     
            OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3  1 2  5  4”,右边的序列是“9  7  10  8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。
     
            左边的序列是“3  1  2 5  4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧。
     
            如果你模拟的没有错,调整完毕之后的序列的顺序应该是。
            2  1  3  5  4
     
            OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下。
            1  2  3 4  5  6 9  7  10  8
     
            对于序列“9  7  10  8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下。
            1  2  3 4  5  6  7  8 9  10
     
            到此,排序完全结束。细心的同学可能已经发现,快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。下面上个霸气的图来描述下整个算法的处理过程。
     
     
            快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。
    代码实现:
     1 public class QuickSort {
     2 
     3     public static void quickSort(int arr[], int _left, int _right) {
     4         int left = _left;
     5         int right = _right;
     6         int temp = 0;
     7         if (left <= right) { // 待排序的元素至少有两个的情况
     8             temp = arr[left]; // 待排序的第一个元素作为基准元素
     9             while (left != right) { // 从左右两边交替扫描,直到left = right
    10 
    11                 while (right > left && arr[right] >= temp)
    12                     right--; // 从右往左扫描,找到第一个比基准元素小的元素
    13                 arr[left] = arr[right]; // 找到这种元素arr[right]后与arr[left]交换
    14 
    15                 while (left < right && arr[left] <= temp)
    16                     left++; // 从左往右扫描,找到第一个比基准元素大的元素
    17                 arr[right] = arr[left]; // 找到这种元素arr[left]后,与arr[right]交换
    18 
    19             }
    20             arr[right] = temp; // 基准元素归位
    21             quickSort(arr, _left, left - 1); // 对基准元素左边的元素进行递归排序
    22             quickSort(arr, right + 1, _right); // 对基准元素右边的进行递归排序
    23         }
    24     }
    25 
    26     public static void main(String[] args) {
    27         int array[] = { 10, 5, 3, 1, 7, 2, 8 };
    28         System.out.println("排序之前:");
    29         for (int element : array) {
    30             System.out.print(element + " ");
    31         }
    32 
    33         quickSort(array, 0, array.length - 1);
    34 
    35         System.out.println("
    排序之后:");
    36         for (int element : array) {
    37             System.out.print(element + " ");
    38         }
    39 
    40     }
    41 
    42 }
  • 相关阅读:
    C# 反射
    WPF之布局
    java网络编程中的BIO,NIO,AIO
    BIO的阻塞
    AttributeView的用法
    将linux文件中的tab更换为空格的三种方法
    安装nginx-ingress控制器,使用
    docker 搭建jenkins
    Manjaro 18.0.1 系统安装后值得看的两篇博客
    github创建自己的项目并进行推送
  • 原文地址:https://www.cnblogs.com/linjiaxin/p/7615196.html
Copyright © 2011-2022 走看看