冒泡排序的基本原理
冒泡排序是一种简单的排序算法,通过重复地遍历待排序的列表,比较相邻的元素并交换它们的位置来完成排序。每一轮遍历会将当前未排序部分的最大(或最小)元素"冒泡"到正确的位置。
算法步骤
- 比较相邻元素:从列表的第一个元素开始,依次比较相邻的两个元素。
- 交换位置:如果前一个元素大于后一个元素(升序排序),则交换它们的位置。
- 重复遍历:对列表的未排序部分重复上述过程,直到列表完全排序。
时间复杂度分析
- 最坏情况:当列表是逆序排列时,需要进行 (O(n^2)) 次比较和交换。
- 平均情况:冒泡排序的平均时间复杂度也是 (O(n^2))。
- 最好情况:当列表已经有序时,只需进行一次遍历,时间复杂度为 (O(n))。
空间复杂度分析
冒泡排序是原地排序算法,仅需要常数级别的额外空间用于交换元素,因此空间复杂度为 (O(1))。
稳定性
冒泡排序是稳定的排序算法,因为相等的元素不会改变相对顺序。
代码实现
cpp
// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = n; i > 0; i--)
{
int flag = 0;
for (int j = 0; j < i-1; j++)
{
if (a[j] > a[j + 1])
{
swap(&a[j], &a[j + 1]);
flag = 1;
}
}
if (flag = 0)
{
break;
}
}
}
优化方法
- 提前退出:如果在某一轮遍历中没有发生任何交换,说明列表已经有序,可以提前终止排序。
- 记录最后一次交换位置:记录每轮遍历中最后一次交换的位置,下一轮遍历只需比较到该位置即可。
适用场景
冒泡排序由于其简单性,适用于小规模数据的排序或教学示例。对于大规模数据,更高效的排序算法(如快速排序或归并排序)更为合适。
选择排序的基本概念
选择排序是一种简单直观的排序算法。其核心思想是通过不断选择未排序部分的最小(或最大)元素,将其放到已排序部分的末尾,直到所有元素排序完成。时间复杂度为 O(n²),适用于小规模数据排序。
算法步骤
- 初始化:将数组分为已排序和未排序两部分,初始时已排序部分为空。
- 查找最小值:遍历未排序部分,找到最小元素的索引。
- 交换元素:将最小元素与未排序部分的第一个元素交换,将其纳入已排序部分。
- 重复操作:重复上述步骤,直到未排序部分为空。
代码实现
cpp
// 选择排序
void SelectSort(int* a, int n)
{
for (int i = 0; i < n / 2; i++)
{
int max = a[n - i - 1];
int min = a[i];
int MAX = n - i - 1, MIN = i;
for (int j = i; j < n - i; j++)
{
if (a[j] > max)
{
max = a[j];
MAX = j;
}
if (a[j] < min)
{
min = a[j];
MIN = j;
}
}
if (MAX == MIN)
{
break;
}
int first = a[i];
int last = a[n - i - 1];
a[MAX] = first;
a[MIN] = a[i];
a[n - i - 1] = max;
a[i] = min;
}
}
时间复杂度分析
- 最好情况 :数组已有序,仍需比较 n(n-1)/2 次,时间复杂度为 O(n²)。
- 最坏情况 :数组逆序,比较和交换次数与最好情况相同,时间复杂度为 O(n²)。
- 平均情况 :时间复杂度稳定为 O(n²)。
空间复杂度
选择排序是原地排序算法,仅需常数级额外空间,空间复杂度为 O(1)。
稳定性
选择排序是非稳定排序算法。例如,数组 [5, 5, 2] 在排序后可能改变相同元素的相对顺序。
优缺点
优点:
- 实现简单,代码易于理解。
- 不占用额外内存空间。
缺点:
- 时间复杂度较高,不适合大规模数据。
- 非稳定排序,可能影响某些应用场景。
适用场景
适用于数据量较小且对稳定性无要求的场景,如教学示例或简单嵌入式系统。
插入排序分析
插入排序是一种简单的排序算法,适用于小规模数据或部分有序的数据。其核心思想是将数组分为已排序和未排序两部分,逐个将未排序元素插入到已排序部分的正确位置。
代码实现中,外层循环从第二个元素开始遍历数组。内层循环将当前元素与已排序部分的元素逐一比较,若已排序元素较大则后移一位,直到找到合适位置插入。
代码实现
cpp
// 插入排序
void InsertSort(int* a, int n)
{
int i = 0;
for (i = 0 ; i < n - 1; i++)
{
int temp = a[i + 1];
int j = 0;
for (j = i; j >= 0 ; j--)
{
if (a[j] > temp)
{
a[j + 1] = a[j];
}
else
{
break;
}
}
a[j + 1] = temp;
}
}
时间复杂度:
- 最坏情况:数组逆序,需完整比较和移动,时间复杂度为 O(n\^2)。
- 最佳情况:数组已有序,仅需比较无需移动,时间复杂度为 O(n)。
空间复杂度为 O(1),属于原地排序算法。插入排序是稳定的排序方法,相同元素的相对位置不会改变。
希尔排序分析
希尔排序是插入排序的改进版本,通过引入间隔(gap)对数据进行分组预处理,使数据逐步趋于有序,最终进行一次插入排序。该方法显著提升了插入排序在处理大规模数据时的效率。
代码中采用动态间隔策略,初始间隔为数组长度,每次循环按 gap = gap / 3 + 1 缩减间隔。每个间隔下对分组数据进行类似插入排序的操作,但比较和移动的步长变为当前间隔值。
代码实现
cpp
// 希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
int i = 0;
for (i = 0; i < n - gap; i++)
{
int temp = a[i + gap];
int j = 0;
for (j = i; j >= 0; j -= gap)
{
if (a[j] > temp)
{
a[j + gap] = a[j];
}
else
{
break;
}
}
a[j + gap] = temp;
}
}
}
时间复杂度:
- 平均时间复杂度取决于间隔序列的选择,通常为 O(n\^{1.3}) 到 O(n\^2)。
- 使用特定间隔序列可优化至 O(n \\log n)。
空间复杂度同样为 O(1)。希尔排序是不稳定的排序算法,分组处理可能导致相同元素的相对位置变化。
关键差异对比
效率:希尔排序通过分组预处理减少了数据移动次数,平均性能优于插入排序。
适用场景:插入排序适合小规模或基本有序数据;希尔排序更适合中等规模数据。
稳定性:插入排序稳定,希尔排序不稳定。
实现复杂度:希尔排序需设计间隔序列,实现略复杂于插入排序。
快速排序原理
快速排序(QuickSort)是一种基于分治思想的高效排序算法。通过选取一个基准元素(pivot),将数组分为两部分:小于基准的元素和大于基准的元素,然后递归地对子数组进行排序。
算法步骤
- 选择基准:从数组中选择一个元素作为基准(通常选择第一个、最后一个或随机元素)。
- 分区操作:重新排列数组,使得小于基准的元素位于左侧,大于基准的元素位于右侧。基准元素位于最终正确位置。
- 递归排序:对左右两个子数组重复上述过程,直到子数组长度为1或0。
时间复杂度分析
- 最优情况:每次分区均匀,时间复杂度为 (O(n \log n))。
- 最坏情况:每次分区极度不平衡(如数组已有序),时间复杂度为 (O(n^2))。
- 平均情况:通过随机化基准选择,平均时间复杂度为 (O(n \log n))。
空间复杂度
快速排序是原地排序算法,递归调用栈的深度影响空间复杂度:
- 最优空间复杂度:(O(\log n))(平衡分区)。
- 最坏空间复杂度:(O(n))(极端不平衡分区)。
代码实现
cpp
// 快速排序递归实现
//三数取中
int GetMidi(int* a, int left, int right)
{
int midi = left + (right - left) / 2;
if (a[left] > a[right])
{
if (a[left] > a[midi])
{
return (a[right] > a[midi] ? right : midi);
}
else if (a[left] < a[midi])
{
return left;
}
}
else if (a[left] < a[right])
{
if (a[left] > a[midi])
{
return left;
}
else if (a[left] < a[midi])
{
return (right < midi ? right : midi);
}
}
else
{
return left;
}
return 0;
}
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int key = a[left];
int begin = left;
int end = right;
while (begin <= end)
{
while (begin < end && a[end] > key)
{
end--;
}
while (begin < end && a[begin] <= key)
{
begin++;
}
if (begin != end)
{
swap(&a[begin], &a[end]);//较小项与较大项交换
}
else
{
swap(&a[begin], &a[left]);
break;
}
}
return begin;
}
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int begin = left;
int end = right;
while (begin <= end)
{
while (begin < end && a[end] >= key)
{
end--;
}
if (begin != end)
{
a[begin] = a[end];
}
else
{
a[begin] = key;
break;
}
while (begin < end && a[begin] <= key)
{
begin++;
}
if (begin != end)
{
a[end] = a[begin];
}
else
{
a[begin] = key;
break;
}
}
return begin;
}
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
int key = a[left];
int* p = a + left;
int* pre = p + 1;
while (pre <= a + right)
{
if (*pre <= key && ++p != pre)
{
swap(p, pre);
}
pre++;
}
swap(&a[left], p);
return (int)(p - a);
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return ;
}
if ((right - left + 1) < 10)//小区间优化
{
InsertSort(a + left, right - left + 1);
return;
}
int midi = GetMidi(a, left, right);//找中间值,使排序更像二分,减少递归次数
if (midi != left)
{
swap(&a[left], &a[midi]);
}
int keyi = PartSort1(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
#include"stack.h"
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
Stack ps;
StackInit(&ps);//初始化栈
StackPush(&ps, right);//入栈边界
StackPush(&ps, left);
while (!StackEmpty(&ps))
{
int begin = StackTop(&ps);
StackPop(&ps);
int end = StackTop(&ps);
StackPop(&ps);
int keyi = PartSort1(a, begin, end);
if (keyi + 1 < end)
{
StackPush(&ps, end);//入栈边界
StackPush(&ps, keyi + 1);
}
if (keyi - 1 > begin)
{
StackPush(&ps, keyi - 1);//入栈边界
StackPush(&ps, begin);
}
}
StackDestroy(&ps);
}
优化策略
- 随机化基准:避免最坏情况,随机选择基准元素。
- 三数取中法:选择首、中、尾三个元素的中值作为基准。
- 小数组切换插入排序:当子数组较小时(如长度≤10),使用插入排序减少递归开销。
稳定性与适用性
快速排序是不稳定的排序算法(相同元素可能交换位置),但因其平均效率高,常作为默认排序实现(如C++ qsort、Java Arrays.sort)。
堆排序的基本概念
堆排序是一种基于二叉堆数据结构的比较排序算法。二叉堆是一种完全二叉树,可以分为最大堆和最小堆。最大堆中每个父节点的值大于或等于其子节点的值,最小堆中每个父节点的值小于或等于其子节点的值。堆排序通常使用最大堆来实现升序排序。
堆排序的步骤
构建最大堆:将无序数组转换为最大堆。从最后一个非叶子节点开始,依次向上调整,确保每个子树都满足最大堆的性质。
交换堆顶与末尾元素:将堆顶元素(最大值)与数组末尾元素交换,并将堆的大小减一。
调整堆:对新的堆顶元素进行下沉操作,使其重新满足最大堆的性质。
重复上述交换和调整步骤:直到堆的大小为1,此时数组已经有序。
堆排序的时间复杂度
堆排序的时间复杂度为O(n log n)。构建堆的时间复杂度为O(n),每次调整堆的时间复杂度为O(log n),共需要进行n-1次调整。
堆排序的空间复杂度
堆排序是一种原地排序算法,空间复杂度为O(1)。不需要额外的存储空间,除了递归调用栈的空间(如果使用递归实现)。
堆排序的稳定性
堆排序不是稳定的排序算法。在交换堆顶元素和末尾元素的过程中,可能会改变相同元素的相对顺序。
堆排序的代码实现
cpp
// 堆排序
void AdjustDwon(int* a, int n, int root)//堆的向下调整
{
int fath = root;
int child = fath * 2 + 1;
while (child < n)
{
if (a[child] > a[child + 1] && child + 1 < n)
{
child++;
}
if (a[child] < a[fath])
{
swap(&a[child], &a[fath]);
fath = child;
child = fath * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
int first = n/2 - 1;
for (int i = first; i >= 0; i--)//建堆
{
AdjustDwon(a, n, i);
}
for (int i = 0; i < n - 1; i++)//依次遍历节点,向下调整堆,使节点成为极值
{
AdjustDwon(&a[i], n-i, 0);
}
}
stack.h
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
stack.c
cpp
#include"stack.h"
// 初始化栈
void StackInit(Stack* ps)
{
ps->_a = NULL;
ps->_top = 0;
ps->_capacity = 0;
}
//检查顺序表是否满栈,并申请空间
void STChack(Stack* ps)
{
if (ps->_top == ps->_capacity)
{
STDataType newmemory = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
STDataType* str = (STDataType*)realloc(ps->_a, newmemory * sizeof(STDataType));
if (str == NULL)
{
perror("realloc false!");
exit(1);
}
ps->_a = str;
ps->_capacity = newmemory;
}
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
if (!ps)
{
perror("StackPush()::ps == NULL");
return;
}
STChack(ps);
ps->_a[ps->_top] = data;
int flag = ps->_a[ps->_top];
ps->_top++;
}
// 出栈
void StackPop(Stack* ps)
{
if (!ps)
{
perror("StackPop()::ps == NULL");
return;
}
if (ps->_a == NULL || ps->_top == 0)
{
perror("空栈!");
return;
}
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
if (!ps)
{
perror("StackTop()::ps == NULL");
return 0;
}
if (ps->_a == NULL || ps->_top == 0)
{
perror("空栈!");
return 0;
}
int flag = ps->_a[ps->_top - 1];
return ps->_a[ps->_top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
return ps->_top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
free(ps->_a);
ps->_a = NULL;
ps->_top = 0;
ps->_capacity = 0;
}
堆排序的优缺点
优点:时间复杂度稳定为O(n log n),适合大规模数据排序。原地排序,空间效率高。
缺点:不稳定排序,不适合需要稳定性的场景。缓存局部性较差,因为堆排序的访问模式跳跃性较大。
堆排序的应用场景
堆排序适用于需要高效排序且对稳定性要求不高的场景。例如,操作系统中的任务调度、优先级队列的实现等。
归并排序的基本原理
归并排序是一种基于分治策略的稳定排序算法。通过递归地将数组分成两半,分别排序后再合并两个有序子数组,最终完成整体排序。其核心操作是合并(Merge) ,时间复杂度为 O(n) ,而分治过程的递归深度为 O(\log n) ,因此总时间复杂度为 O(n \log n)。
算法步骤
-
分解
将当前数组从中间位置分为左右两个子数组,递归地对左右子数组继续分解,直到子数组长度为1(天然有序)。
-
合并
将两个已排序的子数组合并为一个有序数组:
- 创建一个临时数组存放合并结果。
- 使用双指针分别遍历左右子数组,选择较小元素放入临时数组。
- 将剩余未遍历的元素直接追加到临时数组末尾。
代码实现
cpp
// 归并排序递归实现
void _MergeSort(int* a, int* temp, int begin, int end)
{
if (begin >= end)
return;
int mid = (begin + end) / 2;
//递归
_MergeSort(a, temp, begin, mid);
_MergeSort(a, temp, mid + 1, end);
//归并
int num = begin;
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
{
temp[num++] = a[begin2++];
}
else
{
temp[num++] = a[begin1++];
}
}
while (begin2 <= end2)
{
temp[num++] = a[begin2++];
}
while (begin1 <= end1)
{
temp[num++] = a[begin1++];
}
for (int i = begin; i <= end; i++)
{
a[i] = temp[i];
}
}
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("MergeSort()::malloc");
return;
}
_MergeSort(a, temp, 0, n - 1);
free(temp);
temp = NULL;
}
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int)*n);
if (temp == NULL)
{
perror("MergeSort()::malloc");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += gap * 2)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
if (begin2 >= n)
{
break;
}
if (end2 >= n)
{
end2 = n - 1;
}
int num = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
{
temp[num++] = a[begin2++];
}
else
{
temp[num++] = a[begin1++];
}
}
while (begin2 <= end2)
{
temp[num++] = a[begin2++];
}
while (begin1 <= end1)
{
temp[num++] = a[begin1++];
}
for (int j = i; j <= end2; j++)
{
a[j] = temp[j];
}
}
gap *= 2;
}
}
时间复杂度分析
- 最优/最坏/平均情况 :均为 O(n \log n),分解和合并过程的时间复杂度稳定。
- 空间复杂度 :O(n),合并时需要额外临时数组存储结果。
稳定性与适用场景
- 稳定性:归并排序是稳定的,合并时相等元素的相对位置不变。
- 适用场景:适合链表排序或外部排序(数据量过大无法全部加载到内存时),但对小规模数据可能不如插入排序高效。
优化方向
- 小数组切换插入排序
当子数组规模较小时(如长度≤15),插入排序的常数因子更优。 - 避免频繁内存分配
预分配一个全局临时数组,减少合并时的内存开销。 - 自然归并排序
利用输入数据中已有的有序段(Run),减少不必要的合并操作。
与其他排序对比
- 快速排序 :平均 O(n \log n) ,但最坏 O(n^2);归并排序稳定但需要额外空间。
- 堆排序 :空间复杂度 O(1),但不稳定且缓存不友好。
计数排序的基本原理
计数排序是一种非比较排序算法,适用于整数数据且范围不大的情况。核心思想是通过统计每个元素出现的次数,然后根据统计结果将元素放回正确位置。
假设输入数组为 A,长度为 n,元素范围为 [0, k]。需要创建一个计数数组 C(长度为 k+1)和一个输出数组 B(长度为 n)。
计数排序的步骤
统计每个元素出现的次数,将结果存入计数数组 C。例如 C[i] 表示元素 i 在 A 中出现的次数。
对计数数组 C 进行前缀和计算,使得 C[i] 表示小于等于 i 的元素个数。这一步决定了元素的最终位置。
从后向前遍历原数组 A,根据 C 中的值将元素放入输出数组 B 的对应位置,并减少 C 中对应计数。
代码实现
cpp
// 计数排序
void CountSort(int* a, int n)
{
int max = a[0];
int min = a[0];
int i = 0;
for (i = 0; i < n; i++)
{
if (max < a[i])
{
max = a[i];
}
if (min > a[i])
{
min = a[i];
}
}
int* count = (int*)calloc((max - min + 1), sizeof(int));
if (count == NULL)
{
perror("calloc fail!");
return;
}
for (i = 0; i < n; i++)
{
count[a[i] - min]++;
}
int j = 0;
i = 0;
for (j = 0; j <= max - min; j++)
{
while (count[j]--)
{
a[i++] = j + min;
}
}
}
计数排序的时间复杂度
计数排序的时间复杂度为 O(n + k),其中 n 是输入数组长度,k 是数据范围。当 k 远小于 n 时,算法效率较高。
空间复杂度为 O(n + k),需要额外的计数数组和输出数组。
计数排序的适用场景
计数排序适合数据范围小且为整数的情况,例如年龄排序、成绩排序等。对于数据范围大或非整数数据,计数排序可能不适用。
test.c
cpp
#include"Sort.h"
void TestOP()
{
srand((unsigned int)time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (int*)malloc(sizeof(int) * N);
if (!a1 || !a2 || !a3 || !a4 || !a5 || !a6 || !a7) {
perror("malloc fail");
return;
}
for (int i = 0; i < N; ++i)
{
// 数据重复较少
a1[i] = rand() + i;
// 数据重复较多
//a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
//BubbleSort(a7, N);
CountSort(a7, N);
int end7 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("CountSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
void test()
{
int a[] = { 10 , 7 , 6 , 9 , 8 , 1 , 2 , 3 , 4 , 5 };
int n = sizeof(a) / sizeof(int);
BubbleSort(a, n);
InsertSort(a, n);
HeapSort(a, n);
SelectSort(a, n);
ShellSort(a, n);
PartSort1(a, 0, n - 1);
PartSort2(a, 0, n - 1);
PartSort3(a, 0, n - 1);
QuickSort(a, 0, n - 1);
QuickSortNonR(a, 0, n - 1);
MergeSort(a, n);
MergeSortNonR(a, n);
CountSort(a, n);
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
}
int main()
{
TestOP();
return 0;
}
代码功能分析
这段代码实现了一个排序算法的性能测试框架,主要包含两个测试函数 TestOP() 和 test(),以及主函数 main()。代码的核心目的是比较不同排序算法在相同数据集上的性能表现。
代码结构说明
内存分配与初始化
- 使用
malloc动态分配多个整数数组(a1到a7),每个数组大小为N(这里是 10000)。 - 检查内存分配是否成功,失败时输出错误信息并返回。
- 初始化数组数据:
a1[i] = rand() + i确保数据重复较少,注释掉的a1[i] = rand()会生成重复较多的数据。 - 其他数组
a2到a7复制a1的数据,确保所有排序算法处理相同的数据。
排序算法测试
- 使用
clock()记录每个排序算法的开始和结束时间,计算时间差作为性能指标。 - 测试的排序算法包括:
InsertSort(插入排序)ShellSort(希尔排序)SelectSort(选择排序)HeapSort(堆排序)QuickSort(快速排序)MergeSort(归并排序)CountSort(计数排序)
- 输出每种排序算法的耗时(毫秒)。
资源释放
- 使用
free释放动态分配的内存。
辅助测试函数 test()
- 测试一个小规模数组(10 个元素)的排序功能。
- 调用所有排序算法,验证其正确性。
- 输出排序后的数组。
使用注意事项
- 需要确保
Sort.h头文件中已实现所有调用的排序算法(如BubbleSort、InsertSort等)。 - 如果排序算法未完全实现,注释掉未实现的函数调用以避免编译错误。
- 可以通过修改
N的值测试不同数据规模下的性能。 - 切换
a1[i] = rand() + i和a1[i] = rand()可以测试数据重复性对排序性能的影响。
示例输出
运行 TestOP() 会输出类似以下结果(具体数值因硬件和数据而异):
InsertSort:120
ShellSort:15
SelectSort:300
HeapSort:10
QuickSort:5
MergeSort:8
CountSort:2
扩展建议
- 增加重复测试次数,取平均值以减少误差。
- 添加对稳定性、内存占用等指标的测试。
- 支持更大规模数据测试(如 100 万或 1000 万级别)。
