排序算法原理与实现详解

冒泡排序的基本原理

冒泡排序是一种简单的排序算法,通过重复地遍历待排序的列表,比较相邻的元素并交换它们的位置来完成排序。每一轮遍历会将当前未排序部分的最大(或最小)元素"冒泡"到正确的位置。

算法步骤

  1. 比较相邻元素:从列表的第一个元素开始,依次比较相邻的两个元素。
  2. 交换位置:如果前一个元素大于后一个元素(升序排序),则交换它们的位置。
  3. 重复遍历:对列表的未排序部分重复上述过程,直到列表完全排序。

时间复杂度分析

  • 最坏情况:当列表是逆序排列时,需要进行 (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;
		}
	}
}

优化方法

  1. 提前退出:如果在某一轮遍历中没有发生任何交换,说明列表已经有序,可以提前终止排序。
  2. 记录最后一次交换位置:记录每轮遍历中最后一次交换的位置,下一轮遍历只需比较到该位置即可。

适用场景

冒泡排序由于其简单性,适用于小规模数据的排序或教学示例。对于大规模数据,更高效的排序算法(如快速排序或归并排序)更为合适。

选择排序的基本概念

选择排序是一种简单直观的排序算法。其核心思想是通过不断选择未排序部分的最小(或最大)元素,将其放到已排序部分的末尾,直到所有元素排序完成。时间复杂度为 O(n²),适用于小规模数据排序。

算法步骤

  1. 初始化:将数组分为已排序和未排序两部分,初始时已排序部分为空。
  2. 查找最小值:遍历未排序部分,找到最小元素的索引。
  3. 交换元素:将最小元素与未排序部分的第一个元素交换,将其纳入已排序部分。
  4. 重复操作:重复上述步骤,直到未排序部分为空。

代码实现

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. 选择基准:从数组中选择一个元素作为基准(通常选择第一个、最后一个或随机元素)。
  2. 分区操作:重新排列数组,使得小于基准的元素位于左侧,大于基准的元素位于右侧。基准元素位于最终正确位置。
  3. 递归排序:对左右两个子数组重复上述过程,直到子数组长度为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);
}

优化策略

  1. 随机化基准:避免最坏情况,随机选择基准元素。
  2. 三数取中法:选择首、中、尾三个元素的中值作为基准。
  3. 小数组切换插入排序:当子数组较小时(如长度≤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. 分解

    将当前数组从中间位置分为左右两个子数组,递归地对左右子数组继续分解,直到子数组长度为1(天然有序)。

  2. 合并

    将两个已排序的子数组合并为一个有序数组:

    • 创建一个临时数组存放合并结果。
    • 使用双指针分别遍历左右子数组,选择较小元素放入临时数组。
    • 将剩余未遍历的元素直接追加到临时数组末尾。

代码实现

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),合并时需要额外临时数组存储结果。

稳定性与适用场景

  • 稳定性:归并排序是稳定的,合并时相等元素的相对位置不变。
  • 适用场景:适合链表排序或外部排序(数据量过大无法全部加载到内存时),但对小规模数据可能不如插入排序高效。

优化方向

  1. 小数组切换插入排序
    当子数组规模较小时(如长度≤15),插入排序的常数因子更优。
  2. 避免频繁内存分配
    预分配一个全局临时数组,减少合并时的内存开销。
  3. 自然归并排序
    利用输入数据中已有的有序段(Run),减少不必要的合并操作。

与其他排序对比

  • 快速排序 :平均 O(n \log n) ,但最坏 O(n^2);归并排序稳定但需要额外空间。
  • 堆排序 :空间复杂度 O(1),但不稳定且缓存不友好。

计数排序的基本原理

计数排序是一种非比较排序算法,适用于整数数据且范围不大的情况。核心思想是通过统计每个元素出现的次数,然后根据统计结果将元素放回正确位置。

假设输入数组为 A,长度为 n,元素范围为 [0, k]。需要创建一个计数数组 C(长度为 k+1)和一个输出数组 B(长度为 n)。

计数排序的步骤

统计每个元素出现的次数,将结果存入计数数组 C。例如 C[i] 表示元素 iA 中出现的次数。

对计数数组 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 动态分配多个整数数组(a1a7),每个数组大小为 N(这里是 10000)。
  • 检查内存分配是否成功,失败时输出错误信息并返回。
  • 初始化数组数据:a1[i] = rand() + i 确保数据重复较少,注释掉的 a1[i] = rand() 会生成重复较多的数据。
  • 其他数组 a2a7 复制 a1 的数据,确保所有排序算法处理相同的数据。

排序算法测试

  • 使用 clock() 记录每个排序算法的开始和结束时间,计算时间差作为性能指标。
  • 测试的排序算法包括:
    • InsertSort(插入排序)
    • ShellSort(希尔排序)
    • SelectSort(选择排序)
    • HeapSort(堆排序)
    • QuickSort(快速排序)
    • MergeSort(归并排序)
    • CountSort(计数排序)
  • 输出每种排序算法的耗时(毫秒)。

资源释放

  • 使用 free 释放动态分配的内存。

辅助测试函数 test()

  • 测试一个小规模数组(10 个元素)的排序功能。
  • 调用所有排序算法,验证其正确性。
  • 输出排序后的数组。

使用注意事项

  • 需要确保 Sort.h 头文件中已实现所有调用的排序算法(如 BubbleSortInsertSort 等)。
  • 如果排序算法未完全实现,注释掉未实现的函数调用以避免编译错误。
  • 可以通过修改 N 的值测试不同数据规模下的性能。
  • 切换 a1[i] = rand() + ia1[i] = rand() 可以测试数据重复性对排序性能的影响。

示例输出

运行 TestOP() 会输出类似以下结果(具体数值因硬件和数据而异):

复制代码
InsertSort:120
ShellSort:15
SelectSort:300
HeapSort:10
QuickSort:5
MergeSort:8
CountSort:2

扩展建议

  • 增加重复测试次数,取平均值以减少误差。
  • 添加对稳定性、内存占用等指标的测试。
  • 支持更大规模数据测试(如 100 万或 1000 万级别)。
相关推荐
youngee1137 分钟前
hot100-40将有序数组转换为二叉搜索树
数据结构·算法
curry____30338 分钟前
study in pta + 豆包(求区间和)(前缀和算法)(如何处理zhan栈溢出和超出时间复杂度问题)(2025.12.2)
数据结构·c++·算法
红队it39 分钟前
【Spark+Hive】基于Spark大数据旅游景点数据分析可视化推荐系统(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅
大数据·python·算法·数据分析·spark·django·echarts
渡我白衣1 小时前
深入理解算法库的灵魂——彻底掌握 <algorithm> 的范式、迭代器约束、隐藏陷阱与性能真相
数据结构·c++·人工智能·网络协议·mysql·rpc·dubbo
CoovallyAIHub1 小时前
为什么企业如今不应该忽视计算机视觉?计算机视觉如何为企业降本增效、规避风险?
深度学习·算法·计算机视觉
smile_Iris1 小时前
Day 26 常见的降维算法
开发语言·算法·kotlin
dadaobusi1 小时前
全局量子时间
算法
Q741_1471 小时前
C++ 栈 模拟 1047. 删除字符串中的所有相邻重复项 题解 每日一题
c++·算法·leetcode·模拟·
gihigo19981 小时前
快速傅里叶变换(FFT)的应用
算法