【数据结构】八大排序算法(梭哈)

news/2025/3/15 3:32:52/

目录

  • 1.直接插入排序
  • 2. * 希尔排序
    • 关于希尔排序的时间复杂度
  • 3.选择排序
  • 4. * 堆排序
  • 5.冒泡排序
  • 6. * 快速排序
    • 6.1递归快排
      • 6.1.1 hoare版
      • 6.1.2 挖坑法
      • 6.1.3 前后指针法
      • 6.1.4 关于每个区间操作的结束位置总是小于key
      • 6.1.5 关于有序原数据的效率优化
    • 6.2 非递归快排
  • 7. * 归并排序
    • 7.1 递归版归并
    • 7.2 非递归版归并
  • 8. 非比较排序
    • 计数排序
  • 排序算法时间复杂度和稳定性

1.直接插入排序

  • 稳定排序,最坏时间复杂度O(N^2),最好O(N)
//直接插入排序
void InsertSort(int* a, int n)
{//从下标为1处开始for (int i = 1; i < n; i++){int tmp = a[i];int end = i - 1;while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}//因从头开始进行插入比较,即保证了待插入值之前的元素已经是有序的了//故后部的插入比较若已满足排序要求,则直接跳出循环,并将与tmp比较//的end位置的后一位置为tmpelse{break;}}a[end + 1] = tmp;}
}

 
 

2. * 希尔排序

  • 分组插排,先进行预排序,使目标数组接近有序(对间隔为gap的数进行插入排序)
  • 再直接插入排序(gap=1)
  • gap的变化一般为 gap/=2 或 gap=gap/3+1
  • 不稳定排序,时间复杂度O(N*1.3)
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){//控制gapgap /= 2;	for (int i = gap; i < n; i++){//下标为i处的tmp值与在其之前每间隔gap的元素比较//不满足排序顺序则后移//且每次都是与之前的元素比较,以此能保证//每次tmp值比较的停止位置之前都是有序的int tmp = a[i];int end = i - gap;while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}//比较满足排序要求则停止,将与tmp比较的end位置的后一位置为tmpa[end + gap] = tmp;}}
}

关于希尔排序的时间复杂度

  • gap变化到1,执行次数为log2N或log3N
  • 每个gap内的执行次数量级都为N,系数可能不同,
  • gap变化至1时,相当于直接插入排序,并且经过预排序,已经为最好情况,量级为N
  • 结论:希尔排序的时间复杂度为 O(N^1.3),(优于O(N*logN))

 

3.选择排序

  • 可单边遍历,也可两边同时遍历
  • 两边遍历时,每次遍历找到的最小值放左边,最大值放右边
  • 注意考虑left于maxi 或 right 与 mini 重叠时要进行修正
  • 不稳定排序,时间复杂度为O(N^2)
//选择排序
void SelectSort(int* a, int n)
{单边找小//int left = 0;////while (left < n)//{//	int mini = left;//	//注意每次遍历起点为left//	for (int i = left; i < n; i++)//	{//		if (a[i] < a[mini])//		{//			mini = i;//		}//	}//	Swap(&a[left++], &a[mini]);//}//双边同时找//每次遍历范围为[left, right]int left = 0, right = n - 1;while (left < right){int mini = left, maxi = right;for (int i = left; i <= right; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[left++], &a[mini]);//因为这里先交换了left和mini,可能maxi与left重叠//直接交换maxi处的最大值已经通过left被交换到mini了//所以考虑此情况需要重置maxi到已交换到的mini处if (left == maxi){maxi = mini;}Swap(&a[right], &a[maxi]);}}

 

4. * 堆排序

  • 先建堆,再调堆
  • 现在原数组上构建出堆,排升序建小堆,排降序建大堆,
  • 建好堆再将堆顶与尾部数据交换,同时有效位减一,对交换后的堆顶进行向下调整,
  • 若为排升序建小堆,第一次交换并调整后,尾部为最大值,堆顶为次大值,以此循环,完成排序
  • 不稳定排序,时间复杂度为O(N*logN)

void AdjustUp(int* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}
}void AdjustDown(int* a, int parent, int n)
{int child = 2 * parent + 1;while (child < n){if (child + 1 < n && a[child + 1] > a[child]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);}elsebreak;parent = child;child = 2 * parent + 1;}
}
void HeapSort(int* a, int n)
{向上调整建堆//for (int i = 1; i < n; i++)//{//	AdjustUp(a, i);//}//向下调整建堆for (int i = n - 1; i >= 0; i--){AdjustDown(a, (i - 1) / 2, n);}int end = n;//首尾交换向下调整排序for (int i = n - 1; i >= 0; i--){Swap(&a[i], &a[0]);end--;AdjustDown(a, 0, end);}
}

 

