本帖依据学习进度持续更新
《数据结构与算法分析-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);
}