1 排序的概念
排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
2 插入排序
2.1直接插入排序
2.1.1 基本思想
把一个记录插入到已经排好的有序表中,从而得到一个新的有序表。 我们平时玩扑克牌时,理牌的方法就用到了直接插入排序的思想 假设我们手中现在有按顺序排好的 5,7,8三张牌,那么抽到6这张牌该放到哪个位置呢?当然是5和7的中间。此时5,6,7,8四张牌依旧是有序的。实际上,我们是将6依次和这三张牌作比较,发现6比7,8小,所以应该在它两前面,而6比5大,所以应该在5的后面,就这样确定了6的位置
2.1.2代码实现
我们先来看单趟排序 这里tmp为待插入的数,a[end]数组为有序的数组
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
单趟排序同样可以类比上文插扑克牌的过程,5,7,8即为有序数组,现在插入6,遇到7,8都比6大,所以7,8往后移动一个位置,遇到5,6比5大,插到5的后面。 单趟排序只是插入了一个数,但排序我们要使整个数组有序,即需要在单趟排序的外面再嵌套一层循环
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
因为直接插入排序的前提是数组已经是有序的,然后插入一个数,使数组依旧保持有序,所以为了满足这个要求,我们就假设待排序的数组中第一个数是有序的,然后插入第二个数,前两个数有序,插入第三个……前n-1个数有序,插入第n个数。直到所有的数都是有序的。
2.1.3 复杂度分析
Ⅰ时间复杂度 ①最好:要排序的数组本身就是有序的,那么只需要依次比较一遍,为O(n) ②最坏:要排序的数组是逆序的假设为{5,4,3,2,1},那么5需要移动4次到达它的位置,4移动3次,3移动2次,2移动1次,所以有n个数 T(n)=1+2+3+4+……+n-1。所以时间复杂度为O(n2)。 Ⅱ空间复杂度 开辟空间常数次,为O(1)。
2.2 希尔排序
我们前面所讲的直接插入排序只是在在数组基本有序的时候效率很高,在数组完全逆序的时候就显得没有那么有优势了。因此Shell对直接插入排序算法做了改进,提出了希尔排序。
2.2.1 基本思想
先选定一个整数gap,把待排序的所有记录分成几个组,距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后再选gap,重复上述分组和排序的工作。当gap=1时,进行直接插入排序。 假设gap=3,数组为{9,1,2,5,7,4,8,6,3,5}
分好组后 {9,5,8,5}为一组;{1,7,6}为一组;{2,4,3}为一组,对这三组分别进行组内排序 可以看出,一次分组排序后,虽然数组并不是完全有序的,但相对没排序之前是有改变的,相对较大的数在跑到了后面,相对较小的数跑到了前面。接下来再选gap,再进行组内排序,会使数组越来越接近有序,(就可以利用直接插入排序的优势)最后一次进行直接插入排序。
对于gap的选择 gap越大,大的数更快到后面,小的数可以更快到前面,但是越不接近有序。 gap越小,越接近有序
2.2.2 代码实现
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end=end-gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
2.2.3 复杂度分析
希尔排序的时间复杂度不好分析,因为每一次gap的取值都不一样,导致很难去计算。我们只需要记住一个大概值为O(n^1.3)
3 选择排序
基本思想 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
3.1 直接选择排序
3.1.1 基本方法
①遍历一遍数组,选出最大值和最小值 ②将最大值和最后一个元素交换位置;将最小值和第一个元素交换位置。 ③将最大的数和最小的数排除在外,在剩下的数中选出次小的数和次大的数。 ④重复以上操作,直到数组有序。
3.1.2 代码实现
先看单趟排序
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
if (a[i] > a[maxi])
maxi = i;
}
Swap(&a[begin], &a[mini]);
if (a[maxi] == a[begin])
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
接下来,要在剩下的区间内选出次大的数和次小的数,因此begin++,end- -确定新的区间
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
int mini = 0;
int maxi = 0;
while (begin < end)
{
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
if (a[i] > a[maxi])
maxi = i;
}
Swap(&a[begin], &a[mini]);
if (a[maxi] == a[begin])
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
end--;
begin++;
}
}
注:动图这里是每次只选出了一个数,而我们前面代码所实现的是每次选出两个数(最大的和最小的),性能相对比只选一个数有提升
3.1.3 复杂度分析
Ⅰ时间复杂度 无论数组是有序的还是无序的,都需要进行比较,第一次n个数进行比较,第二次n-2个数进行比较…… 所以T(n)=n+n-2+n-4+……+0 时间复杂度为O(n2) Ⅱ空间复杂度 O(1)
3.2 堆排序
点击这里看前面详解堆的博客
4 交换排序
基本思想 根据序列中两个关键值的比较结果来对换这两个记录在序列中的位置,使值较大的记录向序列的尾部移动,值较小的记录向序列的头部移动。
4.1 冒泡排序
4.1.1 基本方法
依次比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
4.1.2 代码实现
先是单趟排序
for (i = 1; i < n; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
}
}
进行第一趟冒泡排序,可以把最大的数交换到最后面,接着在剩下n-1个数中进行冒泡排序,把次大的数交换到后面。不断重复上述操作,直到数组有序。
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n - 1; j++)
{
int i;
int flag = 0;
for (i = 1; i < n - j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
想想添加flag标志的作用何在?
当需要交换的时候把flag置为1,如果一趟排序结束后,flag仍为0,说明没有发生交换,数组此时已经有序,就不需要进行后序的操作了,可以避免已经有序的情况下的无意义循环的判断,在性能上有一些提升。
4.1.3 复杂度分析
Ⅰ时间复杂度 最好:数组已经有序,只需进行一趟比较,比较为n-1次,所以为O(n)。 最坏:数组完全逆序,需要进行比较的次数为 (n-1)+(n-2)+……+1次 所以时间复杂度为O(n2)。 Ⅱ空间复杂度 O(1)
4.2 快速排序
基本思想:
任取待排序元素中的某元素作为基准值,按照该元素将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列分别重复该过程,直到所有元素都排列在相应位置上为止。
将区间按照基准值划分为左右两半部分的常见方式有
4.2.1 hoare版本
基本思路 ①选取最左边的数做key ②右边先走,找到比key小的数就停下来 ③左边走,找到比key大的数,然后左右交换。 ④右边继续走,找小;左边走找大,两者交换。直到两个相遇就停。 ⑤将key和此时相遇位置的数交换,此时一趟下来便可达到key左边的数都比key小,key右边的数都比key大。 我们先来康康动图
可以看出,一趟结束后,4左边的数都比4小,4右边的数都比4大,达到我们想要的结果
试着想一想,为什么我们选取左边的数做key,要右边的数先走,左边先走可不可以呢? 答案是不行,我们必须要保证left和right相遇的位置的值比key小,这样和key交换后,相遇位置的值跑到左边,左边的数都比key要小,符合要求。 小伙伴们如果没完全懂,请看下面的详细分析 第一种情况:right先走,找到小的数,right停下来,left走,遇到了right,此时相遇位置比key小。 第二种情况: right先走,right没有找到比key小的数便遇到了left,此时left要么在key的位置没动(极端情况,right遇到的数都比key大) 要么在上一轮交换后停下的位置(比key小)
如果左边先走,最后的结果会不符合要求
最后的结果,7在左边区间,但比4大,出现错误。综上所述 选取最左边的数当key,右边先走 选取最右边的数当key,左边先走 代码实现 单趟代码
void PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = left;
while (left < right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
}
单趟结束后,key把整个区间划分成了左区间[begin keyi-1],和右区间[keyi+1,end],如何让整个区间都有序呢?和二叉树那里类似,不断去递归调用左右区间,又会得到新的keyi,keyi又划分为左右区间……直到区间不存在或者只剩一个数,此时便是有序的。
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int key = PartSort1(a, begin, end);
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
int PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = left;
while (left < right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
4.2.2 挖坑法
基本思路 ①左边做key,形成一个坑位(挖坑,可以想象成把坑位上的数据移走) ②右边先走,找小,找到后把小的数填到左边的坑位上,此时右边又形成新的坑位。 ③左边走,找大,找到后把大的数填到右边的坑位,此时左边又形成新的坑位。 ④重复以上操作,直到左右相遇,把key填到相遇位置的坑位上 先来看动图 可以看出,挖坑法和hoare版本相比,并没有太多的改变,只不过是左边挖坑天然填的是小的数,右边挖坑天然填的是大的数
代码实现
int PartSort2(int* a, int begin, int end)
{
int key = a[begin];
int piti = begin;
while (begin < end)
{
while (begin < end && a[end] >= key)
{
end--;
}
a[piti] = a[end];
piti = end;
while (begin < end && a[begin] <= key)
{
begin++;
}
a[piti] = a[begin];
piti = begin;
}
a[piti] = key;
return piti;
}
接下来的递归调用和第一个版本一样
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int key = PartSort2(a, begin, end);
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
4.2.3 前后指针版本
基本思路 ①先选左边当key, prev指向key的位置,cur指向prev的后一个位置 ②cur先走,找比key小的数,找到后,先++prev,然后cur和prev所指向的数字交换位置 ③cur继续走,找小的数,直到走到末尾。此时prev所指向的数字和key交换位置。 先看动图 前面两次因为cur找到比key小的数的时候,prev++和cur指向的位置一样,所以不用交换。后面cur遇到比key大的数,cur继续走,而prev不变,一直指向的是比key小的数,二者之间就有了间距(实际上中间间隔的数就是比key大的数。),所以 cur再次遇到比key小的数的时候,prev需要往后移动一位,指向比key大的数,然后二者交换。cur指向末尾的时候,prev此时指向的还是比key小的数,所以最后一步prev和key交换位置。 概括来讲,前后指针法的本质就是把比key小的数往左边放,比key大的数往右边推。 代码实现
int PartSort3(int* a, int begin, int end)
{
int prev = begin;
int cur = begin + 1;
int keyi = begin;
while (cur <= end)
{
if (a[cur] < a[keyi]&&++prev!=cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int key = PartSort3(a, begin, end);
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
学完了快速排序的递归过程,我们当然也要来玩玩快速排序的非递归过程。
4.2.4 快速排序的非递归
我们采用栈来模拟快速排序的递归过程。先来回想下递归的过程,每次通过key把区间划分为左右两个子区间,然后左右两个子区间又通过key继续划分,直到区间内只剩下一个数或者区间不存在。那么既然是模拟递归的过程,当然也要体现划分区间的过程,即先把区间的头和尾入栈,区间出栈的时候单趟排序分割出子区间,分割好后的区间如果存在就要继续入栈,然后出栈划分子区间,直到栈为空。
void QuickSortNonR(int* a, int begin, int end)
{
ST ps;
StackInit(&ps);
StackPush(&ps, end);
StackPush(&ps, begin);
while (!StackEmpty(&ps))
{
int left=StackTop(&ps);
StackPop(&ps);
int right= StackTop(&ps);
StackPop(&ps);
int key = PartSort3(a, left, right);
if (right >key + 1)
{
StackPush(&ps, right);
StackPush(&ps, key+1);
}
if (left < key -1)
{
StackPush(&ps,key-1);
StackPush(&ps,left);
}
}
StackDestroy(&ps);
}
4.2.5 快速排序的复杂度分析
Ⅰ 时间复杂度 快速排序的时间性能取决于快速排序递归的深度 最好 每次key划分左右区间都划分的很均匀,
总共深度为logn次,每次有n个数进行排序,所以时间复杂度为 O(nlogn) 最坏 待排序的序列为正序或者逆序,每次划分都只能得到一个比上一次划分少一个记录的子序列。 所以T(n)=n+(n-1)+(n-2)+…+1 时间复杂度为O(n2)
Ⅱ 空间复杂度 空间复杂度主要是递归造成的栈空间的使用 最好 递归树的深度为log?n,所以空间复杂度为O(logn)。 最坏 待排序的序列为正序或者逆序,空间复杂度为O(n)。
4.2.6 快速排序优化
(1) 三数取中法选key 既然快速排序的时间复杂度取决于递归的深度,我们就尽可能的让递归的深度更少,即每次key划分的左右区间都很均匀
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
return mid;
else if (a[begin] < a[end])
return end;
else
return begin;
}
else
{
if (a[mid] > a[end])
return mid;
else if (a[begin] > a[end])
return end;
else
return begin;
}
}
(2) 递归到小的子区间时,可以考虑使用插入排序 因为小的子区间已经趋近有序,直接插入会更有优势 优化后,快速排序的最终版本
int PartSort3(int* a, int begin, int end)
{
int prev = begin;
int cur = begin + 1;
int keyi = begin;
int mid = GetMidIndex(a, begin, end);
Swap(&a[mid], &a[keyi]);
while (cur <= end)
{
if (a[cur] < a[keyi]&&++prev!=cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
if (end - begin > 10)
{
int key = PartSort3(a, begin, end);
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
else
InsertSort(a + begin, end - begin + 1);
}
5 归并排序
5.1 基本思想
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
5.2 递归实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid,tmp);
_MergeSort(a, mid+1, end,tmp);
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin,tmp + begin,(end - begin + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
5.3 非递归实现
思路 归并排序的前提是要把区间划分成只剩一个元素,然后开始归并排序,一个和一个归,归完后两个和两个归,然后四个和四个归…(相当于后序遍历),那么改成非递归的时候就不需要递归划分区间,我们直接从一个和一个归开始,然后两个和两个归,四个和四个归……直到区间完全有序 代码实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
int j = begin1;
int m = end2 - begin1 + 1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
tmp[j++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * m);
}
gap = gap * 2;
}
free(tmp);
}
5.4 复杂度分析
Ⅰ时间复杂度 每一趟排序都需要把n个记录扫描一遍,共需进行log?n次(每一次分割区间都是均分),所以时间复杂度为O(nlogn) Ⅱ空间复杂度 O(n+logn) n为额外开辟的数组空间,logn为递归调用的栈空间。
6 计数排序(非比较排序)
6.1 基本思想
统计每个数据出现的次数,根据统计结果将序列回写到原来的序列中。 局限性
①只适用于整数 ②当数据范围很大时,空间复杂度就会很高
计数排序更适用于数据范围较为集中的情况。但此时还需注意,如果数据范围为1000~1009时,我们还是要开辟从1-1009的空间,然后去统计每个数出现的次数吗?明显这样很浪费空间,所以我们采用相对映射法 即开辟的空间数为数组中最大数-最小数+1 每个数对应的映射位置不在是自己本身,而是本身-最小的数
6.2 代码实现
void CountSort(int* a, int n)
{
int min = a[0];
int max = a[0];
for (int i = 1; i < n; i++)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
printf("malloc fail\n");
return;
}
memset(count, 0, sizeof(int) * range);
for (int j = 0; j < n; j++)
{
count[a[j] - min]++;
}
int i = 0;
for (int k = 0; k < range; k++)
{
while (count[k]--)
{
a[i++] = k+ min;
}
}
}
7 排序算法复杂度及稳定性总结
稳定性是指:假定在待排序的记录序列中,存在多个具有相同的关键字记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的。
8 源代码
sort.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void PrintArray(int* a, int n);
void InsertSort(int* a, int n);
void ShellSort(int* a, int n);
void SelectSort(int* a, int n);
void BubbleSort(int* a, int n);
void QuickSort(int* a, int begin, int end);
void MergeSort(int* a, int n);
void CountSort(int* a, int n);
stack.h
#include <stdio.h>
#include<assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
bool StackEmpty(ST* ps);
int StackSize(ST* ps);
sort.c
#include"sort.h"
#include"stack.h"
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void PrintArray(int* a, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end=end-gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
int mini = 0;
int maxi = 0;
while (begin < end)
{
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
mini = i;
if (a[i] > a[maxi])
maxi = i;
}
Swap(&a[begin], &a[mini]);
if (a[maxi] == a[begin])
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
end--;
begin++;
}
}
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n - 1; j++)
{
int i;
int flag = 0;
for (i = 1; i < n - j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
break;
}
}
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
return mid;
else if (a[begin] < a[end])
return end;
else
return begin;
}
else
{
if (a[mid] > a[end])
return mid;
else if (a[begin] > a[end])
return end;
else
return begin;
}
}
int PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = left;
while (left < right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
int PartSort2(int* a, int begin, int end)
{
int key = a[begin];
int piti = begin;
while (begin < end)
{
while (begin < end && a[end] >= key)
{
end--;
}
a[piti] = a[end];
piti = end;
while (begin < end && a[begin] <= key)
{
begin++;
}
a[piti] = a[begin];
piti = begin;
}
a[piti] = key;
return piti;
}
int PartSort3(int* a, int begin, int end)
{
int prev = begin;
int cur = begin + 1;
int keyi = begin;
int mid = GetMidIndex(a, begin, end);
Swap(&a[mid], &a[keyi]);
while (cur <= end)
{
if (a[cur] < a[keyi]&&++prev!=cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
if (end - begin > 10)
{
int key = PartSort3(a, begin, end);
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
else
InsertSort(a + begin, end - begin + 1);
}
void QuickSortNonR(int* a, int begin, int end)
{
ST ps;
StackInit(&ps);
StackPush(&ps, end);
StackPush(&ps, begin);
while (!StackEmpty(&ps))
{
int left=StackTop(&ps);
StackPop(&ps);
int right= StackTop(&ps);
StackPop(&ps);
int key = PartSort3(a, left, right);
if (right >key + 1)
{
StackPush(&ps, right);
StackPush(&ps, key+1);
}
if (left < key -1)
{
StackPush(&ps,key-1);
StackPush(&ps,left);
}
}
StackDestroy(&ps);
}
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid,tmp);
_MergeSort(a, mid+1, end,tmp);
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin,tmp + begin,(end - begin + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
printf("malloc fail\n");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
if (end1 >= n || begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
int j = begin1;
int m = end2 - begin1 + 1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[j++] = a[begin1++];
}
else
tmp[j++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * m);
}
gap = gap * 2;
}
free(tmp);
}
void CountSort(int* a, int n)
{
int min = a[0];
int max = a[0];
for (int i = 1; i < n; i++)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
printf("malloc fail\n");
return;
}
memset(count, 0, sizeof(int) * range);
for (int j = 0; j < n; j++)
{
count[a[j] - min]++;
}
int i = 0;
for (int k = 0; k < range; k++)
{
while (count[k]--)
{
a[i++] = k+ min;
}
}
}
test.c
#include "sort.h"
void testInsertSort()
{
int a[]= { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
InsertSort(a, n);
PrintArray(a, n);
}
void testShellSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
ShellSort(a, n);
PrintArray(a, n);
}
void testSelectSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
SelectSort(a, n);
PrintArray(a, n);
}
void testBubbleSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
BubbleSort(a, n);
PrintArray(a, n);
}
void testQuickSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
QuickSort(a,0,n-1);
PrintArray(a, n);
QuickSortNonR(a, 0, n - 1);
PrintArray(a, n);
}
void testMergeSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
MergeSort(a,n);
PrintArray(a, n);
MergeSortNonR(a, n);
PrintArray(a, n);
}
void testCountSort()
{
int a[] = { 9, 1, 2, 5, 7, 4, 8, 6, 3, 5 };
int n = sizeof(a) / sizeof(int);
CountSort(a, n);
PrintArray(a, n);
}
int main()
{
testInsertSort();
testShellSort();
testSelectSort();
testBubbleSort();
testQuickSort();
testMergeSort();
testCountSort();
return 0;
}
stack.c
#include "stack.h"
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType));
ps->top = 0;
ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->top = 0;
ps->capacity = 0;
}
void StackPush(ST* ps,STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity=ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(int));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
|