本文涉及的七种排序算法有:冒泡排序、选择排序、插入排序、希尔排序、堆排序、归并排序、快速排序。
1、冒泡排序
从最后一位开始,相邻的数两两比较,小的换到前面,这样每一趟下来,最小的一个数就像气泡一样从水下漂浮上来。
//冒泡排序
void Sort_Bubble(int *p)
{
int i, j;
int temp;
for (i = 0; i < MAX_SIZE - 1; i++)
{
for (j = MAX_SIZE - 1; j > i; j--)
{
if (p[j] < p[j-1])
{
temp = p[j];
p[j] = p[j-1];
p[j-1] = temp;
}
}
}
}
2、选择排序
每次从剩下k个数中搜索到最小值,与当前位置的数交换,也就是不断地选择当前最小值。相比于冒泡排序,交换次数大大减少了。
//选择排序
void Sort_Select(int *p)
{
int i, j;
int k, min;
int temp;
for (i = 0; i < MAX_SIZE - 1; i++)
{
k = i;
min = p[i];
for (j = i + 1; j < MAX_SIZE; j++)
{
if (p[j] < min)
{
k = j;
min = p[j];
}
}
if (k != i)
{
temp = p[i];
p[i] = p[k];
p[k] = temp;
}
}
}
3、插入排序
第k位前面的k-1个数已经有序排列,将第k位这个数从k-1位开始依次与前面比较,直到找到合适的插入位置。相比于前两种算法,比较次数减少了不少。
//插入排序
void Sort_Insert(int *p)
{
int i, j;
int temp;
for (i = 1; i < MAX_SIZE; i++)
{
if (p[i] < p[i-1])
{
temp = p[i];
j = i - 1;
while (j >= 0 && p[j] > temp)
{
p[j+1] = p[j];
j--;
}
p[j+1] = temp;
}
}
}
4、希尔排序
希尔排序有点类似于插入排序,不过它不像插入排序那样每次一位一位地与前面比较,而是一开始大跨度地与前面比较和交换。这个跨度逐次递减,直到最终减为1。据说,跨度按照2^k-1的规律递减,效果会比较好,也就是……31、15、7、3、1。
//希尔排序
void Sort_Shell(int *p)
{
int i, j;
int k, temp;
k = 1;
while (k < MAX_SIZE)
{
k *= 2;
}
k = k/2 - 1; //确定初始的间隔k=2^n-1
while (k > 0)
{
for (i = k; i < MAX_SIZE; i++)
{
if (p[i] < p[i-k])
{
temp = p[i];
for (j = i-k; j >= 0 && p[j] > temp; j -= k)
{
p[j+k] = p[j];
}
p[j+k] = temp;
}
}
k = (k+1)/2 - 1; //间隔按2^n-1规律递减到1
}
}
5、堆排序
另外,完全二叉树还有一条性质在这里非常有用:如果根节点编号从1开始,那么编号为k的节点,左孩子(如果有的话)编号是2k,右孩子(如果有的话)编号为2k+1。
堆排序的基本原理是,把乱序数列看作一棵完全二叉树,先调整为大顶堆,此时根节点就是最大数,交换根节点与最后一个节点,再把之前n-1个节点调整为大顶堆,重复这个过程。
堆排序有点类似于选择排序,不过在每次选择出最大值的同时,也把中间数字的顺序整理了一下,这样以后的调整会快很多。
//堆排序
void Sort_Heap(int *p)
{
int i;
int temp;
//编号为MAX_SIZE/2之前的节点都不是叶子,从下向上调整
for (i = MAX_SIZE/2; i > 0; i--)
{
BigTopHeap(p, i, MAX_SIZE);
}
for (i = MAX_SIZE; i > 1; i--)
{
temp = p[i-1];
p[i-1] = p[0];
p[0] = temp;
BigTopHeap(p, 1, i-1);
}
}
//构造大顶堆,树根从1开始编号
//所以,数组编号 = 树节点编号 - 1
void BigTopHeap(int *p, int root, int last)
{
int k;
int temp;
temp = p[root-1];
for (k = 2*root; k <= last; k*=2)
{
if (k < last && p[k-1] < p[k]) //左小于右,则转到右
{
k++;
}
if (p[k-1] <= temp) //后面没有大于temp的,则退出循环
{
break;
}
p[root-1] = p[k-1]; //大数移到上一层
root = k; //当前作为树根
}
p[root-1] = temp;
}
6、归并排序
归并排序的基本原理就是把数列不断地拆分成小段,直到一个个数字独立构成一段。然后再不断地把这些小段在合并中排序,最终形成一个有序数列。如下图所示。
//归并排序
void Sort_Merge(int *p)
{
int i;
int q[MAX_SIZE];
i = 1;
while (i < MAX_SIZE)
{
MergeProcess(q, p, i, MAX_SIZE);
i *= 2;
MergeProcess(p, q, i, MAX_SIZE);
i *=2;
}
}
//归并的过程,将相邻两段归并为一段
//数组p分段有序,间隔为i,总长为n,归并到数组q
void MergeProcess(int *q, int *p, int i, int n)
{
int j;
j = 0;
while (j + 2*i < n) //两两合并前面的段,剩下不超过完整的两段
{
Merge(q, p, j, j+i, j+2*i-1);
j += 2*i;
}
if (j + i < n) //剩下两段,其中一段长度完整,一段不完整,也合并它们
{
Merge(q, p, j, j+i, n-1);
}
else //只剩下一段,直接搬下来
{
while (j < n)
{
q[j] = p[j];
j++;
}
}
}
//合二为一:数组p分两段,各自有序,合一为数组q
//left是第1段起始下标,mid是第2段起始下标,end是结束下标
void Merge(int *q, int *p, int first, int second, int end)
{
int i, j, k;
i = first;
j = second;
k = first;
//分别比较两段的最左端数字
while (i < second && j <= end)
{
if (p[i] < p[j])
{
q[k] = p[i];
i++;
}
else
{
q[k] = p[j];
j++;
}
k++;
}
//把有剩余数字的一段复制到末尾
if (i < second)
{
while (i < second)
{
q[k] = p[i];
k++;
i++;
}
}
if (j <= end)
{
while (j <= end)
{
q[k] = p[j];
k++;
j++;
}
}
}
7、快速排序
快速排序的基本原理是,在数列中找到一个数k和一个位置p,使得第一趟比较和交换之后,位置p左边的数都小于等于k,位置p右边的数都大于等于k,之后对左右两边分别重复该过程即可。
//快速排序
void Sort_Quick(int *p)
{
Quick(p, 0, MAX_SIZE - 1);
}
//快速排序递归函数
void Quick(int *p, int left, int right)
{
int pos;
if (left < right)
{
pos = Partition(p, left, right); //获得分割点
Quick(p, left, pos - 1); //对左半部分快速排序
Quick(p, pos + 1, right); //对右半部分快速排序
}
}
//分割数组,并获得分割点位置
int Partition(int *p, int left, int right)
{
int key, temp;
key = p[left];
while (left < right)
{
while (left < right && p[right] >= key)
{
right--;
}
temp = p[left];
p[left] = p[right];
p[right] = temp;
while (left < right && p[left] <= key)
{
left++;
}
temp = p[left];
p[left] = p[right];
p[right] = temp;
}
return left;
}
8、运行结果
取0到9999的一个随机排列,共10000个数,分别用以上七种算法进行排序,验证排序是否正确,并且得到算法消耗的时间。如下图所示。
9、时间性能统计
冒泡排序、选择排序、插入排序这三种属于简单排序算法,它们的时间复杂度均为O(n^2)。希尔排序、堆排序、归并排序、快速排序这四种属于高级排序算法,希尔排序的时间复杂度为O(n^(3/2)),其他三种算法的时间复杂度为O(nlogn)。
通过试验统计各种排序算法所消耗的时间,结果与上面的理论吻合。
10、全部代码
#include <STDIO.H>
#include <STDLIB.H>
#include <TIME.H>
#define MAX_SIZE 10000 //数字个数
#define EXE_TIME 100 //试验重复次数
void RandomArrange(int *);
void SimpleSort(int *, void (*)(int *), const char *);
void HighSort(int *, void (*)(int *), const char *, int);
void CopyArray(int *, int *);
void ShowResult(int *, const char *, double);
bool IsSortCorrect(int *);
void Sort_Bubble(int *);
void Sort_Select(int *);
void Sort_Insert(int *);
void Sort_Shell(int *);
void Sort_Heap(int *);
void BigTopHeap(int *, int, int);
void Sort_Merge(int *);
void MergeProcess(int *, int *, int, int);
void Merge(int *, int *, int, int, int);
void Sort_Quick(int *);
void Quick(int *, int, int);
int Partition(int *, int, int);
int main(void)
{
int num[MAX_SIZE];
//生成随机数
RandomArrange(num);
//简单排序
SimpleSort(num, Sort_Bubble, "冒泡排序");
SimpleSort(num, Sort_Select, "选择排序");
SimpleSort(num, Sort_Insert, "插入排序");
//高级排序
HighSort(num, Sort_Shell, "希尔排序", EXE_TIME);
HighSort(num, Sort_Heap, "堆排序", EXE_TIME);
HighSort(num, Sort_Merge, "归并排序", EXE_TIME);
HighSort(num, Sort_Quick, "快速排序", EXE_TIME);
return 0;
}
//生成0到MAX-1的随机排列
void RandomArrange(int *p)
{
int i, j;
int temp;
srand(time(0));
for (i = 0; i < MAX_SIZE; i++)
{
p[i] = i;
}
for (i = 0; i < MAX_SIZE; i++)
{
j = rand() % MAX_SIZE;
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
printf("已经生成包含%d个数字的随机序列。\n\n", MAX_SIZE);
}
//调用低级排序方法
//p是数组指针,pf是排序函数的指针,q是排序方法字符串
void SimpleSort(int *p, void (*pf)(int *), const char *q)
{
int a[MAX_SIZE];
clock_t start, end;
double sortTime;
printf("%s正在进行...\n", q);
start = clock();
CopyArray(a, p);
pf(a);
end = clock();
sortTime = (double)(end - start);
ShowResult(a, q, sortTime);
}
//调用高级排序方法
//p是数组指针,pf是排序函数的指针,q是排序方法字符串,n是重复试验次数
void HighSort(int *p, void (*pf)(int *), const char *q, int n)
{
int i;
int a[MAX_SIZE];
clock_t start, end, copyTime, totolTime;
double sortTime;
i = 0;
start = clock();
while (i < n)
{
CopyArray(a, p);
i++;
}
end = clock();
copyTime = end - start; //复制数组的时间
printf("%s正在进行...\n", q);
i = 0;
start = clock();
while (i < n)
{
CopyArray(a, p);
pf(a);
i++;
}
end = clock();
totolTime = end - start; //总时间
sortTime = (double)(totolTime - copyTime)/n; //实际排序时间
ShowResult(a, q, sortTime);
}
//把数组q复制到数组p
void CopyArray(int *p, int *q)
{
int i;
for (i = 0; i < MAX_SIZE; i++)
{
p[i] = q[i];
}
}
//输出结论
void ShowResult(int *p, const char *q, double t)
{
printf("%s", q);
if (IsSortCorrect(p))
{
printf("已经完成,耗时 %g 毫秒。\n\n", t);
}
else
{
printf("出现错误。\n\n");
}
}
//检查排序结果是否正确
bool IsSortCorrect(int *p)
{
int i = 0;
while (i < MAX_SIZE && p[i] == i)
{
i++;
}
if (i == MAX_SIZE)
{
return true;
}
else
{
return false;
}
}
//冒泡排序
void Sort_Bubble(int *p)
{
int i, j;
int temp;
for (i = 0; i < MAX_SIZE - 1; i++)
{
for (j = MAX_SIZE - 1; j > i; j--)
{
if (p[j] < p[j-1])
{
temp = p[j];
p[j] = p[j-1];
p[j-1] = temp;
}
}
}
}
//选择排序
void Sort_Select(int *p)
{
int i, j;
int k, min;
int temp;
for (i = 0; i < MAX_SIZE - 1; i++)
{
k = i;
min = p[i];
for (j = i + 1; j < MAX_SIZE; j++)
{
if (p[j] < min)
{
k = j;
min = p[j];
}
}
if (k != i)
{
temp = p[i];
p[i] = p[k];
p[k] = temp;
}
}
}
//插入排序
void Sort_Insert(int *p)
{
int i, j;
int temp;
for (i = 1; i < MAX_SIZE; i++)
{
if (p[i] < p[i-1])
{
temp = p[i];
j = i - 1;
while (j >= 0 && p[j] > temp)
{
p[j+1] = p[j];
j--;
}
p[j+1] = temp;
}
}
}
//希尔排序
void Sort_Shell(int *p)
{
int i, j;
int k, temp;
k = 1;
while (k < MAX_SIZE)
{
k *= 2;
}
k = k/2 - 1; //确定初始的间隔k=2^n-1
while (k > 0)
{
for (i = k; i < MAX_SIZE; i++)
{
if (p[i] < p[i-k])
{
temp = p[i];
for (j = i-k; j >= 0 && p[j] > temp; j -= k)
{
p[j+k] = p[j];
}
p[j+k] = temp;
}
}
k = (k+1)/2 - 1; //间隔按2^n-1规律递减到1
}
}
//堆排序
void Sort_Heap(int *p)
{
int i;
int temp;
//编号为MAX_SIZE/2之前的节点都不是叶子,从下向上调整
for (i = MAX_SIZE/2; i > 0; i--)
{
BigTopHeap(p, i, MAX_SIZE);
}
for (i = MAX_SIZE; i > 1; i--)
{
temp = p[i-1];
p[i-1] = p[0];
p[0] = temp;
BigTopHeap(p, 1, i-1);
}
}
//构造大顶堆,树根从1开始编号
//所以,数组编号 = 树节点编号 - 1
void BigTopHeap(int *p, int root, int last)
{
int k;
int temp;
temp = p[root-1];
for (k = 2*root; k <= last; k*=2)
{
if (k < last && p[k-1] < p[k]) //左小于右,则转到右
{
k++;
}
if (p[k-1] <= temp) //后面没有大于temp的,则退出循环
{
break;
}
p[root-1] = p[k-1]; //大数移到上一层
root = k; //当前作为树根
}
p[root-1] = temp;
}
//归并排序
void Sort_Merge(int *p)
{
int i;
int q[MAX_SIZE];
i = 1;
while (i < MAX_SIZE)
{
MergeProcess(q, p, i, MAX_SIZE);
i *= 2;
MergeProcess(p, q, i, MAX_SIZE);
i *=2;
}
}
//归并的过程,将相邻两段归并为一段
//数组p分段有序,间隔为i,总长为n,归并到数组q
void MergeProcess(int *q, int *p, int i, int n)
{
int j;
j = 0;
while (j + 2*i < n)
{
Merge(q, p, j, j+i, j+2*i-1);
j += 2*i;
}
if (j + i < n)
{
Merge(q, p, j, j+i, n-1);
}
else
{
while (j < n)
{
q[j] = p[j];
j++;
}
}
}
//合二为一:数组p分两段,各自有序,合一为数组q
//left是第1段起始下标,mid是第2段起始下标,end是结束下标
void Merge(int *q, int *p, int first, int second, int end)
{
int i, j, k;
i = first;
j = second;
k = first;
//分别比较两段的最左端数字
while (i < second && j <= end)
{
if (p[i] < p[j])
{
q[k] = p[i];
i++;
}
else
{
q[k] = p[j];
j++;
}
k++;
}
//把有剩余数字的一段复制到末尾
if (i < second)
{
while (i < second)
{
q[k] = p[i];
k++;
i++;
}
}
if (j <= end)
{
while (j <= end)
{
q[k] = p[j];
k++;
j++;
}
}
}
//快速排序
void Sort_Quick(int *p)
{
Quick(p, 0, MAX_SIZE - 1);
}
//快速排序递归函数
void Quick(int *p, int left, int right)
{
int pos;
if (left < right)
{
pos = Partition(p, left, right); //获得分割点
Quick(p, left, pos - 1); //对左半部分快速排序
Quick(p, pos + 1, right); //对右半部分快速排序
}
}
//分割数组,并获得分割点位置
int Partition(int *p, int left, int right)
{
int key, temp;
key = p[left];
while (left < right)
{
while (left < right && p[right] >= key)
{
right--;
}
temp = p[left];
p[left] = p[right];
p[right] = temp;
while (left < right && p[left] <= key)
{
left++;
}
temp = p[left];
p[left] = p[right];
p[right] = temp;
}
return left;
}