本帖依据学习进度持续更新
《数据结构与算法分析-c语言描述》学到第七章,是时候该系统的学习一下排序算法了。首先学到的是插入排序,算法就不赘述了,书上博客上到处都有。书上的两个定理还不太明白:
插入排序
定理7.1
N个互异数的数组的平均逆序数是N(N-1)/4。
定理7.2
通过交换相邻元素进行排序的任何算法平均需要Ω(N^2)时间。
插入排序的算法复杂度应该为O(N^2)才对。
下面是我的测试用例:
void insertionSort(elementType A[], int N) { Int j, P; elementType Tmp; for(P = 1; P < N; P++) { Tmp = A[P]; for(j = P; j > 0 && A[j – 1] > Tmp; j--) A[j] = A[j – 1]; A[j] = Tmp; } }
希尔排序
希尔排序的最后一样增量是1,所以是直接插入排序,但移动次数比直接插入排序少,代入数组测试:
#include<stdio.h> #define elementType int 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 -= Increment) A[j] = A[j-Increment]; A[j] = Tmp; } } void main() { int i; int a[] = {81,94,11,96,12,35,17,95,28,58,41,75,15}; shellSort(a, 13); for(i = 0; i < 13; i++) printf("%d ", a[i]); }
堆排序
我们为了找出一列数第K个最小的数引入二叉堆,最小堆总能从根节点输出最小值,一直使用deleteMin函数就能输出一列排序好的数,因为最小值最先输出,所以这个数列是递减的。由此我们可以得出如果建一个最大堆,使用deleteMax函数就能输出一列递增的序列。时间复杂度为O(NlogN)。
#include<stdio.h> #define leftChild(i) (2*i+1) void Swap(int *a, int *b) { int Tmp = *a; *a = *b; *b = Tmp; } void percDown(int A[], int i, int N) { int Child, Tmp; for(Tmp = A[i]; leftChild(i) < N; i = Child) { Child = leftChild(i); if(Child != N-1 && A[Child + 1] > A[Child]) Child++; if(A[Child] > Tmp) A[i] = A[Child]; else break; } A[i] = Tmp; } void heapSort(int A[], int N) { int i; for(i = N / 2; i >= 0; i--) percDown(A, i, N); for(i = N - 1; i > 0; i--) { Swap(&A[0], &A[i]); percDown(A, 0, i); } } void main() { int i; int a[] = {81,94,11,96,12,35,17,95,28,58,41,75,15}; heapSort(a, 13); for(i = 0; i < 13; i++) printf("%d ", a[i]); }
归并排序
该算法是经典的分治策略,基本的操作是合并两个已排序的表,放到第三个表中。而已排序的表又可以通过归并排序本身递归得到。
#include<stdio.h> #include<stdlib.h> #define elementType int void Merge(elementType A[], elementType Tmp[], int Lpos, int Rpos, int rightEnd) { int i, leftEnd, numElements, Tmppos; leftEnd = Rpos - 1; Tmppos = Lpos; numElements = rightEnd - Lpos + 1; while(Lpos <= leftEnd && Rpos <= rightEnd) if(A[Lpos] <= A[Rpos]) Tmp[Tmppos++] = A[Lpos++]; else Tmp[Tmppos++] = A[Rpos++]; while(Lpos <= leftEnd) Tmp[Tmppos++] = A[Lpos++]; while(Rpos <= rightEnd) Tmp[Tmppos++] = A[Rpos++]; for(i = 0; i < numElements; i++, rightEnd--) A[rightEnd] = Tmp[rightEnd]; } void Msort(elementType A[], elementType Tmp[], int Left, int Right) { int Center; if(Left < Right) { Center = (Left + Right) / 2; Msort(A, Tmp, Left, Center); Msort(A, Tmp, Center + 1, Right); Merge(A, Tmp, Left, Center + 1, Right); } } void mergeSort(elementType A[], int N) { elementType *Tmp; Tmp = malloc(N * sizeof(elementType)); if(Tmp != NULL) { Msort(A, Tmp, 0, N - 1); free(Tmp); } else printf("No space for Tmp array"); } void main() { int i; int a[] = {81,94,11,96,12,35,17,95,28,58,41,75,15}; mergeSort(a, 13); for(i = 0; i < 13; i++) printf("%d ", a[i]); }
快速排序
书上的一种以三数中值作为基准值的快速排序,当数组长度小于3的时候选用插入排序,用的递归实现。
#include<stdio.h> #define Cutoff 3 void Swap(int *a, int *b) { int Tmp = *a; *a = *b; *b = Tmp; } void insertionSort(int A[], int N) { int tmp; int j, p; for( p = 1; p < N; p++ ) { tmp = A[p]; for( j = p; j > 0 && A[j -1] > tmp; j -- ) A[j] = A[j-1]; A[j] = tmp; } } 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]); Swap(&A[Center], &A[Right - 1]); return A[Right - 1]; } void Qsort(int A[], int Left, int Right) { int i, j, Pivot; if(Left + Cutoff <= Right) { i = Left; j = Right - 1; Pivot = Median3(A, Left, 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); } void quickSort(int A[], int N) { Qsort(A, 0, N - 1); } void main() { int i; int a[] = {81,94,11,96,12,35,17,95,28,58,41,75,15}; quickSort(a, 13); for(i = 0; i < 13; i++) printf("%d ", a[i]); }
下面是自己写的以第一个数作为基准值的快速排序。实际上这中基准值的选取是非常糟糕的,如果输入是预排序的或是反序的,第一次分割就总是分割到一边,时间复杂度为O(N^2),仅作为编程练习。
#include<stdio.h> #define Cutoff 3 void Swap(int *a, int *b) { int Tmp = *a; *a = *b; *b = Tmp; } void Qsort(int A[], int Left, int Right) { int i, j, Pivot; <span style="white-space:pre"> </span>if(Left < Right) { Pivot = A[Left]; i = Left; j = Right - 1; Swap(&A[Left], &A[Right]); while(i <= j) { while(A[i] < Pivot) i++; while(A[j] > Pivot) j--; if(i < j) { Swap(&A[i], &A[j]); i++; j--; } else break; } Swap(&A[i], &A[Right]); Qsort(A, Left, i - 1); Qsort(A, i + 1, Right); } } void quickSort(int A[], int N) { Qsort(A, 0, N - 1); } void main() { int i; int a[] = {81,94,11,96,12,35,17,95,28,58,41,75,15}; quickSort(a, 13); for(i = 0; i < 13; i++) printf("%d ", a[i]); }
//以第一个元素作为基准,单向划分的快速排序 #include<stdio.h> void Swap(int *a, int *b) { int Tmp = *a; *a = *b; *b = Tmp; } void Qsort(int A[], int Left, int Right) { int i, j, Pivot; if(Left >= Right) return; Pivot = A[Left]; int pos = Left; for(i = Left+1; i <= Right; ++i) { if(A[i] < Pivot) Swap(&A[++pos], &A[i]); } Swap(&A[pos], &A[Left]); Qsort(A, Left, pos - 1); Qsort(A, pos + 1, Right); } void quickSort(int A[], int N) { Qsort(A, 0, N - 1); }