数据结构排序算法全解析:从基础原理到实战应用

news2025/5/10 4:35:24

在计算机科学领域,排序算法是数据处理的核心技术之一。无论是小规模数据的简单整理,还是大规模数据的高效处理,选择合适的排序算法直接影响着程序的性能。本文将深入解析常见排序算法的核心思想、实现细节、特性对比及适用场景,帮助读者全面掌握这一关键技术。

一、排序的本质与应用场景

1. 排序的定义

排序是将一组记录按照某个关键字的大小,按递增或递减顺序排列的过程。例如,将数组[5,3,9,6,2]排序为[2,3,5,6,9],本质上是通过关键字(这里是数值大小)重新组织数据的顺序。

2. 实际应用场景

  • 电商平台:按价格、销量、评论数对商品进行排序,提升用户体验。
  • 数据库系统:对查询结果排序以满足用户需求(如 “按时间倒序”)。
  • 科学计算:预处理数据以加速后续分析(如统计、机器学习模型训练)。
  • 竞赛与算法题:排序是解决复杂问题的基础(如贪心、动态规划的前置步骤)。

3.常见的排序算法

二、插入排序:增量构建有序序列

1. 直接插入排序(Straight Insertion Sort)

核心思想
将未排序元素逐个插入已排序序列的合适位置,类似整理扑克牌时逐张插入手牌。每次从未排序区间取一个元素,与已排序区间的元素从后向前比较,找到合适位置后插入,后移沿途较大的元素。

算法步骤

  1. 假设前 i 个元素已排序,取第 i+1 个元素作为待插入元素。
  2. 从已排序序列末尾向前搜索,找到第一个比待插入元素大的位置。
  3. 将该位置之后的元素后移一位,腾出空间插入待插入元素。
  4. 重复直至所有元素插入完成。

代码实现(C 语言)

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 && a[end] > tmp) { // 向前搜索插入位置
            a[end + 1] = a[end];     // 元素后移
            end--;
        }
        a[end + 1] = tmp;             // 插入到正确位置
    }
}

特性分析

  • 时间复杂度:最好 O(n)(已有序),最坏 / 平均 O(n^2)
  • 空间复杂度O(1)(原地排序)。
  • 稳定性:稳定(相同元素保持原顺序)。
  • 适用场景:小规模数据或近乎有序的数组(如有序度 > 80%)。

2. 希尔排序(Shell Sort)

核心思想
通过 “增量序列” 将数组分组,对每组进行插入排序,逐步缩小增量至 1(退化为直接插入排序)。增量序列(如 gap = n/3 + 1)的设计是关键,通过分组预排序使数组更接近有序,提升最终插入效率。

算法步骤

  1. 初始化增量 gap = n,通常取 gap = n/2 或 gap = n/3 + 1
  2. 按 gap 分组(下标相差 gap 的元素为一组),对每组进行插入排序。
  3. 更新 gap = gap/3 + 1,重复直至 gap = 1

代码实现(C 语言)

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 && a[end] > tmp) {
                a[end + gap] = a[end]; // 组内元素后移
                end -= gap; // 组内指针前移
            }
            a[end + gap] = tmp; // 插入到组内正确位置
        }
    }
}

特性分析

  • 时间复杂度:平均约 O(n^1.3),优于直接插入排序。
  • 空间复杂度O(1)
  • 稳定性:不稳定(分组可能打乱相同元素顺序)。
  • 适用场景:中等规模数组(n=100~1000)。

三、选择排序:极值定位的直接策略

1. 直接选择排序(Straight Selection Sort)

核心思想
每次从未排序区间选择最小(或最大)元素,与区间起点交换,逐步缩小未排序区间。

算法步骤

  1. 遍历未排序区间 [i, n-1],记录最小元素下标 min_idx
  2. 若 min_idx 不等于 i,交换 a[i] 与 a[min_idx]
  3. 重复直至所有元素排序。

代码实现(C 语言)

void SelectSort(int* a, int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i;
        for (int j = i + 1; j < n; j++) { // 寻找最小值
            if (a[j] < a[min]) {
                min = j;
            }
        }
        if (min != i) { // 交换最小值到当前起点
            swap(&a[i], &a[min]);
        }
    }
}

这样写效率不是很高,我们可以同时寻找最小值和最大值,寻找到最小值和最大值过后,将其交换到首尾节点,以此类推,这样每次搜索的范围就减少了一半,代码如下:

