排序算法集合

1. 冒泡排序

排序的过程分为多趟,在每一趟中,从前向后遍历数组的无序部分,通过交换相邻两数位置的方式,将无序元素中最大的元素移动到无序部分的末尾(第一趟中,将最大的元素移动到数组倒数第一的位置;第二趟中,将第二大的元素移动到数组倒数第二的位置,以此类推)。

每排一趟,数组末尾的有序序列就向前增长一个元素,数组前端的无序部分就减少一个元素。

优化:某趟中,一次交换也没有发生,说明数组已有序,直接结束排序。

整个排序的过程中,剩余无序元素中最大的元素就像气泡一样不断“上浮”到数组末尾,所以该算法被称作冒泡排序。

冒泡排序的思想很简单,类似于递归:

要实现整个数组(n个元素)的有序,可以将数组分为前(n-1)个元素和最后一个元素。

首先确保最后一个元素有序(最大),然后再用相同的方式解决前(n-1)个元素组成的数组的有序性问题。

当然,你也可以当我上面这段话是放屁,因为冒泡排序的算法太过简单,按上面的方式来理解确实小题大做。 

但伴随着简单算法的往往是极高的开销,这也使得冒泡排序没有任何的实际意义,仅作教学作用。

可以说冒泡排序是最拉的排序算法,接下来讲到的所有排序算法,效率都至少为其的十倍以上(实测结果非理论分析,理论分析出的时间复杂度几乎相同)

时间复杂度:最坏情况(元素逆序)O(n^{2}),最好情况(已有序)O(n)

空间复杂度O(1)

//冒泡排序
void BubbleSort(int* arr, int len)
{for (int i = 0; i < len; i++){int flag = 1;for (int j = 0; j < len - 1 - i; j++){if (arr[j] > arr[j + 1]){flag = 0;swap(&arr[j], &arr[j + 1]);}}if (flag)return;}
}

2. 选择排序

顾名思义,就是在每趟排序中,直接选出最大的元素(的下标),然后将其与无序部分末尾的元素进行交换,同时无序部分的末尾向前缩短一个元素。

相对于冒泡排序,二者的做法很像,但选择排序省去了交换相邻元素的过程(效率提高的关键),手段更加直接。

优化:在选出最大元素的同时,将最小元素也选出来,两端同时排序。

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

但是,直接进行优化之后的算法会存在一个小bug:

假设在每趟排序中,将最大元素和最小元素选出来之后,先让最小元素交换到无序部分的前端,再让最大元素交换到无序部分末尾。

那么,当最大元素刚好在无序部分前端时,就会发生如下的过程:

1. 首元素(最大元素)与最小元素交换

2. 最大元素(首元素,此时该位置为最小元素)与末尾元素交换

 可以按照下面的方式解决。

//选择排序
void SelectSort(int* arr, int len)
{int begin = 0, end = len - 1;while (begin < end){int max = begin;int min = begin;for (int i = begin + 1; i <= end; i++){if (arr[i] < arr[min])min = i;if (arr[i] > arr[max])max = i;}swap(&arr[begin], &arr[min]);if (begin == max)swap(&arr[end], &arr[min]);elseswap(&arr[end], &arr[max]);begin++;end--;}
}

时间复杂度: O(n^{2})

空间复杂度O(1)

由于选择排序无法提前结束,所以其时间复杂度为稳定的O(n^{2})

但是,相比于冒泡排序,选择排序在整体上减少了频繁交换的消耗,在一般情况下,效率要远好于冒泡排序。

3. 插入排序

上面的两种算法是在为某个位置选择合适的元素填入,而插入排序则是在为某个元素选择合适的位置去存放,也正是这一差别,使其成为了这三位难兄难弟的老大哥。

为某个元素选择合适的位置,必然只有在其他元素都有序的情况下才能做到(或者局部有序)。

