【基础算法】排序
Kreedy_Ke
·
2022-09-04 19:30:20
·
个人记录
引子
生活中常能看见排序的身影:一次考试的排名,一个班级的队伍,一本字典的排版,都需要按照某种依据来排序。有可能是分数的高低,有可能是身高的参差,有可能是字母的顺序。排序很有用,在信息学竞赛中也不例外。
概述
排序算法(英语:Sorting algorithm)是一种将一组特定的数据按某种顺序进行排列的算法。 ——OI wiki
排序作为一种十分常用的算法,自然是每个信息学竞赛选手都必须牢牢掌握的。而排序的方法多种多样,不同方法有不同的优劣,可以视情况而选择。
当我们在讨论一种排序的算法时,有几样东西是必须注意的:
时间复杂度
不用说,我们肯定都希望时间复杂度越少越好,但有时也并非如此。
空间复杂度
同样也是判断算法优劣的一个参数,一般也是越少越好。
稳定性
一种排序算法是否具有稳定性取决于相等的元素在排序之后的相对位置是否会发生改变。
打个比方,在一个无序序列中有两个相等的元素 a 和 b,且 a 在 b 的前面。在经过具有稳定性的排序算法排序后,a 仍旧在 b 的前面,但在经过不具有稳定性的排序算法排序后,a 不一定在 b 的前面。
基数排序、计数排序、插入排序、冒泡排序、归并排序是稳定排序。
选择排序、堆排序、快速排序不是稳定排序。
接下来将按照 OI wiki 上的顺序介绍几个常用的排序算法。所有算法的代码都是按从小到大的方式排序,且数组下标从 1 开始。
选择排序
不稳定,时间复杂度 O(n^2)
选择排序是一种非常直观易懂的排序算法,但代价就是复杂度太高了。
选择排序的基本思路是:每次遍历一遍序列,找到第 i 小的元素,并将其与第 i 位上的元素交换。这就像是将一个班的人按身高从矮到高排队,现让最矮的人出列站在第一位,再让次矮的人出列站在第二位……以此类推,直到所有人都出列。
以下为伪代码:
void Selection_sort ( int a[] , int len )
{
for ( int i=1 ; i { int pos=i; //记录第i小的数的下标 for ( int j=i+1 ; j<=len ; j++ ) //因为之前的数肯定都排好了,所以从第i+1位开始枚举 if ( a[j]
swap(a[i],a[pos]); //将第i小值与第i位上的数交换 } } 冒泡排序 稳定,时间复杂度 O(n^2) 冒泡排序的思路也非常简单,并且相较于选择排序,其具有稳定性。但平均时间复杂度仍然比较庞大,在数据规模较小时再用吧。 冒泡排序的基本思路是:不断遍历序列,如果相邻两个元素不符合需要的排序顺序的话,则将两个元素交换。直到没有元素可以交换时,排序完毕。 如:将序列按从小到大的顺序排序,当遍历到第 i 个元素时,发现第 i+1 个元素小于它,则将两个元素交换。可以发现,当序列越趋近于有序时,所需的交换次数越少,当序列有序时,有最优时间复杂度 O(n)。 以下为伪代码: void Bubble_sort ( int a[] , int len ) { bool wy=1; //看是否结束排序 while ( wy ) //只要还有可以交换的一对元素,就不断循环 { wy=0; //一开始相信没有可以交换的了 for ( int i=1 ; i if ( a[i]>a[i+1] ) //遇见可以换的就换 wy=1,swap(a[i],a[i+1]); } } 碎碎念:关于为什么冒泡排序叫冒泡排序,OI wiki 上是这么说的: 由于在算法的执行过程中,较小的元素像是气泡般慢慢「浮」到数列的顶端,故叫做冒泡排序。 咱也不知道咋就像了,咱也不敢问 插入排序 稳定,时间复杂度 O(n^2) 插入排序的思路很常见,并且在序列几乎有序时效率很高,但就是算如此其仍有 O(n^2) 的最坏时间复杂度和平均时间复杂度。 插入排序的基本思路是:将整个序列分为“未排序”和“已排序”两个部分,每次从“未排序”的部分中选取一个元素,按照排序要求插入到“已排序”的正确位置上。 与其相似的操作就是整理扑克牌了,每次从桌上选取一张牌按大小顺序插入到自己的手牌中。 以下为伪代码: void Insertion_sort ( int a[] , int len ) { for ( int i=2 ; i<=len ; i++ ) //将第一个元素视作已排序部分 for ( int j=i ; j>1 ; j-- ) //在已排序部分中搜索合适的位置