void SelectSort(int* arr, int n) 
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i < end; i++)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}

		}
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[mini], &arr[begin]);
		Swap(&arr[maxi], &arr[end]);
		begin++;
		end--;
	}
}

特性分析

  • 时间复杂度:固定 O(n^2)(无论数据是否有序)。
  • 空间复杂度O(1)
  • 稳定性:不稳定(相同元素相对顺序可能改变)。
  • 适用场景:仅用于教学演示,实际中极少使用。

2. 堆排序(Heap Sort)

核心思想
利用堆(大顶堆 / 小顶堆)的性质,每次取出堆顶元素(最大值 / 最小值),调整堆结构直至排序完成。升序排序建大顶堆,降序建小顶堆。

算法步骤

  1. 建堆:从最后一个非叶子节点开始,自底向上调整为大顶堆。
  2. 排序:交换堆顶与堆尾元素,堆大小减 1,向下调整堆顶,重复直至堆空。

关键代码(向下调整函数)

void AdjustDown(int* a, int n, int parent) {
    int child = parent * 2 + 1; // 左孩子下标
    while (child < n) {
        if (child + 1 < n && a[child + 1] > a[child]) { // 选较大的孩子
            child++;
        }
        if (a[child] > a[parent]) { // 交换父子节点
            swap(&a[child], &a[parent]);
            parent = child; // 继续向下调整
            child = parent * 2 + 1;
        } else {
            break; // 已满足堆性质
        }
    }
}

void HeapSort(int* a, int n) {
    // 建大顶堆
    for (int i = n/2 - 1; i >= 0; i--) {
        AdjustDown(a, n, i);
    }
    // 排序过程
    for (int i = n - 1; i > 0; i--) {
        swap(&a[0], &a[i]); // 交换堆顶与堆尾
        AdjustDown(a, i, 0); // 调整剩余堆
    }
}

除了向下建堆外还可以采用向上建堆,代码如下:

void HPjustUp(HPDataType* arr,int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

特性分析

  • 时间复杂度:始终 O(n log n)(建堆 O(n),每次调整 O(log n))。
  • 空间复杂度O(1)(原地排序)。
  • 稳定性:不稳定(堆调整可能交换相同元素)。
  • 适用场景:大规模数据且内存受限(如嵌入式设备)。

四、交换排序:通过元素交换实现全局有序

1. 冒泡排序(Bubble Sort)

核心思想
相邻元素比较,逆序则交换,使较大元素逐步 “冒泡” 到数组末尾。通过标志位优化可提前终止(若某轮无交换,说明已有序)。

算法步骤

  1. 遍历数组,比较相邻元素,前大于后则交换。
  2. 每轮最大元素到达末尾,未排序区间缩小 1。
  3. 若某轮无交换,提前终止。

优化代码实现

void BubbleSort(int* a, int n) {
    for (int i = 0; i < n; i++) {
        int exchange = 0; // 标志位判断是否有序
        for (int j = 0; j < n - i - 1; j++) {
            if (a[j] > a[j + 1]) {
                swap(&a[j], &a[j + 1]);
                exchange = 1;
            }
        }
        if (!exchange) 
            break; // 已有序,提前结束
    }
}

特性分析

  • 时间复杂度:最好 O(n),最坏 / 平均 O(n²)
  • 空间复杂度O(1)
  • 稳定性:稳定(相同元素保持原顺序)。
  • 适用场景:小规模数据或教学演示。

2. 快速排序(Quick Sort)

2.1递归法实现快速排序

核心思想
分治策略,选择基准值(Pivot),将数组划分为左(<基准)、中(= 基准)、右(> 基准)三部分,递归排序左右子数组。

三种分区实现

  • Hoare 版本(左右指针法):右指针先找小,左指针找大,交换直至相遇,基准值归位。
  1. 选左端点为基准值,左右指针 leftright 分别指向基准值下一个位置和右端点。
  2. right 先向左找小于基准的元素,left 向右找大于基准的元素,交换两者,直至 left >= right
  3. 交换基准值与 right 位置元素,返回基准最终下标。

代码实现

int QuickSort_Find(int* arr, int left, int right)
{
	int keyi = left; // 基准值
	left++;
	while (left <= right)
	{
		while (left <= right && arr[keyi] < arr[right])// 右指针先找小
		{
			right--;
		}
		while (left <= right && arr[keyi] > arr[left]) // 左指针找大
		{
			left++;
		}
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);// 交换左右找到的逆序对
		}
	}
	Swap(&arr[keyi], &arr[right]);// 基准值归位
	return right;// 返回基准下标
}

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = QuickSort_Find1(arr, left, right);
	QuickSort(arr, left, keyi-1);
	QuickSort(arr, keyi+1, right);
}
  • 挖坑法:基准值形成 “坑”,左右指针交替填充,最终填入基准值。

