初阶数据结构:排序(学习笔记)

news2024/11/15 6:57:27

目录

  • 1. 各种排序算法的分类
  • 2. 插入排序
    • 2.1 直接插入排序
    • 2.2 希尔排序
  • 3. 选择排序
    • 3.1 选择排序
    • 3.2 堆排序
    • 4. 交换排序
    • 4.1 冒泡排序
    • 4.2 快速排序
    • 4.2.1 霍尔法(hoare)
    • 4.2.2 挖坑法(hole)
    • 4.4.3 前后指针法
    • 4.4.4 补充:非递归快排
    • 4.5 快排优化
  • 5. 归并排序
  • 6. 非比较排序:计数排序
  • 7. 排序算法的稳定性

1. 各种排序算法的分类

按照排序逻辑的不同,排序大体可以分为四大类:

  1. 插入排序
  2. 选择排序
  3. 交换排序
  4. 归并排序

接下来,我们进行这些排序的学习

!注:本章内容中的动图并非原创

2. 插入排序

2.1 直接插入排序

  1. 将整个数组的元素,从起始遍历,一次向后移动一步,看作是将一个元素插入到数组中。
  2. 在"插入"的过程中,当新插入的元素小于其前面的元素时,交换两者,循环此步骤,直至前面的元素不小于新插入的元素,到此成功插入一个元素。

过程演示:
在这里插入图片描述

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		for (int j = i; a[j - 1] > a[j]; j--)
		{
			swap(&a[j - 1], &a[j]);
		}
	}
}

2.2 希尔排序

  1. 根据给定组距,进行数据的分组,组内进行插入排序。
  2. 不断减小组距,直至组距为1。
  3. 注:在组距不为1时,都是预排序,让数据更接近有序。
//多趟排
void ShellSort1(int* a, int n)
{
	int gap = n / 2;
	while (gap > 0)
	{
		for (int i = 0; i < gap; i++)
		{
			for (int j = i; j + gap < n; j += gap)
			{
				if (a[j] > a[j + gap])
				{
					swap(&a[j], &a[j + gap]);
				}
			}
		}

		gap /= 2;
	}
}

//一趟排
void ShellSort2(int* a, int n)
{
	int gap = n / 2;
	while (gap > 0)
	{
		for (int i = 0; i + gap < n; i++)
		{
			if (a[i] > a[i + gap])
			{
				swap(&a[i], &a[i + gap]);
			}
		}

		gap /= 2;
	}
}

3. 选择排序

3.1 选择排序

  1. 遍历一次,从数据中选出最小(或最大)的数据放至数据首部。
  2. 多次遍历选择,直至将最后一个数据选走。

过程演示:
在这里插入图片描述

void SelectSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int min = i;
		for (int j = i; j < n; j++)
		{
			if (a[j] < a[min])
			{
				min = j;
			}
		}

		swap(&a[i], &a[min]);
	}
}

选择排序优化:

一次遍历选出最大值与最小值

void SelectSort2(int* a, int n)
{
	for (int i = 0; i < n / 2; i++)
	{
		int max = i;
		int min = i;
		for (int j = i; j < n - i; j++)
		{
			if (a[j] > a[max])
			{
				max = j;
			}

			if (a[j] < a[min])
			{
				min = j;
			}
		}
		swap(&a[max], &a[n - i - 1]);
		swap(&a[min], &a[i]);
	}
}

3.2 堆排序

  1. 建大堆
  2. 交换首尾,size–,向下调整,直到size为0
void AdjustDown(int* a, int n, int root)
{
	int child = root * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}

		if (a[root] < a[child])
		{
			swap(&a[root], &a[child]);
		}

		root = child;
		child = root * 2 + 1;
	}
}

void HeapSort(int* a, int n)
{
	//建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	//交换首尾,调整
	int size = n;
	while (size > 0)
	{
		swap(&a[0], &a[size - 1]);
		size--;
		AdjustDown(a, size, 0);
	}
}

4. 交换排序

4.1 冒泡排序

  1. 建立两个一前一后的指针,用这两个指针遍历整个数组
  2. 若后指针指向的数据大于前指针指向的数据,交换前后指针所指向的元素,之后两指针++,直至遍历完数据,得出一个最大数,需遍历的数据长度减1,此为遍历一趟。
  3. 多次遍历,当长度为0时,排序结束

