【数据结构与算法分析】一文搞定插入排序、交换排序、简单选择排序、合并排序的代码实现并给出详细讲解

news2024/11/26 7:44:42

文章目录

  • 排序相关的基本概念
  • 排序算法及其实现
    • 插入排序
      • 直接插入排序
      • 折半插入排序
      • 希尔排序
    • 交换排序
      • 冒泡排序
      • 快速排序
    • 合并排序
    • 归并排序
    • 简单选择排序
  • 算法比较

排序相关的基本概念

  • 排序:将数组中所有元素按照某一顺序(从小到大或从大到小)重新排列的过程。
  • 排序算法的稳定性:是指排列两特定元素经过排序后其相对位置依然一样,比如两元素值相等的元素a与b,如果排列前元素a在元素b前,排列后依旧如此,则称该排列算法是稳定的;否则,就是不稳定的。
  • 内部排序:是指排序过程中,所有参与排序元素都存放在内存中的排序,如冒泡排序。
  • 外部排序:是指排序过程中,元素不停地在内存与外存之间移动的排序,如特定的归并排序。

排序算法及其实现

插入排序

直接插入排序

  直接插入排序是一种基于比较的排序算法,其主要思想是将待排序序列分为已排序和未排序两个部分,每次将未排序的元素从后往前依次插入到已排序的序列中,直到所有元素都被插入到已排序的序列中,从而得到一个有序的序列。

  其动画演示为:

  其代码实现为:

/**************************************
* 函数功能:对数组进行直接插入排序
* 函数参数:vector<int>&data表示待排序数组
* 函数返回值:无
**************************************/
void insertSort(vector<int> &data)
{
	// 获取数组长度
	int len = data.size();
	// 插入排序核心代码
	for (int i = 1; i < len; ++i)
	{
		// 判断前一个元素是否小于当前元素
		if (data[i - 1] < data[i]) continue;
		// 保存当前元素
		int temp = data[i];
		// 哨兵元素
		int k = i - 1;
		// 判断哨兵元素是否大于当前元素
		while (k >= 0 && temp < data[k])
		{
			data[k + 1] = data[k];
			--k;
		}
		// 将当前元素插入到合适位置
		data[k + 1] = temp;
	}
}

  直接插入排序的时间复杂度为 O(n^2),空间复杂度为 O(1)。在排序过程中,如果待排序的序列已经近似有序,则直接插入排序的效率会很高,因为少需移动已排序部分的元素。但如果待排序序列的规模很大或者是随机分布的,那么直接插入排序的效果会不如其他高效率排序算法,如快速排序等。

折半插入排序

  折半插入排序(Binary Insertion Sort)是一种基于比较的排序算法,与直接插入排序类似,只是在查找元素插入位置时,采用了折半查找的方式,从而使得查找的平均时间复杂度降低到 O(log n) 级别。

  相对于插入排序而言,虽然算法实现上需要执行二分查找,但是由于它减少了比较次数,因此在时间复杂度上有所优化。但是在实际应用中,对于小规模数据,插入排序可能比折半插入排序更加高效,因为对于小规模数据,折半插入排序反而会增加了一些不必要的开销。

  其代码实现为:

/***************************************
* 函数功能:对数组进行折半插入排序
* 函数参数:
* 函数返回值:
****************************************/
void insertHalfSort(vector<int>&data)
{
	// 获取数组长度
	int n = data.size();
	// 折半插入排序
	for (int i = 1; i < n; i++)
	{
		// 临时保存数据
		int temp = data[i];
		// 折半插入的范围
		int left = 0, righ = i-1;
		// 折半比较
		while (left <= righ)
		{
			int mid = (left + righ) / 2;
			if (data[mid] > temp) righ = mid - 1;
			else  left = mid + 1;
		}
		// 移动数据
		for (int j = i-1; j >= left ; j--)
			data[j + 1] = data[j];
		data[left] = temp;
	}
}

  折半插入排序的时间复杂度为 O(n^2),但相对于直接插入排序,它的平均时间复杂度要稍微低一些,为 O(n log n)。在元素规模较大,且待排序序列相对有序时,折半插入排序的效率明显优于直接插入排序。值得注意的是,折半插入排序的空间复杂度为 O(1),是一个原地排序算法。

