赞
踩
以下就是常见基本的排序方式
1、冒泡排序
2、选择排序
3、插入排序
4、希尔排序
5、归并排序
6、快速排序
7、堆排序
8、计数排序
菜鸟教程排序链接
该处的排序原理,讲的其实十分细致。
这里着重介绍下,快速排序和归并排序。
快速排序原理:就是取一个值作为基准,将数组小的移到其左边,大的数据移到右边。反复此过程,就可以实现排序。
相信你看过了菜鸟的代码,快速排序的迭代版本以及递归版本,其中有一个三数取中原则。
根据快速排序原理,我们拿来作为基准值的数,如果能是该数组的中间数,那么就能实现尽量接近2分。
但是倘若一个数组全是【2,2,2,2,2,2,2,2】,再以那样的代码,三数取中也无法改变,所以这个时候,那个数据取完之后,就会导致这次排序走了一次n,并且直到最后一躺,时间复杂度就是n^2
为了解决这个问题,我们在每次排序时,需要相等的放在中间,小的放在前面,大的放在后面。
以下是实例。
int part_sort_returnArrays(int * a,int n,int* returnSize)//如果说,存在全都相等的数字,那么这里不就是单趟遍历,最后导致quiksort是n^2的时间复杂度 //因此这里提供一种修改方案,相等的全部移往中间,但是值得注意,由于返回相当于是一个数组了,那么就不仅仅要i下标,还要整个长度 { if(n<=1) { return 0; } int midi = GetMid_Index(a,0,n-1); swap(&a[0],&a[midi],sizeof(a[0])); int i =0; int temp = a[0]; int count = 0;//计算与temp值相等的数值总个数 int m = n -1; for(int j=0;j<m+1;)//前后指针下标版本 { if(a[j]==temp) { count++; j++; } else if(a[j]<temp) { swap(&a[i++],&a[j],sizeof(a[j])); j++; } else { swap(&a[m--],&a[j],sizeof(a[j])); } } for(int k = 0;k<count;k++)//注意,由于这里每次都多走了一组,实际效率有所下降, { a[i+k] = temp; } *returnSize = count; return i; }
还有一种问题存在,有一种数据,让你每次三数取中都取到比很小的数或者很大的数,那么时间复杂度基本也就是o(N^2)
所以为了解决这种困境,尽量保证每次取中数据随机,大体上取了中间值。
int GetMid_Index(int *a,int left,int right) { int mid = 0; mid = left+ rand()%(right-left);//三数,随机取中,这里应对leetcode的一些专门设计的数据 if(a[mid]>a[left]) { if(a[mid]<a[right]) { return mid; } else if(a[left]>a[right]) { return left; } else { return right; } } else //mid<left { if(a[mid]>a[right]) { return mid; } else if(a[right]<a[left]) { return right; } else { return left; } } }
int* arrs_sort(const int *arr,const int* brr,const int sz1,const int sz2)//这里会申请动态太内存,调用函数请记得释放 ,返回数组的大小一定就是sz1+sz2 { int* rearrs = (int*)malloc(sizeof(int)*(sz1+sz2)); int c = 0; int a = 0,b = 0; int sign = a<sz1 ; sign += b<sz2; while(sign) { switch(sign) { case 2: { if(arr[a]>brr[b]) { rearrs[c++] = brr[b++]; } else { rearrs[c++] = arr[a++]; } break; } case 1:{ if(a<sz1) { rearrs[c++] = arr[a++]; } else if (b<sz2) { rearrs[c++] = brr[b++]; } break; } default :break; } sign = a<sz1; sign += b<sz2; } return rearrs; } //归并排序 //迭代版本 // int* arrs_sort_NonMalloc(const int *arr,const int* brr,const int arr_sz,const int brr_sz)//必须保证第一个数组大小时,sz1是arr传过来要排序 // { // int a = 0,b = 0; // while(b<brr_sz) // { // } // return arr; // } int merge_sort(int arr[],int sz) { int a = 2; for(;a/2<sz;a*=2) { for(int i=0;i<sz;i+=a) { if(i+a<=sz)//保证还有数 { int *t = arrs_sort(&arr[i],&arr[i+a/2],a/2,a/2); memmove(&arr[i],t,a*sizeof(int)); free(t); } else if(i+a/2<sz) { int *t = arrs_sort(&arr[i],&arr[i+a/2],a/2,sz-a/2-i); memmove(&arr[i],t,(sz-i)*sizeof(int)); free(t); } } } return 0; } //归并排序的迭代版本
以上是归并排序的数组迭代版本,这个还是比较好实现。问题关键就是分组去排序。
归并的核心就是合并排序有序数组,所以这里以2或者3底去分组都是可以的。
为什么快速排序会在某些场景需要优化,而归并排序写好在应对大多数场景都比较合适?
排序稳定性:指原本相等的数据,本来应该在前面的数据,排序完成也依然在前面。
举个例子:[5a, 2, 3a, 1, 5b, 4, 3b]
排序完后:[1, 2, 3a, 3b, 4, 5a, 5b]
上述就说明是稳定排序,否则就是不稳定。
归并和快排的稳定性。
我们回到归并和快排两者原理上去,快排一开始就是以某一个数据为基准,去分组,如此循环以往,我们知道,当只有选数据选到中间数,才能找到。在选中间数的时候,就会使得某些数据位置改变。
而我们看归并排序,由于是直接进行数据的合并,对原数据就不会产生过于大的影响。
这里留下我的一个猜测:凡是不稳定排序,数据顺序对其性能都是比较大的,相对的,稳定排序就受数据顺寻影响会很小。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。