数据结构——排序算法分析与总结

一、插入排序

1、直接插入排序

核心思想:把后一个数插入到前面的有序区间,使得整体有序

思路:先取出数组中第一个值,然后再用tmp逐渐取出数组后面的值,与前面的值进行比较,假如我们进行的是升序排序,那么此时前面排序好的数组中,最后一个值最大,如果tmp大于它,就插入后面,否则end -- 往前走,进行比较,(注意:此时数组也要往后赋值),找到比它小的元素,则插在那个元素后面即可如果直到为0的时候,仍然小于,则把它放在下标为0的位置。

最坏情况(数组元素逆序):每次插入,元素都要往后移动

  • 移动次数:1 +2 +3…+ n => 等差数列 ==>O(N^2)

最好情况:(接近有序或者有序),基本不用移动数据 ->O(N)、

稳定性:稳定 

  • 将x插入到[0,end]的有序区间的时候,当区间元素和x的值相同的时候,是将x插入到该区间元素后面,二者的相对顺序不变

图文演示:假如a[ 5 ] = {3 , 2, 1, 4, 5}

代码演示:

//插排二
//时间复杂度 O(N^2)
// 最坏情况 :逆序
// 最好情况: 顺序或者接近有序 O(N)
void InsertSort(int* a, int n) //n表示数组有多少个元素
{for (int i = 0; i < n - 1; i++) //只需要走n-1个元素即可,防止越界{int end = i; //后面下标int tmp = a[end + 1]; //存下后面的值while (end >= 0) //与前面下标元素进行比较{if (a[end] > tmp) { //待插入元素小于前面元素,则数组往后走一位a[end + 1] = a[end];end--;}else break; //找到比待插入元素小的}a[end + 1] = tmp;  //插入当前位置}
}

2、希尔排序

核心思想

1.先选定一个gap,把待排序数据分组,所有距离为gap分在同一组内,并对每一组内的记录进行排序。

预排序:目的是让数组更接近于有序,这样子后续gap为1进行直接插入排序,效率就是O(N)
2.然后取重复上述分组和排序的工作,当到达gap=1时,就是直接插入排序,整体就有序了

  • gap越大:预排序越快,预排序后越不接近有序
  • gap越小:预排序越慢,预排序后越接近有序。
  • 数据是逆序的时候,预排序完成就接近有序,此时预排序的效果最好。

图文演示:

时间复杂度:O(N^1.3)

稳定性:不稳定

  • 相同的值,预排序时可能分到不同的组里面,导致相对顺序发生改变

写法1:多组进行排序

//多组一起进行预排序
void Shellsort1(int* a, int n)
{//int gap = 1; //如果gap为1 则为插入排序//把间隔为gap的数组同时排int gap = n;while (gap > 1){gap /= 2; //gap>1 都是预排序 接近有序//gap=gap/3+1;for (int i = 0; i < n - gap; i++)//注意:i的范围! 否则end+gap会越界{int end = i;//end的范围:[0,n- gap -1]int tmp = a[end + gap];while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];//把a[end]往后移动,以gap为间隔的为一组,所以移动到a[end+gap]位置end -= gap;//下一轮循环,以gap为间隔的为一组,前一个数(end-gap位置对应的值)和 tmp 比较}else break;}a[end + gap] = tmp;}}
}

写法2:每一组分别进行预排序

每一组都进行预排序

  • 一次只排序间隔为gap的元素(同组元素),一共有gap组,所以要循环gap次

需要变动的位置:循环gap次,每次处理一组!

  • 每一组的起始位置是当前组的组号,然后每次变化范围:  gap
