【数据结构】——常见排序

文章目录

  • 一、 冒泡排序
  • 二、 选择排序
  • 三、插入排序
  • 四、 快速排序
    • 1. hoare版本
    • 2. 优化版本
    • 3. 前后指针法
    • 4. 非递归版本
  • 五、 堆排序
  • 六、 希尔排序
  • 七、 归并排序
    • 1. 递归版本
    • 2. 非递归版本
  • 八、 计数排序

在开始之前先准备一个交换数据的函数,排序会经常用到

//交换位置
Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}

一、 冒泡排序

在这里插入图片描述
左右相邻进行比较,最大的或者最小的交换到两边

void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){// 单趟int flag = 0;for (int i = 1; i < n - j; i++){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);flag = 1;}}if (flag == 0)//判断是否全部排序完成{break;}}
}

二、 选择排序

在这里插入图片描述
如图所示,找到最小值,放在最前面,前面的N个数据默认为有序,就从N后继续开始排序
不过在下面的代码中我进行了改进,遍历一次找到最大和最小值放在两侧,提高效率

void SelectSort(int* a, int n)
{int begin = 0;int end = n - 1;while (begin < end){int max = begin;int min = begin;for (int i = begin + 1; i <= end; i++){if (max < a[i]){max = i;}if (min > a[i]){min = i;}}Swap(&a[begin], &a[min]);if (max == begin){max = min;}Swap(&a[end], &a[max]);begin++;end--;}
}

为什么要加

if (max == begin){max = min;}

在这里插入图片描述
就是怕遇到如上图这种情况

三、插入排序

在这里插入图片描述
数据从后向前进行对比,如果找到大于自己的就继续往前对比,找到小于的就插入进去

// 插入排序
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end+1] = tmp;}}

四、 快速排序

1. hoare版本

在这里插入图片描述

int PartSort1(int* a, int left, int right)
{//单趟if(left >= right)return;int keyi = left;int begin = left, end = right;while (begin < end){// 右边找小while (begin < end && a[end] >= a[keyi])//begin < end防止越界{--end;}// 左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);//交换l与r相遇位置和key的位置keyi = begin;//方便分割区间PartSort1(a, left, keyi - 1);//左区间PartSort1(a, keyi + 1, right);//右区间return begin;
}

在这里插入图片描述

2. 优化版本

上面hoare的版本在数据有序的情况下,会尴尬很多,我们的key每次都是最小值或者最大值,递归的深度就会变成N。
所以为了避免这种情况发生,我们key需要是一个接近中位数的值。

两种方法

  1. 随机数选key
  2. 三数取中

那我这里就以三数取中为例:
left、right、mid,三个数进行对比,取大小在中间的那个值。

