【数据结构】排序

目录

1、排序

1.1.概念

1.2.常见排序算法

VS2026Debug模式下各排序算法排序100000各数据所需时间(单位毫秒)

2、常见排序算法的实现

2.1.插入排序

2.1.1基本思想

2.1.2直接插入排序

直接插入排序的特性

直接插入排序的C语言实现

2.1.3希尔排序(缩小增量排序)

希尔排序的基本思想

希尔排序的特性

希尔排序的C语言代码

2.2选择排序

2.2.1基本思想

2.2.2直接选择排序

直接选择排序的特性

直接选择排序的C语言代码

2.2.3堆排序

【数据结构】堆的原理与C语言实现-CSDN博客

堆排序的特性

堆排序的C语言代码

2.3交换排序

2.3.1基本思想

2.3.2冒泡排序

冒泡排序的特性:

冒泡排序的C语言代码

2.3.3快速排序

快速排序有两种优化方法:

快速排序递归代码

快速排序非递归

2.4归并排序

基本思想

归并排序的特性

归并排序C语言代码

3.排序算法复杂度及稳定性分析


1、排序

1.1.概念

排序:使一串记录按照其中某个或某些关键字的大小递增或递减排列起来。

稳定性:在待排序的记录中存在多个具有相同关键字的记录,若经过排序,这些相同关键字的相对次序保持不变,则称这种排序算法是稳定的,否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多时不能同时放在内存中,根据排序的过程不断在内外存之间移动数据的排序。

1.2.常见排序算法

插入排序:直接插入排序、希尔排序

选择排序:选择排序、堆排序

交换排序:冒泡排序、快速排序

归并排序:归并排序

VS2026Debug模式下各排序算法排序100000个数据所需时间(单位毫秒)

2、常见排序算法的实现

2.1.插入排序

2.1.1基本思想

直接插入排序是一种简单的插入排序法,其基本思想:

把待排序的记录按其关键码的大小逐个插入到一个已经排序好的有序序列中,直到所有数据插入完为止,得到一个新的有序序列。(类似于扑克牌)