创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新 的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)

  1. 选左端点为基准值,记录为 key,形成 “坑” 位置 left
  2. right 向左找小于 key 的元素,填入坑位,新坑为 right
  3. left 向右找大于 key 的元素,填入新坑,新坑为 left
  4. 重复直至 left == right,填入 key,返回坑位下标。

  • 前后指针法(Lomuto):前指针标记小于基准的末尾,后指针遍历交换,基准值与前指针交换。

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

代码实现


int QuickSort_Find(int* arr, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);
	return prev;
}
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = QuickSort_Find(arr, left, right);
	QuickSort(arr, left, keyi-1);
	QuickSort(arr, keyi+1, right);
}
2.2迭代法实现快速排序

快速排序的递归实现简洁高效,但在处理大规模数据时可能因递归深度过大导致栈溢出(如最坏情况下递归深度达 O(n))。迭代实现通过显式使用栈(Stack)模拟递归过程,避免了这一问题,更适合工程环境中的稳健性要求。以下是基于 Lomuto 分区方案(前后指针法)的迭代实现详解。

1. 核心思想:栈模拟递归分区

  • 递归转迭代
    递归版本中,每次分区后自动压栈(系统栈)处理左右子区间;迭代版本需手动维护一个栈,显式存储待排序的区间 [left, right]
  • 栈操作逻辑
    • 初始时将整个数组区间 [begin, end] 压栈(注意栈的后进先出特性,先压右端点,再压左端点)。
    • 循环取栈顶区间,进行分区操作,得到基准位置 keyi
    • 将左右子区间(若有效)压栈,继续处理直至栈空。

2. 算法步骤(基于 Lomuto 分区)

  1. 初始化栈:压入初始区间 [left, right](注意顺序:先压右端点,再压左端点)。
  2. 循环处理栈内区间
    • 弹出栈顶元素,得到当前处理区间 [begin, end]
    • 执行分区操作(选择 begin 为基准值,通过前后指针法划分区间)。
    • 记录基准位置 keyi,将左右子区间(若长度大于 1)压栈(先压右子区间,再压左子区间,确保左子区间先处理)。
  3. 分区逻辑
    • prev 指针标记小于基准值的末尾,初始为 begin
    • cur 指针从 begin+1 开始遍历,若 arr[cur] < arr[begin],则 prev++ 并交换 arr[prev] 与 arr[cur]
    • 遍历结束后,交换基准值 arr[begin] 与 arr[prev],使基准值归位。

3. 代码实现(C 语言,含栈结构体定义,完整代码见附录)

// 栈结构体定义(假设已实现基础栈操作)
typedef struct Stack {
    int* data;
    int top;
    int capacity;
} Stack;

// 栈初始化、压栈、弹栈等函数(此处省略具体实现,需确保栈操作正确)
void StackInit(Stack* st);
void StackPush(Stack* st, int value);
int StackPop(Stack* st);
int StackTop(Stack* st);
bool StackEmpty(Stack* st);
void StackDestroy(Stack* st);

void QuickSort(int* arr, int left, int right)
{
	Stack ST;
	StackInit(&ST); // 初始化
	StackPush(&ST, right); // 先压右端点,再压左端点
    StackPush(&st, left);
	StackPush(&ST, left);
	while (!StackEmpty(&ST))
	{
		int begin = StackTop(&ST);// 取出左端点
		StackPop(&ST);
		int end = StackTop(&ST); // 取出右端点
		StackPop(&ST);
		int keyi = begin; // 基准值下标(左端点)
		int prev = begin;// 前指针:标记小于基准的末尾
		int cur = prev + 1; // 后指针:遍历未排序区间
		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)// 发现更小元素,前指针右移,非自身时交换(避免无意义交换)
			{
				Swap(&arr[prev], &arr[cur]);// 交换到prev位置
			}
			cur++;// 后指针继续右移
		}
		Swap(&arr[keyi], &arr[prev]); // 基准值归位到prev位置,此时arr[prev]是基准值
		// 压栈顺序:先右子区间,再左子区间(保证左子区间先被处理)
        // 右子区间:[prev+1, end]
        keyi = prev;
		if (keyi + 1 < end)
		{
			StackPush(&ST, end);
			StackPush(&ST, keyi+1);
		}
        // 左子区间:[begin, prev-1]
		if (begin < keyi - 1)
		{
			StackPush(&ST, keyi - 1);
			StackPush(&ST, begin);
		}
	}
	StackDestroy(&ST);// 释放栈内存
}