5.冒泡排序

  • 稳定排序,时间复杂度为O(N^2)

在这里插入图片描述

void BubbleSort(int* a, int n)
{for (int j = 0; j < n - 1; j++){for (int i = 1; i < n - j; i++){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);}}}
}

 
 

6. * 快速排序

6.1递归快排

6.1.1 hoare版

  • 将头位置的元素作为基准值
  • 小于基准值的放左边,大于基准值的放右边
  • 最后将基准值放至比较结束位置
  • 根据基准值最后位置将数组分割成两个区间(剔除基准值)
  • 对分割后的区间进行分治
  • 不稳定排序,时间复杂度为 O(N*logN)
  • 类似二叉树的前序,递归深度为 logN
    在这里插入图片描述
//hoare版
int PartSort1(int* a, int left, int right)
{//keyi位置的值会影响快排效率//如果a[keyi]本身最大或最小,//为最坏情况,时间复杂度O(N2)//为保证较高效率,决定keyi时//尽量保证其为一个适中对值,将其放到left位置处//优化1.生成一个随机位置keyi/*if (left < right){int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);}*///优化2.三数取中int mid = GetMidPos(a, left, right);Swap(&a[left], &a[mid]);//优化方案使用一组有序的用例进行测试//有序用例的数据个数如果较大,会栈溢出//可在release版本下测试较多的数据int keyi = left;while (left < right){//left和right每次移动都要判断left<rightwhile (left < right && a[right] >= a[keyi]){right--;}while (left < right && a[left] <= a[keyi]){left++;}//交换同时不同各自向中移动,下次循环自会移动Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);return left;
}

 

6.1.2 挖坑法

  • 记录首个数据key
  • 右边先走找到小将其填入坑,同时其所处位置成为坑
  • 左边后走找到大将其填入坑,同时其所处位置成为坑
  • 左后L与R相遇在坑上,最后向此坑中填入key
  • 返回最后的相遇位置作为分割区间点
    在这里插入图片描述
//挖坑法
int PartSort2(int* a, int left, int right)
{//三数取中int mid = GetMidPos(a, left, right);Swap(&a[left], &a[mid]);//优化方案使用一组有序的用例进行测试//有序用例的数据个数如果较大,会栈溢出//可在release版本下测试较多的数据int key = a[left];int pit = left;while (left < right){while (left < right && a[right] >= key){right--;}a[pit] = a[right];pit = right;while (left < right && a[left] <= key){left++;}a[pit] = a[left];pit = left;}a[pit] = key;return pit;
}

 

6.1.3 前后指针法

  • cur向右找小,找到 prev++,cur与prev 交换值
  • cur 没找到,cur++
  • cur 加至越界,跳出循环,key值与此时的prev交换
  • 返回最后的prev 作为分割区间点
//前后指针法
int PartSort3(int* a, int left, int right)
{//三数取中int mid = GetMidPos(a, left, right);Swap(&a[left], &a[mid]);//优化方案使用一组有序的用例进行测试//有序用例的数据个数如果较大,会栈溢出//可在release版本下测试较多的数据int keyi = left;int prev = left, cur = left;while (cur <= right){if (a[cur] >= a[keyi]){cur++;}else{Swap(&a[++prev], &a[cur++]);}}Swap(&a[keyi], &a[prev]);return prev;
}

6.1.4 关于每个区间操作的结束位置总是小于key

  • 因为是右边先走,所以能保证相遇位置能小于左边的 key,即右边向左找小的停止位置一定比key小
  • 有几种R停止的情况
    • R 找到小,L 没找到大,直接遇到 R
    • R 没找到小,直接遇到 L,相遇点小于 key,或者直接走到key

6.1.5 关于有序原数据的效率优化

  • 如果原数据本身是有序的,快速排序的时间复杂度会达到 O(N^2)
  • 优化方案使用一组有序的用例进行测试
  • 有序用例的数据个数如果较大,会栈溢出
  • 可在release版本下测试较多的数据,(debug模式下数据个数为10000都会栈溢出)