希尔排序

  希尔排序(Shell Sort)是一种基于插入排序的快速排序算法。由于插入排序对于在已经排序较为有序的序列插入一个新元素时易产生最糟糕的情况,因此希尔排序的主要思想是通过先将整个待排序序列分成若干个子序列(按照一定的步长进行分组),并对每个子序列进行插入排序,从而使得原来的序列大部分有序。之后再对整个待排序序列进行一次插入排序,以此达到快速排序的目的。
  希尔排序的过程如下:

  • 选择一个增量序列,确保最后一次增量为 1。
  • 按照增量序列将待排序序列分成若干子序列,对每个子序列进行直接插入排序。
  • 每次将增量减半,重复第二步操作,直到增量为 1,对整个序列进行插入排序。

  例如:当我们需要对数据9,1,2 ,5,7,4,8,6,3,5进行排序时,其排序过程可为:
在这里插入图片描述
  其代码实现为:

/***************************************
* 函数功能:对数组进行希尔排序
* 函数参数:vector<int>&data-待排序数组
* 函数返回值:无
****************************************/
void shellSort(vector<int>&data)
{
	int len = data.size();
	// 增量变化
	for (int dk = len / 2; dk >= 1; dk = dk / 2)
	{
		// 遍历数组
		for (int i = dk + 1; i < len; ++i)
		{
			if (data[i] < data[i - dk])
			{
				int j, temp = data[i];
				// 数组记录后移
				for (j = i - dk; j >= dk && temp < data[j]; j -= dk)
					data[j + dk] = data[j];
				// 插入元素
				data[j + dk] = temp;
			}
		}
	}
}

  希尔排序的时间复杂度较为复杂,取决于增量分布的具体方式,一般为O(n log n)O(n^2)

交换排序

冒泡排序

   冒泡排序是一种基本的排序算法,它的原理是比较相邻的两个元素,如果顺序不对则交换。这样一趟下来,最大的元素就排到了最后面,然后再针对剩下的元素重复此操作,直到所有元素都有序为止。其时间复杂度为O(n^2^)

  其动态图解可为:

  其代码实现为:

/***************************************
* 函数功能:对数组进行冒泡排序
* 函数参数:vector<int>&data 待排序的数组
* 函数返回值:无
****************************************/
void bubbleSort(vector<int>&data)
{
	// 获取数组大小
	int len = data.size();
	for (int i = 0; i < len - 1; i++) // 控制排序轮数
	{
		// 定义变量用于判断排序是否完成 提高排序效率
		bool flag = true;
		for (int j = 1; j < len - 1 - i; j++) // 控制排序次数
		{
			// 两数据不符合预设的顺序 应该发生交换
			if (data[j] < data[j-1])
			{
				int temp = data[j - 1];
				data[j - 1] = data[j];
				data[j] = temp;
				// 发生交换,排序未完成
				flag = false;
			}
		}
		// 判断排序是否完成
		if (flag)
			return;
	}
}

  这里为了避免不比较的循环,上述代码进行了改进,即添加标志位flag,用于判断本趟for是否发生交换,如果没有发生交换就说明排序已经完成了。

快速排序

  快速排序(Quicksort)是一种常用的排序算法,它的性能通常比冒泡排序和选择排序要好的多。快速排序采用分治法来实现,它的基本思想是将一个大问题分成小问题,然后再通过递归的方式解决。

  其动态图解为:

  快速排序中的划分操作,即将一个数组划分成两个子数组,其中一个子数组的所有元素都小于等于另一个子数组的所有元素。具体实现为选取第一个元素作为中枢(pivot),然后使用双指针法,将小于中枢的值放在左边,大于中枢的值放在右边,最终得到中枢的位置。具体代码实现可以见下:

/***************************************
* 函数功能:对数组进行快速排序的划分
* 函数参数:vector<int>&data 待排序的数组
* 函数返回值:无
****************************************/
static int partition(vector<int>&data, int left, int right)
{
	// 选取划分中枢
	int tempKey = data[left];
	while (left < right)
	{
		// 将数组右端小于中枢的值移动到左端
		while (data[right] >= tempKey && right > left) --right;
		data[left] = data[right];
		// 将数组左端小于中枢的值移动到右端
		while (data[left] <= tempKey && right > left) ++left;
		data[right] = data[left];
	}
	// 循环跳出的条件左值就为中枢的位置
	data[left] = tempKey;

	return left;
}

  下面这段代码实现了快速排序算法的递归过程。具体实现为:

  • 判断数组的左右下标是否合法,若left >= right,则不需要进行排序;
  • 通过调用partition函数,将数组划分成两个子数组,并将划分后的结果传递给下一次排序;
  • 对左子数组进行排序,即对[left, temp-1]这一段进行排序;
  • 接着对右子数组进行排序,即对[temp+1, right]这一段进行排序。
