【数据结构与算法】常见的排序算法

文章目录

  • 排序的概念
  • 冒泡排序(Bubble Sort)
  • 插入排序(Insert Sort)
  • 选择排序(Select Sort)
  • 希尔排序(Shell Sort)
    • 写法一
    • 写法二
  • 快速排序(Quick Sort)
    • hoare版本(左右指针法)
    • 挖坑法
      • 递归法
      • 非递归法
    • 前后指针法
  • 堆排序
  • 归并排序


排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

冒泡排序(Bubble Sort)

优点:简单,易实现,稳定且不需要额外空间
缺点:效率低
升序思路:
两两比较,相邻的两个元素比较,第一个元素比第二个大就交换。此时最大的元素在最右边,再循环这个动作(最后一个元素不进入循环),直到循环截止,该数组为有序。

动图演示如下:
在这里插入图片描述
核心代码如下:

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++){for (int i = 0; i < n - j-1; i++){if (a[i + 1] < a[i]){Swap(&a[i + 1], &a[i]);}}}
}

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:最坏情况是O(N^2), 最好情况是O(N)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

插入排序(Insert Sort)

优点:效率略高于冒泡和选择排序,稳定
缺点:效率不高

升序思路:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
步骤:
1.从第一个元素开始(默认第一个元素为有序)
2.取下一个元素,从已有的有序元素中从左往右扫描
3.如果该元素大于新插进来的元素,则将该元素移动到下一个位置
4.重复步骤3,直到有序元素中找到小于或等于新元素的元素
5.将新元素插入到该元素后面
6.若有序元素全都大于新元素,则将新元素插入到第一位,即下标为0的位置

动图演示如下:
在这里插入图片描述
核心代码:

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;}}//把tmp元素插入到end后面一个(即插入到小的数后面一个)a[end + 1] = tmp;}
}

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

选择排序(Select Sort)

优点:表现最稳定,时间复杂度永远O(N^2),不需要额外空间
缺点:稳定上讲,不稳定,效率低

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

动图演示如下:
在这里插入图片描述
但我觉得这样稍微有一丢丢慢,可以优化一下,我们可以一次选两个值,一个最小值,一个最大值,分别将其放到序列开头和末尾处。

步骤:
1.首先在序列(未排序)中找到最小的值,放到序列起始位置
2.再找序列中最大的值,放到序列末尾处
3.重复以上动作,直到所有元素有序(即begin和end相遇停止)

核心代码:

void SelectSort(int* a, int n)
{//记录第一个和最后一个下标int begin = 0;int end = n - 1;while (begin < end){//保存最大值,最小值下标int mini = begin;int maxi = begin;//找出最大值和最小值的下标for (int i = begin; i <= end; i++){//找到最小值,将mini最小值下标更新为i位置if (a[i] < a[mini]){mini = i;}//找到最大值,将maxi最大值下标更新为i位置if (a[i] > a[maxi]){maxi = i;}}//此时最小值为序列开头处Swap(&a[mini], &a[begin]);//考虑到万一最大值在begin位置被mini换走,所以增加判断if (begin == maxi){maxi = mini;}//此时最大值在序列末尾处Swap(&a[maxi], &a[end]);//begin往前后,不断更新++begin;//end往前走,不断更新--end;}
}

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:最好情况和最坏情况都是O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

希尔排序(Shell Sort)

优点:效率高于三种简单排序,不需要额外空间
缺点:不稳定

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

步骤:
1.选择一个增量序列,通常为初始增量为数组长度的一半,然后逐步减小增量直至为1。
2.根据选定的增量对数组进行分组,每个分组包含间隔为增量的元素。
3.对每个分组进行插入排序,即将每个元素插入到正确的位置上,使得每个分组内的元素有序。
4.逐步减小增量,重复上述步骤,直到增量为1时完成最后一次插入排序。
5.最终得到一个有序数组。

动图演示如下:
在这里插入图片描述

写法一

思路图:
gap > 1 时是预排序,目的是让它接近有序
gap == 1 时是直接插入排序,目的是为了让它有序