Release模式下测试数据个数为100000的有序数组
在这里插入图片描述
 

两种优化方式

//优化1.生成一个随机位置keyiif (left < right){int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);}优化2.三数取中int mid = GetMidPos(a, left, right);Swap(&a[left], &a[mid]);//优化方案使用一组有序的用例进行测试//有序用例的数据个数如果较大,会栈溢出//可在release版本下测试较多的数据

同样条件下优化后测试
在这里插入图片描述

  • 三数取中取到的key一定是一个较为适中的值
  • 而随机选key有可能仍然是最值
  • 相对来说,三数取中法优化更彻底

 

6.2 非递归快排

  • 非递归的优化目的主要是因为递归快排如果处理较多数据,递归的层次太深,很容易导致栈溢出
  • 将递归改为循环,并且运用栈实现快排的非递归版

非递归快排用过栈实现
在这里插入图片描述

//快速排序非递归
void QuickSortNor(int* a, int left, int right)
{//创建栈ST st;StackInit(&st);st.a = (int*)malloc(sizeof(int) * (right - left + 1));if (st.a == NULL){printf("malloc fail\n");exit(-1);}//首先将数组头尾压入栈StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st)){//依次取栈顶作为一次排序的区间int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int keyi = PartSort3(a, begin, end);//将[begin,keyi-1] [keyi+1,end]区间的边界(若存在)入栈//先入右边界,后入左边界,以确保拿到栈顶先处理左边界if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (keyi - 1 > begin){StackPush(&st, keyi - 1);StackPush(&st, begin);}}
}

 
 

7. * 归并排序

  • 先分割,在由下至上逐层归并
  • 每次归并结果放入一个临时数组
  • 最后将临时数组的数据拷贝到原数组

7.1 递归版归并

在这里插入图片描述

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (left + right) / 2;//分治[left, mid] [mid+1, right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);int i = left;int j = mid+1;int k = 0;//某一半区间全部放入了tmp,跳出循环while (i <= mid && j <= right){if (a[i] < a[j]){tmp[k++] = a[i++];}else{tmp[k++] = a[j++];}}//某一半区间全部放入了tmp,将另一半剩余元素全部移入tmpwhile (j <= right){tmp[k++] = a[j++];}while (i <= mid){tmp[k++] = a[i++];}//将tmp中归并后的有序数组按原位置拷贝到原数组memcpy(a + left, tmp, sizeof(int) * (right - left + 1));}void MergeSort(int* a, int n)
{//开辟一块临时空间暂存归并后的有序数组int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}int left = 0;int right = n - 1;_MergeSort( a, left, right, tmp);
}

 

7.2 非递归版归并

  • 非递归的归并排序主要是要处理越界问题
  • 如果产生越界,根据情况将某一边界进行重置
    在这里插入图片描述
  • 两种实现方法,基本思想一致,只是拷贝的时机不一样,第二种方法可简化到两种情况
  • 一种是梭哈版:一个gap循环内的多组数归并完拷贝一次,
  • 一种非梭哈版:一个gap循环内的一组数归并完随即拷贝
void MergeSortNoR(int* a, int n)
{//梭哈版--一个gap循环内的多组数归并完拷贝一次int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-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;//处理几种越界问题,根据发生前后(调试发现)if (begin2 >= n){end2 = n - 1;}if (end2 >= n){end2 = n - 1;}if (end1 >= n){end1 = n - 1;}//[begin1, end1] [begin2, end2]while (begin1 <= end1 && begin2 <= end2){//这行代码决定排序是否稳定, <= 则稳定//若相等,前面的数先放,可确保相等数据相对位置不变)if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}}memcpy(a, tmp, sizeof(int) * n);gap *= 2;}//非梭哈版,一个gap循环内的一组数归并完随即拷贝int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail\n");exit(-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] ", begin1, end1, begin2, end2);//处理几种越界问题,根据发生前后(调试发现)if (end1 >= n || begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}/*if (begin2 >= n){end2 = n - 1;}if (end2 >= n){end2 = n - 1;}if (end1 >= n){end1 = n - 1;}*///[begin1, end1] [begin2, end2]while (begin1 <= end1 && begin2 <= end2){//这行代码决定排序是否稳定, <= 则稳定//若相等,前面的数先放,可确保相等数据相对位置不变)if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//拷贝memcpy(a + i, tmp + i, sizeof(int)* (end2 - i + 1));}printf("\n");gap *= 2;}
}

  

