本专栏内容为:八大排序汇总 通过本专栏的深入学习,你可以了解并掌握八大排序以及相关的排序算法。
💓博主csdn个人主页:小小unicorn
⏩专栏分类:八大排序汇总
🚚代码仓库:小小unicorn的代码仓库🚚
🌹🌹🌹关注我带你学习编程知识
无论你学习哪种编程语言,在学到循环和数组时,通常都会介绍一种排序算法来作为例子,而这个算法一般就是冒泡排序。并不是它的名称很好听,而是说这个算法的思路最简单,最容易理解。因此,哪怕大家可能都已经学过冒泡排序了,我们还是从这个算法开始我们的排序之旅。
冒泡排序
- 最简单排序的实现
- 冒泡排序算法
- 冒泡排序优化
- 冒泡排序复杂度分析
最简单排序的实现
冒泡排序(Bubble Sort)是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
冒泡的实现在细节上可以有很多种变化,我们将分别就3种不同的冒泡实现代码,来讲解冒泡排序的思想。这里,我们就先来看看比较容易理解的一段。
注意:排序用到的结构与函数在第一部分:排序的基本概念与分类。我们已经实现。详情请点击:八大排序(一)--------排序的基本概念与分类
//对顺序表L作交换排序(冒泡排序初级版本)
void BubbleSort0(SqList* L)
{
int i = 0, j = 0;
for (i = 1; i < L->length; i++)
{
for (j = i+1; j <= L->length; j++)
{
if (L ->r[i] > L->r[j])
{
swap(L, i, j); //交换L->r[i]与r->[j]
}
}
}
}
这段代码严格意义上说,不算是标准的冒泡排序算法,因为它不满足“两两比较相邻记录”的冒泡排序思想,它更应该是最简单的交换排序而已。它的思路就是让每一个关键字,都和它后面的每一个关键字比较,如果大则交换,这样第一位置的关键字在一次循环后一定变成最小值。
如下图所示,假设我们待排序的关键字序列是{9,1,5,8,3,7,4,6,2}
当i=1时,9与1交换后,在第一位置的1与后面的关键字比较都小,因此它就是最小值。
当i=2时,第二位置先后由9换成5,换成3,换成2,完成了第二小的数字交换。后面的数字变换类似,这里不再介绍。
这应该算是最容易写出的排序代码了,不过这个简单易懂的代码,却是有缺陷的。观察后发现,在排序好1和2的位置后,对其余关键字的排序没有什么帮助(数字3反而还被换到了最后一位)。也就是说,这个算法的效率是非常低的。
冒泡排序算法
我们来看看正宗的冒泡算法,有没有什么改进的地方。
//对顺序表L作冒泡排序
void BubbleSort(SqList* L)
{
int i = 0, j = 0;
for (i = 1; i < L->length; i++)
{
for (j = L->length-1; j >= i; j--) //j从后往前循环
{
if (L->r[i] > L->r[j+1]) //若前者大于后者
{
swap(L, i, j+1); //交换L->r[i]与r->[j+1]的值
}
}
}
}
依然假设我们待排序的关键字序列是{9,1,5,8,3,7,4,6,2}
当i=1时,变量j由8反向循环到1,逐个比较,将较小值交换到前面,直到最后找到最小值放置在了第1的位置。
如下图所示,当i=1,j=8时,我们发现6>2,因此交换了它们的位置,j=7时,4>2,所以交换……直到=2时,因为1<2,所以不交换。j=1时,9>1,交换,最终得到最小值1放置第1的位置。
事实上,在不断循环的过程中,除了将关键字1放到第1的位置,我们还将关键字2从第9位置提到了第3的位置,显然这一算法比前面的要有进步,在上十万条数据的排序过程中,这种差异会体现出来。图中较小的数字如同气泡般慢慢浮到上面,因此就将此算法命名为冒泡算法。
后面的数字变换就很简单,这里就不在叙述了。
冒泡排序优化
这样的冒泡程序是否还可以优化呢?答案是肯定的。试想一下,如果我们待排序的序列是{2,1,3,4,5,6,7,8,9}也就是说,除了第1和第2的关键字需要交换外,别的都已经是正常的顺序。
当i=1时,交换了2和1,此时序列已经有序,但是算法仍然不依不饶地将i=2~9以及每个循环中的循环都执行了一遍,尽管并没有交换数据,但是之后的大量比较还是大大地多余了,如下图所示。
当i=2时,我们已经对9与8,8与7,…,3与2作了比较,没有任何数据交换,这就说明此序列已经有序,不需要再继续后面的循环判断工作了。为了实现这个想法,我们需要改进一下代码,增加一个标记变量flag来实现这一算法的改进。
//对顺序表L作冒泡排序(改进版本)
void BubbleSort(SqList* L)
{
int i = 0, j = 0;
status flag = 1; //用flag进行标记
for (i = 1; i < L->length && flag; i++) //若flag为1则有效数据交换
{
flag = 0;
for (j = L->length-1; j >= i; j--) //j从后往前循环
{
if (L->r[i] > L->r[j+1]) //若前者大于后者
{
swap(L, i, j+1); //交换L->r[i]与r->[j+1]的值
flag = 1; //如果有数据交换,则flag为1
}
}
}
}
代码改动的关键就是在变量的for循环中,增加了对flag是否为1的判断。
经过这样的改进,冒泡排序在性能上就有了一些提升,可以避免已经有序的情况下的无意义循环判断。
冒泡排序复杂度分析
分析一下它的时间复杂度。
当最好的情况,也就是要排序的表本身就是有序的,那么我们比较次数,根据最后改进的代码,可以推断出就是n-1次的比较,没有数据交换,时间复杂度为O(n)。
当最坏的情况,即待排序表是逆序的情况,此时需要比较之 ∑ i = 2 n ( i − 1 ) = 1 + 2 + 3 + … + ( n − 1 ) = n ( n − 1 ) / 2 \sum_{i=2}^n (i-1)=1+2+3+…+(n-1)=n(n-1)/2 ∑i=2n(i−1)=1+2+3+…+(n−1)=n(n−1)/2次,并作等数量级的记录移动。因此,总的时间复杂度为O(n2)。