七大排序算法总结

题记:

  直接插入排序(稳定)-->希尔排序  : 属于插入排序

  简单选择排序(稳定)-->堆排序 :属于选择排序

  冒泡排序算法(稳定)-->快速排序  :属于交换排序

   归并排序(稳定)


一.直接插入排序----O(n^2)-----稳定的

        直接插入排序:是一种将一个记录插入到已经安排好序的有序表中,从而得到一个新的记录数增1的有序表。

1.算法流程:

1)初始时, a[0]自成一个有序区, 无序区为a[1, ... , n-1], 令i=1;

2)将a[i]并入当前的有序区a[0, ... , i-1];

3)i++并重复2)直到i=n-1, 排序完成。

时间复杂度:O(n^2)。

示意图:初始无序数列为 49, 38, 65, 97, 76, 13, 27,49

2. C++实现

void InsertSort(SqList *L){

          int i, j;

          for( i=2;i<L->length;i++)  {

               if(L->r[i]<L->r[i-1) {

                   L->r[0]=L->r[i];    //L->r[0]是哨兵位置

                   for(j=i-1;L->r[j]>L->r[0];j--)

                                 L->r[j+1]=L->r[j];

                   L->r[j+1]=L->r[0];

                }

          }

}

//插入排序,版本3:用数据交换代替版本2的数据后移(比较对象只考虑两个元素)

void StraightInsertionSort3(int a[],int n)

{

        for(inti=1; i<n; i++)

               for(int j=i-1; i=0 && a[j]>a[j+1] ;  j--)

                          Swap(a[j], a[j+1]);

}

二、冒泡排序算法----O(n^2)---稳定的

          冒泡排序:是一种交换排序,它基本思想是,两两比较相邻的关键字,如果反序则交换,直到没有反序的记录为止。

算法流程:

1)比较相邻的两个元素,如果前面的数据大于后面的数据,就将两个数据进行交换;这样对数组第0个元素到第n-1个元素进行一次遍历后,最大的一个元素就沉到数组的第n-1个位置;

2)重复第2)操作,直到i=n-1。

时间复杂度分析:O(n^2),冒泡排序是一种不稳定排序算法。

冒泡排序的示例:

2. C++实现

void BubbleSort(int p[], int len){

    int temp;

    for (int i = 0; i < len; i++)

        for (int j = i + 1; j < len; j++){

            if (p[i]>p[j]){

                 temp = p[i];

                p[i] = p[j];

               p[j] = temp;

              }

       }

}

三、简单选择排序-----O(n^2)-----稳定

        简单选择排序算法:就是通过n-i次关键字间的比较,从n-i+1个记录中选择出关键字最小的记录,并和第i(1<=i<=n)个记录作交换。

算法流程:

1)初始时,数组全为无序区a[0, ... , n-1], 令i=0;

2)在无序区a[i, ... , n-1]中选取一个最小的元素与a[i]交换,交换之后a[0, ... , i]即为有序区;

3)重复2),直到i=n-1,排序完成。

时间复杂度分析O(n^2)。

直接选择排序的示例:

2. C++实现

void SelectSort(int *p, int len)

{

         int minIndex, i, j;

         for (i = 0; i < len; i++)

             {

                   minIndex = i;//记录最小数值的下标

                   for (j = i + 1; j < len; j++)

                   {

                          if (p[minIndex]>p[j])

                                   minIndex = j;

                     }          

                 if (minIndex != i)

                   {

                               int temp;

                                temp = p[i];

                                p[i] = p[minIndex];

                                 p[minIndex] = temp;

                      }

          }

}

四、希尔排序算法------O(nlogn)~O(n^2)----不稳定

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

算法流程:

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

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

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

时间复杂度:O(n^(1+e))(其中0

希尔排序的示例:

2. C++实现

//希尔排序

void shellSort(int *p, int len){

         int i, j, increment, temp;

         for (increment = len / 2; increment > 0; increment /= 2){

                 for (i = increment; i < len; i++){

                           if (p[i] < p[i - increment]){

                                  temp = p[i];

                                  for (j = i - increment; j >= 0 && p[j] > temp; j -= increment)

                                        p[j + increment] = p[j];

                                p[j + increment] = temp;

                             }

                 }

       }

}

五、堆排序-----O(nlogn)-----不稳定

              堆排序是一种树形选择排序,是对直接选择排序的有效改进。

1.堆排序是利用堆(假设利用大顶堆)进行排序的方法;基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将它与堆数组的末尾元素交换,此时末尾元素就是最大值)。然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值,如此反复执行,便能得到一个有序序列了。


2. C++实现

//堆排序

void HeapSort(int a[], int n){

//初始化堆,从最后一个有孩子节点的位置开始调整,最后一个有孩子节点的位置为(n-1)/2或n/2

    for (int i = n / 2; i >= 0; i--)

            HeapAdjusting(a, i, n);

    for (int i = n - 1; i>0; i--)//从最后一个节点开始进行调整

    {

              swap(a[0], a[i]); //每次交换后都要进行调整,交换堆顶元素和最后一个元素

              HeapAdjusting(a, 0, i);

        }

}

void HeapAdjusting(int a[], int root, int len)   //大顶堆

{

int temp, child;

temp = a[root];

for (child = 2 * root + 1; child < len; child *= 2)

{

            if (child + 1 < len&&a[child] < a[child + 1])

                   child++;

            if (temp >= a[child])

                      break;

              a[root] = a[child];

              root = child;

}

a[root] = temp;

}


小顶堆

我们先看一个大数据top-K示例:

例子:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

首先,我们知道这是一个典型的top-K问题。

针对大数据问题进行统计首先应该想到的就是Hash_map。所以第一步就是先遍历全部的1千万Query,构建出一个大小为3百万的Hash_map,其中的key值为某条Query,对应的value值为该条Query的查询次数。

