【C++版】排序算法详解

目录

直接插入排序

希尔排序

选择排序

冒泡排序

堆排序 

快速排序

hoare法

挖坑法

 前后指针法

 非递归版本

 快速排序中的优化

归并排序

递归版本

非递归版本

计数排序

 总结


直接插入排序

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

其实我们在打牌的时候就是运用了这种插入的思想


 

动图演示

步骤

  1.  从第一个元素开始,可以认为这个元素是有序的序列
  2. 遍历下一个元素,往这个有序的序列进行插入
  3. 从后往前扫描这个有序序列,如果待插入的元素小于这个有序序列中的元素,就将这个有序序列的元素移到下一位,直到遇到比这个待插入元素小的数据就停下来,将这个元素插入到这个数据的下一个位置。

代码实现: 

//直接插入排序—时间复杂度O(N^2)
void InsertSort(vector<int> &v, int n)
{//end只用到n-2位置即可for (int i = 0; i < n - 1; i++){int end = i;int tmp = v[end + 1];while (end >= 0){if (tmp < v[end]){v[end + 1] = v[end];end--;}else{break;}}v[end + 1] = tmp;}
}

希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数(gap),把待排序文件中所有记录分成多个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取重复上述分组和排序的工作。当gap == 1时,所有记录在同一组内的数已经排好序,最后再来个直接插入排序,那么排序就完成了。

说白了希尔排序其实就是在直接插入排序的基础之上先给这组数分组做预排序,让这组数据接近有序,最好再用直接插入排序的思想,将排序完成。因为直接插入排序在一组数接近有序的情况下效率是非常高的。

在进行预排序的时候gap的选择很关键,太大了使数据接近有序的效果不行,太小了又浪费效率。所以可以参考下面的方法。

《数据结构-用面相对象方法与C++描述》--- 殷人昆

但是我这里还是用了gap = gap / 3 + 1这种方法,预排序完成的更快。

代码实现: 

//希尔排序
void ShellSort(vector<int>& v, int n)
{//gap > 1时是预排序//gap == 1时直接插入排序int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = v[end + gap];while (end >= 0){if (tmp < v[end]){v[end + gap] = v[end];end -= gap;}else{break;}}v[end + gap] = tmp;}}
}

选择排序

选择排序的思想是:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
选择排序在众多排序算法中,效率并不算好,时间复杂度为O(N^2)无论在最好还是最坏的情况下都是一样的,所以在实际情况下,很少人会使用它。

动图演示

代码实现: 

代码上小优化,可以同时找最大和最小的数,使得排序更快