int GetMid(int* a,int left, int right)//三数取中
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[mid] > a[right])//left最大,right最小{return mid;}else if (a[left] < a[right]){return left;}elsereturn right;}else//a[left] < a[mid]{if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]){return left;}elsereturn right;}}void QuickSort(int* a, int left, int right)
{if (left >= right)return;//小区间优化if ((right - left + 1) <= 10){InsertSort(a+left, right - left + 1);}else{int mid = GetMid(a, left, right);//三数取中Swap(&a[left], &a[mid]);int key = left;int begin = left;int end = right;while (begin < end){//走边找小if (begin < end && a[end] >= a[key]){end--;}//左边找大if (begin < end && a[begin] <= a[key]){begin++;}Swap(&a[end], &a[begin]);}Swap(&a[key], &a[begin]);//交换l与r相遇位置和key的位置key = begin;//方便分区间QuickSort(a, left, key - 1);//左区间QuickSort(a, key + 1, right);//右区间}}

这里还没有结束,可以再优化一下,在我们进行快排的时候,如果每次的区间都能二分的化,这个递归的过程是可以看出一颗二叉树,而在满二叉树的叶子节点个数为整棵树的一半,对于快排来说它的最后一层和倒数第二层,是不用去排和只排一个数的,但是却还要开辟栈帧,所以我们这里使用小区间优化带代码如下直接使用当区间小于等于10时进行插入排序。

void QuickSort(int* a, int left, int right)
{if (left >= right)return;//小区间优化if ((right - left + 1) <= 10){InsertSort(a+left, right - left + 1);//每次递归会有偏移,所以要加上偏移量left}else{//递归……}
}

3. 前后指针法

在这里插入图片描述
通过动图我们可以判断出,当cur找到小于key的值,prev++,当++prev != cur,就会进行交换,当cur越界时,再把key和prev进行交换

//快速排序前后指针法
int PartSort1(int* a, int left, int right)
{int mid = GetMid(a, left, right);//三数取中Swap(&a[left], &a[mid]);int key = left;int prve = left;int cur = prve + 1;while (cur <= right){if (a[cur] < a[key] && ++prve != cur)Swap(&a[prve], &a[cur]);cur++;}Swap(&a[key], &a[prve]);return prve;
}

4. 非递归版本

快速排序其实跟二叉树的前序遍历非常相似,都是深度优先遍历。
快速排序非递归可以用栈实现,因为栈是后进先出所以要先把右区间的数压进栈
在这里插入图片描述

//快排非递归方法,用栈实现
#include "Stack.h"
void QuickSortNonR(int* a, int left, int right)
{Stack st;StackInit(&st);StackPush(&st, right);StackPush(&st, left);while (!StackEmpty(&st));{int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int key = PartSort1(a, begin, end);//使用上面的前后指针法辅助单趟排序//[begin,key - 1] key [key + 1, end]if (key + 1 < end){StackPush(&st, end);StackPush(&st, key + 1);}if (key - 1 > begin){StackPush(&st,key - 1);StackPush(&st,begin);}}StackDestroy(&st);
}

五、 堆排序

我们建堆的时候,如果想要实现降序,那么我们应该建小堆,而不是大堆,因为如果我们建了大堆,只是堆成了降序,并不是原来的数组成了降序,升序相反建大堆。
在这里插入图片描述


void HeapSort(int* a, int max)//max为最大个数
{//建堆for (int i = 0; i <max; i++){AdjustUp(a, i);//模拟插入建小堆}//排序int end = max - 1;//找到最后一个元素的下标进行交换while (end>0){Swap(&a[0], &a[end]);AdjustDown(a, 0, end);--end;//最后的有序不再排序}
}

这里还可以再优化,降低时间复杂度

/*建堆方法一for (int i = 1; i < n; i++){AdjustUp(a, i);}*///建堆方法二for (int i = (n - 1 - 1) / 2; i >= 0; i--)//从第一个非叶子节点调整{AdjustDown(a, n, i);} int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

六、 希尔排序

第一步:预排序
第二步:插入排序

在这里插入图片描述

1.希尔排序是对直接插入排序的优化。
2.当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
以gap==3为例

// 希尔排序
void ShellSort(int* a, int n)
{/*int gap = 3;*///第一种方法:一组一组gap走/*for (int j = 0; j < gap; j++){for (int i = j; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}*///第二种方法:一次性走完/*for (int i = 0; i < n - gap; ++i){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}*/

当gap等于1时,和插入排序一摸一样
在这里插入图片描述
为什么i<n-gap
在这里插入图片描述
如图所示,为了防止tmp越界

这里我们可以总结出,在预排序阶段
gap越大,大的可以越快跳到后面,小的可以越快跳到前面,但是越不接近有序
gap越小,跳的就越慢,但是更接近有序。

所以,还可以再优化一下

	//第三种方法:优化一下gap = gap / 3 + 1;int gap = n;while (gap > 1){for (int i = 0; i < n - gap; ++i){gap = gap / 3 + 1;int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

在这个方法中,当gap > 1时是预排序,gap == 1时是插入排序

七、 归并排序

归并排序的思想就是分治法(分而治之)

在这里插入图片描述

1. 递归版本

在这里插入图片描述
如果在原数组上进行比较交换,会导致数据被覆盖,所以我们开辟一个tmp数组,来存放交换结果,再返回给原数组
下面的代码就像是二叉树的后序遍历

void _MergeSort(int* a, int* tmp, int begin, int end)
{if (begin >= end)//返回条件,区间只剩一个值return;int mid = (begin + end) / 2;//左右区间必须这么分,不然会出问题_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}	while (begin2 <= end2){tmp[i++] = a[begin2++];}//需要把偏移量begin加上,每归并一次就拷贝一次回去memcpy(a+begin, tmp+begin, (end - begin + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail!");}_MergeSort(a, tmp, 0, n - 1);free(tmp);tmp = NULL;
}

2. 非递归版本

如果也要使用栈实现的话,就需要两个栈,一个模仿递推的过程,一个模仿回归,就太麻烦了,其实循环就可以实现
在这里插入图片描述
那如果一组4个数据和一组3个数据可以进行归并嘛?也是可以的,因为归并排序并没有要求每组的数据都要相同,只要是有序就可以一起归并。

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail!");}int gap = 1;//gap为每组归并的数据个数while (gap < n){for (int i = 0; i < n; i += 2 * gap)//i代表每组归并的起始位置{int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);int j = i;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, (end2 - i + 1) * sizeof(int));}printf("\n");gap *= 2;}free(tmp);tmp = NULL;
}

我打印了一下数组区间,发现越界了
在这里插入图片描述
通过上图可以看出,越界可以分为两种情况

  1. begin2越界了,后面就没有进行的必要的
  2. end2越界了,调整end2的位置即可

if (begin2 > len - 1)//begin2已经越界,说明只有一组了没必要再排
{break;
}
if (end2 > len - 1)//end2越界了进行修正即可
{end2 = len - 1;
}
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}// gap每组归并数据的数据个数int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap)//i代表每组归并的起始位置{// [begin1, end1][begin2, end2]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 (begin2 >= n)break;// 第二的组begin2没越界,end2越界了,需要修正一下,继续归并if (end2 >= n)end2 = n - 1;int j = i;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;}free(tmp);tmp = NULL;
}