4. 关键细节解析

  • 栈容量初始化
    栈的初始容量可以根据数据规模设置(如 right - left + 1),避免动态扩容带来的性能损耗。
  • 分区优化
    • 代码中 if (prev != cur) 避免了元素与自身交换(当 cur 追上 prev 时,交换无意义)。
    • 基准值固定为左端点,实际工程中应添加 随机化基准值 逻辑(如随机选择基准并与左端点交换),避免最坏情况 O(n²)
  • 压栈顺序
    由于栈是后进先出,先压右子区间 [prev+1, end],再压左子区间 [begin, prev-1],确保左子区间先被处理(与递归的深度优先顺序一致)。

5. 迭代 vs 递归:优缺点对比

特性递归实现迭代实现
实现复杂度简洁(系统栈自动管理区间)较复杂(需手动维护栈结构)
栈溢出风险高(最坏递归深度 O(n)低(栈容量可控,或使用循环队列优化)
空间效率平均 O(log n)(系统栈)平均 O(log n)(用户栈,常数因子略大)
调试难度易(递归调用栈清晰)较难(需跟踪栈内区间状态)
适用场景小规模数据或教学演示大规模数据或生产环境(稳健性优先)

6. 工程优化建议

  1. 随机化基准值
    在分区前随机选择基准值,避免有序数组导致的最坏情况:
    // 随机化基准值(在分区前调用)
    int RandomPivot(int* arr, int begin, int end) {
        srand(time(NULL)); // 初始化随机种子(仅需调用一次)
        int pivot = begin + rand() % (end - begin + 1); // 生成[begin, end]随机下标
        Swap(&arr[begin], &arr[pivot]); // 交换到左端点,作为基准值
        return begin;
    }
    
  2. 小规模区间切换插入排序
    当区间长度小于阈值(如 16)时,切换为插入排序,减少递归 / 迭代开销:
    #define INSERTION_SORT_THRESHOLD 16
    
    if (end - begin + 1 <= INSERTION_SORT_THRESHOLD) {
        InsertSort(arr + begin, end - begin + 1); // 插入排序子数组
        continue;
    }
    

  3. 尾递归优化
    迭代实现本质是尾递归优化的结果,确保每次仅处理一个子区间,另一个子区间通过栈延迟处理,减少栈空间占用。

迭代实现的快速排序通过显式栈管理,在保持平均 O(n log n) 效率的同时,避免了递归栈溢出的风险,是生产环境中的可靠选择。理解其核心逻辑(栈模拟递归、分区策略、基准值优化)能帮助我们更好地应对大规模数据排序需求。结合随机化基准值和插入排序切换等优化,迭代快排能在工程中发挥出卓越的性能。

特性分析
  • 时间复杂度:平均 O(n log n),最坏 O(n²)(可通过随机化基准值优化)。
  • 空间复杂度:递归栈平均 O(log n),最坏 O(n)(非递归实现可避免栈溢出)。
  • 稳定性:不稳定(相同元素可能跨区交换)。
  • 适用场景:大规模数据的通用排序(工程首选,平均效率最高)。

五、归并排序:稳定排序的分治方案

核心思想
分治策略,递归分解数组为子数组,排序后合并(二路归并),确保合并后的数组有序。合并时借助临时数组存储中间结果。

算法步骤

  1. 分解:递归将数组分成左右两半,直至子数组长度为 1(已有序)。
  2. 合并:双指针遍历左右子数组,按顺序合并到临时数组,再拷贝回原数组。

合并逻辑(关键函数)

void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)    //分解数组
	{
		return;
	}
	int mid = (left + right) / 2;
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid+1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)    // 合并左右子数组
	{
		if (arr[begin1] > arr[begin2])
		{
			tmp[index++] = arr[begin2++];
		}
		else
		{
			tmp[index++] = arr[begin1++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	memcpy(&arr[left], &tmp[left], sizeof(int) * (right - left + 1));  // 拷贝回原数组
}
void MergeSort(int* a, int n) {
    int* tmp = (int*)malloc(n * sizeof(int));
    _MergeSort(a, 0, n - 1, tmp); // 递归分解
    free(tmp);
}

特性分析

  • 时间复杂度:始终 O(n log n)(不依赖数据初始状态)。
  • 空间复杂度O(n)(需要临时数组)。
  • 稳定性:稳定(合并时保留相同元素原顺序)。
  • 适用场景:稳定性要求高的场景(如多关键字排序)或链表排序。

六、非比较排序:计数排序(Counting Sort)

核心思想
利用统计频率的方式,将元素按值出现的次数直接映射到目标位置,适用于值域范围较小的整数排序。

算法步骤

  1. 确定值域:找到最小值 min 和最大值 max,计算范围 range = max - min
  2. 统计频率:创建计数数组 count,记录每个值的出现次数。
  3. 回填数组:按值从小到大,根据计数次数将元素填回原数组,保证稳定性。

代码实现

void CountSort(int* arr, int n)
{
	int max = arr[0];
	int min = arr[0];
	int i = 1;
	while (i < n)     // 找值域范围
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
		i++;
	}
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int)); // 初始化计数数组
	if (count == NULL)
	{
		perror("calloc fail");
		exit(1);
	}
	for (i = 0; i < n; i++)    // 统计频率
	{
		count[arr[i] - min]++;
	}
	int index = 0;
	for (i = 0; i < range; i++) // 按值顺序回填
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}

}