/***************************************
* 函数功能:对数组进行快速排序
* 函数参数:vector<int>&data 待排序的数组
* 函数返回值:无
****************************************/
void quickSort(vector<int>&data,int left,int right)
{
	// 排序不合理
	if (left >= right) return;
	// 获取一次划分 并且将划分后最终结果保存  并传递给下一次
	int temp = partition(data,left,right);
	// 再次进行左半部分排序
	quickSort(data,left,temp - 1);
	// 再次进行右半部分排序
	quickSort(data, temp + 1, right);
}

  可以将这段代码与划分操作的实现结合起来,得到完整的快速排序算法实现。

合并排序

  合并排序,名字来源于小编。😜😜😜
  这段代码实现的是归并排序中的合并操作。归并排序采用分治策略,将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排序后的子数组进行合并。具体实现为:

  • 首先获取要合并的两个数组data1和data2的大小;
  • 创建一个新的vector类型的数组res,用于存储合并后的结果;
  • 使用while循环遍历两个数组,将两个数组中值更小的元素添加到result中;
  • 最后再处理未完成的数组元素;
  • 返回合并后的数组res。
/***************************************
* 函数功能:对数组进行合并排序
* 函数参数:vector<int>&data1 归并数组1 vector<int>&data2  归并数组2
* 函数返回值:返回归并后的数组
****************************************/
vector<int> mergeSort(vector<int>&data1, vector<int>&data2)
{
	// 获取带归并数据的大小
	int len1 = data1.size(), len2 = data2.size();
	// 返回结果
	vector<int> res;
	int i = 0, j = 0;
	// 遍历两个数组 将数值更小的值放入结果集
	while (i < len1 && j < len2)
	{
		// 数组1中数据更大
		if (data1[i] > data2[j])
		{
			res.push_back(data2[j]);
			j++;
		}
		else
		{
			res.push_back(data1[i]);
			i++;
		}
	}
	// 遍历未完成的数组
	while (i < len1)
		res.push_back(data1[i++]);
	while (j < len2)
		res.push_back(data2[j++]);

	return res;
}

归并排序

  归并排序是一种经典的排序算法,它采用分治策略。将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排序后的子数组进行合并。

  其动态图解为:

  下面这段代码实现的是归并排序中的合并操作。具体实现为:

  • 根据传入的参数low、high和mid,定义int类型的变量i、j和k;
  • 创建一个大小为high-low+1的临时数组B,用于存储归并排序的结果;
  • 将A中的元素复制到B中;
  • 接着使用while循环和if语句,将B中两个子数组的元素进行比较,并将较小的元素放到A数组中;
  • 最后再处理未完成的数组元素;
  • 释放临时数组B。

(上述的合并排序就源自此处)

  其代码实现为:

/***************************************
* 函数功能:对数组进行合并排序
* 函数参数:vector<int>&Array归并数组   int front归并排序的左起始位置 int mid归并排序的中间位置 int end归并排序的右终止位置
* 函数返回值:无
****************************************/
void Merge(vector<int> &Array, int front, int mid, int end) {
    vector<int> LeftSubArray(Array.begin() + front, Array.begin() + mid + 1);
    vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + end + 1);
    int idxLeft = 0, idxRight = 0;
    LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());
    RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());
    for (int i = front; i <= end; i++) {
        if (LeftSubArray[idxLeft] < RightSubArray[idxRight]) {
            Array[i] = LeftSubArray[idxLeft];
            idxLeft++;
        } else {
            Array[i] = RightSubArray[idxRight];
            idxRight++;
        }
    }
}

  下面这段代码实现的是归并排序算法的递归过程。具体实现为:

  • 判断数组的左右下标是否合法,若low >= high,则不需要进行排序;
  • 找到中间位置mid,将数组分为两个子数组;
  • 对左子数组进行排序,即对[low, mid]这一段进行排序,调用mergeSort2函数;
  • 对右子数组进行排序,即对[mid+1, high]这一段进行排序,调用mergeSort2函数;
  • 将左右两个排序好的子数组,进行合并,调用Merge函数。