过程演示:
在这里插入图片描述

void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		int flag = 1;
		for (int j = 0; j + 1 < n - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				swap(&arr[j], &arr[j + 1]);
				flag = 0;
			}
		}

		if (flag)
		{
			break;
		}
	}
}

4.2 快速排序

4.2.1 霍尔法(hoare)

  1. 将数据的首位确定为对照key,定义两个指针left(数据首部),right(数据尾部)。
  2. 右侧指针反向遍历数组,寻找小于key的值,当找到后停止,左侧指针正向遍历数组,寻找大于key的值,找到后将两指针指向的数据交换。
  3. 重复上述步骤2,直至左右指针相遇,交换key元素与左右指针同时指向的元素,此为一趟排序。
  4. 将数据分割为[0,key - 1]与[key + 1,n],在这两个区间内再进行上述步骤2,3。直至所有元素的位置都被确认。

过程演示:
在这里插入图片描述

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int key = left;
	int keyi = a[key];

	while (left < right)
	{
		while (left < right && a[right] >= keyi)
		{
			right--;
		}

		while (left < right && a[left] <= keyi)
		{
			left++;
		}

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

	if (a[left] < keyi)
	{
		swap(&a[left], &a[key]);
		key = left;
	}

	return key;
}

4.2.2 挖坑法(hole)

  1. 选择首位数据为key,然后将数据的首位标记为hole,创建两个指针left(首位 ),right(数据尾部)。
  2. 右侧指针找寻找小于key元素的值,找到后,将所找到的元素填充至挖好的"洞"里,此元素原位置标记为新的洞,然后,移动左侧指针寻找大于key元素的值,找到后,将找到的元素填入洞中。重复上述步骤,直至左右指针相遇,将key值填入左右指针相遇的位置,此时即确定好了key的位置。
  3. 在[left,key - 1]与[key + 1, right]的区间中,重复步骤2,直至所有位置都被确定。

过程演示:

在这里插入图片描述