尽管数组在整体上无序,我们却可以将开头的一个元素看作是无序,接着将第二个元素插入其中。

于是,前两个元素就变得有序了,我们又可以将第三个元素插入其中……以此类推,数组便逐渐有序了。

插入排序,就是将数组分为两个部分:数组前端的有序部分(开始只有一个元素),和数组后半段的无序部分。然后将无序部分的元素逐个插入到有序部分。

而对于插入,我们采用的办法是:

从后向前遍历有序部分,如果当前位置的元素比要插入的元素大,则将当前位置的元素向后移动一个元素(为要插入的元素腾位置);如果当前位置的元素要比插入的元素小,则将要插入的元素插入到当前位置的后面(自己原本的位置,或者是第一种情况腾出来的位置)。

优化: 可优化为希尔排序,详见下文。

//插入排序
void InsertSort(int* arr, int len)
{for (int i = 1; i < len; i++){int temp = arr[i];int j = i;while(j > 0 && arr[j - 1] > temp){arr[j] = arr[j - 1];j--;}arr[j] = temp;}
}

时间复杂度:最坏情况(元素逆序)O(n^{2}),最好情况(已有序)O(n)

空间复杂度O(1)

虽然看上去与冒泡排序一模一样,但是其实际的消耗要小得多,相比于选择排序也要更优。

4. 希尔排序

 在插入排序算法中,越小的元素越早插入越好,越大的元素越晚插入越好。

当元素恰好逆置时,算法的消耗较大,几乎等价于冒泡排序。

而希尔排序就是希尔为解决插入排序痛点而设计的算法,解决这一痛点的方法就是,先对整个数组进行跨度较大的粗调,再进行更加细致的调整(只有数据量较大时希尔排序的优化才有意义,否则直接使用插入排序即可)。

我们发现,如果较大的元素插入得较早,之后就会进行大量的操作将该元素后移,并且每次只移动一个元素。

如果我们能够快速地(一次跳过多个元素)将这些较大的元素调整到靠后的位置,使得数组基本有序,插入排序的时间复杂度就会无限接近于最好情况。

那么如何进行粗调呢?

1. 将数组的元素分为若干组(记组数为gap),每一组元素的下标模上gap所得的值相同(即每一组元素的下标为模gap的同余关系,类似于分层抽样)。

2. 对每一组的元素进行插入排序(这样一来,较大的元素每次向后移动的元素个数为gap,粗调的效率就远高于直接插入排序)。

3. 缩小gap的值,重复上述过程(一般做法为gap=gap/3+1,+1的目的是使得最后一趟排序时gap为1,也就是对整个数组进行一次插入排序)。

//希尔排序(插入排序优化)
void ShellSort(int* arr, int len)
{int gap = len;while (gap > 1){gap = gap / 3 + 1;for (int group = 0; group < gap; group++){for (int i = group + gap; i < len; i += gap){int temp = arr[i];int j = i;while (j > group && arr[j - gap] > temp){arr[j] = arr[j - gap];j -= gap;}arr[j] = temp;}}}
}

可以看到,最内层的两层循环其实就是将插入排序中的“1”替换为了“gap”,将起点从“0+1”改成了“group + gap”。

控制group的循环就是在切换插入排序的组别。

优化:如果觉得四层循环看起来很唬人的话,我们也可以优化掉一层循环(但效率不变)

//希尔排序X(各组同时排,优化掉三重循环,但效率相同)
void ShellSort_X(int* arr, int len)
{int gap = len;while (gap > 1){gap = gap / 3 + 1;for (int i = gap; i < len; i++){int temp = arr[i];int j = i;while (j > 0 && arr[j - gap] > temp){arr[j] = arr[j - gap];j -= gap;}arr[j] = temp;}}
}

时间复杂度:平均情况O(n^{1.3}),最好情况O(n(logn)^{2})

空间复杂度O(1)