/***************************************
* 函数功能:对数组进行归并排序
* 函数参数:vector<int>&Array归并数组   int front归并排序的左起始位置  int end归并排序的右终止位置
* 函数返回值:无
****************************************/
void MergeSort(vector<int> &Array, int front, int end) {
    if (front >= end)
        return;
    int mid = (front + end) / 2;
    MergeSort(Array, front, mid);
    MergeSort(Array, mid + 1, end);
    Merge(Array, front, mid, end);
}

  可以将递归过程与合并操作的实现结合起来,得到完整的归并排序算法实现。

简单选择排序

  简单选择排序(Selection Sort)是一种简单直观的排序算法,它的基本思想是:每一轮从待排序的元素中选出最小(或最大)的一个元素,存放到序列的起始位置,直到全部待排序的元素排完为止。

  其动态图解为:

具体实现为:

  • 使用两个for循环,外层循环依次遍历待排序数组的每个元素,内层循环则从当前元素往后查找,找到后面最小的元素;
  • 记录最小元素的位置min,找到最小元素后,如果该元素不在当前位置i,则将当前元素与最小元素交换位置。

  其代码实现为:

/***************************************
* 函数功能:对数组进行简单选择排序
* 函数参数:vector<int>&data1 排序数组
* 函数返回值:无
****************************************/
void selectSort(vector<int>& date)
{
	// 遍历选择
	for (int i = 0; i < date.size(); ++i)
	{
		// 记录最小元素的位置
		int min = i;
		// 遍历查找i后面的元素
		for (int j = i + 1; j < date.size(); ++j)
			if (date[j] < date[min])
				min = j;
		// 判断当前元素是否为最小值
		if (min != i)
		{
			// 不使用临时变量完成交换 但是注意数据是否会越界
			date[min] = date[min] + date[i];
			date[i] = date[min] - date[i];
			date[min] = date[min] - date[i];
		}
	}
}

  简单选择排序的时间复杂度为O(n^2),空间复杂度为O(1)。由于每次选择最小元素时需要进行循环比较,因此不适合处理大量数据。可以将直接选择排序和其他排序算法的实现结合起来,得到更完整的排序算法实现。

算法比较

在这里插入图片描述
  至于折半插入排序,其平均时间复杂度为O(n^2^),最优时间复杂度为O(nlogn),最差时间复杂度为O(n^2^),是一种稳定的排序算法稳定。

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

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

相关文章

DJ2-5 内容分发网络 CDN

目录 单一的大规模数据中心 内容分发网络 CDN 单一的大规模数据中心 存在三个问题&#xff1a; ① 如果客户远离数据中心&#xff0c;服务器到客户的分组将跨越许多通信链路并很可能通过许多 ISP&#xff0c;给用户带来恼人的时延。 ② 流行的视频很可能经过相同的通信链路…

[C++11] 智能指针

长路漫漫&#xff0c;唯剑作伴。 目录 长路漫漫&#xff0c;唯剑作伴。 为什么需要智能指针 RAII 使用RAII思想管理内存 重载 * 和-> 总结一下智能指针的原理&#xff1a; C的智能指针和拷贝问题 auto_ptr (C98) ​编辑 auto_ptr的实现原理…

EmGUCV中类函数 FastFeatureDetector使用详解

FastFeatureDetector Class 释义&#xff1a;FAST&#xff08;加速检测特&#xff09;关键点检测器&#xff0c;源自 E. Rosten ("Machine learning for high-speed corner detection, 2006). 继承关系&#xff1a;Emgu.CV.Features2D.FastFeatureDetector 派生&#xff…

记录好项目D5

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是 商品信息管理系统 技术栈&#xff1a;SpringBoot Mybatis Thymelea…

MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性

\ &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭…

细谈容器化技术实现原理--以Docker为例

目录 一、Docker解决了什么 二、容器的发展过程 三、容器基础 3.1. 容器实现的原理&#xff1a; ⚠️原理详解&#xff1a; 3.1.1. Namespace 3.1.2. Cgroups 3.1.3. chroot 四、Volume 4.1. Docker是如何做到把一个宿主机上的目录或者文件&#xff0c;挂载到容器里面…

4. 数组更新检测

4.1 v-for更新监测 当v-for遍历的目标结构改变, Vue触发v-for的更新 情况1: 数组翻转 情况2: 数组截取 情况3: 更新值 口诀: 数组变更方法, 就会导致v-for更新, 页面更新 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set() 这些方法会触发数组改…