//选择排序—时间复杂度O(N^2)
void SelectSort(vector<int>& v, int n)
{int begin = 0, end = n - 1;int mini = begin, maxi = begin;while (begin < end){for (int i = begin + 1; i < end; i++){//找小的数if (v[i] < v[mini])mini = i;//找大的数if (v[i] > v[maxi])maxi = i;}Swap(v[begin], v[mini]);//如果begin和maxi重叠,那么需要修正maxiif (begin == maxi){maxi = mini;}Swap(v[end], v[maxi]);begin++;end--;}
}

 冒泡排序

冒泡排序的基本思想是:每次比较两个相邻的元素,如果他们的顺序错误就把它们交换过来,就好像水底下的气泡一样逐渐向上冒。 

 动图演示

代码实现: 

//冒泡排序—时间复杂度O(N^2)
void BubbleSort(vector<int>& v, int n)
{for (int i = 0; i < n; i++){int exchange = 0;for (int j = 1; j < n - i; j++){if (v[j - 1] > v[j]){Swap(v[j - 1], v[j]);exchange = 1;}}if (exchange == 0){break;}}
}

堆排序 

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据,是一个二叉树,在排序之前需要建堆,建堆的方法有两种,①建大堆,即根节点的元素要比孩子结点的元素要大。②建小堆,即根节点的元素比孩子结点的元素要小需要注意的是排升序要建大堆,排降序建小堆。

堆的性质

  1. 堆中某个节点的值总是不大于或不小于其父节点的值;
  2. 堆总是一棵完全二叉树。

 

 代码实现: 

//堆排序—建大根堆
void AdjustDown(vector<int> &v, int n, int root)
{int child = root * 2 + 1;while (child < n){//选出左右孩子中大的那个if (child + 1 < n && v[child + 1] > v[child])//防止数组越界{child++;}//孩子和父亲比较if (v[child] > v[root]){Swap(v[child], v[root]);root = child;child = root * 2 + 1;}else{break;}}
}void HeapSort(vector<int>& v, int n)
{for (int i = (n - 1 - 1 ) / 2; i >= 0; i--){AdjustDown(v, n, i);//向下调整前提:左右子树必须是大/小堆}// o(N*logN)int end = n - 1;while (end > 0){Swap(v[0], v[end]);AdjustDown(v, end, 0);end--;}
}

 快速排序

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

要实现快速排序可以有以下几种方法:

hoare法

需要注意的是选择左边的数作为key值,那必须右边先开始走,右边作为key值,那必须左边先开始走。因为这样做才能保证左边和右边相遇的时候是比key值要小的。

下面的挖坑法也是这样。 

代码实现: 

void QuickSort(vector<int>& v, int begin, int end)
{//如果只剩一个值或者区间不存在直接返回if (begin >= end){return;}int left = begin, right = end;int keyi = left;while (left < right){//右边先走,找到比key小的就停下来while (left < right && v[right] >= v[keyi]){right--;}//左边走,找到比key大的就停下来while (left < right && v[left] <= v[keyi]){left++;}Swap(v[left], v[right]);}Swap(v[keyi], v[left]);keyi = left;//左区间QuickSort(v, begin, keyi - 1);//右区间QuickSort(v, keyi + 1, end);
}

挖坑法

代码实现: 

void QuickSort(vector<int>& v, int begin, int end)
{//如果只剩一个值或者区间不存在直接返回if (begin >= end){return;}int left = begin, right = end;int key = v[begin];int piti = begin;while (left < right){//右边先走,找到比key小的就停下来while (left < right && v[right] >= key){right--;}v[piti] = v[right];piti = right;//左边走,找到比key大的就停下来while (left < right && v[left] <= key){left++;}v[piti] = v[left];piti = left;}v[piti] = key;//左区间QuickSort(v, begin, piti - 1);//右区间QuickSort(v, piti + 1, end);
}

 前后指针法

代码实现: 

void QuickSort(vector<int>& v, int begin, int end)
{//如果只剩一个值或者区间不存在直接返回if (begin >= end){return;}int prev = begin, cur = prev + 1;int keyi = begin;while (cur <= end){if (v[cur] < v[keyi] && ++prev != cur)Swap(v[prev], v[cur]);cur++;}Swap(v[prev], v[keyi]);keyi = prev;//左区间QuickSort(v, begin, keyi - 1);//右区间QuickSort(v, keyi + 1, end);
}

 非递归版本

递归的问题,在极端场景下如果深度太深,会出现栈溢出,所以我们要改成非递归版本

我这里就用个栈来模拟递归过程,当然你用个队列来实现也是可以的。

步骤:

  1. 先将大区间进行入栈,先入区间右边的数,再入区间左边的数
  2. 取栈顶元素,划分为小区间
  3. 将这些小区间全部入栈

一直重复这三步就模拟出了递归的过程

int PartQuickSort(vector<int> &v, int begin, int end)
{int key = v[begin];int piti = begin;while (begin < end){//右边先走,找到比key小的就停下来while (begin < end && v[end] >= key){end--;}v[piti] = v[end];piti = end;//左边走,找到比key大的就停下来while (begin < end && v[begin] <= key){begin++;}v[piti] = v[begin];piti = begin;}v[piti] = key;return piti;}//利用栈来模拟递归从场景—栈是先进后出
void QuickSortNonR(vector<int> &v, int begin, int end)
{stack<int> st;//先入右在入左st.push(end);st.push(begin);while (!st.empty()){int left = st.top();st.pop();int right = st.top();st.pop();int keyi = PartQuickSort(v, left, right);//左区间入栈if (left < keyi - 1){st.push(keyi - 1);st.push(left);}//右区间入栈if (keyi + 1 < right){st.push(right);st.push(keyi + 1);}}
}

快速排序中的优化

①对于选择key值的优化,key的选择对于快速排序来说至关重要,因为如果你选择的key值为最大值或者最小值时快速排序的效率就会有所影响,key值最好的是取这组数据的中间值来达到完全二分的情况。所以折中一下在一组数据的首、尾、中间位置去中间值。

//三个数取中间值做为key
int GetMidKeyi(vector<int>& v, int begin, int end)
{int mid = (begin + end) / 2;if (v[begin] < v[mid]){if (v[mid] < v[end]){return mid;}else if (v[begin] < v[end]){return end;}else{return begin;}}else//v[begin] > v[mid]{if (v[mid] > v[end]){return mid;}else if (v[begin] < v[end]){return begin;}else{return end;}}
}

②对于递归的小区间优化,在递归到小区间时,我们可以选择用插入排序来完成排序工作,因为此时这段小区间的数据肯定是接近有序的而插入排序在接近排序的情况下效率最高为O(N)。

归并排序

归并排序的思想:归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

动图演示

代码实现:

递归版本

//归并排序—时间复杂度O(N*logN)
//空间复杂度O(N)
void _MergeSort(vector<int> &v, int begin, int end, vector<int> &tmp)
{//递归返回的条件if (begin >= end)return;int mid = (begin + end) / 2;//进行递归_MergeSort(v, begin, mid, tmp);_MergeSort(v, mid + 1, end, tmp);int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin1;while (begin1 <= end1 && begin2 <= end2){if (v[begin1] < v[begin2]){tmp[i++] = v[begin1++];}else{tmp[i++] = v[begin2++];}}//如果后面还有数,也要拷贝到tmp中去while (begin1 <= end1)tmp[i++] = v[begin1++];while (begin2 <= end2)tmp[i++] = v[begin2++];//深拷贝int j = begin, k = end - begin + 1;while(k-- && j <= end){v[j] = tmp[j];j++;}
}void MergeSort(vector<int> &v, int n)
{vector<int> tmp(n, 0);_MergeSort(v, 0, n - 1, tmp);
}

非递归版本

注意边界问题防止越界,对于越界的区间直接进行修正即可

//非递归版本
void MergeSortNonR(vector<int>& v, int n)
{vector<int> tmp(n, 0);int gap = 1;while (gap < n){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;//修正边界,防止越界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;}int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (v[begin1] < v[begin2]){tmp[j++] = v[begin1++];}else{tmp[j++] = v[begin2++];}}//如果后面还有数,也要拷贝到tmp中去while (begin1 <= end1)tmp[j++] = v[begin1++];while (begin2 <= end2)tmp[j++] = v[begin2++];}//深拷贝for (int k = 0; k < n; k++){v[k] = tmp[k];}gap *= 2;}
}

这里是归并完一次,就将tmp数据一次性拷回原数组中。

你也可以归并完一部分就将这一部分的数据拷回原数组中去,也就是下面的这种写法。

或者

void MergeSortNonR(vector<int>& v, int n)
{vector<int> tmp(n, 0);int gap = 1;while (gap < n){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;//修正边界,防止越界if (end1 >= n || begin2 >= n){break;}else if (end2 >= n){end2 = n - 1;}int k = end2 - begin1 + 1;int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (v[begin1] < v[begin2]){tmp[j++] = v[begin1++];}else{tmp[j++] = v[begin2++];}}//如果后面还有数,也要拷贝到tmp中去while (begin1 <= end1)tmp[j++] = v[begin1++];while (begin2 <= end2)tmp[j++] = v[begin2++];int m = i;while (k-- && m < n){v[m] = tmp[m];m++;}}gap *= 2;}
}

由于我这里用的是C++来编写的,所以在将tmp数组中的数据拷贝回原数组中时, 不能使用memcpy函数进行拷贝,因为这里涉及到了深浅拷贝的问题,memcpy是浅拷贝。如果你用的是C语言来写的话就可以使用memcpy函数。

计数排序

计数排序的思想:统计相同元素出现的次数,然后将统计的结果写回到原数组中去。

 

对于B应该开多少空间来存放数据合适,使用A中的最大值 - 最小值 + 1,即为B应该开的空间数。时间复杂度:O(max(range,N)) ,空间复杂度:O(range),range为所开空间数。

计数排序的局限性:

  1. 如果是浮点数,字符串之类的就排不了了。
  2. 如果数据范围很大,空间复杂度就会很高,相对不合适。

代码实现:

//计数排序
//时间复杂度:O(max(range,N))
//空间复杂度:O(range)
void CountSort(vector<int>& v, int n)
{int max = v[0], min = v[0];for (auto vv : v){if (vv < min)min = vv;if (vv > max)max = vv;}int range = max - min + 1;vector<int> count(range, 0);//统计次数for (int i = 0; i < n; i++){count[v[i] - min]++;}//回写排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){v[j++] = i + min;}}
}

 总结

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

计数排序在这里不做比较。 

今天的分享就到这里,如果觉得有所收获的话,就给博主三连吧,创作不易,你的支持将是我创作的动力。

谢谢!!!

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

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

相关文章

【EI会议征稿通知】第五届计算机信息和大数据应用国际学术会议(CIBDA 2024)

第五届计算机信息和大数据应用国际学术会议&#xff08;CIBDA 2024&#xff09; 2024 5th International Conference on Computer Information and Big Data Applications 第五届计算机信息和大数据应用国际学术会议&#xff08;CIBDA 2024&#xff09;将于2024年3月22-24日在…

运行VUE提示找不到模块validate-engines.js...

原来好好的&#xff0c;突然提示找不到模块validate-engines.js&#xff0c;CMD命令行输入npm -v不是内部或外部命令&#xff0c;node -v可以查看到版本号。 解决&#xff1a; 1. 卸载nodejs&#xff0c;重新下载安装文件&#xff1a;下载nodejs 2. 到目录&#xff1a;C:\Us…

深度学习与神经网络Pytorch版 3.2 线性回归从零开始实现 1.生成数据集

3.2 线性回归从零开始实现 目录 3.2 线性回归从零开始实现 一 &#xff0c;简介 1. 原理 2. 步骤 3. 优缺点 4. 应用场景 二 &#xff0c;代码展现 1. 生成数据集(完整代码) 2. 各个函数解析 2.1 torch.normal()函数 2.2 torch.matmul()函数 2.3 d2l.plt.scatter(…

18.通过telepresence调试部署在Kubernetes上的微服务

Telepresence简介 在微服务架构中,本地开发和调试往往是一项具有挑战性的任务。Telepresence 是一种强大的工具,使得开发者本地机器上开发微服务时能够与运行在 Kubernetes 集群中的其他服务无缝交互。本文将深入探讨 Telepresence 的架构、运行原理,并通过实际的案例演示其…

在mgre环境下配置ospf

实验规则如上图所示&#xff1a; 首先规划IP 配置缺省路由&#xff0c;使得公网全网可达 此处在r1上进行配置&#xff1a; 由此可以实现&#xff0c;公网已经全网可达&#xff1a; 其次&#xff0c;再分配 全连的MGRE网段&#xff0c;全连的MGRE网段每个路由器都是中心站点&…

基于springboot招聘信息管理系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括招聘信息管理系统的网络应用&#xff0c;在外国招聘信息管理系统已经是很普遍的方式&#xff0c;不过国内的线上管理系统可能还处于起步阶段。招聘信息管理系统具有招聘信息管…

配置nginx作为静态文件托管服务器

下载nginx windows上是个压缩包 解压后, 使用命令行输入 nginx 进行启动 nginx -s stop 进行停止 nginx -s status 查看状态 可以配置一下环境变量 主要是配置文件, windows的nginx配置文件在 conf文件夹下 在http标签下 添加如下配置 其他地方不用更改,保持原样即可, 以…

git diff查看比对两次不同时间点提交的异同

git diff查看比对两次不同时间点提交的异同 用 git diff命令&#xff1a; git diff commit-id-1 commit-id-2 不同commit-id在不同的时间点提交产生&#xff0c;因为也可以认为git diff是比对两个不同时间点的代码异同。 git diff比较不同commit版本的代码文件异同_git diff c…

2024年航海制造工程与海洋工程国际会议(ICNMEME2024)

一、【会议简介】 2024年航海制造工程与海洋工程国际会议(ICNMEME2024)旨在将研究人员、工程师、科学家和行业专业人士聚集在一个开放论坛上&#xff0c;展示他们在导航制造工程与海洋工程领域的激励研究和知识转移理念。然而&#xff0c;我们也认识到&#xff0c;工程师的未来…

代码随想录算法训练营第二十天 |654.最大二叉树,617.合并二叉树,700.二叉搜索树种的搜索,98.验证二叉搜索树(待补充)

654.最大二叉树 1、题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 2、文章讲解&#xff1a;代码随想录 3、题目&#xff1a; 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的…

petalinux2022.2启动文件编译配置

安装必要运行库: sudo apt-get install iproute2 gawk python3 python sudo apt-get install build-essential gcc git make net-tools libncurses5-dev tftpd sudo apt-get install zlib1g-dev libssl-dev flex bison libselinux1 gnupg wget git-core diffstat sudo apt-ge…

react实现滚动到顶部组件

新建ScrollToTop.js import React, { useState, useEffect } from react; import ./ScrollToTop.css;function ScrollToTop() {const [isVisible, setIsVisible] useState(true);// Show button when page is scorlled upto given distanceconst toggleVisibility () > {…

处理Servlet生命周期事件

处理Servlet生命周期事件 接收关于 Servlet生命周期事件通知的类称为事件侦听器。这些侦听器实现Servlet API中定义的一个或多个servlet事件侦听器接口。侦听器类的逻辑分类如下: servlet请求侦听器Servlet上下文侦听器HTTP会话侦听器1. servlet请求侦听器 servlet请求侦听器…

专业138总分420+中国科学技术大学843信号与系统考研经验中科大电子信息通信

**今年中科大专业课843信号与系统138分&#xff0c;总分420顺利上岸&#xff0c;梦圆中科大&#xff0c;也是报了高考失利的遗憾&#xff0c;总结一下自己的复习经历&#xff0c;希望可以给大家提供参考。**首先&#xff0c;中科大843包括信号与系统&#xff0c;和数字信号处理…

网络隔离场景下访问 Pod 网络

接着上文 VPC网络架构下的网络上数据采集 介绍 考虑一个监控系统&#xff0c;它的数据采集 Agent 是以 daemonset 形式运行在物理机上的&#xff0c;它需要采集 Pod 的各种监控信息。现在很流行的一个监控信息是通过 Prometheus 提供指标信息。 一般来说&#xff0c;daemonset …

线性代数------矩阵的运算和逆矩阵

矩阵VS行列式 矩阵是一个数表&#xff0c;而行列式是一个具体的数&#xff1b; 矩阵是使用大写字母表示&#xff0c;行列式是使用类似绝对值的两个竖杠&#xff1b; 矩阵的行数可以不等于列数&#xff0c;但是行列式的行数等于列数&#xff1b; 1.矩阵的数乘就是矩阵的每个…

记录springboot bug

mybatis bug mapper 自动生成xml 产生错误 首先我这个bug十分奇怪,不管是报错,还是解决方法 首先,我还原我bug的过程 我首先要在 ordersMapper生成一个方法 本来是这样的方法 Mapper public interface OrdersMapper extends BaseMapper<Orders> {List<GoodsSales…

C语言——深入理解指针2

目录 1. 野指针1.1 野指针成因1.1.1 指针未初始化1.1.2 指针越界访问1.1.3 指针指向的空间释放 1.2 如何规避野指针1.2.1 指针初始化1.2.2 小心指针越界1.2.3 指针变量不再使用时&#xff0c;及时置NULL&#xff0c;指针使用之前检查有效性1.2.4 避免返回局部变量的地址 2. ass…

Linux:进度条的创建

目录 使用工具的简单介绍&#xff1a; \r &#xff1a; fflush &#xff1a; 倒计时的创建&#xff1a; 倒计时的工作原理&#xff1a; 进度条的创建&#xff1a; 不同场景下、打印任意长度的进度条&#xff1a; main .c procbor.c 测试效果&#xff1a; 使用工具…

YOLOv8实例分割实战:TensorRT加速部署

课程链接&#xff1a;https://edu.csdn.net/course/detail/39273 PyTorch版的YOLOv8支持高性能实时实例分割方法。 TensorRT是针对英伟达GPU的加速工具。 本课程讲述如何使用TensorRT对YOLOv8实例分割进行加速和部署&#xff0c;实测推理速度提高3倍以上。  采用改进后的t…