int PartSort2(int* a, int left, int right)
{
	int hole = left;
	int keyi = a[hole];
	
	while (left < right)
	{
		//额外检查,越界可能
		while (left < right && a[right] >= keyi)
		{
			right--;
		}

		if (a[right] < keyi)
		{
			a[hole] = a[right];
			hole = right;
		}

		while (left < right && a[left] <= keyi)
		{
			left++;
		}

		if (a[left] > keyi)
		{
			a[hole] = a[left];
			hole = left;
		}
	}

	a[hole] = keyi;

	return hole;
}

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

	int key = PartSort2(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

4.4.3 前后指针法

思路1:

  1. 将数据首位设置为key,创建两个指针pre(首部),cur(首部 + 1)。
  2. cur开始遍历整个数组,如果cur指针指向的值小于key,那么pre指针一同++,否则pre指针不动,直至cur再次寻找到小于key的值,此时,pre++,然后将两指针指向的值交换。如此,反复直至cur遍历完整个数组,最后,将key与pre指针指向的值交换。
  3. 在[left,key - 1]与[key + 1, right]的区间中,重复步骤2,直至所有位置都被确定。

思路2:

  1. [left + 1,pre]区间为小于key的值,[pre + 1,cur - 1]为大于key的值,[cur,right]为未遍历到的值。
  2. cur指针遍历寻找小于pre指针的数据,找到后pre++,交换两指针所指向的值。

过程演示:
在这里插入图片描述

int PartSort3(int* a, int left, int right)
{
	int pre = left;
	int cur = left + 1;
	int keyi = a[left];

	while (cur <= right)
	{
		if (a[cur] < keyi)
		{
			swap(&a[++pre], &a[cur]);
		}

		cur++;
	}

	swap(&a[left], &a[pre]);

	return pre;
}

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

	int key = PartSort3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

4.4.4 补充:非递归快排

  1. 将原本递归传递的区间存储到栈中,用时从栈中取出
void QuickSortNonR(int* a, int left, int right)
{
	Stack stack;
	StackInit(&stack);

	//插入第一次遍历区间范围
	StackPush(&stack, left);
	StackPush(&stack, right);

	while (!StackEmpty(&stack))
	{
		//取出区间值进行运算
		right = StackTop(&stack);
		StackPop(&stack);
		left = StackTop(&stack);
		StackPop(&stack);

		int key = PartSort3(a, left, right);
		
		//区间遍历顺序:左区间,右区间
		if (key + 1 < right)
		{
			StackPush(&stack, key + 1);
			StackPush(&stack, right);
		}

		if (left < key - 1)
		{
			StackPush(&stack, left);
			StackPush(&stack, key - 1);
		}
	}
}

4.5 快排优化

  1. 三数取中(getmid)
  2. 当递归到小区间时,可以转而进行插入排序
//三数取中
int GetMidNum(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	
	if(a[mid] > a[left])
	{
		if(a[mid] < a[right])
		{
			return mid;
		}
		else
		{
			if(a[right] > a[left])
			{
				return right;
			}
			else
			{
				return left;
			}
		}
	}
	else
	{
		if(a[mid] > a[right])
		{
			return mid;
		}
		else
		{
			if(a[left] < a[right])
			{
				return left;
			}
			else
			{
				return right;
			}
		}
	}
}

5. 归并排序

思路1:

归并逻辑:二叉树的遍历(深度优先:左右根)

  1. 将需要进行归并的区间范围视作结点,根结点的区间为整个数组
  2. 左右孩子结点为将区间范围一分为2,左孩子为前半区间,右孩子为后半区间
  3. 对每次得到的新区间都进行上述处理,直至区间中的元素数(<=2),即视为叶子结点。
  4. 按照后序遍历二叉树的顺序,对结点区间内的数据进行插入排序。

过程演示:
在这里插入图片描述
递归法:
在这里插入图片描述

void _mergesort(int* a, int* tmp, int left, int right)
{
	//深度优先
	if (left >= right)
	{
		return;
	}

	int mid = (right + left) / 2;
	_mergesort(a, tmp, left, mid);
	_mergesort(a, tmp, mid + 1, right);

	//插入
	int i = left;
	int j = mid + 1;
	int k = left;
	while (i <= mid && j <= right)
	{
		//当存在相同的数时
		if (a[i] <= a[j])
		{
			tmp[k++] = a[i++];
		}
		else
		{
			tmp[k++] = a[j++];
		}
	}

	while (i <= mid)
	{
		tmp[k++] = a[i++];
	}

	while (j <= right)
	{
		tmp[k++] = a[j++];
	}

	memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));

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

	free(tmp);
}

思路2:

  1. 将数组的元素分为两个两个一组,将每一组都使用插入排序调整为有序,遍历一遍数组
  2. 将一组中的元素数翻倍,重复步骤1,遍历完成再次翻倍,直至一组中的元素数包含整个数组,排序完成
  3. 当剩余元素不足一组时,将剩余元素也算作一组

在这里插入图片描述

非递归法:

void MergeSortNonR(int* a, int n)
{
	int* c_a = (int*)malloc(n * sizeof(int));

	int gap = 1;
	while (gap < n)
	{
		//确定初始区间
		int begin1 = 0;
		int end1 = begin1 + gap - 1;

		int begin2 = end1 + 1;
		int end2 = begin2 + gap - 1;
		//检测防止越界
		if (end2 >= n)
		{
			end2 = n - 1;
		}

		while (begin1 < n)
		{
			//插入
			int i = begin1;
			int j = begin2;
			int k = begin1;
			while (i <= end1 && j <= end2)
			{
				if (a[i] <= a[j])
				{
					c_a[k++] = a[i++];
				}
				else
				{
					c_a[k++] = a[j++];
				}
			}

			while (i <= end1)
			{
				c_a[k++] = a[i++];
			}

			while (j <= end2)
			{
				c_a[k++] = a[j++];
			}

			//拷贝回原数组
			memcpy(a + begin1, c_a + begin1, (end2 - begin1 + 1) * sizeof(int));
			
			//向后调整区间
			begin1 = end2 + 1;
			end1 = begin1 + gap - 1;

			begin2 = end1 + 1;
			end2 = begin2 + gap - 1;

			//判断是否越界
			if (end1 >= n)
			{
				end1 = n - 1;
			}

			if (end1 < n && end2 >= n)
			{
				end2 = n - 1;
			}
		}

		gap *= 2;
	}

	free(c_a);
}