特性分析

  • 时间复杂度:o(n+range)(k 为值域范围,线性时间)。
  • 空间复杂度:o(range)(依赖值域,稀疏数据可能浪费空间)。
  • 稳定性:稳定(按值顺序填充,保留原顺序)。
  • 适用场景:值域小的整数排序(如 0-100 分成绩排序)。

七、算法对比与选型指南

算法时间复杂度空间复杂度稳定性典型场景
直接插入排序O (n²)(平均)O(1)稳定小规模 / 近乎有序数组
希尔排序O (n^1.3)(平均)O(1)不稳定中等规模数组
堆排序O (n log n)(固定)O(1)不稳定大规模数据 / 内存受限场景
快速排序O (n log n)(平均)O(log n)不稳定通用大规模排序(首选)
归并排序O (n log n)(固定)O(n)稳定稳定性要求高 / 链表排序
计数排序O (n + k)(线性)O(k)稳定值域小的整数排序(k 较小)

注:稳定性是假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

选型建议

  • 小规模数据:直接插入排序(简单高效,近乎有序时最优)。
  • 大规模数据
    • 优先快速排序(平均效率最高,需随机化基准值防最坏情况)。
    • 稳定性优先选归并排序,内存紧张选堆排序。
  • 特殊场景
    • 整数且值域小:计数排序(线性时间,如成绩、年龄排序)。
    • 链表排序:归并排序(无需随机访问,递归更自然)。

结语

排序算法是数据结构的核心基础,每种算法都体现了不同的设计思想:插入排序的增量构建、快速排序的分治分区、计数排序的统计映射等。理解它们的核心逻辑、复杂度特性及适用场景,是在实际开发中做出正确选择的关键。从基础的 O(n²) 算法到高效的 O(n log n) 算法,再到突破比较限制的线性排序,排序算法的演进史也是计算机科学优化思维的缩影。掌握这些知识,不仅能写出更高效的代码,还能培养 “根据问题特性选择合适工具” 的工程思维。

附录

1.比较相同数据各排序算法运行时间的代码

//test.c
void TestOP()
{
    srand(time(0));
    const int N = 200000;
    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);
    int* a8 = (int*)malloc(sizeof(int) * N);
    for (int i = 0; i < N; ++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];
        a8[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);
    int end7 = clock();

    int begin8 = clock();
    CountSort(a8, N);
    int end8 = 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("BubbleSort:%d\n", end7 - begin7);
    printf("CountSort:%d\n", end8 - begin8);
    free(a1);
    free(a2);
    free(a3);
    free(a4);
    free(a5);
    free(a6);
    free(a7);
    free(a8);
}

