综述由AI生成使用 C 语言实现的八大排序算法,包括插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序(含 Hoare、挖坑法、前后指针法及非递归实现)、归并排序(递归与非递归)以及计数排序。内容涵盖各算法的基本思想、稳定性、时间复杂度与空间复杂度分析,并提供了完整的代码示例及优化策略(如三数取中、小区间优化)。旨在帮助读者深入理解排序原理并掌握实际编码实现。
宁静27 浏览
前言
到这里,我们已经完成顺序表、链表、栈、队列、堆以及链式二叉树的学习,现在我们开始利用 C 语言实现八大排序算法,同时会附上详细讲解说明。
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成对应个组,所有距离为 gap 的记录分在同一组内,并对每一组内的记录进行排序。然后,取 gap=gap/3+1,重复上述分组和排序的工作。当到达 gap=1 时,所有记录在统一组内排好序。
希尔排序特性总结:
1.希尔排序是对直接插入的优化
2.当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比
希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定,我们常用的是令 gap=gap/3+1;
问题:为什么要让 gap 由大到小呢?
answer:gap 越大,数据挪动得越快;gap 越小,数据挪动得越慢。前期让 gap 较大,可以让数据更快得移动到自己对应的位置附近,减少挪动次数。
我们先尝试进行一次 gap 分组排序:
int gap = 5;
for (int i = 0;i < n - gap;i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
这是令 gap=5,进行的一次排序,那我们只需要再加上一次循环,保证 gap 最后为 1,就可以实现希尔排序了,以下是完整代码:
voidShellSort(int* arr, 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 = arr[end + gap];
for (end;end >= 0;end -= gap)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
希尔排序的时间复杂度很难准确计算出来,大概是 O(N^1.3),不稳定
3. 选择排序
3.1 直接选择排序
选择排序,即每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。
代码展示:
voidSelectSort(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int pmin = begin;
for (int i = begin;i <n;i++)
{
if (arr[i] < arr[pmin])
{
pmin = i;
}
}
Swap(&arr[begin], &arr[pmin]);
begin++;
}
}
相遇的场景分析:
L 遇 R:R 先走,停下来,R 停下条件是遇到比 key 小的值,R 停的位置一定比 key 小,L 没有找到大的,遇到 R 停下了
R 先走,找小,没有找到比 key 小的,直接跟 L 相遇了。L 停留的位置 R 遇 L:是上一轮交换的位置,上一轮交换,把比 key 小的值,换到 L 的位置了
我们不一定要求以左为 key,但是一定要保证以哪边为 key,相反的一边先走。
代码展示:
voidQuickSort(int* arr, int left, int right)
{
if (left >= right) return;
int pkey = left;
int begin = left, end = right;
while (begin < end)
{
while (begin<end && arr[end]>=arr[pkey])
{
end--;
}
while (begin<end && arr[begin]<=arr[pkey])
{
begin++;
}
Swap(&arr[begin], &arr[end]);
}
Swap(&arr[begin], &arr[pkey]);
QuickSort(arr, left, begin - 1);
QuickSort(arr, begin+1,right);
}
voidQuickSort(int* arr, int left, int right)
{
if (left >= right) return;
int pkey = Getmidnum(arr,left,right);
Swap(&arr[pkey], &arr[left]);
pkey = left;
int begin = left, end = right;
if (end - begin + 1 < 10)
{
InsertSort(arr + left, end - begin + 1);
}
else
{
while (begin < end)
{
while (begin < end && arr[end] >= arr[pkey])
{
end--;
}
while (begin < end && arr[begin] <= arr[pkey])
{
begin++;
}
Swap(&arr[begin], &arr[end]);
}
Swap(&arr[begin], &arr[pkey]);
QuickSort(arr, left, begin - 1);
QuickSort(arr, begin + 1, right);
}
}
4.2.2 挖坑法
挖坑法的单趟排序的基本步骤如下:
1、选出一个数据(一般是最左边或是最右边的)存放在 key 变量中,在该数据位置形成一个坑。
2、还是定义一个 L 和一个 R,L 从左向右走,R 从右向左走。(若在最左边挖坑,则需要 R 先走;若在最右边挖坑,则需要 L 先走)。
3、在走的过程中,若 R 遇到小于 key 的数,则将该数抛入坑位,并在此处形成一个坑位,这时 L 再向后走,若遇到大于 key 的数,则将其抛入坑位,又形成一个坑位,如此循环下去,直到最终 L 和 R 相遇,这时将 key 抛入坑位即可。(选取最左边的作为坑位)
voidCountSort(int* arr, int n)
{
int min = arr[0], max = arr[0];
for (int i = 0;i < n;i++)
{
if (min > arr[i]) min = arr[i];
if (max < arr[i]) max = arr[i];
}
int range = max - min + 1;
int* count = (int*)calloc(range, sizeof(int));
for (int i = 0;i < n;i++)//把 arr 中出现的数字进行计数,数字与下标的差值为 min
{
count[arr[i] - min]++;//映射
}
int j = 0;
for (int i = 0;i < range;i++)
{
while(count[i]--)
{
arr[j++] = i + min;
}
}
free(count);
count = NULL;
}