C语言数据结构-----常用七种排序介绍、分类、实现及性能比较

news2025/1/21 12:15:22

前言

①排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
②稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
③内部排序:数据元素全部放在内存中的排序。
④外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
在这里插入图片描述

文章目录

  • 前言
  • 1.冒泡排序
  • 2.插入排序
  • 3.希尔排序
  • 4.直接选择排序
  • 5.堆排序
  • 6.快速排序(qsort)
    • 6.1 hoare法
    • 6.2 前后指针法
    • 6.3 挖洞法
  • 7.归并排序
  • 8.排序性能比较


1.冒泡排序

冒泡排序是我们刚接触C语言时就经常使用的排序,大家应该都清楚什么时冒泡排序,这里就不做介绍。
在这里插入图片描述

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		bool exchange = false;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				int temp;
				temp = a[j - 1];
				a[j - 1] = a[j];
				a[j] = temp;
				exchange = true;
			}
		}
		if (exchange == false)
			break;
	}
}

2.插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

在这里插入图片描述

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定
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)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
				break;
		}
		a[end + 1] = tmp;
	}
}

3.希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
总结一下,步骤就是两步:
1.预排序
2.插入排序

在这里插入图片描述

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定。
  4. 研究表明希尔排序的时间复杂度约为O(N1.3)
void ShellSort(int* a, int n)
{
	int gap = n;
	// gap > 1时是预排序,目的让他接近有序
	// gap == 1是直接插入排序,目的是让他有序
	while (gap > 1)
	{
		//gap = gap / 2;
		gap = gap / 3 + 1;//现希尔排序的常用的两种gap取值

		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;
		}
	}
}

4.直接选择排序

其思想是: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
在这里插入图片描述

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定
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[mini])
			{
				mini = i;
			}

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

		++begin;
		--end;
	}
}

5.堆排序

具体见:
链接: C语言数据结构-----二叉树(2)堆的深入理解及应用、链式二叉树的讲解及代码实现

void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下
		if (child + 1 < size && 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;
	}
}

6.快速排序(qsort)

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

其有三种三种版本,hoare版本,挖坑法版本,前后指针版本。让我们一种一种来看。

6.1 hoare法

用一个形象生动的动画来说明一下此方法
在这里插入图片描述

再配上一个简单的说明:
①先以6为基准,然后慢慢走Lift和Right。最后比基准小的在左边,比基准大的在右边。
②Right先走,走到比基准小的值就停下,然后Left走,走到比基准大的值就停下,然后交换Left和Right的值。
③然后Right继续走,走到比基准小的值就停下,然后Left走,走到比基准大的值就停下,然后继续交换。
④Right和Left相遇了,就把基准值与相遇位置的值交换。便完成了比基准小的在左边,比基准大的在右边的操作。

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

void hoare_QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int midi = GetMidi(a, begin, end);
		Swap(&a[midi], &a[begin]);

		int left = begin, right = end;
		int keyi = begin;

		while (left < right)
		{
			// 右边找小
			while (left < right && a[right] >= a[keyi])
			{
				--right;
			}

			// 左边找大
			while (left < right && a[left] <= a[keyi])
			{
				++left;
			}

			Swap(&a[left], &a[right]);
		}

		Swap(&a[left], &a[keyi]);
		keyi = left;

		// [begin, keyi-1] keyi [keyi+1, end]
		hoare_QuickSort(a, begin, keyi - 1);
		hoare_QuickSort(a, keyi + 1, end);
	}
}

6.2 前后指针法

用一个形象生动的动画来说明一下此方法
在这里插入图片描述

配上一个简短的说明:
①以6为基准值,前驱指针prev先走,cur再走。当cur的值大于基准值时,prev不动,cur继续走,当cur的值小于基准值时,cur不动。此时,prev向前走,交换cur与prev的值。这样小的数就到前面,大的数就到后面了。
②cur继续走,以此类推。cur遇到比基准大的数就继续走,知道遇到比基准小的数,prev才动,然后交换。
③继续上述步骤,直到cur越界,然后交换基准值和prev的值。

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

void point_QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	if (end - begin + 1 <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int midi = GetMidi(a, begin, end);
		Swap(&a[midi], &a[begin]);
		int keyi = begin;

		int prev = begin;
		int cur = prev + 1;
		while (cur <= end)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
				Swap(&a[prev], &a[cur]);

			++cur;
		}

		Swap(&a[prev], &a[keyi]);
		keyi = prev;

		// [begin, keyi-1] keyi [keyi+1, end]
		point_QuickSort(a, begin, keyi - 1);
		point_QuickSort(a, keyi + 1, end);
	}
}

6.3 挖洞法

