数据结构-常见排序算法总结

Sort Algorithm(ASC)

[TOC] //怎么生成目录,纠结ing

插入排序

每一趟排序都将待排元素插入到已有序的序列中,但不能保证该元素的插入位置是全局有序的。

Insertion Sort

直接插入排序

  • 最好情况:$O(n)$
  • 最坏情况:$O(n^2)$
  • 平均情况:$O(n^2)$
  • 辅助空间:O(1)
  • 稳定性:稳定
  • 每一趟排序只能保证当前有序,并不能保证全局有序
  1. 核心思想
    数组中的每一个待排元素与已部分有序的元素依次比较,如果待排元素小于当前已有序的元素,则交换两者位置,并继续向前比较。

  2. 程序示例

    public static void insertionSort(int[] arr){
        for (int i=1;i<arr.length;i++){
            for (int j=i-1;j>=0;j--){
                if (arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    直接插入排序采用两层循环:

    • 外层循环用来跟踪每一个待排元素,从1到 n-1,刚开始arr[0]是默认有序的。
    • 内层循环负责当前待排元素与已部分有序的元素进行逐一比较,确定当前待排元素的最终插入位置。
  4. 优化分析*
    在内层循环中,每当逆序(已有序元素大于待排元素时)的情况出现时,就执行一次 swap 操作,交换两元素的位置,这样会导致交换次数较多。可以考虑通过比较确定最后的插入位置,然后将交换操作推迟到最后进行。这样只需要进行一次交换操作,但已有序元素的移动次数是不会改变的。

Shell Sort

希尔排序(缩小增量排序)

  • 步长+直接插入排序
  • 最好情况:$O(n)$
  • 最坏情况:$O(n^2)$
  • 平均情况:$O(n^{1.3})$
  • 辅助空间:O(1)
  • 稳定性:不稳定
  • 每一趟排序只能保证当前有序,并不能保证全局有序
  1. 核心思想
    希尔排序,又称缩小增量排序,使用步长来确定每一次的待排子序列,然后使用直接插入排序对当前待排子序列进行排序。在完成步长为 h 的排序后,通过证明可以保证,对于每一个 i 与 i+h 元素都是有序的。然后缩小步长,再次使用直接插入排序。直到最后步长为1,最后一次序列已经基本有序,最后采用一次直接插入排序后就可以得到最终的有序数组。

  2. 程序示例

    public static void shellSort(int[] arr){
        //第一层循环控制步长
        for (int gap=arr.length/2;gap>0;gap=gap/2){
            //第二层循环控制满足步长的所有子序列
            for (int i=gap;i<arr.length;i++){
                //第三层循环控制当前满足步长的子序列的排序,采用直接插入排序的方式进行排序
                for (int j=i;j<arr.length;j+=gap){
                    if (arr[j]<arr[j-gap]){
                        swap(arr,j,j-gap);
                    }
                }
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    希尔排序采用三层循环:

    • 第一层循环负责确定每一趟的步长,并按一定的规则(这里采用/2折半的规则),循环减小步长到1。
    • 第二层循环针对当前步长,依次确定满足该步长的待排子序列。
    • 第三层循环针对当前确定的待排子序列,采用直接插入排序的方法进行排序。

选择排序

基本思想:比较+交换

Selection Sort

简单选择排序

  • 最好情况:$O(n^2)$
  • 最坏情况:$O(n^2)$
  • 平均情况:$O(n^2)$
  • 辅助空间:O(1)
  • 稳定性:不稳定
  • 每一趟排序保证全局有序
  1. 核心思想
    简单选择排序,是选择排序中的一种,遵循其基本思想:比较+交换。即通过依次比较确定当前趟最小元素,并根据情况与当前待排元素交换。具体做法是在每趟排序中,通过将当前待排元素与与剩余所有元素比较,找到最小的元素并与当前元素交换(如果当前待排元素就是最小,则不用交换)。

  2. 程序示例

    public static void selectionSort(int[] arr){
        for (int i=0;i<arr.length;i++){
            //这里采用 min 来标记最小元素的下标,方便 swap 的时候通过下标进行交换
            int min=i;
            //第二层循环负责将当前元素与剩余所有元素比较,找到这些元素中的最小值,如果该最小值不是当前元素,则在退出循环后将当前元素与该最小元素交换
            //因为每一次寻找当前趟最小元素都是与所有待排元素比较,因此,简单选择排序是全局有序的
            for (int j=i+1;j<arr.length;j++){
                if (arr[j]<arr[min]){
                    min=j;
                }
            }
            if (min!=i){
                swap(arr,i,min);
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    简单选择排序,采用两层循环:

    • 外层循环用于控制当前待排元素及其待排位置。
    • 内存循环用于基于当前待排位置和待排元素,循环依次与序列中剩余其它元素比较,如果存在元素小于当前待排元素,则采用一个临时变量始终标记符合该条件的元素。在退出内层循环后,判断经过一趟完整比较后,最小元素是否为当前待排元素,如果是,说明当前待排元素是当前趟的最小值,当前位置即全局有序位置,不交换;如果不是,则采用 swap 交换。

Heap Sort

堆排序

  • 最好情况:$O(nlog_2n)$
  • 最坏情况:$O(nlog_2n)$
  • 平均情况:$O(nlog_2n)$
  • 辅助空间:O(1)
  • 稳定性:不稳定
  • 每一趟排序保证全局有序
  1. 核心思想
    堆排序,先根据父节点与子节点下标关系(i,2i+1,2i+2)将待排序列构造为大顶堆(实际上还是在数组中存储)。然后执行循环操作:每次将顶点元素与最后一个元素swap,交换后,当前最大元素已经位于数组的末尾,因为原来的末尾元素到了顶点位置,有可能破坏堆的性质,则通过堆调整来修正堆性质。循环执行这一组操作,直到堆中只有一个元素的时候,该待排序列已然有序。

  2. 程序示例

    public static void heapSort(int[] arr){
        buildHeap(arr);
        int length=arr.length;
        while (length>0){
            swap(arr,0,length-1);
            length--;
            adjustToHeap(arr,length,0);
        }
    
    }
    
    public static void buildHeap(int[] arr){
        for (int i=0;i<arr.length/2;i++){
            adjustToHeap(arr,arr.length,i);
        }
    }
    
    public static void  adjustToHeap(int[] arr,int length,int index){
        int indexOfMax=index;
        while (true){
            int leftIndex=getLeftChild(index);
            int rightIndex=getRightChild(index);
            if (leftIndex<length&&arr[leftIndex]>arr[indexOfMax]){
                indexOfMax=leftIndex;
            }
            if (rightIndex<length&&arr[rightIndex]>arr[indexOfMax]){
                indexOfMax=rightIndex;
            }
            if (indexOfMax!=index){
                swap(arr,index,indexOfMax);
                index=indexOfMax;
                continue;
            }
            else {
                break;
            }
        }
    }
    
    public static int getLeftChild(int i){
        return 2*i+1;
    }
    
    public static int getRightChild(int i){
        return 2*i+2;
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    在采用堆排序前,需要对待排序列构造大顶堆。具体构造方式,稍后详述

    • 在生成大顶堆后,满足大顶堆的性质,第一个元素 arr[0]是当前0~arr.length-1中的最大值。接下来对该大顶堆进行 N-1 次的交换和调整。
    • 具体做法是,在每次交换和调整中,将堆顶的最大值与堆尾元素互换,这样,该最大值就位于正确顺序位置上。因为互换后有可能破坏大顶堆顺序,因此必须在互换后调用 adjustToHeap 进行堆性质修正。

交换排序

Bubble Sort

冒泡排序

  • 最好情况:$O(n^2)$
  • 最坏情况:$O(n^2)$
  • 平均情况:$O(n^2)$
  • 辅助空间:O(1)
  • 稳定性:稳定
  • 每一趟排序保证全局有序
  1. 核心思想
    冒泡排序,在每一趟排序中,都依次比较左右两个元素,如果不符合大小关系,则交换位置,并继续比较下一个坐标位置。在一趟比较结束后,当前趟最大的元素会沉底。

  2. 程序示例

    public static void bubbleSort(int[] arr){
        //对于 N 个元素的序列,需要 N-1 趟排序
        for (int i=1;i<arr.length;i++){
            //在每一趟排序后,下标为 arr.length-i的元素已经位于全局有序位置,因此下一趟比较的最后待排位置为 arr.length-i-1
            //每一趟比较中,当前下标的左右两个元素如果顺序不当,则互相交换,较大的向后调整,如果满足大小要求,则不交换。这样依次对剩余元素进行比较,直到 arr.length-i
            for (int j=0;j<arr.length-i;j++){
                if (arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    冒泡排序采用两层循环,对于 N 个元素的冒泡排序,需要进行 N-1 趟排序,第 i 趟排序需要比较 N-i 次。

    • 第一层循环控制冒泡排序的趟数,从1到 N-1趟冒泡排序。
    • 第二层循环控制每一趟的冒泡排序,因为经过第 i-1 趟的排序,第 N-i+1 个位置已经有序,所以在第 i 趟的冒泡排序中,只需要从第一个元素一直比较到第 N-i 个元素。
  4. 优化分析
    针对最好情况为$O(n)$的情形,可以在外层循环中设置一个变量用来检查上一趟是否存在交换操作,在内层循环中,发生 swap 操作则置该变量为 true,否则默认为 false。在退出内层循环后,外层逻辑会判断如果变量为 true 说明上一趟待排序列不满足顺序条件,继续本趟排序;如果为 false 说明在上一趟中待排序列所有元素已经有序。因此在本趟排序可以直接 break,已经提前完成了序列排序。

Quick Sort

快速排序

  • 最好情况:$O(nlog_2n)$
  • 最坏情况:$O(n^2)$
  • 平均情况:$O(nlog_2n)$
  • 辅助空间:$O(nlog_2n)$
  • 稳定性:不稳定
  • 每一趟排序保证轴 key 全局有序
  1. 核心思想
    循环比较交换+分而治之的思想。在每一趟的排序操作中,当 i不等于j 的时候,将大于 key 的元素都交换在 key 的右边,小于 key 的元素都交换在 key 的左边。当 i等于j 的时候,就是 key 的最终有序位置。但是经过一趟排序操作,仅保证了当前 key 位于正确有序的位置 i,但对于start~i-1和 i+1~end 是无序的,因此需要在这两段上递归调用刚刚的排序操作。

  2. 程序示例

    public static void quickSort(int[] arr,int start,int end){
        if (start<end){
            int i=start;
            int j=end;
            int key=arr[start];
            while (i!=j){
                //从后向前循环找到第一个小于 key 的元素
                while (i<j&&arr[j]>=key){
                    j--;
                }
                //将该元素替换到 key 的左边
                if (i<j){
                    arr[i]=arr[j];
                    i++;
                }
                //从前向后循环找第一个大于 key 的元素
                while (i<j&&arr[i]<key){
                    i++;
                }
                //将该元素替换到 key 的右边
                if (i<j){
                    arr[j]=arr[i];
                    j--;
                }
            }
            //当 i=j 的时候,说明在这一趟交换过后,所有大于 key 的元素都位于 key 的右边,所有小于 key 的元素都位于 key 的左边
            //而此时 i==j 即表示 key 应该插入的位置,即原先的 arr[start]元素的最终位置
            arr[i]=key;
            quickSort(arr,start,i-1);
            quickSort(arr,i+1,end);
        }
    }
    
  3. 程序分析
    快速排序在每一趟排序中,先选定一个轴,通过交换来确定轴的最终位置,在一趟排序后,大于或小于该轴的元素会位于该轴的两侧。

    • 外层循环用来控制这趟排序是否结束,当 i 等于 j 的时候,说明待排序列的比较已经结束,该位置即为轴 key 的最终有序位置。
    • 在第一个内层循环中,先从后向前循环查找第一个小于 key 的元素,如果存在则退出循环并将该元素交换至 key 的 i 位置,然后再进入第二个内层循环。如果没有存在一个小于 key 的元素,说明在从后向前的查找中,key 后面的元素都大于 key,因此,说明 key 就是在正确的有序位置。
    • 在第二个内层循环中,从前向后循环查找第一个大于 key 的元素,如果存在则退出循环并交换至当前 j 位置,然后退出该循环,判断 i 是否等于 j,不等于则说明该趟还没比较结束,继续从第一个内层循环开始,继续排序操作。直至 i 等于 j。
  4. 优化分析
    快速排序对于越无序的序列效果越好,但对于基本有序序列,在选定 key 后,有可能剩余的 N-1个元素均大于或小于 key,导致分而治之的效果不明显,没有明显的均分待排序列,使得效率接近于插入排序$O(n^2)$的效果。改善这样的情况,可以尝试采用随机 key 的方式,每一趟排序,均在当前 start~end 中随机选取 key 作为轴,而不是始终设定为 arr[start]为轴,尽可能通过随机化减少有序影响,实现或接近分治效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容