//多组一起进行预排序
void Shellsort2(int* a, int n)
{//int gap = 1; //如果gap为1 则为插入排序//把间隔为gap的数组同时排int gap = n;while (gap > 1){//目的是为了保证最后能让gap为1,进行直接插入排序gap /= 2; //gap>1 都是预排序 接近有序//gap=gap/3+1;for (int j = 0; j < gap; j++) {for (int i = j; i < n - gap; i += gap)//注意:i的初始值!!和变动范围 i+=gap{int end = i;//end的范围:[0,n- gap -1]int tmp = a[end + gap];//下一轮循环,以gap为间隔的为一组,前一个数(end-gap位置对应的值)和 tmp 比较while (end >= 0){if (a[end] > tmp){a[end + gap] = a[end];end -= gap;}else break;}a[end + gap] = tmp;//以gap为间隔的为一组,把tmp放在end + gap位置 }}}
}

二、选择排序

1、直接选择排序

核心思想:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,....,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

时间复杂度:遍历一遍才能选出一个数或者两个数,无论什么情况都是O(N^2)。

稳定性:不稳定

  • 在区间当中找到最大和最小的数和区间左右端点位置的值交换,可能会导致两个相同的值相对顺序发生变化

图文演示:

代码演示:

方法一:每次选择一个数,比如一个移动范围内的最小值