在这里插入图片描述
核心代码:

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){//循环躺数gap = gap / 3 + 1;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;}}}}
}

写法二

多组并排
核心代码:

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;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;}}
}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定

时间复杂度:
在这里插入图片描述
空间复杂度:O(1)

快速排序(Quick Sort)

hoare版本(左右指针法)

步骤:
1.选出一个key,一般选最左边或者最右边的。(男左女右,这里我就选最左边的了)。
2.定义一个begin和一个end,begin从左往右走,end从右往左走。
注:若你选的key在左边,那右边的end就要先走,反之右边,则左边的begin需要先
3.假设end先走,往左走,在行走过程中,若end遇到小于key的值就停下来,这时候begin走,往右走,当begin遇到大于key的值也停下来,然后将begin和end内容交换,然后再end走…反复这样走直到begin和end相遇,将他们相遇的下标内容与key进行交换。
4.这时候key的左边都是小于key的,而key的右边都是大于key的。
5.以key为划分点,将它的左序列和右序列分别进行上面这种排序,直到左右序列都只剩一个数值,或者不存在数值,就停止。
在这里插入图片描述
核心代码:

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int left = begin;int right = end;int keyi = begin;while (left < right){//选的key为左边,所以右边先走,右边找比key小的,&&前面的判断是为了防止left和right越界,或者说互相错过while (left < right && a[right] >= a[keyi]){--right;}//右边走完左边走,左边找大,找比key大的值,和上面的循环一样while (left < right && a[left] <= a[keyi]){++left;}//交换,目的是要把比key大的值都放key的左边,比key小的值都放key的右边Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;//区间//[begin,keyi-1] keyi [keyi+1,end]//递归QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

挖坑法

递归法

步骤:
1.选出一个数据,一般选最左边或者最右边的,思想与hoare版本类似,选出的数存在变量key中,此时,原来的位置就会形成一个空位坑位,所以叫做挖坑法。
2. 依旧是定义两个变量,一个往左走,一个往右走,和上面方法一样,若你的坑位在左边,右边就先走,否则左边先走。
…思路与hoare一样的
在这里插入图片描述

int PartSort2(int* a, int begin, int end)
{int keyi = a[begin];int hole = begin;while (begin < end){//右边找小,填到左边的坑while (begin < end && a[end] >= keyi){--end;}a[hole] = a[end];hole = end;//左边找到,填到右边的坑while (begin < end && a[begin] <= keyi){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = keyi;return keyi;
}
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort2(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

非递归法

核心代码:

void QuickSortNonR(int* a, int begin, int end)
{ST s;STInit(&s);STPush(&s, end);STPush(&s, begin);while (!STEmpty(&s)){int left = STTop(&s);STPop(&s);int right = STTop(&s);STPop(&s);int keyi = PartSort3(a, left, right);// [left, keyi-1] keyi [keyi+1, right]if (left < keyi - 1){STPush(&s, keyi - 1);STPush(&s, left);}if (keyi + 1 < right){STPush(&s, right);STPush(&s, keyi + 1);}}STDestroy(&s);
}
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort2(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

前后指针法

步骤:
还是一样的
1.选出一个key,选最左边的或者最右边的
2.首先定义prev指针在序列起始位置,然后定义指针cur在prev的下一个,即prev+1
3.cur遇到比key大的值,++cur
4.cur遇到比key小的值,++prev,交换prev和cur位置的值,再++cur
在这里插入图片描述
核心代码:

int PartSort3(int* a, int begin, int end)
{int prev = begin;int cur = prev + 1;int keyi = begin;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;return prev;
}
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

堆排序

这个排序我们之前讲过的,这里我就不再复习啦~
需要的老铁请点这里----->堆、堆排序

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

归并排序

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

步骤:
1.分解:将待排序的序列分解成若干个子序列,每个子序列包含1个元素。
2.合并:将相邻的子序列两两合并,形成新的有序子序列,直到无法再合并。
3.重复合并步骤直到得到最终的排序结果。

在这里插入图片描述
动态演示如下:

在这里插入图片描述
核心代码:

void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid + 1, end, tmp);//[begin,mid] [mid+1,end]归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, 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++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin+1));
}
//归并排序
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){printf("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

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

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

相关文章

前端Vue uView 组件<u-search> 自定义右侧搜索按钮样式

前言 uView 文档的效果不是ui设计的样式 需要重新编辑 原效果 ui设计效果 解决方案 设置里说明的需要传一个样式对象 这个对象 需要写在 script 标签里面 这里需要遵循驼峰命名 比如font-size 改为 fontSize lineHeight和textAlign为水平锤子居中效果 searchStyle: {ba…

Box86源码解读记录

1. 背景说明 Github地址&#xff1a;https://github.com/ptitSeb/box86 官方推荐的视频教程&#xff1a;Box86/Box64视频教程网盘 2. 程序执行主体图 Box86版本: Box86 with Dynarec v0.3.4 主函数会执行一大堆的初始化工作&#xff0c;包括但不限于&#xff1a;BOX上下文 …

【ARMv8/v9 系统寄存器 4 -- ARMv8 通用寄存器详细介绍】

文章目录 ARMv8 通用寄存器通用寄存器X30 寄存器和链接寄存器&#xff08;LR&#xff09;程序计数器&#xff08;PC&#xff09;ARMv8 X30和PC之间的关系小结 ARMv8 通用寄存器 在ARMv9架构中&#xff08;这也适用于ARMv8&#xff0c;因为ARMv9是其进化版本&#xff09;&#…

腾讯云coding代码托管平台配置问题公钥拉取失败提示 Permission denied(publickey)

前言 最近在学校有个课设多人开发一个游戏&#xff0c;要团队协作&#xff0c;选用了腾讯云的coding作为代码管理仓库&#xff0c;但在配置的时候遇到了一些问题&#xff0c;相比于github&#xff0c;发现腾讯的coding更难用&#xff0c;&#xff0c;&#xff0c;这里记录一下…

如何设计与管理一个前端项目

目录 前端项目设计 前端项目搭建 洞察项目瓶颈 方案调研与选型对比 前端项目管理 合理的分工排期 风险把控 及时反馈与复盘 结束语 如果说基础知识的掌握是起跑线&#xff0c;那么使大家之间拉开差距的更多是前端项目开发经验和技能。对于一个项目来说&#xff0c;从框…

【Android Studio】【NCNN】YOLOV8安卓部署

目录 下载Android Studio 克隆安卓项目 关于自训练模型闪退问题 下载Android Studio 下载Android Studio&#xff0c;配置安卓开发环境&#xff0c;这个过程比较漫长。 安装cmake&#xff0c;注意安装的是cmake3.10版本。 根据手机安卓版本选择相应的安卓版本&#xff0c…

彻底解决python的pip install xxx报错(文末附所有依赖文件)

今天安装pip install django又报错了&#xff1a; C:\Users\Administrator>pip install django WARNING: Ignoring invalid distribution -ip (d:\soft\python\python38\lib\site-pac kages) Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting djan…

论文阅读:The Unreasonable Ineffectiveness of the Deeper Layers 层剪枝与模型嫁接的“双生花”

作者实证研究了针对流行的开放式预训练 LLM 系列的简单层修剪策略&#xff0c;发现在不同的 QA 基准上&#xff0c;直到去掉一大部分&#xff08;最多一半&#xff09;层&#xff08;Transformer 架构&#xff09;后&#xff0c;性能的下降才会降到最低。为了修剪这些模型&…

探索 IPv6 协议:互联网的新一代寻址

目录 一.概述 IPv4 的问题和 IPv6 的新特性 IPv6 协议体系 二.IPv6 寻址架构&#xff1a;巨大的地址空间与灵活的寻址模式 IPv6 寻址概述 地址表示方法 地址前缀与地址类型标识 单播地址 任播地址 多播地址 特殊的 IPv6 地址 IPv6 主机与路由器寻址 地址分配 三.I…

数控六面钻适用场景-不止家具制造

在快节奏的现代生活中&#xff0c;家具作为我们生活的重要组成部分&#xff0c;其美观度和实用性日益受到人们的关注。而在这背后&#xff0c;一个不可或缺的“工匠”正默默地发挥着它的作用——那就是数控六面钻。 数控六面钻&#xff0c;顾名思义&#xff0c;是一种高度自动…

关于测试管理后台,生成表单-审核表单,删除表单的测试总结

测试的时候&#xff0c;我们经常遇到一些生成表单状态进行审核&#xff0c;关于这个想了一些测试点&#xff0c;记录分享下。 通过2方面去考虑即可 1&#xff0c;权限-审核权限 权限的实现需要确定到底是低代码平台自己所带的工作流还是自己完全代码实现逻辑&#xff1b; 如…

msix packaging tool打包问题

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

Java开发:Java代码实现电脑文件拷贝(包含文件、视频、音频)、并做了 IO 读写性能优化。

文章目录 Java文件拷贝效果演示代码示例&#xff1a; Java文件拷贝 效果演示 Java实现电脑文件拷贝 代码示例&#xff1a; package com.makefullstack.myio.test;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;public class…

2024-AIDD-人工智能药物设计-利用深度学习优化中药复方益气解毒方对胶质母细胞瘤靶点的药物设计研究

利用深度学习优化中药复方益气解毒方对胶质母细胞瘤靶点的药物设计研究 1. 引言 1.1 研究背景与意义 1.1.1 胶质母细胞瘤的治疗现状 1.1.1.1 胶质母细胞瘤的流行病学 1.1.1.1.1 全球与地区发病率对比 1.1.1.1.2 年龄与性别分布统计 1.1.1.2 传统治疗手段的局限性 1.1.1…

k8s集群部署

部署k8s集群 要求&#xff1a; 主机192.168.199.149&#xff08;master&#xff09;node节点&#xff08;192.168.199.150,192.168.199.151&#xff09;2个cpu或更多 所有机器可以联网&#xff0c;湖湘之间可以ping同&#xff0c;关闭防火墙&#xff0c;selinux&#xff0c;…

多模态模型Mini-Gemini:代码模型数据均开源,MiniCPM小钢炮2.0全家桶四连发,可以在Android 手机端上运行的大模型,效果还不错

多模态模型Mini-Gemini&#xff1a;代码模型数据均开源&#xff0c;MiniCPM小钢炮2.0全家桶四连发&#xff0c;可以在Android 手机端上运行的大模型&#xff0c;效果还不错。 多模态模型Mini-Gemini&#xff1a;代码模型数据均开源 香港中文大学终身教授贾佳亚团队提出多模态模…

国内注册Claude 3流程

国内注册Claude 3流程 Claude 3是什么注册过程准备国外IP节点准备谷歌账号或者邮箱准备接码平台接码平台WildCard输入验证码继续注册 使用聊天功能识图功能文件解析编码能力 Cloud 3 已经推出两个月了&#xff0c;当时可是轰动一时&#xff0c;但是其并不对国内开放&#xff0c…

提取网页元数据的Python库之lassie使用详解

概要 Lassie是一个用于提取网页元数据的Python库,它能够智能地抓取网页的标题、描述、关键图像等内容。Lassie的设计目的是为了简化从各种类型的网页中提取关键信息的过程,适用于需要预览链接内容的应用场景。 安装 安装Lassie非常简单,可以通过Python的包管理器pip进行安…

nodejs后台babel在线热编译jsx

浏览器加载react/vue组件时&#xff0c;遇到es6转es5&#xff0c;jsx转js...时&#xff0c;一种方法是用webpack离线编译&#xff0c;一种方法是在后台用babel在线热编译&#xff08;为了效率部署前可以预热&#xff09;。 我比较喜欢在线热编译&#xff0c;好处是发布时快&am…

C++动态内存管理:与C语言动态内存管理的差异之争

当你改错一行代码的时候&#xff1a; 当你想要重构别人的代码时&#xff1a; 目录 前言 一、C/C的内存分布 二、C/C语言中的动态内存管理 三、new与delete的实现原理 总结&#xff1a; 前言 在C中&#xff0c;内存管理是一个至关重要的主题。正确地管理内存可以避免内存泄…