本来网上关于排序算法的资料已经一大堆了,随便一搜就有了,而且有一些介绍得还是很不错的,也没什么必要再写一篇博客来介绍。但是,虽然为了自己更好地梳理这些知识,我还是自己把过程走一遍,希望对于读者也能有所帮助吧。当然了,参考资料来源有很多,如果有重复的地方,并非有意剽窃,请谅解。
排序在算法导论里面是很重要的一部分,在很多大公司的面试题中也经常出现。无论什么算法,排序的关键步骤就是比较还有交换数据位置两步。对于初学编程的人而言,不局限于c或者c++,最早掌握的排序算法应该是冒泡排序吧!一般而言,如果学点编程用于搞51单片机或者嵌入式等小工程的话,其实冒泡法也勉强可以用,因为思路是很简单的。但是思路简单,不一定是很多人可以一次性就写出完全正确的代码,特别是在边界问题处理上。所以,看了很多算法书之后,表面上懂了很多,实际上,一动手才发现自己并没有完全掌握,这也是我为什么写这篇博文的主要原因了。下面进入正题,详细介绍几种排序算法。先列出几种排序算法的复杂度吧!
插入排序 O(n^2)
冒泡排序 O(n^2)
选择排序 O(n^2)
快速排序 O(n log n)
堆排序 O(n log n)
归并排序 O(n log n)
基数排序 O(n)
希尔排序 O(n^1.25)
------------------------------------------------------------------------------------------------------
一、冒泡排序(Bubbler Sort)
何谓冒泡法,先看一下右边的图,水泡从下往上冒在破裂之前是逐渐变大的,物理原理也很简单,就是上面水压比下面低。所以,冒泡排序法就是在排序的时候通过比较相邻的大小,尽量把大的数安排在最上面(有时候是右边,看具体情况)。
现在考虑一个数列(3 1 4 1 5 9 2 6 5 4),将其从左往右递增从小到大排序。步骤很简单。
算法步骤:
过程演示:
position: 0 1 2 3 4 5 6 7 comparing 7 and 6 95 45 15 78 84 51 24 12 -------------------------------------------------- comparing 6 and 5 95 45 15 78 84 51 12 24 -------------------------------------------------- comparing 5 and 4 95 45 15 78 84 12 51 24 -------------------------------------------------- comparing 4 and 3 95 45 15 78 12 84 51 24 -------------------------------------------------- comparing 3 and 2 95 45 15 12 78 84 51 24 -------------------------------------------------- comparing 2 and 1 95 45 12 15 78 84 51 24 -------------------------------------------------- comparing 1 and 0 95 12 45 15 78 84 51 24 第二轮不需要再对这两个位置的数进行比较了。因为位置0一定放了最小的数 -------------------------------------------------- comparing 7 and 6 12 95 45 15 78 84 51 24 -------------------------------------------------- comparing 6 and 5 12 95 45 15 78 84 24 51 -------------------------------------------------- comparing 5 and 4 12 95 45 15 78 24 84 51 -------------------------------------------------- comparing 4 and 3 12 95 45 15 24 78 84 51 -------------------------------------------------- comparing 3 and 2 12 95 45 15 24 78 84 51 -------------------------------------------------- comparing 2 and 1 12 95 15 45 24 78 84 51 第三轮不需要再对这两个位置的数进行比较了。 -------------------------------------------------- comparing 7 and 6 12 15 95 45 24 78 84 51 -------------------------------------------------- comparing 6 and 5 12 15 95 45 24 78 51 84 -------------------------------------------------- comparing 5 and 4 12 15 95 45 24 51 78 84 -------------------------------------------------- comparing 4 and 3 12 15 95 45 24 51 78 84 -------------------------------------------------- comparing 3 and 2 12 15 95 24 45 51 78 84 第四轮不需要再对这两个位置的数进行比较了 -------------------------------------------------- comparing 7 and 6 12 15 24 95 45 51 78 84 -------------------------------------------------- comparing 6 and 5 12 15 24 95 45 51 78 84 -------------------------------------------------- comparing 5 and 4 12 15 24 95 45 51 78 84 -------------------------------------------------- comparing 4 and 3 12 15 24 95 45 51 78 84 第五轮不需要再对这两个位置的数进行比较了 -------------------------------------------------- comparing 7 and 6 12 15 24 45 95 51 78 84 -------------------------------------------------- comparing 6 and 5 12 15 24 45 95 51 78 84 -------------------------------------------------- comparing 5 and 4 12 15 24 45 95 51 78 84 第六轮不需要再对这两个位置的数进行比较了 -------------------------------------------------- comparing 7 and 6 12 15 24 45 51 95 78 84 -------------------------------------------------- comparing 6 and 5 12 15 24 45 51 95 78 84 第七轮不需要再对这两个位置的数进行比较了。 -------------------------------------------------- comparing 7 and 6 12 15 24 45 51 78 95 84 结束! -------------------------------------------------- 12 15 24 45 51 78 84 95c语言源代码:
#include <stdio.h> #define LENGTH 8 void main() { int i, j,tmp, number[LENGTH] = {95, 45, 15, 78, 84, 51, 24, 12}; for (i = 0; i < LENGTH; i++) { for (j = LENGTH - 1; j > i; j--) { if (number[j] < number[j-1]) { tmp = number[j-1]; number[j-1] = number[j]; number[j] = tmp; } } } //结果输出 for (i = 0; i < LENGTH; i++) { printf("%d ", number[i]); } printf("\n"); }
------------------------------------------------------------------------------------------------------
二、插入排序(Insertion Sort)
冒泡法对于已经排好序的部分是不再访问的,插入排序却要,因为它的方法就是抽出未排序的部分的第一个元素,然后和前面已经排好序的数进行对比,如果这个数比前面的都大,那就保持住,跳到下一个位置;如果前面有比这个还大的数,就插到这个这个更大的数前面去。这个算法速度会非常之快,但是如果数组本身是逆序的话,速度也就非常之慢了。position: 0 1 2 3 4 5 6 7 start: 95 45 15 78 84 51 24 12 step 1: 45 95 15 78 84 51 24 12 ** check the figure at position 1 step 2: 15 45 95 78 84 51 24 12 ** check the figure at position 2 step 3: 15 78 45 95 84 51 24 12 ** check the figure at position 3 step 4: 15 78 84 45 95 51 24 12 ** check the figure at position 4 step 5: 15 78 84 51 45 95 24 12 ** check the figure at position 5 step 6: 24 15 78 84 51 45 95 12 ** check the figure at position 6 step 7: 12 24 15 78 84 51 45 95 ** check the figure at position 7 end : 12 24 15 78 84 51 45 95c源代码:
#include <stdio.h> #define LENGTH 8 void shift(int number[],int i,int j); void main() { int i, j, number[LENGTH] = {95, 45, 15, 78, 84, 51, 24, 12}; for (i = 1; i < LENGTH; i++) { for (j=1;j<i;j++) { if (number[i]>number[i-j]) break; } //将i位置的数据已到i-j处 shift(number,i-j,i); } //结果输出 for (i = 0; i < LENGTH; i++) { printf("%d ", number[i]); } printf("\n"); } void shift(int number[],int i,int j) { int temp=number[j]; while(j!=i) { number[j]=number[j-1]; j--; } number[i]=temp; }
------------------------------------------------------------------------------------------------------
三、选择排序(Selection Sort)
选择排序就好比打擂台,觉得自己比较能打了,就去和擂台上的去pk一下。比如说有8个数,先在8个数里面选出最大的数,然后腾出最后的位置,把这个数放在那里。接着比较前面七个数,最大的数放在倒数第二个位置,以此类推。思路类似于冒泡排序,但是不同的是选择排序法不需要那么多次移位操作,所以性能是要比冒泡法优秀的。
过程演示:
position : 0 1 2 3 4 5 6 7 start: 95 45 15 78 84 51 24 12 95最大,放后面 step 1 : 45 15 78 84 51 24 12 95 84最大,放后面 step 2 : 45 15 78 51 24 12 84 95 78最大,放后面 step 3 : 45 15 51 24 12 78 84 95 51最大,放后面 step 4 : 45 15 24 12 51 78 84 95 45最大,放后面 step 5 : 15 24 12 45 51 78 84 95 24最大,放后面 step 6 : 15 12 24 45 51 78 84 95 15最大,放后面 step 7 : 12 15 24 45 51 78 84 95 12最大,放后面 step 8 : 12 15 24 45 51 78 84 95 结束 end : 12 15 24 45 51 78 84 95
c源代码:
#include <stdio.h> #define LENGTH 8 void shift(int number[],int i,int j); void main() { int i,k,number[LENGTH] = {95, 45, 15, 78, 84, 51, 24, 12}; int max; int j=LENGTH; while(j--) { max=0; for (i = 0; i < j; i++) { if (number[i]>number[max]) max=i; } //移动函数,将i处的数值挑出来放到j处 shift(number,max,j); } //结果输出 for (i = 0; i < LENGTH; i++) { printf("%d ", number[i]); } printf("\n"); } void shift(int number[],int i,int j) { int temp=number[i]; while(j!=i) { number[i]=number[i+1]; i++; } number[j]=temp; }
------------------------------------------------------------------------------------------------------
四、快速排序(Quick Sort)
有没有既不浪费空间又可以快一点的排序算法呢?那就是“快速排序”啦!光听这个名字是不是就觉得很高端呢?快速排序是非常优秀的排序算法,初学者可能觉得有点难理解,其实它是一种“分而治之”的思想,把大的拆分为小的,小的再拆分为更小的,所以你一会儿从代码中就能很清楚地看到,用了递归。
快速排序资料也是非常多的,我自己在网上看了一份很好地资料,清晰易懂。自己写也还是无法达到这种效果的,这里我就直接引用过了,再稍微变动一下。
假设我们现在对“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。想一想,你有办法可以做到这点吗?
方法其实很简单:分别从初始序列“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先出动,这一点非常重要。至于为什么,作者只留了一句:请自己想一想为什么?我解释一下吧!因为我们的基准数在左边,然后我们的目的是为了把基准数放到中间位置去,使得左边都小于这个数,右边都大于这个数。如果我们是从左往右搜索,也就是说哨兵i先走的话,如果搜索到了大于基准数的数,而哨兵j在找到小于基准数之前已经碰到了哨兵i,难道要让基准数和哨兵i找到的数交换吗?例如:
3 1 2 5 4
哨兵i先找到了5,但是哨兵j无法找到2,所以要将3和5交换,变成了
5 1 2 3 4
显然,这样是不对的,大数又跑到前面去了。也就是说,我们要保证这个和基准数交换的数得小于基准数,所以,只有哨兵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
如果没弄懂,那我就再说一下吧。就是从右往左扫的时候,发现2是满足2<3的,但是从左往右扫的时候,还没找到大于3的时候已经和哨兵j碰面了,所以,搜索也结束了。所以把基准数3和2换一下,就成了:
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)。其实快速排序是基于一种叫做“二分”的思想。
c源代码:
#include <stdio.h> int a[101],n;//定义全局变量,这两个变量需要在子函数中使用 void quicksort(int left,int right) { int i,j,t,temp; if(left>right) return; temp=a[left]; //temp中存的就是基准数 i=left; j=right; while(i!=j) { //顺序很重要,要先从右边开始找 while(a[j]>=temp && i<j) j--; //再找右边的 while(a[i]<=temp && i<j) i++; //交换两个数在数组中的位置 if(i<j) { t=a[i]; a[i]=a[j]; a[j]=t; } } //最终将基准数归位 a[left]=a[i]; a[i]=temp; quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程 quicksort(i+1,right);//继续处理右边的 ,这里是一个递归的过程 } int main() { int i,j,t; //读入数据 scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&a[i]); quicksort(1,n); //快速排序调用 //输出排序后的结果 for(i=1;i<=n;i++) printf("%d ",a[i]); getchar();getchar(); return 0; }
6 1 2 7 9 3 4 5 10 8
运行结果是:
1 2 3 4 5 6 7 8 9 10
在用这种高大上的算法的时候自然不能忘记他的作者。快速排序由 C.A.R.Hoare(东尼霍尔,Charles Antony Richard Hoare)在1960年提出,之后又有许多人做了进一步的优化。如果你对快速排序感兴趣可以去看看东尼霍尔1962年在Computer Journal发表的论文“Quicksort”以及《算法导论》的第七章。快速排序算法仅仅是东尼霍尔在计算机领域才能的第一次显露,后来他受到了老板的赏识和重用,公司希望他为新机器设计一个新的高级语言。你要知道当时还没有PASCAL或者C语言这些高级的东东。后来东尼霍尔参加了由Edsger Wybe Dijkstra(1972年图灵奖得主,这个大神我们后面还会遇到的到时候再细聊)举办的“ALGOL 60”培训班,他觉得自己与其没有把握去设计一个新的语言,还不如对现有的“ALGOL 60”进行改进,使之能在公司的新机器上使用。于是他便设计了“ALGOL 60”的一个子集版本。这个版本在执行效率和可靠性上都在当时“ALGOL 60”的各种版本中首屈一指,因此东尼霍尔受到了国际学术界的重视。后来他在“ALGOL X”的设计中还发明了大家熟知的“case”语句,后来也被各种高级语言广泛采用,比如PASCAL、C、Java语言等等。当然,东尼霍尔在计算机领域的贡献还有很多很多,他在1980年获得了图灵奖。
好吧。。挣扎了很久,自己也写出来代码来,其实对过程了解的话也不难。结果发现和上面的代码基本是类似的。不过,还是把自己写的贴出来吧。
#include <stdio.h> #define LENGTH 8 void QuickSort(int number[],int i,int j); void main() { int i,number[LENGTH] = {15, 45, 95, 78, 84, 51, 24, 12}; QuickSort(number,0,LENGTH-1); //结果输出 for (i = 0; i < LENGTH; i++) { printf("%d ", number[i]); } } void QuickSort(int number[], int i, int j) { int guardee_i=i,guardee_j=j; if (guardee_i>=guardee_j) return; int temp,k; while(guardee_i!=guardee_j) { while(number[guardee_j]>number[i]&&guardee_j>=i) guardee_j--; while(number[guardee_i]<=number[i]&&guardee_i<guardee_j) guardee_i++; //交换数据 temp=number[guardee_i]; number[guardee_i]=number[guardee_j]; number[guardee_j]=temp; } //交换数据 temp=number[guardee_i]; number[guardee_i]=number[i]; number[i]=temp; QuickSort(number,i,guardee_i-1); QuickSort(number,guardee_i+1,j); }
------------------------------------------------------------------------------------------------------
五、堆排序(Insertion Sort)
堆排序算法
c源代码:
#include <stdio.h> #define LENGTH 8 void HeapBuild(int number[],int i); void HeapSort(int number[],int i); void main() { int i,number[LENGTH] = {15, 45, 95, 78, 84, 51, 24, 12}; HeapBuild(number,LENGTH-1); HeapSort(number,LENGTH-1); //结果输出 for (i = 0; i < LENGTH; i++) { printf("%d ", number[i]); } } //堆构建 void HeapBuild(int number[],int i) { int temp; if (i==0) return; if(number[i]>number[(i-1)/2]) { //交换 temp=number[i]; number[i]=number[(i-1)/2]; number[(i-1)/2]=temp; } HeapBuild(number,--i); } //堆调整和排序 void HeapSort(int number[],int i) { int temp; int last=(i-1)/2; if (i==0) return ; temp=number[i]; number[i]=number[0]; number[0]=temp; int j=0; while(j<last) { int children = 2*j+1; if(number[j]<number[children]&&children<i) { temp=number[children]; number[children]=number[j]; number[j]=temp; } children++; if(number[j]<number[children]&&children<i) { temp=number[children]; number[children]=number[j]; number[j]=temp; } j++; } i--; HeapSort(number,i); }
------------------------------------------------------------------------------------------------------
五、堆排序(Insertion Sort)
(待续!!!)