数据结构——六大排序 (插入,选择,希尔,冒泡,堆,快速排序)

news2024/11/30 9:53:16

1. 插入排序

1.1基本思路

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

我们熟知的斗地主就是一个插入排序

 1.2 代码实现

我们这里将一个无序数组变成有序数组

  1. 插入排序时间复杂度分析
  • 最优情况:待排序的数组是有序的

只需当前数跟后一个数比较一下

一共需要比较N- 1次

时间复杂度为 : O ( N )

  • 最坏情况:待排序数组是逆序的

有可能每次移动完的数在次向后移动一下
时间复杂度为:O ( N ^2)

如图所示:

 在这里我们要定义两个变量,end和tmp,一个指向第一个元素,一个指向后面的元素

end<tmp时不移动(反之)

代码展示:

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// [0, end] 有序,插入tmp依旧有序
		int end = i;
		int tmp = a[i + 1];

		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}

		a[end + 1] = tmp;
	}
}

2. 选择排序

2.1基本思路

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

这里注意一下有的题里面会考:

以前我们是每一趟选一个最大或最小
优化后:一趟选两个,最大的和最小的
分别放在数组的左右端

2.2代码实现

  1. 选择排序时间复杂度分析

选择排序不管数组有序还是无序
它都需要一遍一遍的遍历数组
即使我们这里做了一些优化
它还是要走2/N次,再乘以次数N

时间复杂度都是: O(N^2)

这个为了更直观展示我们直接就导入一个图片来说明

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

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

			if (a[i] < a[mini])
			{
				mini = i;
			}
		}

		Swap(&a[begin], &a[mini]);
		// 如果maxi和begin重叠,修正一下即可
		if (begin == maxi)
		{
			maxi = mini;
		}

		Swap(&a[end], &a[maxi]);

		++begin;
		--end;
	}
}

3. 希尔排序

3.1 基本思路:

  • 在直接插入排序上做优化:
    1. 分组预排序,使数组接近有序
    2. 直接插入

时间复杂度的分析:

一般默认希尔排序的时间复杂度为:

O ( N * log2 N) 或
O (N * log3 N)

根据大量实验得出的数据最终我们将复杂度定为:
时间复杂度范围:

也可以直接看作: n1.3

由图可知:
这里的分组预排和插入的思路是一样的

3.2 代码实现

//希尔排序
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)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

4. 堆排序

4.1 基本思路

  1. 建立一个大堆(升序建大堆)
  2. 将堆顶元素与最后一个元素交换
  3. 交换后堆元素减一,重新调整堆

一次循环过后,我们就把数组中最大值放到最后一个位置,下一次循环把倒数第二大值,放在倒数第二个位置,以此类推直到将数组变成有序的。

  1. 排升序——建大堆
  2. 排降序——建小堆
  3. 左孩子 :child = parent * 2 + 1
  4. 右孩子:child = parent * 2 + 2。
  5. parent = (child - 1) / 2

