标签:
本文主要比较一下各种排序的性能(平均时间复杂度和最差情况)和基本实现。
这个默认按照从小到大排列,输入的数据可以重复,假设输入的数组为A,下标从0到N-1
注意在比较算法复杂度时,我们会关注键值的比较次数和交换次数。
1、冒泡排序
冒泡排序如果不是因为名字比较好记,没有任何优势。它的思路是一趟又一趟的比较数组(或者链表也可以)中相邻的两个元素,如果前一个比后一个大,则交换。这样,每一轮之后,最大的那个元素被“沉”到数组的最后去。
void Bubble_Sort(int A[], int N){
for(int i = 0; i<=N-2; i ++)
for(int j = 0; i<=N-2-i; j++)
if(A[j]>A[j+1])
swap(A+j,A+j+1);
}
可以发现,在平均情况和最差情况是,键值的比较次数和交换次数都是O(N^2)
在最好情况下,键值的比较次数还是O(n^2),但是代码经过优化后键值的交换次数为O(N)
public void bubbleSort(int arr[]) {
boolean didSwap;
for(int i = 0, len = arr.length; i < len - 1; i++) {
didSwap = false;
for(int j = 0; j < len - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
swap(arr, j, j + 1);
didSwap = true;
}
}
if(didSwap == false)
return;
}
}
参见http://www.cnblogs.com/melon-h/archive/2012/09/20/2694941.html
同时,冒泡排序还是一个稳定的算法。
2、插入排序
插入排序的思想是对于一个A[i],我们假设它之前的都已经排序好了,这时关键是向前寻找A[i]的位置,j=i-1~0,比较A[i]和A[j],如果A[j]大,则A[j]向后移,直到A[j]小时,就是A[i]的位置。
void Insertion_Sort(int A[], int N){
int tmp,i,j;
for(i = 1; i<N; i++){
tmp = A[i];
//比较i之前的元素和A[i]的大小
for(j = i; j!=0&&A[j-1]>tmp; j--)
A[j] = A[j-1]; //移出空位
A[j] = tmp;
}
}
插入排序是对冒泡排序的改进,也是稳定的算法。
最差的情况是逆序(从大到小排列)时间复杂度是O(N^2)
3、希尔排序
以D间隔进行排序,为了每次不止消除一个逆序对。
D=2^k-1(比较好的 D)
void Shell_Sort(int A[], int N){
int tmp,i,j;
for(int D = N/2; D>0; D/=2){//shell增量
for(i = D; i<N; i++){ //插入排序
tmp = A[i];
//比较i之前的元素和A[i]的大小
for(j = i; j!=0&&A[j-D]>tmp; j-=D)
A[j] = A[j-D]; //移出空位
A[j] = tmp;
}
}
}
4、选择排序
对当前的下标位置i时,我们要找到i+1到N-1中的最小的下标min_i与i位置的元素交换
void Selection_Sort(int A[], int N){
int i,j,min_i;
for(i = 0; i <N-1; i++){
min_i = i;
//找最小元交换
for(j = i+1; j<N; j++)
if(A[j]<A[min_i]) min_i =j;
swap(A+j,A+min_i);
}
}
5、堆排序
首先构建一个最大堆,这样最大堆的第一个元素就是最大的元素,将它与堆的最后一个元素交换后,堆的规模减一再将剩下的堆重新调整为最大堆,重复操作即可。
先考虑堆的建立,有两种方法
方法1:通过插入操作,将N个元素一一插入到一个初始为空的堆,O(nlgN)
这样,堆排序的操作为一直deleteMin,将最小的元素存起来,
void Heap_Sort(int A[], int N){
BuilDHeap(A); //O(N)
for(i = 0; i<N; i++)
TmpA[i]=DeleteMin(A); //O(log(N))
for(i =0; i<N; i++) //O(N)
A[i]=TmpA[i];
}
上面的问题是多使用了一个额外的数组来储存。
另外一个种堆排序,
#define LeftChild(i) (2*(i)+1) //以0为起点的堆
void PercDown(int A[], int i, int N){
//从i向左右儿子过滤,建成以i为根的最大堆
int child,parent;
int tmp;
tmp = A[i]; //寻找tmp需要放的位置
for(parent = i; parent*2<=N-1; parent = child){
child = LeftChild(i);
if(child!=N-1&&A[child]<A[child+1])//右儿子较大
child++;
if(tmp>A[child]) break;
else A[parent]=A[child];
}
A[parent]=tmp;
}
void Heap_Sort(int A[], int N){
int i;
for(i=N/2; i>=0; i--) //build heap
PercDown(A,i,N);
for(i=N-1; i>0;i--){
swap(&A[0],&A[i]); //DeleteMax
PercDown(A,0,i);
}
}
优点,堆排序是一种在位的排序方法,适合数据量非常大的场合。
6、快速排序
在数据量一般的情况下,是性能最好的排序方法。主要的思想是分治,它本身有很多trick,stl里中的sort就是使用快速排序的,它的源码也值得好好研究下。这里主要说下主要的几个trick
1、主元pivot的选择,先使用一个median3的函数,选择出left(0),right(N-1),center三个位置上的中位数,并将中位数放在right-1的位置。(这样只要考虑A[left+1,right-2]事实证明主元的选择对性能有较大影响,这种方式较好。
2、i从left开始,j从right-1开始移动,直到两者相交
3、停止的时候,交换A[i]和A[right-1],递归左右两个子序列
4、当序列的长度小于阈值时,不递归而使用才插入排序 ,这样也能显著调高速度
//Swap two numbers
void swap(int *a, int *b){
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
//choose the median of left, center and right
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);
//put median in right-1
swap(A+center,A+right-1);
return A[right-1];
}
//Insertion sort ---for small size array and index array
void Insertion_Sort(int *A, int N){
int p; int i; int tmp;
for(p = 1; p < N; ++p){
tmp = A[p];
for(i = p; i!=0&&A[i-1]>tmp; --i)
A[i]=A[i-1];
A[i] = tmp;
}
}
void Quicksort(int *A, int left, int right){
if(right-left>10){
int pivot = median3(A, left, right);
int i = left; int j = right-1;
for(;;){
while(A[++i]<pivot){}
while(A[--j]>pivot){}
if(i<j)
swap(A+i,A+j);
else
break;
}
swap(A+i,A+right-1);
Quicksort(A,index_A,left,i-1);
Quicksort(A,index_A,i+1,right);
}
else
Insertion_Sort(A+left,right-left+1);
}
具体的性能比较可以参考http://blog.sina.com.cn/s/blog_77795cad01011txt.html
7、位排序
还有一个处理大数据的方法,参见之前的一篇文章
海量数据处理
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/whzyb1991/article/details/47842351