数据结构中的常见排序
一、基数排序
基数排序的思想比较好理解,即是从各位数开始比较起,一直比较到最高位位置,每次比较都是在前一次比较的基础上进行的。
代码如下:
/* 基数排序,即为按照每位数来将其分类,然后在前一次分类的顺序基础上,再次进行分类,直到所有分类标准都执行完毕 时间复杂度为n */ #include <iostream> using namespace std; int getnumbypos(int num,int dight){ int value=-1; while(dight>0){ value=num%10; num=num/10; dight--; } return value; } void radixsort(int * s,int len,int dight){ int * buttet=new int[len]; int count[dight]; for(int i=1;i<=dight;i++){ for(int j=0;j<dight;j++){ count[j]=0; } for(int j=0;j<len;j++){ if(getnumbypos(s[j],i)>=0){ count[getnumbypos(s[j],i)]++; } } for(int j=1;j<dight;j++){ count[j]=count[j]+count[j-1]; } for(int j=len-1;j>=0;j--){ buttet[count[getnumbypos(s[j],i)]-1]=s[j]; count[getnumbypos(s[j],i)]--; } for(int j=0;j<len;j++){ s[j]=buttet[j]; } } } int main(){ int list[10]={278,109,63,930,589,184,505,269,8 ,83}; radixsort(list,10,10); for(int i=0;i<10;i++){ cout<<list[i]<<" "; } cout<<endl; return 0; }
二、二路归并排序
二路归并排序的思想是开始就将数列划分为两个部分,然后依次递归的对这两部分执行二分操作,直到所有的部分都只包含一个元素位置,此时,再分别对这些部分执行归并操作。直到整个数列都被归并完毕。
代码如下:
/* 二路归并排序,采用了递归的做法,它首先将整个队列划分为两个部分,再一次对这两个部分进行二分操作, 直到每个部分只包含一个元素为止,然后再依次对这些只包含一个元素的部分开始进行归并,然后递归操作会依次向上 进行回溯,最后返回已排好序的队列,排序完成。 时间复杂度n*log2n */ #include <iostream> using namespace std; void merge(int array[], int p, int q, int r) { int n1 = q - p + 1; int n2 = r - q; int *l; l = (int*)malloc(sizeof(int)*n1); int *r; r = (int*)malloc(sizeof(int)*n2); int i = 0; int j = 0; for(i; i < n1; i++) l[i] = array[i + p]; for(j; j < n2; j++) r[j] = array[j + q +1]; i = j = 0; int k = p; while(i!=n1 && j!= n2) { if(l[i] <= r[j]) array[k++] = l[i++]; else array[k++] = r[j++]; } while(i < n1) array[k++] = l[i++]; while(j < n2) array[k++] = r[j++]; free(l); free(r); } void merge(int * b,int s,int mid,int t){ int a[t-s+1]; int k=0;for(int i=s;i<=t;i++){ a[k++]=b[i]; }int i,j; int id=s; for(i=s,j=mid+1;i<=mid&&j<=t;){if(a[i-s]<a[j-s]){ b[id++]=a[i-s]; i++; }else{ b[id++]=a[j-s]; j++; } }while(i<=mid){ b[id++]=a[i-s]; i++; } while(j<=t){ b[id++]=a[j-s]; j++; } } void twoguibing(int * a,int s,int e){ int * bb; if(s<e){ int mid=(s+e)/2; twoguibing(a,s,mid); twoguibing(a,mid+1,e); cout<<"before sort: "; for(int i=s;i<=e;i++){ cout<<a[i]<<" "; } cout<<endl; merge(a,s,mid,e); cout<<"after sort: "; for(int i=s;i<=e;i++){ cout<<a[i]<<" "; } cout<<endl; } } int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1};//1 1 2 3 4 4 5 6 65 98 twoguibing(s,0,9); for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
三、快读排序
快速排序的思想是每次都选择一个标志,将整个数列划分为两个部分,然后在依次递归的对这两个部分通用选取标志进行划分。直到每个部分大小变为1为止。
代码如下:
/* 快速排序就是首先将s[0]选定为我们的划分标志,然后,将队列划分为两个部分。然后再分别对这两个部分进行划分。 直到每个部分只包含一个元素为止。 时间复杂度n*log2n 不稳定排序,数列有序则退化为冒泡排序。 */ #include <iostream> using namespace std; void swap(int & a,int & b){ int temp=a; a=b; b=temp; } int func(int * s,int b,int e){ int flag=s[b]; while(b<e){ while(b<e&&s[e]>=flag) e--;//一定要>=否则如果最后两个数与flag相同,那么整个while就会陷入死循环中。 swap(s[b],s[e]); while(b<e&&s[b]<=flag) b++; swap(s[b],s[e]); } return b; } void quicksort(int * s,int b,int e){ if(b<e){ int pos=func(s,b,e); quicksort(s,b,pos-1); quicksort(s,pos+1,e); } } int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1};//1 1 2 3 4 4 5 6 65 98 quicksort(s,0,9); for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
四、堆排序
堆排序,通过每次构造一个大顶堆或者小顶堆,来查找出当前数列的最大值或最小值,然后交换堆顶与最后一个叶子节点,同时,将排序的范围减一,直到排序的范围减小到1,记住,初始时需要先将数列构造为大顶堆或小顶堆。
代码如下:
/* 时间复杂度n*log2n,我们直到堆排序,需要首先构造一个大顶堆或者小顶堆,然后开始选出最大值或最小值,在这一过程中,将其移动 到堆顶,然后交换堆顶和堆尾,缩小堆的大小,再次排序,直到堆的大小为1为止。 */ #include <iostream> using namespace std; int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1};//1 1 2 3 4 4 5 6 65 98 for(int i=10;i>0;i--){//之所以将i设置为从10开始,就是考虑了初始数列构造大顶堆或小顶堆的问题。 if(i<10){//初始时,无需交换堆顶和最后一个叶子结点。 int temp=s[i]; s[i]=s[0]; s[0]=temp; } for(int j=(i)/2;j>=0;j--){ int fav=s[j]; int fa=j; int child=2*j+1; while(child<i){ if(child+1<i&&s[child]<s[child+1]){ child++; } if(s[child]>s[fa]){//一直顺着查找下去。 s[fa]=s[child]; fa=child; child=2*fa+1; }else{ break; } s[fa]=fav; } } } for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
五、希尔排序
希尔排序通过设置间隔,来将数列进行分组,然后对于每组的数据使用直接插入排序。之后,在将间隔减小为其的一半,直到间隔减小到0为止。
代码如下:
/* 通过设置间隔来实现,首先将间隔设置为队列大小的一半,对相同间隔内的元素执行直接插入排序。 时间复杂度n到n*n */ #include <iostream> using namespace std; int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1}; int jiange=10/2; while(jiange>=1){ for(int i=jiange;i<10;i++){ int j=i-jiange; int val=s[i];//在后面的变换中,s[i]对应的值已经发生了变化。 while(s[j]>val&&j>=0){ s[j+jiange]=s[j]; j=j-jiange; } s[j+jiange]=val; } jiange=jiange/2; } for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
六、直接插入排序
直接插入排序的思想就是假设初始的有序序列大小为1,然后依次扩大这个有序序列。
代码如下:
/* 首先有序队列只包含第一个元素,随后逐渐扩充其大小,每次将新值与有序队列末尾的值进行比较, 找到其位置。 时间复杂度为n*n; */ #include <iostream> using namespace std; int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1}; for(int i=1;i<10;i++){ if(s[i]>s[i-1]){ continue; }else{ int j=i-1; int val=s[i]; while(s[j]>val){ s[j+1]=s[j]; j--; } s[j+1]=val; } } for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
七、冒泡排序
冒泡排序的每次遍历都会比较临近元素的大小,不满足要求就交换其顺序,每次遍历都会找到一个当前查找范围的最大值或最小值。直到查找范围缩小到1。
代码如下:
#include <iostream> using namespace std; int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1};//1 1 2 3 4 4 5 6 65 98 for(int i=0;i<10;i++){ for(int j=0;j<9-i;j++){ if(s[j]>s[j+1]){ int temp=s[j]; s[j]=s[j+1]; s[j+1]=temp; } } } for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
八、简单选择排序
简单选择排序会在每次遍历中找到它的最小值或最大值,然后缩小查找的范围,直到查找范围缩小到1.
代码如下:
/* 每次找出最小值放置,时间复杂度n*n */ #include <iostream> using namespace std; int main(){ int s[10]={4,1,5,2,4,3,98,6,65,1}; for(int i=0;i<10;i++){ int min=i; for(int j=i+1;j<10;j++){ if(s[j]<s[min]){ min=j; } } if(min!=i){ int temp=s[i]; s[i]=s[min]; s[min]=temp; } } for(int i=0;i<10;i++){ cout<<s[i]<<" "; } cout<<endl; return 0; }
总结:
(1)平方阶(o(n2))排序
各类简单排序:直接插入、直接选择和冒泡排序;
(2)线性对数阶(o(nlog2n))排序
快速排序、堆排序和归并排序;
(3)o(n1+§))排序,§是介于0和1之间的常数。
希尔排序
(4)线性阶(o(n))排序
基数排序,此外还有桶、箱排序。
说明:
当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至o(n);
而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为o(n2),每次划分都会将数列划分为一个空数组和一个大小减一的数组,故比较次数为n-1,n-2,n-2,.......1,故为o(n2)。
原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。
稳定性:
排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。
稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序