2.1.2直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1]......array[i-1]个元素已经排好序,此时将第i个元素与前面的array[[n-1],array[i-2]......依次比较,找到插入位置将第i个元素插入,原来位置的元素后移。

直接插入排序的特性

  1. 元素集合越接近有序,直接插入排序算法的效率越高。
  2. 时间复杂度O(N^2)
  3. 空间复杂度O(1)
  4. 稳定性:稳定

直接插入排序的C语言实现

cpp 复制代码
void InsertSort(int* a, int n) {
	for (int i = 0; i < n - 1; i++) {
		int end = i;
		// [0,end]是有序的,end+1位置的值插入到[0,end]中,保持有序
		int tmp = a[end + 1];
		while (end >= 0) {
			if (tmp < a[end]) {
				a[end + 1] = a[end];
				--end;
			}
			else {
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

2.1.3希尔排序(缩小增量排序)

希尔排序法又称缩小增量法。

希尔排序的基本思想

先选定一个整数gap,把待排序文件中的所有记录分为gap组,所有距离为gap的记录分为同一组,并对每一组分别进行排序。然后取更小的gap,重复上述步骤直到gap==1时所有记录在同一个组内排好序。

希尔排序的特性

  1. 希尔排序是对直接插入排序的优化
  2. 当gap>1时都是预排序,目的是让数组更接近有序。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方式很多。可以认为希尔排序的时间复杂度大致为O(N^1.3)。
  4. 稳定性:不稳定

希尔排序的C语言代码

这里gap的取值方式为 gap / 3 + 1

cpp 复制代码
void ShellSort(int* a, int n) {
	int gap = n;
	while (gap > 1) {
		gap = gap / 3 + 1;
		// +1可以保证最后一次gap一定是1
		// gap>1时是预排序,gap==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 -= gap;
				}
				else {
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

2.2选择排序

2.2.1基本思想

每次从待排序数据元素中取出最小(或最大)的一个元素,存放在序列的起始位置,直到全部排序完成。

2.2.2直接选择排序

在元素集合array[i]--array[n-1]中选择关键码最大(或最小)的元素

若它不是这组数据的最后一个(或第一个)元素,则将其与最后一个(或第一个)元素交换

在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤直到集合剩余1个元素

直接选择排序的特性

  1. 直接选择排序效率不是很高,实际中很少使用
  2. 时间复杂度O(N^2)
  3. 空间复杂度O(1)
  4. 稳定性:不稳定

直接选择排序的C语言代码

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

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

2.2.3堆排序

堆排序是指利用堆这种数据结构设计的排序算法,它是选择排序的一种,通过堆来进行选择数据。

注意:排升序要建大堆,排降序要建小堆

堆的详细介绍可以看这篇文章

【数据结构】堆的原理与C语言实现-CSDN博客

堆排序的特性

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度O(1)
  3. 稳定性:不稳定

堆排序的C语言代码

cpp 复制代码
void AdjustDown(int* a, int n, int parent)
{
	// 先假设左孩子小
	int child = parent * 2 + 1;

	while (child < n)  // 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)
{
	// 向下调整建堆 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

2.3交换排序

2.3.1基本思想

交换 就是根据序列中两个记录的键值的比较结果来对换这两个记录在序列中的位置。

交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

2.3.2冒泡排序

冒泡排序的特性:

  1. 时间复杂度O(N^2)
  2. 空间复杂度O(1)
  3. 稳定性:稳定

冒泡排序的C语言代码

cpp 复制代码
void BubbleSort(int* a, int n) {
	for (int j = 0; j < n; j++) {
		int flag = 0;
		for (int i = 1; i < n - j; i++) {
			if (a[i - 1] > a[i]) {
				Swap(&a[i - 1], &a[i]);
				flag = 1;
			}
		}
		if (flag == 0) {
			break;
		}
	}
}

2.3.3快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元序列中某元素为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列的值均小于基准值,右子序列的值均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序的特性:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(logN)
  3. 稳定性:不稳定

快速排序有两种优化方法:

(1)三数取中法选基准值:可以避免选择到最大值或最小值,避免效率降低

(2)递归到小的子区间时可以考虑使用插入排序

快速排序递归代码

cpp 复制代码
int GetMidi(int* a, int left, int right) {
	int midi = (left + right) / 2;
	if (a[left] < a[midi]) {
		if (a[midi] < a[right]) {
			return midi;
		}
		else if (a[left] < a[right]) {
			return right;
		}
		else {
			return left;
		}
	}
	else { // a[left] > a[midi]
		if (a[midi] > a[right]) {
			return midi;
		}
		else if (a[left] < a[right]) {
			return left;
		}
		else {
			return right;
		}
	}
}

// hoare版本的单趟
int PartSort1(int* a, int left, int right) {
	// 三数取中
	int midi = GetMidi(a, left, right);
	Swap(&a[midi], &a[left]);

	int keyi = left;
	int begin = left, end = right;
	while (begin < end) {
		while (begin < end && a[end] >= a[keyi]) {
			--end;
		}
		while (begin < end && a[begin] <= a[keyi]) {
			++begin;
		}
		Swap(&a[begin], &a[end]);
		return begin;
	}

	Swap(&a[keyi], &a[begin]);
}

// 前后指针法单趟
int PartSort2(int* a, int left, int right) {
	// 三数取中
	int midi = GetMidi(a, left, right);
	Swap(&a[midi], &a[left]);

	int keyi = left;
	int prev = left;
	int cur = prev + 1;

	// 继续
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}

	Swap(&a[keyi], &a[prev]);
	return prev;
}

void QuickSort(int* a, int left, int right) {
	if (left >= right) {
		return;
	}

	if (right - left + 1 < 10) {
		InsertSort(a + left, right - left + 1);
	}
	else {

		//int keyi = PartSort1(a, left, right);
		int keyi = PartSort2(a, left, right);

		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}

// 在深度太深的情况下递归会有栈溢出的风险

快速排序非递归

快速排序的非递归实现使用栈或数组都可以

循环每走一次,取栈顶区间,左右子区间入栈

cpp 复制代码
#include"stack.h"

// 快速排序非递归
void QuickSortNonR(int* a, int left, int right) {
	ST st;
	STInit(&st);

	STPush(&st, right);
	STPush(&st, left);

	// 栈不为空就继续,栈为空就结束
	// 循环每走一次,相当于一次递归
	while (!STEmpty(&st)) {
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		int keyi = PartSort2(a, begin, end);
		// [begin, keyi-1] keyi [keyi+1, end]
		// 先入右再入左
		if (keyi + 1 < end) {
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (begin < keyi - 1) {
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}

	STDestroy(&st);
}

2.4归并排序

基本思想

归并排序是建立在归并操作上的一种有效的排序算法,该算法采用的是分治法的一个非常经典的应用。将已有序的两个子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表, 成为二路归并。

归并排序的特性

  1. 归并排序的缺点是需要O(N)的空间复杂度,归并排序更多的是解决在磁盘的外排序问题
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

归并排序C语言代码

cpp 复制代码
// 归并排序
void _MergeSort(int* a, int* tmp, int begin, int end) {
	if (begin == end) {
		return;
	}
	
	int mid = (begin + end) / 2;
	// [begin,mid] [mid+1,end] 两个区间有序就可以进行归并
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid + 1, end);

	// 归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;

	int i = begin;
	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) {
		perror("malloc fail");
		return;
	}

	_MergeSort(a, tmp, 0, n - 1);

	free(tmp);
	tmp = NULL;
}

3.排序算法复杂度及稳定性分析

|--------|-------------------|-----------|----------|---------------|-----|
| 排序方式 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
| 冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
| 简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
| 直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
| 希尔排序 | O(nlogn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不稳定 |


相关推荐
杨福瑞4 小时前
数据结构:队列
c语言·数据结构
青山的青衫4 小时前
【二分查找-开区间思维】
算法
Swift社区4 小时前
LeetCode 449 - 序列化和反序列化二叉搜索树
算法·leetcode·职场和发展
CoderYanger5 小时前
贪心算法:3.最大数
java·算法·leetcode·贪心算法·1024程序员节
lxmyzzs5 小时前
【图像算法 - 37】人机交互应用:基于 YOLOv12 与 OpenCV 的高精度人脸情绪检测系统实现
算法·yolo·人机交互·情绪识别
muyouking115 小时前
Zig 语言实战:实现高性能快速排序算法
算法·排序算法
CoderYanger5 小时前
贪心算法:5.最长递增子序列
java·算法·leetcode·贪心算法·1024程序员节
慕容青峰5 小时前
【牛客周赛 107】E 题【小苯的刷怪笼】题解
c++·算法·sublime text
算法熔炉5 小时前
深度学习面试八股文(2)——训练
人工智能·深度学习·算法