常用的排序算法

来源:互联网 发布:苹果非编软件 编辑:程序博客网 时间:2024/05/16 10:21

排序算法有很多,是一个很杂但是很重要的知识点,复习一下:

笼统地来说,排序分为内部排序(Internal Sorting)和外部排序(External Sorting),我们讨论的一般是内部排序。从实现方式上讲,排序又可以分为比较排序和非比较排序,前者的时间复杂度在O(nlogn)——O(n^2)之间,后者均为O(n),在文章末尾有关于各种排序算法时间复杂度、空间复杂度、是否稳定等性质的总结。

经典的排序算法主要有以下几种,其中1——7属于比较排序,8、9是非比较排序。

1、冒泡排序(Bubble Sort)

(1)原汁原味的冒泡。。

void bubble_sort(int a[],int n){int i,j,tmp;for(i=0;i<n-1;i++){//n个元素需要排序n-1趟for(j=0;j<n-i-1;j++){//每做一次外循环就有i+1个元素归位(注意i从0开始,所以是i+1),那么就只需要排剩下的n-i-1个if(a[j]>a[j+1]){tmp=a[j+1];a[j+1]=a[j];a[j]=tmp;}}}}

对于n个元素只需要n-1趟,每趟都让一个元素归位,大的或者小的元素就像冒泡泡一样跑到前边或者后边去了。。。

(2)改进版的冒泡——鸡尾酒排序(Cocktail Sort)

这家伙其实就是两趟冒泡排序,看一个例子(假设按升序排列,蓝色加粗的表示每次排好不用再管的元素):

比如9 8 4 12 7 2 5

第一趟先从左到右找到最大的,是12,然后把12扔到最后去:

9 8 4 7 2 5 12

然后再从右往左扫回来(不再管12了),找到最小的2,扔前面:

2 9 8 4 7 5 12

再从左往右(不再管2和12),找到了9,扔到最后:

2 8 4 7 5 9 12

再从右往左,这回是4:

2 4 8 7 5 9 12

如此往复。。。

就得到答案了:2 4 5 7 8 9 12

这个过程就特别像调鸡尾酒啊。。。左左右右来来回回摇来摇去的=。=所以这个排序算法还有一堆很萌的名字:比如“Shaker Sort”,“Happy Hour Sort”之类的=。=(难不成命名的人觉得这个过程很欢乐=。=?)

代码实现:

void cocktail_sort(int a[],int n){int low=0,high=n-1;int index=low;while(high>low){for(int i=low;i<high;i++){if(a[i]>a[i+1]){swap(a[i],a[i+1]);index=i;}}high=index;for(int i=high;i>low;i--){if(a[i]<a[i-1]){swap(a[i],a[i-1]);index=i;}}low=index;}}
鸡尾酒排序的代码还是很好理解的,每趟用一个index来回记录高低位即可。


2、插入排序(Insertion Sort)

是最简单的排序之一:

#include<iostream>using namespace std;void insertion_sort(int a[],int n){int i,j,tmp;for(i=1;i<n;i++){tmp=a[i];for(j=i;j>0&&a[j-1]>tmp;j--)a[j]=a[j-1];a[j]=tmp;}}int main(){int a[9]={1,9,0,45,88,3,5,7,100};insertion_sort(a,9);for(int i=0;i<9;i++)cout<<a[i]<<" ";return 0;}
运行效果截图:

插入排序的思想是:每一次排序都严格保证位置i之前的元素都比i处小,在排序的过程中只要发现比它大的,就让这个更大的元素向前覆盖,比如:

1 5 7 9 10 3

现在排到了3这里,也就是tmp=3,需要检查3前面的,发现10比它大,于是10向前覆盖:

1 5 7 9 10 10

再看9,9仍然大于3,也要向前覆盖:

1 5 7 9 9 10

再看7,同理:

1 5 7 7 9 10

再看5:

1 5 5 7 9 10

最后到1,发现1小于3,于是执行a[j]=tmp,3占据a[1],得到:

1 3 5 7 9 10

3、希尔排序(Shell Sort)

是一个叫做Donald Shell的计算机科学家发明的排序算法,也是最早突破二次时间屏障的第一批算法之一,可以看成是insertion sort的加强版,查了一下,Shell在2015年11月2日逝世了,享年91岁,对大牛的逝去表示哀悼。。。

其实算法思想并不复杂,例如对一堆待排序的数:9 8 3 5 12 0 7 2 6 88 21

我们把它按照所谓的“增量序列”来分组,常用的序列是二分的,这也是希尔本人的序列,即按照n/2,n/4,...这样进行下去(除不尽就向下取整,例如9/2这里其实是floor(9/2)=4),这个序列叫做“Shell's increment sequence”,除此之外还有Hibbard's increment sequence, Sedgewick's increment sequence等。。(后尤其是面这位Sedgewick学过算法的同学应该都不陌生。。Knuth的徒弟,红黑树就是因为他和另一位叫Leonidas John Guibas的计算机科学家而得名的orz)不同的序列对算法性能有很大影响,其中的分析比较复杂,在此不表。这里为了简便起见,选择的是Shell’s increment sequence。

回到上面的例子,9 8 3 5 12 0 7 2 6 88 21的长度是11,那么就按照5,2,1的间隔来分组(下面的例子参考了CY老师的课件,我认为她是明白这个思想的。。。但是过程和后面的代码对不上号,所以有点误导人。。。这个过程不看也罢,可以直接看后面的代码和代码后我给的说明):

间隔5的为一组(以相同颜色区分):

9 8 3512 072 68821

然后排序:

9 7 2 12 0 8 3 6 88 21

再按照2的间隔来分组:

0 8 3 5 12 9 726 88 21

然后排序:

0 2 3 12 8 7 9 6 88 21

最后间隔为1的,也就是全部序列,排序,然后就得到结果了:

0 2 3 5 6 7 8 912 21 88

代码(和上面的过程对不上号,但是是《Data Structure and Algorithm Analysis》(Weiss著)一书给的例程):

void shell_sort(int a[],int n){int i,j,increment;int tmp;for(increment=n/2;increment>0;increment/=2){for(i=increment;i<n;i++){//内部其实是选择排序(Insertion Sort)tmp=a[i];for(j=i;j>=increment;j-=increment){if(tmp<a[j-increment])a[j]=a[j-increment];elsebreak;}a[j]=tmp;}}}
按照代码,执行过程是:

对于9 8 3 5 12 0 7 2 6 88 21,一开始的增量是5,i=5

我们先找到第5个元素,再回过头去看0,然后i=6,看6-increment,即1,再然后是i=7,看2,i=8,看3......这样一直下去,所以分组应该是:

9,0

7,8

2,3

6,5

88,12

21,0

下一次增量是2,i=2...仿上...

所以CY姥姥给的那个过程看看算了。。。别太当真=。=


4、选择排序(Selection Sort)

我总是把它的名字和插入排序混淆orz

选择排序的思想是:从第一个元素开始扫描,选出这堆数中最小(最大)的那个,与第一个元素交换,如此循环往复。例如:

8 9 5 1 3 11

从8开始,找到了最小的1,交换:

1 9 5 8 3 11

从9开始,找到了最小的3,交换:

1 3 5 8 9 11

从5开始,一找发现它自己就是最小的,那就乖乖呆着不交换了=。=(这个地方在代码里也有体现)

1 3 5 8 9 11

如此循环往复,把后面的数都检查完就得到:

1 3 5 8 9 11

代码很简单:

void selection_sort(int a[],int n){int i,j,min;for(i=0;i<n;i++){min=i;//假设当前元素是最小的,min记录它的下标for(j=i+1;j<n;j++){if(a[j]<a[min])min=j;}if(min!=i)//如果这个元素本身就是最小的,就不要交换了swap(a[i],a[min]);}}

5、堆排序(Heap Sort)

这是一个很重要的内容,想把它和堆放到一起写,所以这里先略。


6、归并排序(Merge Sort)

归并排序的算法思想是divide and conquer,这个东西和外部排序有很大的联系。。。(外部排序用的就是归并的思想)算法可以递归实现,也可以非递归实现,先来通过例子

看看这个过程:

比如有两个数组:2 4 7 9和0 11 7

首先指针p1指到2,p2指到0,2和0比,0小,把0放进储存结果的数组,p2移动到11,11和2比,2小,把2放进储存结果的数组,p1移到4......如此往复




代码:

/*去年复习照着书用C写的。。。*/void MergeSort(int A[],int N){//MergeSort的作用就是去调用MSort//TmpArray用来暂时存放结果int * TmpArray;TmpArray=(int *)malloc(sizeof(int)*N);if(TmpArray!=NULL){MSort(A,TmpArray,0,N-1);free(TmpArray);}else{printf("Fatal Error!No spcace for TmpArray!\n");exit(0);}}void MSort(int A[],int TmpArray[],int Left,int Right){int center;if(Left<Right){center=(Right+Left)/2;MSort(A,TmpArray,0,center);MSort(A,TmpArray,center+1,Right);Merge(A,TmpArray,Left,center+1,Right);}}void Merge(int A[],int TmpArray[],int Lpos,int Rpos,int RightEnd){int i,LeftEnd,NumElements,TmpPos;LeftEnd=Rpos-1;//左边的终止位置=右边的开始位置-1NumElements=RightEnd-Lpos+1;//需要排序的个数=右边终止位置-左边起始位置+1TmpPos=Lpos;while(Lpos<=LeftEnd&&Rpos<=RightEnd)if(A[Lpos]<=A[Rpos])TmpArray[TmpPos++]=A[Lpos++];elseTmpArray[TmpPos++]=A[Rpos++];while(Lpos<=LeftEnd)TmpArray[TmpPos++]=A[Lpos++];while(Rpos<=RightEnd)TmpArray[TmpPos++]=A[Rpos++];for(i=0;i<NumElements;i++,RightEnd--)A[RightEnd]=TmpArray[RightEnd];}

例程一共有3个函数:

第一个遵守排序函数的定义,写成voidxxxsort(Type A[],int N),里面定义了一个数组,用来存放排序结果。

第二个是递归函数,从0排到中间,从中间+1排到右边,再调用排序函数Merge。这个函数有四个参数:原数组,存放结果的数组,确定范围的左、右。

第三个是具体过程的实现,有五个参数:原数组,存放结果的数组,左端第一个位置,右端第一个位置和最末位。


7、快速排序(Quick Sort)

肥肠重要。。。。也肥肠常用。。。算法思想是divide and conquer




代码(直接copy了课件上的例程):

#define Cutoff 3int Median3(int A[],int Left,int Right);void Qsort(int A[],int Left,int Right);void  Quicksort(int A[],int N){Qsort( A, 0, N - 1 ); /*A:the array*//*0:Left index*//*N-1:Right index*/}int Median3(int A[],int Left,int Right){     int Center=(Left+Right)/2;    if(A[Left]>A[Center])        swap(A[Left],A[Center]);     if(A[Left]>A[Right])        swap(A[Left],A[Right]);    if(A[Center]>A[Right])        swap(A[Center],A[Right]);     /* Invariant: A[ Left ] <= A[ Center ] <= A[ Right ] */     swap(A[Center],A[Right-1]); /* Hide pivot */     /* only need to sort A[ Left + 1 ] … A[ Right – 2 ] */    return A[Right-1];/* Return pivot */ }void Qsort(int A[],int Left,int Right){   int i,j;     int Pivot;    if(Left+Cutoff<=Right){/* if the sequence is not too short */        Pivot=Median3(A,Left,Right);/* select pivot */        i=Left;     j=Right-1;/* why not set Left+1 and Right-2? */        for(;;){ while(A[++i]<Pivot){}/* scan from left */while(A[--j]>Pivot){}/* scan from right */if(i<j)swap(A[i],A[j]);/* adjust partition */elsebreak;/* partition done */}        swap(A[i],A[Right-1]);/* restore pivot */         Qsort(A,Left,i-1);/* recursively sort left part */        Qsort(A,i+1,Right);/* recursively sort right part */    }  /* end if - the sequence is long */    else /* do an insertion sort on the short subarray */         insertion_sort(A+Left,Right-Left+1);}

8、基数排序(???)

暂时不写


9、桶排序(Bucket Sort)

暂时不写


附:

性能比较:

先略。。


原创粉丝点击