2.栈的代码

//Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	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 
bool StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);
//Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
// 初始化栈 
void StackInit(Stack* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	if (ps->capacity == ps->top)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	ps->arr[ps->top++] = data;

}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 

bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}
// 出栈 
void StackPop(Stack* ps)
{
	assert(!StackEmpty(ps));
	--ps->top;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->arr[ps->top - 1];
}
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->top;

}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	if (ps->arr != NULL)
		free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

3.所有排序算法的代码

//sort.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>


// 插入排序
void InsertSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n); 

//直接选择排序
void SelectSort(int*arr, int n);

//堆排序
void HeapSort(int* arr, int n);

//快速排序
void QuickSort(int* arr, int left, int right);

//归并排序
void MergeSort(int* arr, int n);

//冒泡排序
void BubbleSort(int* arr, int n);

//计数排序
void CountSort(int* arr, int n);
//Sort.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Sort.h"
#include"Stack.h"
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}
void ShellSort(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];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end+gap] = tmp;
		}
	}
}

void SelectSort(int* arr, int n) 
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i < end; i++)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}

		}
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[mini], &arr[begin]);
		Swap(&arr[maxi], &arr[end]);
		begin++;
		end--;
	}
}
void HPjustDown(int* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* arr, int n)
{
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		HPjustDown(arr, i, n);
	}
	int tmp = n - 1;
	while (tmp)
	{
		Swap(&arr[0], &arr[tmp]);
		HPjustDown(arr, 0, tmp);
		tmp--;
	}
}
//int QuickSort_Find1(int* arr, int left, int right)
//{
//	int keyi = left;
//	left++;
//	while (left <= right)
//	{
//		while (left <= right && arr[keyi] < arr[right])
//		{
//			right--;
//		}
//		while (left <= right && arr[keyi] > arr[left])
//		{
//			left++;
//		}
//		if (left <= right)
//		{
//			Swap(&arr[left++], &arr[right--]);
//		}
//	}
//	Swap(&arr[keyi], &arr[right]);
//	return right;
//}
//int QuickSort_Find2(int* arr, int left, int right)
//{
//	int keyi = left;
//	int prev = left;
//	int cur = prev + 1;
//	while (cur <= right)
//	{
//		if (arr[cur] < arr[keyi] && ++prev != cur)
//		{
//			Swap(&arr[prev], &arr[cur]);
//		}
//		cur++;
//	}
//	Swap(&arr[keyi], &arr[prev]);
//	return prev;
//}
//void QuickSort(int* arr, int left, int right)
//{
//	if (left >= right)
//	{
//		return;
//	}
//	int keyi = QuickSort_Find1(arr, left, right);
//	QuickSort(arr, left, keyi-1);
//	QuickSort(arr, keyi+1, right);
//}
void QuickSort(int* arr, int left, int right)
{
	Stack ST;
	StackInit(&ST);
	StackPush(&ST, right);
	StackPush(&ST, left);
	while (!StackEmpty(&ST))
	{
		int begin = StackTop(&ST);
		StackPop(&ST);
		int end = StackTop(&ST);
		StackPop(&ST);
		int keyi = begin;
		int prev = begin;
		int cur = prev + 1;
		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[prev], &arr[cur]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);
		keyi = prev;
		if (keyi + 1 < end)
		{
			StackPush(&ST, end);
			StackPush(&ST, keyi+1);
		}
		if (begin < keyi - 1)
		{
			StackPush(&ST, keyi - 1);
			StackPush(&ST, begin);
		}
	}
	StackDestroy(&ST);
}
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) / 2;
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid+1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] > arr[begin2])
		{
			tmp[index++] = arr[begin2++];
		}
		else
		{
			tmp[index++] = arr[begin1++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//for (int i = left; i <= right; i++)
	//{
	//	arr[i] = tmp[i];
	//}
	memcpy(&arr[left], &tmp[left], sizeof(int) * (right - left + 1));
}
void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(arr, 0, n - 1, tmp);
	free(tmp);
}
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j + 1], &arr[j]);
			}
		}
	}
}
void CountSort(int* arr, int n)
{
	int max = arr[0];
	int min = arr[0];
	int i = 1;
	while (i < n)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
		i++;
	}
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
		exit(1);
	}
	for (i = 0; i < n; i++)
	{
		count[arr[i] - min]++;
	}
	int index = 0;
	for (i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2339101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UMG:ListView

1.创建WBP_ListView,添加Border和ListView。 2.创建Object,命名为Item(数据载体&#xff0c;可以是其他类型)。新增变量name。 3.创建User Widget&#xff0c;命名为Entry(循环使用的UI载体).添加Border和Text。 4.设置Entry继承UserObjectListEntry接口。 5.Entry中对象生成时…

每天学一个 Linux 命令(18):mv

​​可访问网站查看&#xff0c;视觉品味拉满&#xff1a; http://www.616vip.cn/18/index.html 每天学一个 Linux 命令&#xff08;18&#xff09;&#xff1a;mv 命令功能 mv&#xff08;全称&#xff1a;move&#xff09;用于移动文件/目录或重命名文件/目录&#xff0c;是…

ubuntu24.04上使用qemu和buildroot模拟vexpress-ca9开发板构建嵌入式arm linux环境

1 准备工作 1.1 安装qemu 在ubuntu系统中使用以下命令安装qemu。 sudo apt install qemu-system-arm 安装完毕后&#xff0c;在终端输入: qemu- 后按TAB键&#xff0c;弹出下列命令证明安装成功。 1.2 安装arm交叉编译工具链 sudo apt install gcc-arm-linux-gnueabihf 安装之…

IntelliSense 已完成初始化,但在尝试加载文档时出错

系列文章目录 文章目录 系列文章目录前言一、原因二、使用步骤 前言 IntelliSense 已完成初始化&#xff0c;但在尝试加载文档时出错 File path: E:\QtExercise\DigitalPlatform\DigitalPlatform\main\propertyWin.ui Frame GUID:96fe523d-6182-49f5-8992-3bea5f7e6ff6 Frame …

【更新完毕】2025泰迪杯数据挖掘竞赛A题数学建模思路代码文章教学:竞赛论文初步筛选系统

完整内容请看文末最后的推广群 基于自然语言处理的竞赛论文初步筛选系统 基于多模态分析的竞赛论文自动筛选与重复检测模型 摘要 随着大学生竞赛规模的不断扩大&#xff0c;参赛论文的数量激增&#xff0c;传统的人工筛选方法面临着工作量大、效率低且容易出错的问题。因此&…

服务器内存规格详解

服务器内存规格详解 一、内存安装原则与配置规范 1. 内存槽位安装规则 规则描述CPU1对应的内存槽位至少需配置一根内存禁止混用不同规格&#xff08;容量/位宽/rank/高度&#xff09;内存条&#xff0c;需保持相同Part No.推荐完全平衡的内存配置&#xff0c;避免通道/处理器…

Vue3+Vite+TypeScript+Element Plus开发-22.客制Table组件

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 Header响应式菜单缩展 Mockjs引用与Axios封装 登录设计 登录成功跳转主页 多用户动态加载菜单 Pinia持久化 动态路由 -动态增加路由 动态路由-动态删除…

江苏广电HC2910-创维代工-Hi3798cv200-2+8G-海美迪安卓7.0-强刷包

江苏广电HC2910-创维代工-Hi3798cv200-28G-海美迪安卓7.0-强刷包 说明 1、由于原机的融合网关路由不能设置&#xff0c;原网口无法使用&#xff0c;需要用usb2.0的RJ45usb网卡接入。 通过usb接口网卡联网可以实现百兆网口连接。原机usb3.0的接口可以以接入硬盘&#xff0c;播放…

nvm切换node版本后,解决npm找不到的问题

解决方法如下 命令行查看node版本 node -v找到node版本所对应的npm版本 点击进入node版本 npm对应版本下载 点击进入npm版本 下载Windows 压缩包 下载完成后&#xff0c;解压&#xff0c;文件改名为npm 复制到你nvm对应版本的node_modules 下面 将下载的npm /bin 目录…

Windows系统安装MySQL安装实战分享

以下是在 Windows 系统上安装 MySQL 的详细实战步骤&#xff0c;涵盖下载、安装、配置及常见问题处理。 一、准备工作 下载 MySQL 安装包 访问 MySQL 官网。选择 MySQL Community Server&#xff08;免费版本&#xff09;。根据系统位数&#xff08;32/64位&#xff09;下载 …

Vue 人看 React useRef:它不只是替代 ref

如果你是从 Vue 转到 React 的开发者&#xff0c;初见 useRef 可能会想&#xff1a;这不就是 React 版的 ref 吗&#xff1f;但真相是 —— 它能做的&#xff0c;比你想象得多得多。 &#x1f440; Vue 人初见 useRef 在 Vue 中&#xff0c;ref 是我们访问 DOM 或响应式数据的…

零成本自建企业级SD-WAN!用Panabit手搓iWAN实战

我们前面提到过&#xff0c;最开始了解到Panabit&#xff0c;是因为他的SD-WAN产品&#xff08;误以为是外国货&#xff1f;这家国产SD-WAN神器竟能免费白嫖&#xff0c;附Panabit免费版体验全记录&#xff09;&#xff1b;现在发现&#xff0c;其SD-WAN的技术基础是iWAN&#…

Unity-微信截图功能简单复刻-03绘制空心矩形

思路-绘制空心矩形 拓展UGUI的Graphic类,实现拖拽接口。 开始拖拽时记录鼠标位置&#xff0c; 使用拖拽中的鼠标位置和记录的位置&#xff0c;计算矩形顶点&#xff0c;绘制矩形。 两个三角形合并为一个矩形&#xff0c;作为空心矩形的一条边&#xff0c;四个边合并为空心矩形…

国产品牌芯洲科技100V降压芯片系列

SCT2A25采用带集成环路补偿的恒导通时间(COT)模式控制&#xff0c;大大简化了转换器的片外配置。SCT2A25具有典型的140uA低静态电流&#xff0c;采用脉冲频率调制(PFM)模式&#xff0c;它使转换器在轻载或空载条件下实现高转换效率。 芯洲科技100V降压芯片系列提供丰富的48V系…

研一自救指南 - 07. CSS面向面试学习

最近的前端面试多多少少都会遇到css的提问&#xff0c;感觉还是要把重点内容记记背背。这里基于b站和我自己面试的情况整理。 20250418更新&#xff1a; 1. BFC Block Formatting Context&#xff0c;一个块级的盒子&#xff0c;可以创建多个。里面有很多个块&#xff0c;他们…

图灵奖得主LeCun:DeepSeek开源在产品层是一种竞争,但在基础方法层更像是一种合作;新一代AI将情感化

图片来源&#xff1a;This is World 来源 | Z Potential Z Highlights&#xff1a; 新型的AI系统是以深度学习为基础&#xff0c;能够理解物理世界并且拥有记忆、推理和规划能力的。一旦成功构建这样的系统&#xff0c;它们可能会有类似情感的反应&#xff0c;但这些情感是基…

使用Redis5.X部署一个集群

文章目录 1.用Redis5.x来创建Cluste2. 查看节点信息 nodes3. 添加节点 add-node4.删除节点 del-node5.手动指定从节点 replicate6.检查集群健康状态 check 建议使用5.x版本。 首先&#xff0c;下载Redis&#xff0c;根据自己的环境选择版本。 一键启动Redis集群文件配置。 ech…

Ubuntu Linux 中文输入法默认使用英文标点

ubuntu从wayland切换到x11, 然后安装fcitx(是fcitx4版本)和 fcitx-googlepinyin, 再sudo dpkg -i 安装百度输入法deb包. 在fcitx配置中, 附加组件,打勾高级, 取消打勾标点支持和全角字符. 百度输入法就可以默认用英文标点了. 而google拼音输入法的问题是字体大小没法保存,每…

Java漏洞原理与实战

一、基本概念 1、序列化与反序列化 (1)序列化:将对象写入IO流中&#xff0c;ObjectOutputStream类的writeobject()方法可以实现序列化 (2)反序列化:从IO流中恢复对象&#xff0c;ObjectinputStream类的readObject()方法用于反序列化 (3)意义:序列化机制允许将实现序列化的J…

第十届团体程序设计天梯赛-上理赛点随笔

2025.4.19来到军工路580号上海理工大学赛点参加cccc 校内环境挺好的&#xff0c;校内氛围也不错&#xff1b;临走前还用晚餐券顺走一袋橘子 再来说说比赛 首先是举办方服务器爆了&#xff0c;导致前10分钟刷不出题&#xff0c;一个多小时还上交不了代码 然后就是我用py总有几…