6. 非比较排序:计数排序

  1. 根据数据的范围,创建一个合适大小的数组。
  2. 下标对应数据,根据数据中各个数字的出现次数在对应的下标处计数++。
  3. 限制:
    <1> 数据范围不可跨度太大,会导致空间复杂度过高
    <2>只能用来处理整形数据。

过程演示:
在这里插入图片描述

void CountSort(int* a, int n)
{
	//选出最大值与最小值
	int min = a[0];
	int max = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}

		if (a[i] > max)
		{
			max = a[i];
		}
	}

	//开辟空间
	int size = max - min + 1;
	int* count = (int*)malloc(size * sizeof(int));
	memset(count, 0, n * sizeof(int));

	//计数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	//读数
	int index = 0;
	for (int i = 0; i < size; i++)
	{
		while (count[i])
		{
			a[index++] = i + min;
			count[i]--;
		}
	}
}

7. 排序算法的稳定性

算法稳定性的判断标准:数据中相同数据在排序后,他们的相对位置是否变化。

在这里插入图片描述

  1. 直接插入排序(稳定,时间复杂度:O( n 2 n^2 n2))
  2. 希尔排序(不稳定,时间复杂度略小于O( n 2 n^2 n2))
  3. 选择排序(稳定,O( n 2 n^2 n2))
  4. 堆排序(不稳定,O( n n n * logn))
  5. 冒泡排序(稳定,O( n 2 n^2 n2))
  6. 快速排序(不稳定,O( n n n * logn))
  7. 归并排序(不稳定,O( n n n * logn))
  8. 计数排序(稳定,O(n,Max))

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

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

相关文章

【项目管理】CMMI-质量保证过程

质量保证过程&#xff08;PQA)&#xff1a;通过质量保证活动&#xff0c;确保过程与产品满足过程、规程及相应的要求&#xff0c;确保问题得到关注与解决&#xff0c;使工作人员和管理者能够客观地了解过程与相关的工作产品。QA工程师应实施质量保证策划活动&#xff0c;客观地…

常用的17个运维监控系统(必备知识)

1. Zabbix Zabbix 作为企业级的网络监控工具&#xff0c;通过从服务器&#xff0c;虚拟机和网络设备收集的数据提供实时监控&#xff0c;自动发现&#xff0c;映射和可扩展等功能。 Zabbix的企业级监控软件为用户提供内置的Java应用服务器监控&#xff0c;硬件监控&#xff0c…

【Android】源码解析 Activity 的构成

本文是基于 Android 14 的源码解析。 当我们写 Activity 时会调用 setContentView() 方法来加载布局。现在来看看 setContentView() 方法是怎么实现的&#xff0c;源码如下所示&#xff1a; 路径&#xff1a;/frameworks/base/core/java/android/app/Activity.javapublic void…

uniapp报错:request:fail abort statusCode:-1 Chain validation failed

uniapp报错信息记录 场景: 半年没碰过的app&#xff0c;今个儿突然无法登录了。 打开控制台&#xff0c;报错信息如下 {msg: request:fail abort statusCode:-1 Chain validation failed}奇怪的是用 apifox 调用相关的接口&#xff0c;可以正常运行&#xff0c;app却不行。 好…

怎么将pom在文件放到src下方

今天在IDEA从git拉取项目的时候&#xff0c;发现pom.xml文件在文件夹src的上方&#xff0c;平时看惯了项目的pom.xml文件在文件夹src的下方&#xff0c;应该怎么去设置呢&#xff1f; 点击设置——>点击Folder Always on Top 即可 参考&#xff1a;http://t.csdnimg.cn/s34…

LCR 126. 斐波那契数

解题思路&#xff1a; 动态规划 class Solution {public int fib(int n) {if (n < 1) return n;int[] dp new int[n1];dp[0] 0;dp[1] 1;for (int i 2; i < n; i) {//取模运算防止整数溢出dp[i] (dp[i - 1] dp[i - 2]) % 1000000007;}return dp[n];} }

600+企业齐聚!第十四届中国国际储能大会暨展览会即将在杭州盛大开幕