建好Hash_map以后,我们接下来的问题就是如何在3百万的Query中找出10个最热门的Query,也就是要用到排序算法。排序算法中效率最高的时间复杂度为O(n*log(n)),

这是最简单粗暴的方法,也是最直接的方法。或者我们进一步优化,该题目是要求寻找top-K问题,那么我们可以直接去前K个Query构建一个数组,然后

对其进行排序。遍历剩余的全部Query,如果某条Query的查询次数大于数组中最小的一个,将数组中最小的Query剔除,加入这条新的Query。

接着调整数组顺序,依次进行遍历,这样的最坏情况下的复杂度为O(n*K)。

但是还可以继续优化寻找top-K的操作,那就是借助小根堆来实现。基于以上的分析,我们想想,有没有一种既能快速查找,又能快速移动元素的数据结构呢?回答是肯定的,那就是堆。

具体过程是,堆顶存放的是整个堆中最小的数,现在遍历N个数,把最先遍历到的k个数存放到最小堆中,并假设它们就是我们要找的最大的k个数,X1>X2...Xmin(堆顶),而后遍历后续的(n-K)个数,一一与堆顶元素进行比较,如果遍历到的Xi大于堆顶元素Xmin,则把Xi放入堆中,而后更新整个堆,更新的时间复杂度为logK,如果Xi

一个有关小根堆解决top-K问题的小动画,请点击这个链接

思想与上述算法二一致,只是算法在算法三,我们采用了最小堆这种数据结构代替数组,把查找目标元素的时间复杂度有O(K)降到了O(logK)。那么这样,采用堆数据结构,算法三,最终的时间复杂度就降到了O(n*logK),和算法二相比,又有了比较大的改进。

六、归并排序------O(nlogn)-------稳定


1. 归并排序:是将两个(或者两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列都是有序的。然后再把有序子序列合并为整体有序序列。


归并排序示例

2. C++实现代码

//merge两个有序数列为一个有序数列

void MergeArr(int a[], int first, int mid, int last, int temp[])

{

int i = first, j = mid + 1;

int m = mid, n = last;

int k = 0;

//通过比较,归并数列a和b

while (i <= m && j <= n)

{

         if(a[i]<a[j])

                  temp[k++]=a[i++];

          else

                  temp[k++]=a[j++];

 }

//将数列a或者b剩余的元素直接插入到新数列后边

while (i <= m)

           temp[k++] = a[i++];

while (j <= n)

         temp[k++] = a[j++];

  for (i = 0; i<k;i++)

           a[first+i]=temp[i];

}

//归并排序

void MSort(int a[], int first, int last, int temp[])

{

          if (first<last)

          {

                     int mid=(first+last)/2;

                    MSort(a, first, mid, temp);

                    MSort(a, mid + 1, last, temp);

                     MergeArr(a, first, mid, last, temp);

          }

void MergeSort(int a[], int len)//为了调用方便封装了一个函数接口

{

       int *temp = new int[len];

      MSort(a, 0, len - 1, temp);

      delete temp;

}


归并排序


归并排序算法
合并两个有序序列


主函数调用

七、快速排序------O(nlogn)-------不稳定

              1. 快速排序:是通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。


基本思想

2.C++实现代码

//快速排序

void QSort(int a[], int L, int R)

{

         if (L<R)

        {

                   int low = L, high = R, pivotkey = a[low];// pivotkey:枢轴

                  while (low<high)

                 {//从右向左找小于基准值a[i]的元素

                              while (low<high&& a[high]>=pivotkey)

                                                     high--;

                                if(low<high)

                                       a[low++] = a[high];

                              //从左向右找大于基准值a[i]的元素

                              while (low<high&& a[high]<pivotkey)

                                           low++;

                             if(low<high)

                                          a[high--] = a[low];

                 }

                 //将基准值填入最后的坑中

                  a[low] = pivotkey;

                 //递归调用,分治法的思想

                 QSort(a, L, low - 1);

                QSort(a, low + 1, R);

         }

}

void QuickSort(int a[], int len)  //封装函数接口

{

               QSort(a, 0, len - 1);

}


快速排序



主函数调用


后记:

1.

(1)当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);

(2)而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n^2);

(3)原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。

2.

稳定性:排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对次序发生了改变,则称该算法是不稳定的。

稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另

一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会

改变的。另外,如果排序算法稳定,可以避免多余的比较。

稳定的排序算法:冒泡排序、插入排序、归并排序、选择排序。

不是稳定的排序算法:快速排序、希尔排序、堆排序。

3.

选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据:

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

(1)待排序的记录数目n的大小;

(2)记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

(3)关键字的结构及其分布情况;

(4)对排序稳定性的要求。

4.

设待排序元素的个数为n.

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

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

堆排序:如果内存空间允许且要求稳定性的;

归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

(2)当n较大,内存空间允许,且要求稳定性:归并排序

(3)当n较小,可采用直接插入或直接选择排序。

直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

直接选择排序:当元素分布有序,如果不要求稳定性,选择直接选择排序。

(4)一般不使用或不直接使用传统的冒泡排序。


                                                                                                                        Pitfall

                                                                                                                        2016.6

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

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,657评论 0 33
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,704评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,101评论 0 52
  • 今晚看了一部纪录片《迪奥与我》,讲述的是迪奥的创意总监Raf Smisons加入迪奥之后第一次准备时装秀的过程。讲...
    行壹1阅读 221评论 0 1
  • 入职第5天,第二周,周一。早上6点起,赶上6:30的832公车,到达黄石东7点。走一站进入广外,7:20广外发车。...
    梦海蓉阅读 90评论 0 0