//直接选择排序 
//写法1:每次选择一个数
void SelectSort1(int* a, int n)
{int begin = 0;while (begin < n - 1) {//当区间只有一个元素就不需要选了所以循环条件为:end > 0int mini = begin;for (int i =  begin + 1; i < n; i++) {// 在[begin, n-1]中选取最小值if (a[mini] > a[i]) mini = i;}swap(a[begin], a[mini]);begin++;}
}

方法2:每次选择两个数

思路:每次从要排序的区间当中找到最大和最小的数,如果是排序,那么把他区间的最大的数和区间右端点对应值交换,把区间中最小的数和区间左端点对应值交换,然后缩小区间重复上述步骤,直到区间只有一个数。

//写法2:每次选择两个数
void SelectSort2(int* a, int n)
{int begin = 0, end = n - 1;while (begin <end){int mini = begin, maxi = end;for (int i = begin; i <= end; i++){if (a[i] < a[mini]) mini = i; //找出最小的值下标if (a[i] > a[maxi]) maxi = i; //找出最大的值下标}swap(a[begin], a[mini]);//最小值放到begin位置//注意:如果begin和maxi一样,即begin就是最大值的位置//因为下面一步begin位置和值已经和mini位置交换了,所以就导致了mini位置放的才是最大值了//所以需要特判一下,如果begin和maxi相同,那么经过上面一步交换之后,mini位置放的才是最大值if (begin == maxi) maxi = mini;//加以优化,避免最大的数出现在第一个swap(a[end], a[maxi]);//最大值放到end位置begin++, end--; //缩小区间}
}

2、堆排序

核心思想

     1.首先需要先建堆,只需要从最后一个叶子结点的父节点开始,在数组当中从后往前去向下调整即可共n个元素。

  • 共n个元素,最后一个结点的下标为: n -1
  • 它的父亲结点的下标为:parent = (child - 1)/2 = (n - 1- 1)/2

        2.建好堆之后,将堆顶数据与堆的最后一个数据交换,然后对根位置进行一次堆的向下调整,但是调整时被交换到最后的那个数不参与向下调整,然后缩小堆中有效数据个数,剩下的元素进行向下调整,其余数又成一个大堆…重复上述步骤,直到堆中只剩下一个元素。

       注意:

            a、排升序建大堆

            b、排降序建小堆

对于N个元素建堆,我们用筛选法建堆,从 N/2 (即最后一个非叶子节点)的元素开始建堆

时间复杂度分析:无论哪种方法建堆:都是O(N*logN)

  • 建堆的时间复杂度 + 调堆的时间复杂度 N*logN

稳定性:不稳定

  • 在调堆的时候,可能会导致相同元素的相对顺序改变

图文演示:

代码演示:

//排升序最后是建大堆
void AdjustDwon(int* a, int n, int root)
{int parent = root;int child = parent * 2 + 1; //默认是左孩子,右孩子比左孩子大1while (child < n){//1、选出左右孩子中大的那一个if (child+1<n && a[child + 1] > a[child]) //建一个大堆{child += 1;}//2、然后再与父亲比较,若比父亲大就发生交换if (a[child] > a[parent])//让父亲永远大于儿子{Swap(&a[child], &a[parent]);parent = child; //交换下标child = parent * 2 + 1;}else break; //记得跳出循环}
}//第一个和最后一个交换,把它不看作堆里面,前n-1个数向下调整,选出次大的数,再跟倒数第二个位置交换
void HeapSort(int* a, int n)
{//把数组建成大堆for (int i = (n-1-1) / 2; i >= 0; i--) //筛选法选出最后一个非叶子节点{AdjustDwon(a, n, i); //建大根堆}int end = n - 1;while (end > 0)//开始向下调整{Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);--end;}
}

 举例:将整数数组(7-6-3-5-4-1-2)按照堆排序的方式原地进行升序排列,请问在整个排序过程中,数组的顺序是_____。(6-5-3-2-4-1-7)


三、交换排序

1、冒泡排序

核心思想:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

  • n个元素,只需排序n-1次,就可以让n个数有序

优化:如果提前有序了(某一趟冒泡当中没有元素交换),就不需要再冒泡了。

时间复杂度:

  • 最坏情况:第一轮:N个数比较交换,第二轮:N-1个数比较交换… ,此时相当于是等差数列,复杂度为O(N^2)
  • 最好情况:数组接近有序/有序,某一趟冒泡当中没有元素交换直接结束,O(N)

稳定性:稳定

  • 相邻元素进行比较,相同的元素之间不进行交换
void BubbleSort(int* a, int n)
{
//每一趟可以确定一个元素到准确位置,n个元素只需要进行n-1趟for (int i = 0; i < n - 1; i++) {bool flag = 0;
//每一趟都可以少比较一个已经确定好的数for (int j = 0; j < n - 1 - i; j++){if (a[j] > a[j + 1]) {swap(a[j], a[j + 1]);flag = 1;}}if (flag == 0) break; //说明没有进行交换,此时已经有序,跳出即可}
}

 2、快速排序

快速排序是基于 分治法 的一个排序算法。

核心思想:取待排序区间上的某一个元素作为基准值,根据处理方法,将待排序区间上的元素划分为:左区间的元素小于基准值,右区间的元素大于基准值,然后对左右区间重复这个过程,直到所有元素都排列在相应位置上为止

代码演示:

最简便模板:(记住这个即可)

void QuickSort(int q[], int l, int r) //先分治再递归
{if (l >= r) return; //递归截止条件int x = q[l + r >> 1]; // 但是一般选择第一个或者最后一个int i = l - 1, j = r + 1; //注意:l-1 和 r + 1while (i < j)//x左边都是小于x的,右边都是大于x的 {do i++; while (q[i] < x);do j--; while (q[j] > x);//假设上面截止时且满足i < j,此时a[i] >= x, a[j] <= x if (i < j) swap(q[i], q[j]);}QuickSort(q, l, j);QuickSort(q, j + 1, r);
}

比如快速排序的第一趟结果:

比如关键字(20,15,14,18,21,36,40,10)以20为基准进行排序。
第一步,先从后往前找出小于基准数20的数,并与基准数20交换得:10,15,14,18,21,36,40,20)。
第二步,再从前往后找出大于基准数20的数,并与基准数20交换得:10,15,14,18,20,36,40,21)。
再一次执行第一步与第二步,直到最后基准数左边的序列都小于基准数,基准数右边的序列都大于基准数。
所得结果:(10,15,14,18,20,36,40,21)。为第一趟排序的结果。


四、归并排序

核心思想:根据左右区间的值,计算一个中间值mid,先让[left,mid] [mid+1,right]两个区间有序, 然后这两个有序区间进行归并 (归并到临时数组),将临时数组的内容拷贝回去。

时间复杂度:O(N*logN)

空间复杂度:O(N)

稳定性:稳定

  • 归并的时候,相同的值,先拷贝左区间的值,再拷贝右区间的

归并操作,也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。

如 设有数列{6,202,100,301,38,8,1}

初始状态:6,202,100,301,38,8,1。

第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;

第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;

第三次归并后:{1,6,8,38,100,202,301},比较次数:4;

总的比较次数为:3+4+4=11;

逆序数为14;(因此我们可以用 归并算法 来计算逆序数)

代码演示:

const int N = 10010;
int tmp[N];
void merge_sort(int q[], int l, int r) //先归并再分治
{if (l >= r) return; //递归截止int mid = l + r >> 1; //记录中间值merge_sort(q, l, mid);   merge_sort(q, mid + 1, r);int k = 0, i = l, j = mid + 1; //k表示tmp储存的元素数量while (i<=mid && j<=r) //注意下面都是<=,保证了它的稳定性{if (q[i] <= q[j])tmp[k++] = q[i++]; else  tmp[k++] = q[j++];}//防止分布不均while (i <= mid) tmp[k++] = q[i++];while (j <= r) tmp[k++] = q[j++]; //注意i的范围是[l,r]		for (i = l, j = 0; i <= r; i++,j++) q[i] = tmp[j]; //拷贝}


五、计数排序——鸽巢原理

核心思想:统计相同元素出现次数,根据统计的结果将序列写回到原来的序列中

统计每个数据出现的次数 count[a[i]]++
适合数据集中的数组进行排序
  时间复杂度O(N+range)
  空间复杂度 O(range)

稳定性:不稳定

  • 计数到count数组中,每个元素已经没有顺序了
void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 0; i < n; ++i){if (a[i] > max)max = a[i];   //找出最大值if (a[i] < min)min = a[i];  // 找出最小值		}int range = max - min + 1;  //比如109 100 实际有10个值int* count = (int*)malloc(sizeof(int) * range);//数组元素进行映射。此时x元素映射在x - min位置memset(count, 0, sizeof(int) * range);for (int i = 0; i < n; ++i){count[a[i] - min]++;}//将count数组的内容映射回去原数组,此时对应的值为i + minint j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}free(count);
}

上述八大排序的稳定性以及复杂度

注意:

1、对于快速排序:如果加了三数取中 + 三路归并 最坏就不是O(N^2)

2、为了绝对的速度选快排,为了省空间选堆排,为了稳定性选归并

3、时间复杂度:O(N*logN),额外空间复杂度低于O(N),且稳定的基于比较的排序是不存在的

最后下面我们再提一个排序


九、基数排序

代码演示:

bool check(int* arr, int l, int r)
{for (int i = l + 1; i < r; i++){if (arr[i] < arr[i - 1]) return true;}return false;
}// 扩大范围 从 0 - 99为基数
void radix_sort(int* arr, int l, int r)
{
#define K 65536int* cnt = (int*)malloc(sizeof(int) * K);int* temp = (int*)malloc(sizeof(int) * (r - l));// round 1memset(cnt, 0, sizeof(int) * K);for (int i = l; i < r; i++) cnt[arr[i] % K] += 1; //求前缀和for (int i = 1; i < K; i++) cnt[i] += cnt[i - 1];for (int i = r - 1; i >= l; i--) temp[--cnt[arr[i] % K]] = arr[i]; //记录当前数字应该存放的位置memcpy(arr + l, temp, sizeof(int) * (r - l));// round 2memset(cnt, 0, sizeof(int) * K);for (int i = l; i < r; i++) cnt[arr[i] / K] += 1; //求前缀和for (int i = 1; i < K; i++) cnt[i] += cnt[i - 1];for (int i = r - 1; i >= l; i--) temp[--cnt[arr[i] / K]] = arr[i]; //记录当前数字应该存放的位置memcpy(arr + l, temp, sizeof(int) * (r - l));}

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

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

相关文章

代谢组数据分析七:从质谱样本制备到MaxQuant搜库

前言 LC-MS/MS Liquid Chromatography-Mass Spectrometry&#xff08;LC-MS/MS &#xff0c;液相色谱-质谱串联&#xff09;可用于残留化合物检测、有机小分子检测、鉴定和定量污染物以及在医药和食品领域添加剂检测和生物小分子等检测。 LC-MS/MS一般包含五个步骤&#xff…

熟悉Redis吗,那Redis的过期键删除策略是什么

对于Redis&#xff0c;我们业务开发一般都只关心Redis键值对的查询、修改操作&#xff0c;可能因为懒或者只想能用就行&#xff0c;呵呵。很少关心键值对存储在什么地方、键值对过期了会怎么样、Redis有没什么策略处理过期的键、Redis处理过期键又有什么作用&#xff1f;但这些…

LabVIEW智能变电站监控系统设计与实现

LabVIEW智能变电站监控系统设计与实现 随着电力系统和智能化技术的快速发展&#xff0c;建立一个高效、可靠的变电站监控系统显得尤为重要。通过分析变电站监控系统的需求&#xff0c;设计了一个基于LabVIEW软件的监控平台。该平台利用虚拟仪器技术、传感器技术和无线传输技术…

5W 1.5KVDC 隔离 宽电压输入 DC/DC 电源模块——TP05DB 系列

TP05DB系列电源模块额定输出功率为5W&#xff0c;应用于2:1及4:1电压输入范围 4.5V-9V、9V-18V、18V-36V、36V-72V、9V-36V和18V-72V&#xff0c;40-160VDC的输入电压环境&#xff0c;输出电压精度可达1%&#xff0c;具有输出过流保护等功能。可广泛应用于通信、铁路、自动化以…

机器学习 | 时间序列预测中的AR模型及应用

自回归模型&#xff0c;通常缩写为AR模型&#xff0c;是时间序列分析和预测中的一个基本概念。它们在金融、经济、气候科学等各个领域都有广泛的应用。在本文中&#xff0c;我们将探索自回归模型&#xff0c;它们如何工作&#xff0c;它们的类型和实际例子。 自回归模型 自回…

【小迪安全2023】第61天:服务攻防-中间件安全CVE复现K8sDockeruettyWebsphere

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

IaC实战指南:DevOps的自动化基石

基础设施即代码&#xff08;Infrastructure as Code&#xff0c;IaC&#xff09;是指利用脚本、配置或编程语言创建和维护基础设施的一组实践和流程。通过IaC&#xff0c;我们可以轻松测试各个组件、实现所需的功能并在最小化停机时间的前提下进行扩展。更值得一提的是&#xf…

STM32单片机实战开发笔记-独立看门狗IWDG

嵌入式单片机开发实战例程合集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11av8rV45dtHO0EHf8e_Q0Q?pwd28ab 提取码&#xff1a;28ab IWDG模块测试 1、功能描述 STM32F10X内置两个看门狗&#xff0c;提供了更高的安全性&#xff0c;时间的精确下性和使用的灵活性…

聊聊BitLocker

最近有消息称微软决定在Windows 11 24H2中默认开启BitLocker&#xff0c;这个消息在网上引起了不小的波澜。有人说&#xff0c;对于我们这些普通用户来说&#xff0c;BitLocker真的有必要吗&#xff1f; 什么是BitLocker BitLocker 是一项 Windows 安全功能&#xff0c;可为整…

Qt与MySQL连接

QT连接Mysql数据库&#xff08;详细成功版&#xff09;-CSD N博客 我的MySQL是64位的&#xff0c;所以我的Qt的套件也需要是64位的 遇到的问题&#xff1a; &#xff08;available drivers中已经有QMYSQL QMYSQL3&#xff0c;还是not loaded&#xff09; QSqlDatabase: QMYS…

Splashtop 荣获 TrustRadius 颁发的“2024年度最受欢迎奖”

2024年5月8日 加利福尼亚州库比蒂诺 Splashtop 在全球远程访问和支持解决方案领域处于领先地位&#xff0c;该公司正式宣布将连续第三年荣获远程桌面和远程支持类别的“TrustRadius 最受欢迎奖”。Splashtop 的 trScore 评分高达8.6分&#xff08;满分10分&#xff09;&#x…

[图解]DDD架构好简单我学会了-学会也没啥用

1 00:00:03,720 --> 00:00:05,920 内部共有&#xff0c;首先是内部的 2 00:00:08,150 --> 00:00:09,220 所以不能说什么 3 00:00:09,630 --> 00:00:10,730 不能跟外部连在一起 4 00:00:10,740 --> 00:00:15,280 比如说&#xff0c;功能架构&#xff0c;可以吗 …

企业计算机服务器中了faust勒索病毒如何处理,faust勒索病毒解密恢复

随着网络技术的不断发展与应用&#xff0c;越来越多的企业利用网络走向了数字化办公模式&#xff0c;网络也极大地方便了企业生产运营&#xff0c;大大提高了企业生产效率&#xff0c;但对于众多企业来说&#xff0c;企业的数据安全一直是大家关心的主要话题&#xff0c;保护好…

Flutter笔记:Widgets Easier组件库(10)快速处理承若型对话

Flutter笔记 使用Widgets Easier组件库快速处理承若型对话 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://…

IoTDB 入门教程 基础篇⑦——数据库管理工具 | DBeaver 连接 IoTDB

文章目录 一、前文二、下载iotdb-jdbc三、安装DBeaver3.1 DBeaver 下载3.2 DBeaver 安装 四、安装驱动五、连接数据库六、参考 一、前文 IoTDB入门教程——导读 二、下载iotdb-jdbc 下载地址org/apache/iotdb/iotdb-jdbc&#xff1a;https://maven.proxy.ustclug.org/maven2/o…

ai可以做思维导图吗?当然是可以的!

ai可以做思维导图吗&#xff1f;在快节奏的现代生活中&#xff0c;思维导图作为一种高效的信息组织和表达工具&#xff0c;越来越受到人们的青睐。随着人工智能&#xff08;AI&#xff09;技术的不断发展&#xff0c;AI思维导图软件也应运而生&#xff0c;它们不仅能够帮助用户…

为什么现在越来越多的人会选择陪诊

现在越来越多的人选择陪诊的原因有多方面。 首先&#xff0c;随着人口老龄化、医疗资源分配不均等问题的日益突出&#xff0c;许多老年人和病患在就医过程中面临诸多困难&#xff0c;如挂号、排队、取药等繁琐的手续和流程。陪诊服务能够为他们提供极大的便利&#xff0c;帮助…

【Gateway远程开发】0.5GB of free space is necessary to run the IDE.

【Gateway远程开发】0.5GB of free space is necessary to run the IDE. 报错 0.5GB of free space is necessary to run the IDE. Make sure that there’s enough space in following paths: /root/.cache/JetBrains /root/.config/JetBrains 原因 下面两个路径的空间不…

【excel】统计单元格内数字/字符串的数量

文章目录 一、问题二、步骤&#xff08;一&#xff09;将A1中的数字分解出来&#xff0c;在不同的单元格中显示&#xff08;二&#xff09;统计每个数字出现的个数&#xff08;三&#xff09;去重 三、尾巴 一、问题 单元格中有如下数值&#xff1a;12345234534545&#xff0c…

Nginx(参数设置总结)

文章目录 Nginx&#xff08;工作机制&参数设置&#xff09;1.Master&Worker工作机制1.示意图2.解释3.Nginx争抢机制4.accept_mutex解决惊群现象5.多进程结构不用多线程结构的好处6.IO多路复用&#xff0c;实现高并发7.优势 2.参数配置1.work_processes1.基本介绍2.work…