数字储能网讯&#xff1a;由中国化学与物理电源行业协会主办&#xff0c;中国化学与物理电源行业协会储能应用分会和中国储能网联合承办的第十四届中国国际储能大会暨展览会将于2024年3月10日在杭州国际博览中心盛大启幕&#xff0c;大会主题为“共建储能生态链&#xff0c;共创…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记01_比价

1. 科技正在改善我们的生活 1.1. 从表象看&#xff0c;网络世界为我们带来了诸多便利 1.1.1. 比价网站的创建、各式各样的电商促销、数不尽的手机应用程序的确降低了商品的售价&#xff0c;提升了产品的品质&#xff0c;丰富了消费者的选择 1.2. …

【PCL】(十七)从距离图像中提取点云的边界

&#xff08;十七&#xff09;从测距图像中提取边界 Object(Obstacle) border&#xff1a;属于物体的最外面的可见点&#xff1b; Shadow border&#xff1a;背景中与物体相邻的点&#xff1b; Veil points&#xff1a;Object border和Shadow border之间的插值点。 以下代码…

web坦克大战小游戏

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的订阅后,私信本人,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、贪吃蛇、飞机大战、坦克大战、开心消消乐、扑鱼达人、扫雷、打地鼠、斗地主等等。 <!DOCTYPE htm…

express+mysql+vue,从零搭建一个商城管理系统8--文件上传,大文件分片上传

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、安装multer&#xff0c;fs-extra二、新建config/upload.js三、新建routes/upload.js四、修改routes下的index.js五、修改index.js六、新建上传文件test.html七、开启jwt验证token&#xff0c;通过login接…

渗透测试专用firefox浏览器 v2024.2最新版!!!

前言 之前做了一个firefox渗透测试专用浏览器v1&#xff0c;并且经过广大师傅的意见以及建议部分功能进行了调整以及新增。 如有相关意见&#xff0c;可以加入我们交流群进行反馈。 更新内容 去除DarkReader&#xff08;夜间模式&#xff09; 修改firefox主题&#xff0c;使…

【促销定价】背后的算法技术3-数据挖掘分析

【促销定价】背后的算法技术3-数据挖掘分析 01 整体分析1&#xff09;整体概览2&#xff09;类别型特征概览3&#xff09;数值型特征概览 02 聚合分析1&#xff09;天维度2&#xff09;品维度3&#xff09;价格维度4&#xff09;数量维度 03 相关分析1&#xff09;1级品类2&…

如何准备2024年汉字小达人:历年考题练一练-18道选择题解析

距离2024年第11届汉字小达人比赛还有八个多月的时间&#xff0c;建议如果有可能都让孩子参加一下&#xff0c;无需报名费&#xff0c;如果没时间准备也可以直接上阵参赛&#xff0c;检验一下孩子语文字、词、成语和古诗文方面的掌握情况。一方面可以激发孩子学习语文的兴趣&…

高级货,极大提高效率,个人非常喜欢

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; FileConverter中文版是一款免费软件&#xff0c;具有强大的功能。它支持多种文件格式的转换&#xff0c;包括视频、音频、文档等。您可以批量转换文件…

Unity 动态加载音频和音效

想要加载音效和音频需要两个组件&#xff1a; 听&#xff1a; 播&#xff1a; 一收一发 在层级中&#xff0c;右键创建 音频源 &#xff0c;放入物体的子物体中。 播放 方式一 拖动需要播放的音频文件到&#xff0c;音频源组件中。 using System.Collections; using Syst…

如何在Linux系统使用docker部署Apache Superset并结合内网穿透实现公网远程访问

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…

C# WinForm AndtUI第三方库 Tree控件使用记录

环境搭建 1.在NuGet中搜索AndtUI并下载至C# .NetFramework WinForm项目。 2.添加Tree控件至窗体。 使用方法集合 1.添加节点、子节点 using AntdUI; private void UpdateTreeView() {Tree tvwTestnew Tree();TreeItem rootTreeItem;TreeItem subTreeItem;Dictionary<str…

微信小程序用户隐私保护指引设置

场景&#xff1a;开发小程序时&#xff0c;有时候需要获取用户隐私信息&#xff0c;在提交小程序审核时&#xff0c;需要填写一份隐私保护协议&#xff0c;经常由于填写不规范导致审核不通过&#xff0c;在网上找到了一份模块可供参考 步骤&#xff1a;小程序后台-》设置-》服…