当前位置: 首页 > news >正文

【C语言之排序】-------六大排序

                                                          作者主页:作者主页

                                                          数据结构专栏:数据结构

                                                         创作时间 :2024年5月18日

前言:

今天我们就给大家带来几种排序的讲解,包括冒泡排序,插入排序,希尔排序,选择排序,堆排序,快速排序等等,在讲解之前我先给大家一个网站,用于查看各种排序的动图,这样有助于我们更加清晰的去了解各种排序:排序动图

冒泡排序:

首先我们来讲解一下冒泡排序,这是我们学习编程第一个接触到的排序,也是较为容易理解的一个排序,下面我们来看一下她是如何实现的。

这就是一个大概的冒泡排序的实现过程,其实就是每次都把最大的一个放到最后面,然后下一轮就不需要去管后面最大的那几个。

下面我们来看一下代码应如何实现:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;void print(int* arr, int n) 
{for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}
}int main() 
{int arr[] = { 23,235,13,123523,2342,2342,1,41523,3456,6547,546 };int n = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < n - 1; i++){for (int j = 0; j < n - 1 - i; j++) {if (arr[j] > arr[j + 1]) {  swap(arr[j], arr[j + 1]);}}}print(arr, n);return 0;
}

这就是冒泡排序实现的过程,这里我们就不过多的去说了,还是比较简单的。

时间复杂度:最坏情况:O(N^2)
      最好情况:O(N)
空间复杂度:O(1)

插入排序:

下面我们来看一下插入排序,插入排序的步骤大概是这样的。

1.从第一个元素开始,该元素可以认为已经被排序
2.取下一个元素tem,从已排序的元素序列从后往前扫描
3.如果该元素大于tem,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.tem插入到该元素的后面,如果已排序所有元素都大于tem,则将tem插入到下标为0的位置
6.重复步骤2~5

图像演示:

这个图像还是比较清晰易懂的,下面我们来看一下代码吧。

void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int temp = arr[end + 1];while (end >= 0){if (temp < arr[end]){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = temp;}
}

插入排序其实就是我们从第一个开始,然后我们用tmp记录要被插入的元素,然后一直和前面的作比较,如果大于前面的,那么就结束,如果小于前面的,我们就令end+1的位置等于end,然后end--就可以了,最后再将temp放到不符合这个条件的位置,就完成了一轮插入排序,后面也是这样循环进行的。

时间复杂度:最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
      最好情况下为O(N),此时待排序列为升序,或者说接近升序。
空间复杂度:O(1)

希尔排序:

下面我们来讲一下希尔排序,这个排序其实是比较难懂的,但他的时间复杂度也是较为小的,下面我们看一下如何实现这个排序:

大致步骤:

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

代码:

void ShellSort(int* arr, int n)
{int gap = n;while (gap > 1){gap /= 2;for (int i = 0; i < n - gap; i++){int end = i;int temp = arr[end + gap];while (end >= 0){if (temp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = temp;}}
}

时间复杂度平均:O(N^1.3)
空间复杂度:O(1)

选择排序:

思路:

每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。
实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

下面我们来看一下动图:

这里我们图片仅供参考,因为我们要用两个下标,一个最大值,一个最小值,以此来提高效率。

void SelectSort(int* arr, int n)
{int begin = 0, end = n - 1;while (begin < end){int max = begin, min = begin;for (int i = begin; i <=end; i++){if (arr[i] < arr[min]){min = i;}if (arr[i] > arr[max]){max = i;}}swap(arr[begin], arr[min]);//为防止最大值在begin位置被换走,我们还要加个判断if (begin == max){max = min;}swap(arr[max], arr[end]);++begin;--end;}
}

 时间复杂度:最坏情况:O(N^2)
      最好情况:O(N^2)
空间复杂度:O(1)

堆排序:

想要更好的了解堆排可以查看这篇博文:堆

在学习堆排之前,首先我们要知道社么是堆,堆其实就是一个二叉树,然后有大根堆和小根堆,然后他们分别长这样:

然后我们这里排序的思路就是,比如说大根堆,他的最大值一定在堆顶,然后我们就让他和最后一个数交换,然后不再去管他,再得到一个新的大根堆,继续进行这个操作,这样是不是就实现了呢。

然后这里我们首先还要建堆,然后我们升序的话就建大堆,直接把最大的放在最后一个,然后就不把他看作堆里的数据了。反之,降序就建小堆。下面我们先来看一下如何建堆。

//向上调整(小根堆)
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){//if (a[child] < a[parent])//小于就换就相当于建小堆if (a[child] > a[parent])//大于就换就会变成大堆{std::swap(a[child], a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//建堆
void HPPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc failed!!!");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size++] = x;//向上调整AdjustUp(php->a, php->size - 1);
}

这就是c语言种建堆的一个过程,还是比较麻烦的。

然后有了堆之后,排序就比较简单了。

void HeapSort(int* a, int n)
{//首先建堆//升序:建大堆//降序:建小堆/*for (int i=0;i<n;i++){AdjustUp(a, i);}*///            求最后一个节点的父亲for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;///这里>0即可,因为=0时只剩下最后一个,就不再需要继续进行了while (end>0)//思路就是:比如我们升序排序,那么我们就利用大根堆,每次都将最大的那个数放在最顶上,然后将它和最后一个交换,然后让整体的大小--,那么最后一个就不再会受影响{std::swap(a[0], a[end]);AdjustDown(a, end, 0);--end;}}

堆排最好的最坏的时间复杂度均为O(n*logn)

空间复杂度为O(1)

快速排序:

快排还是这几个排序种比较麻烦的有一个,但其实也是比较好用的一个

我们这里将会用三种方法来实现这个快速排序:

hoare版本(左右指针法):

思路:
1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

//快速排序   hoare版本(左右指针法)
void QuickSort(int* arr, int begin, int end)
{//只有一个数或区间不存在if (begin >= end)return;int left = begin;int right = end;//选左边为keyint keyi = begin;while (begin < end){//右边选小   等号防止和key值相等    防止顺序begin和end越界while (arr[end] >= arr[keyi] && begin < end){--end;}//左边选大while (arr[begin] <= arr[keyi] && begin < end){++begin;}//小的换到右边,大的换到左边swap(&arr[begin], &arr[end]);}swap(&arr[keyi], &arr[end]);keyi = end;//[left,keyi-1]keyi[keyi+1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr,keyi + 1,right);
}

挖坑法:

递归:

挖坑法思路与hoare版本(左右指针法)思路类似
1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑
2、还是定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走)

//快速排序法  挖坑法
void QuickSort1(int* arr, int begin, int end)
{if (begin >= end)return;int left = begin,right = end;int key = arr[begin];while (begin < end){//找小while (arr[end] >= key && begin < end){--end;}//小的放到左边的坑里arr[begin] = arr[end];//找大while (arr[begin] <= key && begin < end){++begin;}//大的放到右边的坑里arr[end] = arr[begin];}arr[begin] = key;int keyi = begin;//[left,keyi-1]keyi[keyi+1,right]QuickSort1(arr, left, keyi - 1);QuickSort1(arr, keyi + 1, right);
}
非递归:
//单趟排
int PartSort(int* arr, int begin, int end)
{int key = arr[begin];while (begin < end){while (key <= arr[end] && begin < end){--end;}arr[begin] = arr[end];while (key >= arr[begin] && begin < end){++begin;}arr[end] = arr[begin];}arr[begin] = key;int meeti = begin;return meeti;
}void QuickSortNoR(int* arr, 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 mid = PartSort(arr, left, right);//当左区间>=mid-1则证明左区间已经排好序了if (left < mid - 1){st.push(mid - 1);st.push(left);}//当mid+1>=右区间则证明右区间已经排好序if (right > mid + 1){st.push(right);st.push(mid + 1);}}
}

前后指针法:

思路:
1、选出一个key,一般是最左边或是最右边的。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。

经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。

然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作

//快速排序法  前后指针版本
void QuickSort2(int* arr, int begin, int end)
{if (begin >= end)return;int cur = begin, prev = begin - 1;int keyi = end;while (cur != keyi){if (arr[cur] < arr[keyi] && ++prev != cur){swap(&arr[cur], &arr[prev]);}++cur;}swap(&arr[++prev],&arr[keyi]);keyi = prev;//[begin,keyi -1]keyi[keyi+1,end]QuickSort2(arr, begin, keyi - 1);QuickSort2(arr, keyi + 1, end);}

相关文章:

  • LabVIEW软件开发人员如何在软件开发中捕捉需求?
  • 前端 Web 与原生应用端 WebView 通信交互 - HarmonyOS Next
  • “Kubectl 如何工作案例:编写自定义 Kubectl 命令
  • 【机器学习】基于OpenCV和TensorFlow的MobileNetV2模型的物种识别与个体相似度分析
  • Spring Boot项目中,如何在yml配置文件中读取maven pom.xml文件中的properties标签下的属性值
  • 27、matlab傅里叶变换:fft()函数
  • Git配置SSH-Key
  • win+mac通用的SpringBoot+H2数据库集成过程。
  • SwiftUI中ContentUnavailableView的使用(iOS 17、tvOS 17推出的新组件)
  • Verilog实现的数字钟
  • 测试记录3:WLS2运行Linux界面
  • 【golang学习之旅】Go中的cron定时任务
  • 扩展 Kafka 集群从三台节点到四台节点的过程
  • C++结合OpenCV进行图像处理与分类
  • 植物大战僵尸杂交版2.0.88最新版安装包
  • JAVA_NIO系列——Channel和Buffer详解
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaScript新鲜事·第5期
  • Linux快速复制或删除大量小文件
  • Logstash 参考指南(目录)
  • Mac转Windows的拯救指南
  • Octave 入门
  • Spring Boot快速入门(一):Hello Spring Boot
  • SpringBoot几种定时任务的实现方式
  • vuex 学习笔记 01
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 我有几个粽子,和一个故事
  • 积累各种好的链接
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • ###C语言程序设计-----C语言学习(3)#
  • #、%和$符号在OGNL表达式中经常出现
  • #{} 和 ${}区别
  • #1015 : KMP算法
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (175)FPGA门控时钟技术
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (接口自动化)Python3操作MySQL数据库
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (十三)Maven插件解析运行机制
  • (转)人的集合论——移山之道
  • (轉)JSON.stringify 语法实例讲解
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET 中的轻量级线程安全
  • .NET 中让 Task 支持带超时的异步等待
  • .net6+aspose.words导出word并转pdf
  • [ C++ ] STL---stack与queue
  • []sim300 GPRS数据收发程序
  • [Angular] 笔记 20:NgContent
  • [BSGS算法]纯水斐波那契数列
  • [BZOJ4010]菜肴制作