八、 计数排序

在这里插入图片描述

第一步:统计每个值出现的次数
第二步:排序

在这里插入图片描述
那如果我的数据是{100, 104, 109, 109, 102},那count数组是不是就要开辟109个空间呢,那就太浪费空间了,所以我们要相对映射,按范围开空间,那么就用最大的值减最小的值。当我们要返回时,下标加最小的值就可以恢复了。

void CountSort(int* a, int n)
{int min = a[0];int max = a[0];//从第一个值开始对比找极大极小值for (int i = 1; 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));//calloc可以将数组里的每一个值初始化为0//计数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.mzph.cn/web/27951.shtml

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

相关文章

银河麒麟系统安装

新建虚拟机 选择典型下一步&#xff1a; 稍后安装操作系统 下一步 &#xff1a; 选择Linux Ubuntu 64位 下一步&#xff1a; 设置名称 选择一个虚拟机的位置进行下一步&#xff1a; 磁盘大小默认给20G 下一步&#xff1a; 点击完成 编辑虚拟机设置 点击CD/DVD(SATA) 使用IS…

Mysql数据库的锁机制

MySQL 中的锁可以按照粒度分为锁定整个表的表级锁&#xff08;table-level locking&#xff09;和锁定数据行的行级锁&#xff08;row-level locking&#xff09;表级锁开销小&#xff0c;加锁快&#xff0c;但是支持的并发度低&#xff0c;行级锁相反。 行锁 Innodb实现了以…

C#医院体检系统源码 PEIS源码 系统核心功能、特点、类型、设备对接-PACS放射科设备对接:DR、CT、MRI、钼靶。

C#医院体检系统源码 PEIS源码 系统核心功能、特点、类型、设备对接-PACS放射科设备对接:DR、CT、MRI、钼靶。 体检系统是为体检中心、医院体检科等体检机构专门开发的全流程管理系统。该系统通过软件实现检测仪器数据的自动提取&#xff0c;内置多级医生工作台&#xff0c;旨在…

每日一练:攻防世界:ewm

这道题我尝试了使用montagegaps解题&#xff0c;但是没有解出来&#xff0c;图片数量不是很多&#xff0c;可以尝试用PS直接拼图&#xff0c;但是这样学不到东西&#xff0c;我也就没尝试&#xff0c;直接看的官方WP 这段代码应该是改变工作目录到small&#xff0c;并且变量当…

基于springboot的宠物商城网站

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的宠物商城网站,java项目…

Linux C编译器从零开发二

自定义分词器 test.c #include <ctype.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h>typedef enum {TK_PUNCT, // PunctuatorsTK_NUM, // Numeric literalsTK_EOF, // …

基于carsim的线控转向仿真(1)--carsim车辆模型目标角度跟踪

一、Rwa转向执行总成建模 Rwa包括齿轮齿条机构、转向组件以及转向执行电机&#xff1b;如下图&#xff0c;电机输出轴通过齿轮减速增扭后&#xff0c;再经过一个半径为rp的小齿轮&#xff0c;直接带动齿条左右移动。齿条的移动通过转向摇臂&#xff0c;带动车轮转动&#xff0c…

PySide(PyQt)实现鼠标画框局部放大

按住鼠标左键画框&#xff0c;裁切画面并局部放大&#xff0c;可以用来生成ROI 1、在QtDesigner中创建ui文件&#xff0c;命名为crop.ui&#xff1a; 2、自定义脚本ImageLabel.py &#xff1a; from PySide6.QtCore import Qt, QRect, Signal, QPoint from PySide6.QtGui impo…

Redis高并发高可用

1. 复制机制 在分布式系统中&#xff0c;为了解决单点问题&#xff0c;通常会将数据复制多个副本部署到其他机器&#xff0c;以满足故障恢复和负载均衡等需求。Redis提供了复制功能&#xff0c;实现了相同数据的多个Redis副本。复制功能是高可用Redis的基础&#xff0c;后面的…

长亭网络通信基础

长亭笔试之前就已经学过一遍了 这算温故而知新吧 TCP/IP 首先我在这里默写一下之前的7层和4层 应用层 应 【表示层 数据格式转换 传 【会话层 …

课设--学生成绩管理系统(一)

欢迎来到 Papicatch的博客 文章目录 &#x1f349;技术核心 &#x1f349;引言 &#x1f348;标识 &#x1f348;背景 &#x1f348;项目概述 &#x1f348; 文档概述 &#x1f349;可行性分析的前提 &#x1f348;项目的要求 &#x1f348;项目的目标 &#x1f348;…

Android 蓝牙配对Settings应用里面的简要流程记录

Android 蓝牙配对Settings应用里面的简要流程记录 文章目录 Android 蓝牙配对Settings应用里面的简要流程记录一、前言二、Settings蓝牙配对的关键代码1、接收蓝牙请求的地方 AndroidManifest.xml2、BluetoothPairingRequest3、BluetoothPairingService4、BluetoothPairingDial…

宿舍用电管理模块一进三出的升级改造

宿舍用电管理模块一进三出石家庄光大远通电气有限公司产品在高校日常管理工作中,宿舍管理是一项重要工作。宿舍管理内容复杂,而且涉及学生的日常生活,意义重大。其中,学生宿舍内漏电,超负荷用电,违规用电等现象一直是困扰后勤管理的普遍问题。随着学生日常生活方式以及生活用品…

驱动开发(五):Linux内核定时器

驱动开发系列文章&#xff1a; 驱动开发&#xff08;一&#xff09;&#xff1a;驱动代码的基本框架 驱动开发&#xff08;二&#xff09;&#xff1a;创建字符设备驱动 驱动开发&#xff08;三&#xff09;&#xff1a;内核层控制硬件层 驱动开发&#xff08;四&#xff…

移动端超超超详细知识点总结(Part4)

rem基础 1. rem单位 rem (root em)是一个相对单位&#xff0c;类似于em&#xff0c;em是父元素字体大小。不同的是rem的基准是相对于html元素的字体大小。比如&#xff0c;根元素&#xff08;html&#xff09;设置font-size12px; 非根元素设置width:2rem;则换成px表示就是24p…

数据防泄漏的六个步骤|数据防泄漏软件有哪些

在当前复杂多变的网络安全环境下&#xff0c;数据防泄漏软件成为了企业信息安全架构中不可或缺的一环。下面以安企神软件为例&#xff0c;告诉你怎么防止数据泄露&#xff0c;以及好用的防泄露软件。 1. 安企神软件 安企神软件是当前市场上备受推崇的企业级数据防泄漏解决方案…

【ARM-Linux篇】智能家居语音模块配置

1. pin脚配置&#xff1a; 2. 命令词自定义基本信息&#xff1a; 3. 命令词自定控制详情: • 测试&#xff1a;串口模块可先通过串口助手验证每个指令的准确性&#xff0c; 然后运行wiringOP中的serialTest程序(需把/dev/ttyS2改成/dev/ttyS5) 然后语音接收到指令后(比如喊你好…

10大wordpress外贸主题

手动工具wordpress外贸模板 适合生产套筒扳、管钳、工具箱、斧子、锤子、防爆工具、螺丝刀、扳手等手动工具的厂家。 https://www.jianzhanpress.com/?p4806 Invisible Trade WP外贸网站模板 WordPress Invisible Trade外贸网站模板&#xff0c;做进出口贸易公司官网的word…

开源高效API管理工具:RAP

RAP&#xff1a;简化API开发&#xff0c;提升团队协作效率- 精选真开源&#xff0c;释放新价值。 概览 RAP&#xff08;RESTful API Project&#xff09;是一个开源的API管理工具&#xff0c;由阿里巴巴团队开发并维护。它旨在帮助前后端开发人员通过一个统一的平台来设计、开…

软件方案评审与模块优化:从FOC模块出发的电控平台建设

一、背景 洞悉模块发展趋势&#xff0c;定制行业应用特点&#xff0c;明确优化方向与阶段性目标 随着科技进步的飞速发展&#xff0c;模块化设计已成为众多行业产品开发的核心理念。无论是软件系统、硬件组件&#xff0c;还是复杂系统中的功能模块&#xff0c;都需要对其发展…