初见希尔排序的代码,可能会误以为其效率极低,但实际上希尔排序的效率极高,甚至在数据量较大时,效率高出上文介绍的算法不止一个量级(百倍以上)!

接下来的几种算法,于希尔排序同属一个量级,但效率有略微差别。

其排序的过程极其复杂,具有许多不可控因素,不能简单地通过循环的层数来计算时间复杂度。

希尔排序的代码很难理解,但是其解决问题的思想却很值得我们学习借鉴。

5. 堆排序

详见这篇文章:全面详解堆-CSDN博客

6. 快速排序

详见这篇文章:C语言实现快速排序算法-CSDN博客 

值得一提的是,快速排序正如其名,快且综合实力强。

C语言中,qsort函数的底层算法便是采用的快速排序算法。

7. 归并排序 

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

若将两个有序表合并成一个有序表,称为二路归并。

以二路归并为例,假如一个数组从中间位置被分为了前后两个部分,而这两个部分都是有序的,那么我们就可以采用与合并两个有序链表相同的思路:比较两个序列中的最小值,将较小的那个插入到新的序列中,不断重复这样的操作,直到原序列中的值全部插入到新序列中。

但前提是,前后两个子序列都要有序。

所以我们的首要任务是使得前后两个子序列有序,而使其有序的方式依然如上。

于是我们找到的递归的思路:

void _MergeSort(int* arr, int* copy, int begin, int end)
{if (begin >= end)return;//区间不能分为[begin,mid-1]和[mid,end]int mid = (begin + end) / 2;_MergeSort(arr, copy, begin, mid);_MergeSort(arr, copy, mid + 1, end);//归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int cur = begin;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){copy[cur++] = arr[begin1++];}else{copy[cur++] = arr[begin2++];}}while (begin1 <= end1) { copy[cur++] = arr[begin1++]; }while (begin2 <= end2) { copy[cur++] = arr[begin2++]; }memcpy(arr + begin, copy + begin, (end - begin + 1) * sizeof(int));
}//归并排序
//时间复杂度O(n*logn),空间复杂度O(n)
void MergeSort(int* arr, int len)
{int* copy = (int*)malloc(sizeof(int) * len);if (copy == NULL){perror("malloc fail");return;}_MergeSort(arr, copy, 0, len - 1);free(copy);
}

和快速排序类似,既然这个算法是用递归的方式实现的,我们就会考虑其非递归的实现方式。

但与快速排序不同,快速排序的递归方式类似于二叉树的前序遍历,但是归并排序类似于后序遍历。

参考栈与递归的实现-CSDN博客会发现,二叉树前序遍历的非递归实现很简单,但是后序遍历的非递归实现则十分麻烦。

将递归算法转化为非递归算法,除了利用栈来模拟实现,也可以尝试直接利用迭代来实现。

思路就是将数组按gap个数据为一组分为多组,每两个相邻的组进行插入合并,随后gap变为原来的两倍,再次重复以上过程,直到gap大于数组长度。

当然,最后一组可能分配不到gap个数据,也可能只能分出奇数个组,针对这些问题,进行插入合并(归并)的代码进行了调整。