基于深度学习的高精度鸽子检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度鸽子检测识别系统可用于日常生活中或野外来检测与定位鸽子目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的鸽子目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

XSS注入——反射性XSS

xss注入的攻击步骤&#xff1a; 1.查找可能存在的注入点&#xff08;搜索框&#xff0c;留言板&#xff0c;注册&#xff09; 2.观察输出显示位置&#xff1a; html&#xff1a; 尖括号外部&#xff0c; 尖括号内部: 引号内部》闭合&#xff0…

Django | 基于pycharm的django配置52张全流程截图,红星给你一步一步的男妈妈式教学

演示版本&#xff1a;【windows系统】python3.10pycharm2023.1.2django4.2.2 &#xff08;本教程全程在虚拟机中演示&#xff0c;读者无需配置虚拟机&#xff0c;直接按教程安装即可&#xff09; 目录 1.搞到必要的安装包 2.事先准备 3.安装chrome浏览器&#xff08;也可以…

国产MCU-CW32F030开发学习--按键检测

国产MCU-CW32F030开发学习–按键检测 bsp_key 按键驱动程序用于扫描独立按键&#xff0c;具有软件滤波机制&#xff0c;采用 FIFO 机制保存键值。可以检测 如下事件&#xff1a; 按键按下。 按键弹起。 长按键。 长按时自动连发。 我们将按键驱动分为两个部分来介绍&#xff…

C语言学习笔记:顺序结构

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 程序与…

《面试1v1》Spring基础

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

浅谈微前端

本文呢是我梳理的一个扫盲文&#xff0c;由于最近团队准备使用微前端对项目进行改造&#xff0c;所以我呢就先浅了解一下&#xff1a; 微前端到底是什么&#xff1f; 为什么要使用微前端&#xff1f; 都有哪些微前端方案&#xff1f; 微前端有什么不好的地方吗&#xff1f; 通过…

48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll

文章目录 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll48.1 概述48.2 操作指导 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll 48.1 概述 为了保证公平性及降低功耗&#xff0c;当虚拟机vCPU空闲时&#xff0c;虚拟机将执行WFx/HLT指令退出到宿主机中&#xff0c;并触发上…

计算机视觉 - 基于黄金模板比较技术的缺陷检测

一、黄金模板比较概述 基于黄金模板比对的检测是一种常见的视觉应用。当进行缺陷检查而其他缺陷检测方法是不可行的时候,使用金模板比较。另外当物体的表面或物体的形状非常复杂时,此技术特别有用。 虽然说黄金模板比较的技术的思路很简单,但是真正落地实施确不是一件十分容…

广告数仓:数仓搭建(二)

系列文章目录 广告数仓&#xff1a;采集通道创建 广告数仓&#xff1a;数仓搭建 广告数仓&#xff1a;数仓搭建(二) 文章目录 系列文章目录前言DWD层创建1.建表广告事件事实表 2.数据装载初步解析日志解析IP和UA标注无效流量编写脚本 总结 前言 这次我们完成数仓剩下的内容 D…

Web服务器群集:Web基础与HTTP协议

目录 一、理论 1.Web基础 2.HTTP协议 二、实验 1.浏览本地HTML页面 三、总结 一、理论 1.Web基础 &#xff08;1&#xff09;域名和DNS ① 域名 网络是基于TCP/IP 协议进行通信和连接的&#xff0c;每一台主机都有一个唯一的标识&#xff08;固定的IP地 址&#xff0…

【Java面试】什么是SpringMVC?它的工作流程是什么样子的?

文章目录 什么是MVC&#xff1f;MVC组件组件前端控制器DispatcherServlet处理器映射器HandlerMapping处理器适配器HandlAdapter视图解析器ViewResolver处理器Handler视图View 工作原理具体执行流程 什么是MVC&#xff1f; M&#xff1a;model&#xff0c;模型层&#xff0c;包…

搅拌机打蒜机不转维修

打蒜机不转维修&#xff1a;打蒜机用的18650电池&#xff0c;霍尔传感器&#xff0c;Dp0206场效应管。故障为按一下开关显示红灯&#xff1a;电池电压低&#xff01;按下启动按钮电机动一下就再不动了。如果给电池两边加一个5伏电源&#xff0c;打蒜机电机运行正常。那么我把充…