【数据结构】经典排序

news2024/11/27 6:14:34

【数据结构】八大排序

  • 1. 排序的概念和运用
    • 1.1 概念
    • 1.2 运用
  • 2. 常规的排序算法介绍
  • 一. 插入排序
    • 1.1 直接插入排序
    • 1.2 希尔排序
  • 二. 选择排序
    • 2.1 选择排序
    • 2.2 堆排序
  • 三. 交换排序
    • 3.1 冒泡排序
    • 3.2 快速排序
      • 3.2.1 Hoare法
      • 3.2.2 挖坑法
      • 3.2.3 前后指针/左右指针法
      • 3.2.4 分治法/递归法
      • 3.2.5 非递归法
  • 四. 归并排序
  • 五.总结

1. 排序的概念和运用

1.1 概念

什么是排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

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

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

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

比较排序:通过比较两个元素的大小来确定元素在内存中的次序,我们常见的入选择排序、插入排序、比较排序与归并排序都属于比较排序。

非比较排序:通过确定每个元素之前,应该有多少个元素来排序,常见的非比较排序有基数排序、计数排序与桶排4序。

1.2 运用

排序在我们日常生活中运用十分常见,例如在京东买东西时,可以通过销量,评论数,新品,价格进行排序。

又比如世界五百强企业等,我们都可以运用排序。
在这里插入图片描述
在这里插入图片描述

2. 常规的排序算法介绍

在这里插入图片描述

// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
//选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n)
// 快速排序
void QuickSort(int* a, int left, int right);
// 归并排序
void MergeSort(int* a, int n)

一. 插入排序

1.1 直接插入排序

  • 基本思想:

直接插入排序是一种简单的插入排序法。

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

实际中我们玩扑克牌时,就用了插入排序的思想:
在这里插入图片描述

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…

的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

在这里插入图片描述

void InsertSort(int* a, int n)
	{
		assert(a);//断言,不为空指针
		for (int i = 0;i < n - 1;i++)
		{
			int end = i;
			int x = a[end + 1];//记录最后一个元素,为要插入的目标元素
			while (end >= 0)
			{
				//升序
				//如果目标元素大于tmp中元素,则往后移
				if (a[end] > x)
				{
					a[end + 1] = a[end];
					end--;
				}
				else
				{
					break;
				}
			}
			a[end + 1] = x;//将要插入的元素,插到不大于该数据的最后一个位置
		}
	}

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

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

1.2 希尔排序

基本思想:

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

希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

在这里插入图片描述

void ShellSort(int* a, int n)
	{
		int gap = n;
		while (gap > 1)
		{
			gap /= 2;//确保最后一次的gap==1,直接插入排序
			for (int i = 0;i < n - gap;i++)
			{
				int end = i;
				int x = a[end + gap];
				while (end >= 0)
				{
					if (a[end] > x)
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
					a[end + gap] = x;
				}
			}
		}
	}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的
    了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的
    对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3—
    N^2)。
  4. 稳定性:不稳定。

二. 选择排序

基本思想:

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

2.1 选择排序

基本思想:

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

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。

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

void SelectSort(int* a, int n)
	{
		int left = 0;
		int right = n - 1;//记录下标
		while (left < right)
		{
			int min = left;
			for (int i = left;i < right;i++)
			{
				if (a[i] < a[min])//比较,交换下标
				{
					min = i;//遍历找到最小值的下标,并记录
				}
			}
			swap(&a[left], &a[min]);//交换
			left++;
		}
	}

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

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

2.2 堆排序