//归并排序非递归
void MergeSortNonR(int* arr, int len)
{int* copy = (int*)malloc(sizeof(int) * len);if (copy == NULL){perror("malloc fail");return;}for (int gap = 1; gap < len; gap *= 2){for (int i = 0; i < len; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//第二组都越界,不归并了if (begin2 >= len){break;}//第二组越界部分,修正并归并if (end2 >= len){end2 = len - 1;}int cur = i;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){copy[cur++] = arr[begin1++];}else{copy[cur++] = arr[begin2++];}}while (begin1 <= end1) { copy[cur++] = arr[begin1++]; }while (begin2 <= end2) { copy[cur++] = arr[begin2++]; }memcpy(arr + i, copy + i, ((end2 - i + 1) * sizeof(int)));}}free(copy);
}

时间复杂度O(nlogn)

空间复杂度O(n)

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

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

相关文章

【scikit-learn010】sklearn算法模型清单实战及经验总结(已更新)

1.一直以来想写下基于scikit-learn训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架模型算法包相关技术点及经验。 3.欢迎批评指正,欢迎互三,跪谢一键…

代码随想录算法训练营day41

题目&#xff1a;01背包理论基础、416. 分割等和子集 参考链接&#xff1a;代码随想录 动态规划&#xff1a;01背包理论基础 思路&#xff1a;01背包是所有背包问题的基础&#xff0c;第一次看到比较懵&#xff0c;完全不知道dp数据怎么设置。具体分析还是dp五部曲&#xff…

C#WPF数字大屏项目实战04--设备运行状态

1、引入Livecharts包 项目中&#xff0c;设备运行状态是用饼状图展示的&#xff0c;因此需要使用livechart控件&#xff0c;该控件提供丰富多彩的图形控件显示效果 窗体使用控件 2、设置饼状图的显示图例 通过<lvc:PieChart.Series>设置环状区域 3、设置饼状图资源样…

js 数字精确度

事情的起源&#xff1a; 项目中 填写的赔付金额是小数 传给后端需要 *100 9.87 *100 传给后端后是986.9999999999999 后端直接取整 就变成了9.86了 0.1 0.2 ! 0.3 console.log(0.1 0.2) //0.30000000000000004 console.log(0.1 0.2 0.3) //false1. 数字的存储 浮点数是用…

【数据库】SQL--DQL(初阶)

文章目录 DCL1. 基本介绍2. 语法2.1 基础查询2.2 条件查询2.3 聚合函数2.4 聚合查询2.5 分组查询2.6 排序查询2.7 分页查询2.8 综合案例练习2.9 执行顺序 3. DQL总结 DCL 更多数据库MySQL系统内容就在以下专栏&#xff1a; 专栏链接&#xff1a;数据库MySQL 1. 基本介绍 DQL英…

全息之镜,未来的眼镜

全息之镜&#xff0c;作为未来眼镜的一种设想和展望&#xff0c;凭借其独特的全息技术&#xff0c;将在未来带来全新的视觉体验和应用场景。以下是关于全息之镜未来的详细分析和展望&#xff1a; 一、技术原理与特点 全息之镜利用全息技术&#xff0c;通过干涉、衍射和折射等…

k8s自定义资源你会创建吗

创建自定义资源定义 CustomResourceDefinition 当你创建新的 CustomResourceDefinition&#xff08;CRD&#xff09;时&#xff0c;Kubernetes API 服务器会为你所 指定的每一个版本生成一个 RESTful 的 资源路径。CRD 可以是名字空间作用域的&#xff0c;也可以是集群作用域的…

4.2 索引及其操作

对数据库中的表进行查询操作时有两种搜索扫描方式&#xff0c;一种是全表扫描&#xff0c;另一种就是使用表上建立的索引进行扫描。 全表扫描要查找某个特定的行&#xff0c;必须从头开始一一查看表中的每一行&#xff0c;与查询条件做对比&#xff0c;返回满足条件的记录&…

JVM学习-Jprofiler

JProfiler 基本概述 特点 使用方便&#xff0c;界面操作友好对被分析的应用影响小(提供模板)CPU&#xff0c;Tread&#xff0c;Memory分析功能尤其强大支持对jdbc,noSql,jsp,servlet,socket进行分析支持多种模式(离线、在线)的分析支持监控本地、远程JVM跨平台&#xff0c;拥…

Vue01-vue的简介

一、Vue是什么&#xff1f; 一套用于构建用户界面的渐进式javaScript框架。 构建用户界面&#xff1a; 渐进式&#xff1a; 目前Vue的地位&#xff1a;生态完善&#xff0c;国内前端工程师必备技能。 二、Vue的特点 一个XXX.vue就是一个组件&#xff0c;封装的概念&#xff0c…

2025 QS 世界大学排名公布,北大清华跻身全球前20

一年一度&#xff0c;2025 QS 世界大学排名公布&#xff01; QS&#xff08;Quacquarelli Symonds&#xff09;是唯一一个同时将就业能力与可持续发展纳入评价体系的排名。 继去年 2024 QS 排名因为“墨尔本超耶鲁&#xff0c;新南悉尼高清华”而荣登微博热搜之后&#xff0c…

小米路由器如何设置去广告功能,如何设置小米路由器的自定义Hosts(小米路由器如何去除小米广告、去除小米电视盒子开屏广告、视频广告)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 实现方案 📒📝 操作步骤📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 小米设备的广告一直是用户头疼的问题,无论是开屏广告、应用内广告还是系统广告,都影响了用户体验。本文将详细介绍如何通过小米路由器实现去除广告…

测试基础09:缺陷(bug)生命周期、定位方式和管理规范

课程大纲 1、缺陷&#xff08;bug&#xff09;生命周期 2、缺陷&#xff08;bug&#xff09;提交规范 2.1 宗旨 简洁、清晰、可视化&#xff0c;减少沟通成本。 2.2 bug格式和内容 ① 标题&#xff1a;一级功能-二级功能-三级功能_&#xff08;一句话描述bug&#xff1a;&…

Angular17(2):angular项目中使用NG-ZORRO

1、使用Angular CLI创建空项目 ng new angular-admin-web --stylescss 2、执行ng add ng-zorro-antd命令安装 &#xff08;1&#xff09;ng add ng-zorro-antd 在angular项目下运行命令 ng add ng-zorro-antd 跟随选项便可完成初始化配置&#xff0c;包括引入国际化文件&…

alist配合onlyoffice 实现在线预览

alist配合onlyoffice 实现在线预览 文章目录 alist配合onlyoffice 实现在线预览一、安装onlyoffice二、增加view.html文件三、安装nginx&#xff0c;并增加conf配置文件四、alist预览配置增加 一、安装onlyoffice 我是采用docker安装&#xff0c;采用的版本是7.2&#xff0c; …

Web网站攻击技术

文章目录 Web应用体系结构脆弱性分析HTTP协议安全问题Cookie的安全问题 常见Web应用攻击及防范SQL注入攻击及防范SQL注入原理 防御注入漏洞跨站脚本(XSS)攻击及防范跨站脚本(XSS)攻击原理 跨站脚本攻击类型储存式XSS反射式XSSDOM式XSS Cookie欺骗及防范CSRF攻击及防范防御CSRF攻…

每天五分钟深度学习PyTorch:Tensor张量的索引和切片

本文重点 有时候当我们拥有一个Tensor张量的时候,我们可能需要获取它某一维度的信息,那么此时我们就需要索引和切片的技术,它们可以帮助我们解决这些问题。 切片操作 a是四维的,然后默认是从第一维开始取,逗号表示取不同的维度 a[:2]表示第一维取0,1,后面三维取所有 …

【QT5】<总览三> QT常用控件

文章目录 前言 一、QWidget---界面 二、QPushButton---按钮 三、QRadioButton---单选按钮 四、QCheckBox---多选、三选按钮 五、margin&padding---边距控制 六、QHBoxLayout---水平布局 七、QVBoxLayout---垂直布局 八、QGridLayout---网格布局 九、QSplitter---…

【保姆级讲解Outlook邮箱的使用技巧】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Mysql:通过一张表里的父子级,递归查询并且分组分级

表&#xff1a;gc_jzst_single_base 需求&#xff1a;要求返回这张表里符合条件的数据&#xff0c;且有父子级关系的&#xff0c;展示为同一组且分级&#xff0c;给后续业务调用 代码 WITH RECURSIVE t1 AS (SELECTsingle_id,old_build_single_id,single_name,bulid_code,1 A…