关于排序的一点思考
排序有很多种方法,最原始的方法莫过于插入排序。
插入排序
算法实现:
void InsertionSort(ElementType A[],int N){
int i,j;
ElementType Tmp;
for ( i = 1;i < N ; i++){
Tmp = A[i];
for(j = i; j > 0 && Tmp < A[j-1]; j++)
A[j] = A[j-1];
A[j] = Tmp;
}
}
时间复杂度分析:
插入排序是最简单粗暴的。插入排序的实现过程就像,假设有一篮球队选拔队员,让队员们从矮到高站好,插入排序的过程就像。让这一队先乱序站好,然后从第二个人开始(数组中下标从1开始),先让这个人出列(赋值给Tmp,这样做的目的是为了避免频繁的交换操作),让这个人和他前面的人比较,如果高过他,则把高的往后挪,直到碰到比他矮的(前面的以排好序,这里不考虑数值相同的情况)才结束,然后把这个人补到空位里面去(A[j]=Tmp)。很明显,这种排序很麻烦。
插入排序的时间复杂度显然是O(N2)。
希尔排序
算法实现:
void ShellSort(ElementType A[],int N){
int i,j,Increment;
ElementType Tmp;
for(Increment = N/2;Increment > 0;Increment /=2)
for(i = Increment; i < N;i++){
Tmp = A[i];
for(j = i;j > Increment && Tmp < A[j - Increment];j++)
A[j] = A[j - Increment];
A[j] = Increment;
}
}
希尔排序的评价:
个人感觉,希尔排序是一个很复杂的排序方法。
希尔排序的效率和增量序列(Increment)的选取有关。
堆排序:
堆排序是稳定的排序。(具体后面总结的时候和其他几种排序比较之后再提)
首先我们说的堆当然默认是二叉堆(binary heaps)。
为什么要使用堆排序?
因为够快和够稳定。理论上,堆排序的时间复杂度是O(NlogN),比希尔排序快(虽然实际的运用中希尔排序要更好一些)。这个时间是怎么来的?我们可以建一个堆,然后再将堆顶元素删除,将得到的元素记录在另一个数组中(当然这样会增加时间和空间的开销,不过这些开销并没有太多关系)。最后当堆中的元素都记录到另一个数组之后,整个排序就完成了,我们得到了一个递增的序列。另外的一个策略是(如果你不想使用一个增加开销的数组),我们把delete出来的数放在数组的最后一个位置,不断重复就可以得到一个有序序列。虽然对于小顶堆来说,这个序列是递减的。如果我们想要得到一个递增的序列的话,只需要调整一下heap order(变成大顶堆)再deleteMax就可以得到递增的序列了。
算法实现:
#define LeftChild(i) (2*(i)+1)
void PercDown(ElementType A[],ElementType i,int N){
int Child;
ElementType Tmp;
for(Tmp = A[i];LeftChild(i) < N;i=Child){
}
}
void HeapSort(ElementType A[],int N){
/*build heap*/
int i,j
for(i=N/2;i>=0;i++)
percdown(A,i,N);
for(j=N-1;j>0;j--){
swap(&A[0],&A[j]);
perdown(A,0,i);
}
}
快速排序
快速排序正如他的名字所示的那样,理论上是最快。
算法实现:
快速排序很重要的一点是pivot的选取。这里采用的方法是三数中值法,去头尾和中间三个元素,然后取三个数的中值作为pivot的值。(下面对快排做具体分析的时候会解释pivot的选取和时间复杂度的问题)
#define Cutoff 3
/*Median3用来选取pivot 并且返回pivot*/
ElementType Median3(ElementType A[], int Left, int Right){
int Center;
Center = (Left + Right)/2;
if(A[Left] > A[Right])
Swap(&A[Left],&A[Right]);
if(A[Left] > A[Center])
Swap(&A[Left],&A[Center]);
if(A[Center] > A[Right])
Swap(&A[Center],&A[Right]);
Swap(&A[Center],&A[Right-1]);
return A[Right-1];
}
void QSort(ElementType A[], int Left, int Right){
//对于元素较少的子数组来说,不能用三数中值法来选取枢纽元素,不能用快速排序来实现,可选用插入排序。
if(Left + Cutoff <= Right){
ElementType pivot;
int i,j;
i = Left;
j = Right;
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]);
QSort(A,Left,i-1);
QSort(A,i + 1,Right);
}
else
InsertionSort(A+Left, Right-Left+1);
}