8. 非比较排序

计数排序

  • 统计每个数据出现的次数
  • 将次数相对映射至计数数组,(根据原数据范围实现相对映射)
  • 根据计数数组重写原数组
  • 适用于范围不大且数据集中的整型排序
  • 时间复杂度为 O(N+range),range 为原数据的分布范围
void CountSort(int* a, int n)
{//相对映射int min = a[0], max = a[0];for (int i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){printf("calloc fail\n");exit(-1);}//计数for (int i = 0; i < n; i++){count[a[i] - min]++;}//重写int i = 0;for (int j = 0; j < range; j++){while (count[j]--){a[i++] = j + min;}}free(count);count = NULL;
}

 

排序算法时间复杂度和稳定性

稳定性:排序后相等元素的相对位置是否改变
在这里插入图片描述


http://www.ppmy.cn/news/45170.html

相关文章

超详细Linus安装maven、配置maven环境

1、如果不确定之前是否安装maven&#xff0c;可以先检查是否已经安装maven mvn -v如果安装了maven&#xff0c;删除掉 yum remove maven2、创建maven的安装路径 mkdir /usr/local/maven在/usr/local/maven里创建maven仓库文件夹 mkdir maven-repo3、查看linus版本&#xf…

CSS基础——盒子模型的一些属性概念

目录 display visibility overflow 文档流 元素在文档流中的特点 块元素 内联元素 浮动 float 浮动元素特点 清除浮动 clear 小练习 效果图 具体实现 高度塌陷问题 BFC 特点 如何开启BFC 解决方案 本篇的最终练习 效果图如下&#xff1a; 具体实现 disp…

搭建Spark Standalone集群

文章目录 一&#xff0c;Spark Standalone架构&#xff08;一&#xff09;client提交方式&#xff08;二&#xff09;cluster提交方式 二&#xff0c;Spark集群拓扑三&#xff0c;前提条件&#xff1a;安装配置了分布式Hadoop环境四&#xff0c;在master虚拟机上安装配置Spark&…

多元统计分析-橄榄油数据集

目录 1.数据集介绍 2.相关任务 3.答案解析 第一问 第二问 第三问 第四问 4.完整答案 1.数据集介绍 橄榄油数据集&#xff0c;该数据由从一组传感器中获得的关于 16 种橄榄油的 5 个属性以及6个物理化学质量参数的11个变量组成&#xff0c;这16种油中的前5种产自希腊&am…

【Windows 内核】Windows如何实现高性能 AIO

文章目录 Windows 通过 I/O 完成端口&#xff08;IOCP&#xff09;实现GetQueuedCompletionStatus 原理 Windows 通过 I/O 完成端口&#xff08;IOCP&#xff09;实现 在 Windows 上&#xff0c;异步 I/O 的实现方式主要是通过 I/O 完成端口&#xff08;IOCP&#xff09;实现的…

元宇宙:虚拟仿真技术的全面提升

在当今数字化的世界中&#xff0c;我们经常听到虚拟现实、增强现实、混合现实等技术的名词&#xff0c;这些技术的应用越来越成熟。其中&#xff0c;虚拟仿真技术是一种通过计算机技术来模拟实际场景和对象的过程&#xff0c;它为我们提供了更多的可能性。而最近备受瞩目的元宇…

ChatGPT 70+款可以免费使用的AI工具,建议收藏

ChatGPT风靡全球&#xff0c;人人可用&#xff01; 小红书上有关ChatGPT的笔记已有10w篇&#xff0c;相关话题浏览量也达到了1.12亿次。其中讨论最为热烈的&#xff0c;要数“ChatGPT使用教程”。&#xff08;当然&#xff0c;类似的话题还包括&#xff0c;教你如何使用Midjour…

南方猛将加盟西方手机完全是臆测,他不会希望落得兔死狗烹的结局

早前南方某科技企业因为命名的问题闹得沸沸扬扬&#xff0c;于是一些业界人士就猜测该猛将会加盟西方手机&#xff0c;对于这种猜测可以嗤之以鼻&#xff0c;从西方手机以往的作风就可以看出来它向来缺乏容纳猛将的气量。一、没有猛将的西方手机迅速沉沦曾几何时&#xff0c;西…