基本思想:

  • 原则: 先将原数组建成堆,需要注意的是排升序要建大堆,排降序建小堆。 注:以大堆为例:树中所以父亲结点值都 >= 孩子,根是最大的。

  • 建堆: 一个根节点与子节点数据如果不符合大堆结构,那么则对根节点数据进行向下调整,而向下调整的前提是左右子树也符合大堆结构,所以从堆尾数据的根节点位置开始向下调整建大堆。
    注:堆的两个特性:
    结构性:用数组表示的完全二叉树。
    有序性:任一结点的关键字是其子树所以结点是最大结点(或最小结点)

  • 排序: 大堆堆顶数据一定是待排数据中最大的,将堆顶数据与堆尾数据交换,交换后将除堆尾数据看成新堆,对现堆顶数据进行向下调整成大堆,以此循环直至排列完毕。

  • 向下调整: 找到子节点中的较大数据节点比较,如果父节点数据比大子节点小则交换,直到不符合则停止向下交换,此时再次构成了一个大堆结构。

// 堆排序(升序)/建大堆
	void HeapSort(int* a, int n)
	{
		int i;
		//建大堆
		//最后一个结点的1父亲下标:[(n-1)-1]/2;
		for (i = (n - 1 - 1) / 2; i >= 0; i--)
		{
			Adjustdown(a, n, i);
		}
		for (i = n - 1; i >= 0; i--)
		{
			Swap(&a[0], &a[i]);//与当前堆尾数据交换
			Adjustdown(a, i, 0);//对交换后堆顶数据进行向下调整
		}
	}
	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[parent] < a[child])
			{
				Swap(&a[parent], &a[child]);
				//更新下标
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

三. 交换排序

基本思想:

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

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

3.1 冒泡排序

基本思想:

每次遍历数组,对相邻的数据进行比较,大的元素往后放。

在这里插入图片描述

void BubbleSort(int* a, int n)
	{
		for (int i = 0;i < n-1;i++)//遍历数组,n个元素只需要n-1趟,每次比较,都可以将最大值放末尾
		{
			for (int j = 0;j < n - i - 1;j++)//n-1-i:因为每遍历一趟,可以确定一个元素,所以n-1-i;
			{
				if (a[j] > a[j + 1])
				{
					swap(&a[j],&a[j+1])
				}
			}
		}
	}

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

3.2 快速排序

基本思想:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列。

左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

3.2.1 Hoare法

在这里插入图片描述

int PartSort1(int* a, int left, int right)
{
 int keyi = left;  //取最左边的元素做key

 while (left < right)
 {
  //右先走,找小
  //left<right:避免left和right错过或者越界
  //=:避免死循环
  while (left < right && a[right] >= a[keyi])
  {
   right--;
  }

  //左后走,找大
  while (left < right && a[left] <= a[keyi])
  {
   left++;
  }
  Swap(&a[left], &a[right]);
 }
 //记录二者相遇的位置并让其与key交换
 int meet = left;
 Swap(&a[meet], &a[keyi]);
 return meet;//返回相遇时下标值
}

3.2.2 挖坑法

int PartSort2(int* a, int left, int right)
{
 int mid = GetMidIndex(a, left, right);
 Swap(&a[mid], &a[left]);//使得中间值永远在最左,便于决定谁先走
 int key = a[left];//保存key值
 int pivot = left;//保存坑下标
 while (left < right)
 {
  //右边先找
  while (left<right && a[right]>=key)
  {
   right--;
  }
  //填坑
  a[pivot] = a[right];
  pivot = right;
  //再从左边找
  while (left < right && a[left] <= key)
  {
   left++;
  }
  //填坑
  a[pivot] = a[left];
  pivot = left;
 }
 a[pivot] = key;
 return pivot;
}

3.2.3 前后指针/左右指针法

int PartSort3(int* a, int left, int right)
{
 int mid = GetMidIndex(a, left, right);
 Swap(&a[mid], &a[left]);
 //初始化前后指针
 int cur = left+1, prev = left;
 while (cur < right)
 {
  if(a[cur]<a[left] )//找到比基准值小的
  Swap(&a[++prev], &a[cur]);

  cur++;
 }
 Swap(&a[prev], &a[left]);//遍历结束将基准值放在定位点
 return prev;
}

3.2.4 分治法/递归法

在了解快排的思想之后,我们会发现快速排序的排序过程是这样的:

先选定一个 key 做基准值,经过单趟排序后 key 左边位置的元素都小于 key,右边位置的元素都大于 key,这就使得 key 的位置被最终确定,即我们一趟排序可以确定一个元素的位置。

现在我们只需要对 key 的左区间和右区间再进行单趟排序即可;key 的左区间经过单趟排序之后又会确定一个元素的位置,然后再对该元素的左右区间进行单趟排序,直到 key 的左右区间只有一个元素 (一个元素本身就有序,不需要再进行排序) 或者不存在左右区间时说明排序完成。

左右区间又可以被划分为左右区间,即不断被划分为子问题,这就是递归的思想。

// 快速排序递归实现
void QuickSort(int* a, int left, int right)
{
 //如果左右区间相等或者右区间小于左区间直接返回
 if (right <= left)
  return;
 //单趟排序 -- 确定单个元素的位置
 int keyi = PartSort1(a, left, right);
 //递归左区间
 QuickSort(a, left, keyi - 1);
 //递归右区间
 QuickSort(a, keyi + 1, right);
}

3.2.5 非递归法

void QuickSortNonR(int* a, int left, int right)
{
 //首先构建一个栈(C语言来说需要自己实现)
 ST st;
 StackInit(&st);
 StackPush(&st, left);//将左右区间入栈
 StackPush(&st, right);
 while (!StackEmpty(&st))
 {
  int end = StackTop(&st);//读取区间数据
  StackPop(&st);
  int begin = StackTop(&st);
  StackPop(&st);

  int mid = PartSort3(a, begin, end);//排序(排好基准值)
  //划分基准值的左右区间
  int begin1 = mid + 1, end1 = end;
  //先入右边区域(栈的特点是先入后出)
  if (end1 - begin1 + 1 > 1)
  {
   StackPush(&st, begin1);
   StackPush(&st, end1);
  }
  //再将左边区域入栈
  int begin2 = begin, end2 = mid-1;
  if (end2 - begin2 + 1 > 1)
  {
   StackPush(&st, begin2);
   StackPush(&st, end2);
  }
 }
 //到空栈则排序结束
 StackDestroy(&st);//栈销毁
}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。
  2. 时间复杂度:O(N*logN)。
    1. 空间复杂度:O(logN)。
  3. 稳定性:不稳定。

四. 归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法 (Divide and
Conquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

在这里插入图片描述

void _MergeSort(int* a, int left, int right, int* tmp)
	{
		//当区间大小为1时,无左右区间,直接返回
		if (left >= right)
			return;

		int mid = left + (right - left) / 2;

		//递归左区间有序
		_MergeSort(a, left, mid, tmp);
		//递归右区间有序
		_MergeSort(a, mid + 1, right, tmp);

		//当左右区间都有序时开始归并 -- 取小的尾插
		int left1 = left, right1 = mid;  //左区间
		int left2 = mid + 1, right2 = right;  //右区间

		//在tmp数组中的相应位置尾插
		int i = left;
		while (left1 <= right1 && left2 <= right2)
		{
			if (a[left1] <= a[left2])
			{
				tmp[i++] = a[left1++];
			}
			else
			{
				tmp[i++] = a[left2++];
			}
		}

		//将较大的数组链接到tmp后面
		while (left1 <= right1)
		{
			tmp[i++] = a[left1++];
		}
		while (left2 <= right2)
		{
			tmp[i++] = a[left2++];
		}

		//将tmp中已归并的数据拷贝到原数组的相应区间上
		memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
	}

	// 归并排序递归实现
	void MergeSort(int* a, int n)
	{
		//开辟一个额外数组用于归并
		int* tmp = (int*)malloc(sizeof(int) * n);
		if (tmp == NULL)
		{
			perror("malloc fail\n");
			exit(-1);
		}

		//归并排序
		_MergeSort(a, 0, n - 1, tmp);

		//销毁
		free(tmp);
		tmp = NULL;
	}

归并排序的特性总结:

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

五.总结

在这里插入图片描述

💓 感谢阅读!

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

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

相关文章

windows11 安装 webassembly,遇到的各种错误

1.最开始是尝试在 虚拟机 centos 7 安装的(因为不想安装vs2015) 但是无奈 各种错误.最终无法解决. 2.尝试在windows安装,吐槽一下官方文档 的安装提示是错误的(太老了) 参考以下文章: https://blog.csdn.net/weixin_45482422/article/details/119459918 https://blog.csdn.…

C++中this指针的特性,存放位置,能否为空?

文章目录 一、this指针的特性二、this指针存在哪里&#xff1f;三、this指针可以为空吗&#xff1f; 一、this指针的特性 我们学习过C知道&#xff0c;成员函数没有直接存放在类而是放在了公共代码区&#xff0c;这样当多个对象调用同一个函数就不需要再创建一个函数成员了。 …

libevent高并发网络编程 - 02_libevent缓冲IO之bufferevent

文章目录 1. 为什么需要缓冲区&#xff1f;2. 水位3. bufferevent常用API3.1 evconnlistener_new_bind()3.2 evconnlistener_free()3.3 bufferevent_socket_new()3.4 bufferevent_enable()3.5 bufferevent_set_timeouts()3.6 bufferevent_setcb()3.7 bufferevent_setwatermark(…

全面解析Linux指令和权限管理

目录 一.指令再讲解1.时间相关的指令2.find等搜索指令与grep指令3.打包和压缩相关的指令4.一些其他指令与热键二.Linux权限1.Linux的权限管理2.文件类型与权限设置3.目录的权限与粘滞位 一.指令再讲解 1.时间相关的指令 date指令: date 用法&#xff1a;date [OPTION]… [FOR…

缓冲区的flip

流和缓冲区都是用来描述数据的。计算机中&#xff0c;数据往往会被抽象成流&#xff0c;然后传输。比如读取一个文件&#xff0c;数据会被抽象成文件流&#xff1b;播放一个视频&#xff0c;视频被抽象成视频流。处理节点为了防止过载&#xff0c;又会使用缓冲区削峰&#xff0…

巴西大神开发的 ARPL 黑群晖DSM系统引导在线编译工具

ARPL 是一款黑群晖系统引导在线编译工具&#xff0c;目前支持最新群晖系统DSM 7.1.1&#xff0c;今天为了折腾升级这个群晖系统DSM 7.1.1浪费了一天的时间&#xff0c;ARPL是巴西人一位大神开发的黑群晖系统引导在线编译工具&#xff0c;使用下来非常的不错&#xff0c;可惜没有…

3网络互联-3.4【实验】【计算机网络】

3网络互联-3.4【实验】【计算机网络】 前言推荐3网络互联3.4 IP分组转发与静态路由实验目的实验内容及实验环境实验原理1.路由器2.路由(Routing)3.IP分组的转发4.路由的构建5.静态路由设计原则 实验过程1&#xff0e;搭建一个仅包含直连路由的网络拓扑&#xff0c;观察路由器的…

时间序列分析

一、移动平均法 1.一次移动平均法 公式&#xff1a; 预测标准误差: 本质&#xff1a;用前N次数据预测t1期的数据 规律&#xff1a;如果实际数据波动较大&#xff0c;N值越大&#xff0c;预测到的数据波动越小 注意&#xff1a;一般不适用于波动较大的数据。用一次移动平均法…

Kafka原理之消费者

一、消费模式 1、pull(拉)模式(kafka采用这种方式) consumer采用从broker中主动拉取数据。 存在问题&#xff1a;如果kafka中没有数据&#xff0c;消费者可能会陷入循环中&#xff0c;一直返回空数据 2、push(推)模式 由broker决定消息发送频率&#xff0c;很难适应所有消费者…

【MySQL】 InnoDB

学习笔记&#xff0c;来源黑马程序员MySQL教程 文章目录 逻辑存储结构架构内存架构磁盘结构后台线程 事务原理概述redo logundo log MVCC基本概念实现原理1、隐藏字段2、undo log3、readview 总结 逻辑存储结构 一个表空间对应一张表一 页 对应B树上一个 节点Trx id&#xff1a…

Git cat命令的用法

cat (全称 concatenate) 命令是 Linux/类 Unix 操作系统中最常用的命令之一。cat 命令允许我们创建单个或多个文件、查看文件内容、连接文件和重定向终端或文件中的输出。 语法&#xff1a; cat [OPTION] [FILE]...1.终端查看一个文件内容 cat file01.txt2.终端查看多个文件…

熵、信息量、条件熵、联合熵、互信息简单介绍

熵、信息量、条件熵、联合熵、互信息简单介绍 近期在看对比学习论文&#xff0c;发现有不少方法使用了互信息这种方式进行约束&#xff0c;故在此整理一下网上查阅到的关于互信息的相关内容。 一、熵、信息量 关于熵的讨论&#xff0c;这个知乎专栏写的挺不错的。 熵在信息论…

【更新日志】填鸭表单TduckPro v5.1 更新

hi&#xff0c;各位Tducker小伙伴。 填鸭表单pro迎来了v5.1版本&#xff1b;本次我们进行了许多的功能新增和优化&#xff0c;能够让我们在日常使用中获得更好的体验。 让我们一起来康康新功能吧。 01 新增Pro功能 新增登录后才能填写表单。 新增表单卡片一键发布。 新增矩…

【C++学习】CC++内存管理

目录 一、C&C内存管理 二、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 三、C内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作符自定义类型 四、operator new与operator delete函数 4.1 operator new与operator delete函数&#x…

【云原生】使用外网Rancher2.5.12在阿里云自建内网K8s 1.20集群

目录 一、目标二、解决方案三、草图四、版本信息五、资源规划六、必要条件七、开始部署1、安装Docker2、安装Rancher3、解析Rancher Server URL域名4、创建K8s集群5、注册K8s集群节点 八、验证 一、目标 在云平台搭建一套高可用的K8s集群 二、解决方案 第一种&#xff1a;使…

横向移动-利用IPC$

环境主机 本次都是在内网自己搭的靶机实验 上线主机&#xff1a;windows2008R2 - 192.168.31.46 需要移动到的主机&#xff1a;windows2012 - 192.168.31.45 实验演示 1.确定域控 通过命令net time /domain&#xff0c;发现存在域 这里我们通过ping来发现域控的ip&#xff0c;…

UGUI Scroll Rect滚动矩形组件

1、概述 当需要在小区域显示占用大量空间的内容时&#xff0c;可以使用Scroll Rect。滚动矩形提供了滚动浏览此内容的功能。 通常&#xff0c;将Scroll Rect与Mask结合在一起以创建滚动视图&#xff0c;在该视图中&#xff0c;只有Scroll Rect内部的可滚动内容可见。它也可以…

类和对象【1】

全文目录 引言&#xff08;初识面向对象&#xff09;类和对象定义类访问限定及封装类定义的两种方式 类实例化与类对象大小this指针 总结 引言&#xff08;初识面向对象&#xff09; C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通…

NSSCTF之Misc篇刷题记录⑩

NSSCTF之Misc篇刷题记录⑩ [CISCN 2022 初赛]ez_usb[SWPUCTF 2021 新生赛]你喜欢osu吗&#xff1f;[SWPUCTF 2021 新生赛]Bill[SWPUCTF 2021 新生赛]二维码不止有二维码[HGAME 2022 week1]好康的流量[红明谷CTF 2022]MissingFile[广东省大学生攻防大赛 2021]这是道签到题[羊城杯…

TOGAF架构开发方法—阶段 F:迁移规划

本章介绍迁移规划;也就是说&#xff0c;如何通过最终确定一个 详细的实施和迁移计划。 一、目标 F阶段的目标是&#xff1a; 最终确定架构路线图以及支持实施和迁移计划确保实施和迁移计划与企业的管理和实施方法相协调 企业整体变更组合的变化确保关键利益相关者了解工作包和…