4.2 代码实现

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 = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序
void HeapSort(int* a, int n)
{
	//建堆--升序建大堆
	//向下调整建堆 --时间复杂度:O(n)
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

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

5. 快速排序

5.1基本思路

快速排序递归版1——hoare

注意点:

  1. 左边选keyi,右边先走;右边选keyi,左边先走
  2. 右边选比 a[keyi]小的值,左边先比 a[keyi] 大的值
  3. 注意特殊情况对边界的处理。

5.1.2 代码实现 

int GetMidnum(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if(a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	//a[left] <= a[mid]
	else
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}

int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	int mid = GetMidnum(a, left, right);
	Swap(&a[mid], &a[keyi]);
	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;
	return keyi;

}

5.2快速排序递归版2——挖坑法

5.2.1基本思路

这个和hoare在理解的思维上有所不同,挖坑法的思路是比较好理解的。不同区哪个先走的问题。

言外之意就是挖个坑,往里面塞数

 5.2.2 代码实现

int GetMidnum(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if(a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	//a[left] <= a[mid]
	else
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}


int PartSort2(int* a, int left, int right)
{
	int mid = GetMidnum(a, left, right);
	Swap(&a[mid], &a[left]);
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[left] = key;
	return left;

}

5.3 前后指针法

5.3.1 基本思路

  • 定义两个指针cur和prev
  • cur指向第二个元素
  • prev指向cur前面的元素
  • c找比基准值小的值
  • 找到后停下,p向后走一格
  • 再交换c和p指向的值
  • 最后c走完数组后:
  • 交换key和prev的值

 5.3.2 代码实现

int GetMidnum(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if(a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	//a[left] <= a[mid]
	else
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}


int PartSort3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	int mid = GetMidnum(a, left, right);
	Swap(&a[mid], &a[left]);
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && prev++ != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return prev;
}

6.归并排序

6.1基本思路

  • 要使数组整体有序就要将,左半部分和右半部分变为有序,后进行单次归并排序,将数组拆分为两个部分A和B,要使A数组有序就要将,A也拆分为两个部分,使左/右半部分都有序,一直拆分直到数组只有一个元素。

6.2 代码实现

void MergeSortNonR(int* a, int n)
{
	int* tem = (int*)malloc(sizeof(int) * n);
	int begin = 0;
	int end = n - 1;
	int gap = 1;

	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//printf("修正前:[ %d , %d ] [ %d , %d ]\n",begin1,end1,begin2,end2);
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			//printf("修正后:[ %d , %d ] [ %d , %d ]\n", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tem[j++] = a[begin1++];
				}
				else
				{
					tem[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tem[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tem[j++] = a[begin2++];
			}
			memmove(a + i, tem + i, sizeof(int) * (end2 - i + 1));
			
		}
		gap *= 2;
	}
}

归并排序非递归版2(整体归并思路实现)

//归并排序 -- 非递归版2
void MergeSortNonR2(int* a, int n)
{
	int* tem = (int*)malloc(sizeof(int) * n);
	int begin = 0;
	int end = n - 1;
	int gap = 1;

	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//printf("修正前:[ %d , %d ] [ %d , %d ]\n",begin1,end1,begin2,end2);
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if(begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if(end2 >= n)
			{
				end2 = n - 1;
			}
			//printf("修正后:[ %d , %d ] [ %d , %d ]\n", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tem[j++] = a[begin1++];
				}
				else
				{
					tem[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tem[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tem[j++] = a[begin2++];
			}
		}
		memmove(a, tem, sizeof(int) * n);
		gap *= 2;
	}
}

以上就是今天讲的六大排序,感谢您的收藏和点赞,我们下期再见


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

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

相关文章

CVE-2017-15715

CVE-2017-15715 一、环境搭建二、漏洞原理三、漏洞复现 一、环境搭建 如下介绍kali搭建的教程 cd ~/vulhub/httpd/CVE-2017-15715 // 进入指定环境 docker-compose build // 进行环境编译 docker-compose up -d // 启动环境docker-compose ps使用这条命令查看当前正在…

放射显影多肽1778691-88-5,DOTA Methyltetrazine ,四甲基四嗪修饰大环配体

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 中文名称&#xff1a;四甲基四嗪修饰大环配体 英文名称&#xff1a;FOLATE-NOTA&#xff0c; Methyltetrazine-DOTA 规格标准&#xff1a;1g、5g、10g CAS&#xff1a;1778691-88-5 分子式&#xff1a;C37H52N12O12 分子量…

学习opencv.js之基本使用方法(读取,显示,灰度化,边缘检测,特征值点检测)

opencv.js是什么 OpenCV.js 是 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;的 JavaScript 版本。OpenCV 是一个广泛使用的计算机视觉和图像处理库&#xff0c;提供了一系列功能强大的算法和工具&#xff0c;用于处理图像、视频、特征提取、对象识别等…

php裁剪图片,并给图片加上水印

本次以裁剪四个图片为例&#xff0c;图片如下 代码如下 public function cutImg($imgUrl){try{// 读取原始图片$src_img imagecreatefromjpeg($imgUrl);// 获取原始图片的宽度和高度$src_width imagesx($src_img);$src_height imagesy($src_img);// 计算每个部分的宽度和高…

【C语言督学训练营 第十九天】关于C语言语法的一些补充

文章目录 1.条件运算符与逗号运算符2.自增自减运算符3.位运算4.switch do-while补充5.二维数组&二级指针6.总结 1.条件运算符与逗号运算符 条件运算符是C语言中唯一的一种三目运算符。三目运算符代表有三个操作数;双目运算符代表有两个操作数,如逻辑与运算符就是双目运算符…

传统工厂不再使用蓝牙LoRa而选择使用星斗1号之原因详解

物联网技术在不断发展的同时&#xff0c;化工企业对安全生产的重视也在逐渐增强。 在传统工厂进行安全管理数字化转型前&#xff0c;蓝牙Lora是其最为常用的化工人员定位技术&#xff0c;也曾广泛应用于工厂设备监控、数据传输、人员管理等。 然而&#xff0c;定位技术升级&a…

linux driver probe deferral 机制

1. 背景介绍 在偶然的一次实验中(具体是pinctrl实验)&#xff0c;我发现有些平台的pincontroller驱动起得很晚&#xff0c;而pinctrl client驱动却起得很早&#xff0c;在设备驱动模型中probe之前又会进行管脚复用的相关设置&#xff0c;按照常理来讲&#xff0c;这就产生了某…

前端工程中的设计模式应用

本文旨在系统性介绍一下23种设计模式&#xff0c;给出通俗易懂的案例、结构图及代码示例&#xff0c;这也是我自身学习理解的过程。或许其中的几种设计模式写的并不是很清晰明了易懂&#xff0c;更详细的可根据提到的参考文献进行深入学习。 什么是设计模式 设计模式这个概念是…

Python 算法基础篇之字符串操作:索引、切片、常用方法

Python 算法基础篇之字符串操作&#xff1a;索引、切片、常用方法 引言 1. 字符串的概念和创建2. 字符串的索引3. 字符串的切片4. 字符串的常用方法 a ) 查找子字符串 b ) 替换子字符串 c ) 拆分和连接字符串 总结 引言 字符串是一种常见的数据类型&#xff0c;在 Python 中对…

又整新活,新版 IntelliJ IDEA 有点东西!

作为一个经常使用IntelliJ IDEA来写代码的老用户&#xff0c;每次对于JetBrains软件的更新都是非常关注的。 这不最近这段时间&#xff0c;JetBrains连发了多个软件的EAP版本&#xff1a; 同时JetBrains的官博中也宣布了一个重要的新特性&#xff0c;那就是&#xff1a; 在所…

X.509数字证书的基本原理

一、前言 数字证书是现代互联网中个体间相互信任的基石。 如果没有了数字证书&#xff0c;那么也就没有了各式各样的电商平台以及方便的电子支付服务。 数字证书是网络安全中的一个非常重要组成部分。如果要学好网络安全&#xff0c;那么必须充分理解它的原理。 目前我们所…

keepalived 实现双机热备

文章目录 一、说明二、概念解释三、环境准备四、操作过程五、验证 一、说明 我们经常听说 nginx keepalived 双机热备&#xff0c;其实在这里&#xff0c;双机热备只是利用 keepalived 实现两个节点的故障切换&#xff0c;当主节点挂了&#xff0c;备用节点顶上&#xff0c;保…

深入浅出关于网易邮箱开启smtp服务教程

各平台邮箱开启SMTP服务教程 一、QQ邮箱 &#xff08;服务器地址&#xff1a;smtp.qq.com&#xff09; 第一步&#xff1a;复制https://mail.qq.com/ 登录QQ邮箱后电击左上角设置&#xff0c;如图&#xff1a; 第二步&#xff1a;点击进入“帐户”页面 &#xff0c;如图&…

CodeLocator简单使用(AndroidStudio中点击布局元素确认对应view信息)快速接手陌生项目利器

对于陌生项目的一些改动或重构需求时&#xff0c;如果可以在APP点点就能确定知道当前管理哪个activity或fragment必然是省去了很多去代码里搜来搜去的时间。在社群讨论中发现这款AS插件:CodeLocator ,虽然有2年没更新了 今天也试一试看看是否有帮助。 首先下载最新版本的插件&…

通过OSG实现对模型的日照模拟

1. 加载模型 通过OpenSceneGraph加载一个倾斜摄影的场景模型数据&#xff1a; #include <iostream> #include <Windows.h>#include <osgViewer/Viewer> #include <osgDB/ReadFile>using namespace std;int main() {string osgPath "D:/Data/Da…

Appium自动化测试知识点

一、App环境搭建 1、安装jdk&#xff0c;配置jdk环境变量 2、Android SDK环境安装 3、Appium server安装 4、模拟器的安装&#xff08;夜神模拟器&#xff09; 5、安装appium-python-client Python第三方库 二、App自动化测试原理 如何通过代码操作不同操作系统&#xff08;…

基于Dubbo分布式网上售票系统

一、项目介绍 民航售票是一个高度依赖信息业的行业。但在机票销售的管理和规范这方面上存在着很多各种各样的问题。例如订票是客运行业中的一个最基本的业务,表面上看,它只是机票站业务的一个简单的部分,但是它涉及到管理与客户服务等多方面,关系到民航公司能否正常运作。…

log4j--动态打印日志文件到指定文件夹

文章目录 log4j--动态打印日志文件到指定文件夹1、添加Maven依赖2、配置文件 log4j.properties3、编写日志打印工具类 LogUtil4、工具类调用 log4j–动态打印日志文件到指定文件夹 1、添加Maven依赖 <!-- log4j日志相关坐标 --><dependency><groupId>org.s…

无虚拟 DOM 版 Vue 进行到哪一步了?

前言 就在一年前的 Vue Conf 2022&#xff0c;尤雨溪向大家分享了一个非常令人期待的新模式&#xff1a;无虚拟 DOM 模式&#xff01; 我看了回放之后非常兴奋&#xff0c;感觉这是个非常牛逼的新 feature&#xff0c;鉴于可能会有部分人还不知道或者还没听过什么是 Vue 无虚…

哈佛“聘请”AI担任导师,主讲教授:别全信它的,学生应“批判性地思考”

就在人们为AI聊天机器人的利弊争论不休时&#xff0c;哈佛宣布了一个重磅决定&#xff1a;将利用类似ChatGPT的聊天机器人来帮助授课了。 负责的还是计算机系的旗舰项目 —— 计算机科学导论&#xff0c;也就是著名的 CS50。借助机器人导师&#xff0c;哈佛的 CS50 项目将拥有…