用一个形象生动的动画来说明一下此方法
在这里插入图片描述
再配一个简短的说明:
①先选出基准值,然后基准值的位置为坑位。Right先走,遇到比基准值小的数就停下。然后把这个小于基准值的数放到坑位,这个位置变成新的坑位。
②然后left走,遇到比基准值大的数停下,把这个大于基准值的数放到坑位,这个位置再变成新的坑位。
③以此类推,直到left和right相遇,然后将基准值放入最后的坑位即可。

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

int dig(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		// 右边找小,填到左边的坑
		while (begin < end && a[end] >= key)
		{
			--end;
		}

		a[hole] = a[end];
		hole = end;

		// 左边找大,填到右边的坑
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}

		a[hole] = a[begin];
		hole = begin;
	}

	a[hole] = key;
	return hole;
}

void dig_QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = dig(a, begin, end);
	dig_QuickSort(a, begin, keyi - 1);
	dig_QuickSort(a, keyi + 1, end);
}

7.归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
在这里插入图片描述

在这里插入图片描述
简而言之就是先一个一个排,然后两个两个排,然后四个四个排,然后再全排序。

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid][mid+1, end]
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// [begin, mid][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, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

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

	free(tmp);
}

8.排序性能比较

void TestOP()
{
	srand(time(0));
	const int N = 100000;
	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);

	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];
	}

	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();
	hoare_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();

	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("hoare_QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

在这里插入图片描述
各种排序跑10W个数所需要的时间如上。
快速排序的三种算法,时间都差不多。只不过思想不一样罢了。

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

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

相关文章

Guava的TypeToken在泛型编程中的应用

第1章&#xff1a;引言 在Java世界里&#xff0c;泛型是个相当棒的概念&#xff0c;能让代码更加灵活和类型安全。但是&#xff0c;泛型也带来了一些挑战&#xff0c;特别是当涉及到类型擦除时。这就是TypeToken大显身手的时候&#xff01; 作为Java程序员的咱们&#xff0c;…

前端-如何用echarts绘制含有多个分层的波形图

一、效果图展示 先展示一下实际的效果图 用户选择完需要的波形参数字段之后&#xff0c;页面开始渲染图表&#xff0c;有几个参数就要渲染几个grid&#xff0c;也就是几行波形。 二、绘制逻辑 拿到所选的参数数据之后 1.首先是给横坐标轴的里程-数据注入 2.修改tooltip&am…

鸿蒙开发者工具安装及入门程序

下载工具DevEco Studio IDE 官网下载&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 开发工具的安装 解压下载好的压缩包&#xff0c;一路无脑安装即可&#xff0c;安装完的使用方法类似于IDEA、WebStorm的使用&#xff0c;快捷键一致&#xff0c;默认黑…

运算符号、算术运算符、赋值运算符、比较(关系)运算符、逻辑运算符、位运算符、条件运算符

运算符是一种特殊的符号&#xff0c;用以表示数据的运算、赋值和比较等。 运算符的分类&#xff1a;按照功能分为&#xff1a;算术运算符、赋值运算符、比较&#xff08;或关系&#xff09;运算符、逻辑运算符、位运算符、条件运算符、Lambda运算符。 按照操作数的个数分为&am…

el-select如何去掉placeholder属性

功能要求是&#xff1a;当el-select的disabled属性为true的时候不展示“请选择”字样 1、要去掉 el-select 元素的 placeholder 属性&#xff0c;可以在代码中将其设置为空字符串。 <el-select placeholder"" ...></el-select> 注意&#xff1a;这种方…

多个bean获取同一个Service,获取的内存地址是同一块;引用bean地址存储在一个map中

public class UserService {public void test() {System.out.println("---test----");} } Testpublic void doesNotContain1(){// 创建Spring容器AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext();// 向容器中注册…

odoo17核心1——概述

odoo17发布了&#xff0c;如果说odoo16是一个承前启后的版本&#xff0c;那么odoo17则完全抛弃了历史包袱&#xff0c;全面简化了前端代码&#xff0c;是一个里程碑式的版本。 在学习odoo的过程中&#xff0c;结合对源码的阅读&#xff0c;对odoo的设计哲学有了一些自己的感悟…

【XR806开发板试用】通过http请求从心知天气网获取天气预报信息

1. 开发环境搭建 本次评测开发环境搭建在windows11的WSL2的Ubuntu20.04中&#xff0c;关于windows安装WSL2可以参考文章: Windows下安装Linux(Ubuntu20.04)子系统&#xff08;WSL&#xff09; (1) 在WSL的Ubuntu20.04下安装必要的工具的. 安装git: sudo apt-get install git …

桶装水送水小程序:提升服务质量的利器

随着移动互联网的发展&#xff0c;越来越多的消费者通过手机在线购物和订购商品。如果你是一名桶装水供应商&#xff0c;想要拓展线上业务&#xff0c;那么开发一个桶装水微信小程序将是一个明智的选择。本文将指导你从零开始开发一个桶装水微信小程序&#xff0c;让你轻松完成…

Google推出Gemini AI开发——10年工作经验的Android开发要被2年工作经验的淘汰了?

应用程序中利用 Gemini 前言&#xff08;可略过&#xff09;、使用 Gemini Pro 开发应用程序正文、Android Studio 中构建Gemini API Starter 应用第 1 步&#xff1a;在 AI 的新项目模板的基础上进行构建第 2 步&#xff1a;生成 API 密钥第 3 步&#xff1a;开始原型设计 正文…

2024年【安全生产监管人员】模拟考试题库及安全生产监管人员理论考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产监管人员模拟考试题库是安全生产模拟考试一点通总题库中生成的一套安全生产监管人员理论考试&#xff0c;安全生产模拟考试一点通上安全生产监管人员作业手机同步练习。2024年【安全生产监管人员】模拟考试题…

【小黑嵌入式系统第十二课】μC/OS-III程序设计基础(二)——系统函数使用场合、时间管理、临界区管理、使用规则、互斥信号量

上一课&#xff1a; 【小黑嵌入式系统第十一课】μC/OS-III程序设计基础&#xff08;一&#xff09;——任务设计、任务管理&#xff08;创建&基本状态&内部任务&#xff09;、任务调度、系统函数 文章目录 一、系统函数使用场合1.1 时间管理1.1.1 控制任务的执行周期1…

数据分析基础之《numpy(5)—合并与分割》

了解即可&#xff0c;用panads 一、作用 实现数据的切分和合并&#xff0c;将数据进行切分合并处理 二、合并 1、numpy.hstack 水平拼接 # hstack 水平拼接 a np.array((1,2,3)) b np.array((2,3,4)) np.hstack((a, b))a np.array([[1], [2], [3]]) b np.array([[2], […

AndroidStudio无法新建Java工程解决办法

我用的 AS 版本是 Android Studio Giraffe | 2022.3.1 Build #AI-223.8836.35.2231.10406996, built on June 29, 2023 以往新建工程都是 New project >> Empty Activity &#xff0c; 有个选择 Java 还是 Kotlin 语言的选项&#xff0c; 之后会默认生成一个 MainActi…

DB207S-ASEMI迷你贴片整流桥DB207S

编辑&#xff1a;ll DB207S-ASEMI迷你贴片整流桥DB207S 型号&#xff1a;DB207S 品牌&#xff1a;ASEMI 封装&#xff1a;DBS-4 最大平均正向电流&#xff1a;2A 最大重复峰值反向电压&#xff1a;1000V 产品引线数量&#xff1a;4 产品内部芯片个数&#xff1a;4 产品…

【华为数据之道学习笔记】6-5数据地图的核心价值

数据供应者与消费者之间往往存在一种矛盾&#xff1a;供应者做了大量的数据治理工作、提供了大量的数据&#xff0c;但数据消费者却仍然不满意&#xff0c;他们始终认为在使用数据之前存在两个重大困难。 1&#xff09;找数难 企业的数据分散存储在上千个数据库、上百万张物理表…

JS逆向基础

JS逆向基础 一、什么是JS逆向&#xff1f;二、接口抓包三、逆向分析 一、什么是JS逆向&#xff1f; 我们在网站进行账号登录的时候对网页源进行抓包就会发现我们输入的密码在后台会显示为一串由字母或数字等符号&#xff0c;这就是经过加密呈现的一段加密文字&#xff0c;而分…

个人信息图片如何批量建码?批量图片二维码的方法

当我们需要给工作人员的证件图片批量生成二维码时&#xff0c;如何处理能够快速的将每张图片单独生成一张二维码使用呢&#xff1f;对于有这个需求的小伙伴来说&#xff0c;最快捷的方式可以用图片二维码生成器来处理&#xff0c;通过图片批量建码功能来完成制作&#xff0c;下…

【什么是泛型,有什么好处】

✅什么是泛型&#xff0c;有什么好处 ✅典型回答✅泛型是如何实现的✅什么是类型擦除&#xff1f;&#x1f4dd;C语言对泛型的支持&#x1f4dd;泛型擦除的缺点有哪些&#xff1f; ✅对泛型通配符的理解&#x1f4dd;泛型中上下界限定符 extends 和 super 有什么区别&#xff1…

YTM32的Flash控制器EFM模块详解

YTM32的Flash控制器EFM模块详解 文章目录 YTM32的Flash控制器EFM模块详解Flash存储器控制器EFM模块概述YTM32的Flash存储器特性Flash的约定术语存储器的地址空间及定义 使用Flash存储器擦写Flash的操作流操作Flash的时长 EFM的其他关于Flash的“骚操作”交换启动操作&#xff0…