冒泡排序
今天,有个编程大概三年的过来面试,其中有道题目是冒泡排序,当然他也很快坐出来,并且答案也是对的,但是仔细一问,他自己也不怎么理解冒泡排序,问问怎么提高冒泡效率做法,他也答不上来,所以这篇文章就让我们好好的讲讲冒泡排序。
如果已经了解了冒泡排序,那么可以直接进入到总结篇:https://www.cnblogs.com/gdouzz/p/10759399.html
(一)什么是冒泡排序
直接上例子把,既然是冒泡,那就有冒泡的过程(假设从小到大排列),请看接下来的例子:
有一个数组 arr=[4,5,6,3,2,1] ,那接下来就来模拟冒泡的过程
a[5] 1 1 1 1 6 a[4] 2 2 2 6 1 a[3] 3 3 6 2 2 a[2] 6 6 3 3 3 a[1] 5 5 5 5 5 a[0] 4 4 4 4 4
注意:我这个数据的方向,写成竖的形式,这样就很助于理解冒泡的过程(上面是第一次冒泡的最后结果)。
下面是第一次冒泡的过程
首先,我们取4,5比较 也就是arr[0]和arr[1],因为我们之前说了,这次是从小到大的排序,arr[0]和arr[1]不用交换位置。
a[5] 1 a[4] 2 a[3] 3 a[2] 6 a[1] 5 a[0] 4
接下来,我们取a[1]和a[2]进行交换,还是因为arr[1]小于arr[2]的话呢,不用交换位置。结果如下:
a[5] 1 1 a[4] 2 2 a[3] 3 3 a[2] 6 6 a[1] 5 5 a[0] 4 4
再接下来,我们取a[2]和a[3]进行交换,a[2]大于a[3]要进行位置交换。结果如下:
a[5] 1 1 1 a[4] 2 2 2 a[3] 3 3 6 a[2] 6 6 3 a[1] 5 5 5 a[0] 4 4 4
再然后呢,我们取a[3]和a[4]进行交换,根据大小关系,需要交换位置,结果如下:
a[5] 1 1 1 1 a[4] 2 2 2 6 a[3] 3 3 6 2 a[2] 6 6 3 3 a[1] 5 5 5 5 a[0] 4 4 4 4
再过来,我们取a[4]和a[5]进行交换,根据大小关系,需要交换位置,结果如下:
a[5] 1 1 1 1 6 a[4] 2 2 2 6 1 a[3] 3 3 6 2 2 a[2] 6 6 3 3 3 a[1] 5 5 5 5 5 a[0] 4 4 4 4 4
我们这一次冒泡就结束了,通过这个过程,我们大概也知道了一些冒泡的规律
1、一次冒泡之后,最后的那个数一定是在自己的正确位置了,至少有一个数回到了正确的位置上。
2、接下来的第二次冒泡,可以少比较一次(比前面那一趟)。
根据这两个规则,我们继续完成接下来的冒泡
第二次冒泡
a[5] 6 6 6 6
a[4] 1 1 1 5
a[3] 2 2 5 1
a[2] 3 5 2 2
a[1] 5 3 3 3
a[0] 4 4 4 4
第三次冒泡
a[5] 6 6 6 a[4] 5 5 5 a[3] 1 1 4 a[2] 2 4 1 a[1] 4 2 2 a[0] 3 3 3
第四次冒泡
a[5] 6 6 a[4] 5 5 a[3] 4 4 a[2] 1 3 a[1] 3 1 a[0] 2 2
第五次冒泡
a[5] 6 a[4] 5 a[3] 4 a[2] 3 a[1] 2 a[0] 1
上面就是整个的冒泡过程,经过这个冒泡过程之后,大家的代码也在心里面了描绘出来了。
public static int[] BubbleSort(int[] arr) { //第一层循环,总共六个元素,只需要冒泡五次。 for (var i = 0; i < arr.Length - 1; i++) { //每次冒泡完了之后,都会有一个元素的位置被确定,不用再进行交换 for (var j = 0; j < arr.Length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { var temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; }
(二)冒泡排序的改进
改进思路:减少交换的次数。我们知道,如果数据很早的到达了有序状态,那么剩下的交换次数可以跳过,下面就是对这个的改进。
举个极端的例子:arr=[1,2,3,4,5] 再下面这个算法中,就可以减少多次的循环,还有很多类似的例子,大家可以好好品尝一下这段代码。
public static int[] BubbleSort(int[] arr) { for (var i = 0; i < arr.Length - 1; i++) { bool swapFlag = false; for (var j = 0; j < arr.Length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { var temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapFlag = true; } } if (!swapFlag) { break; } } return arr; }
相关的代码在:https://github.com/gdoujkzz/Data-Structure-For-CSharp.git
(三)冒泡排序的复杂度分析
//冒泡排序 //交换过程 //交换,就是位置调换过来的意思。 //冒泡排序的交换过程:n次的交换代码。 //冒泡排序的比较过程:也是一样n次的比较过程。 //下面来一个最坏情况 5,4,3,2,1 按照从小到大排序 //按照从小到大排序 //1 //2 //3 //4 //5 //第一趟排序,过程如下 //5比4大(比较一次),需要交换位置(交换位置一次) //5比3大(比较一次),需要交换位置(交换位置一次) //5比2大(比较一次),需要交换位置(交换位置一次) //5比1大(比较一次),需要交换位置(交换位置一次) //第一趟排序结束。 可以看出交换次数,和比较次数的规律了。 //最坏情况下,比较次数和交换次数都是一样,五个数字的话呢,就是4+3+2+1 等差数列 n(n-1)/2 。这里是要比较10次,交换10次,最差情况下//最好情况下,1,2,3,4,5 从小到大排序。 //交换次数为零次,比较次数为o(n)。
//我们取平均的交换次数为n(n-1)/4 比较次数为(n+n(n-1)/2)/2,可以知道平均交换次数是没有比较次数那么多的,取比较次数作为时间复杂度(把常量省略)得到o(n²)
总结:冒泡排序的时间复杂度为o(n²),空间复杂度(除去本身之外,所需的额外空间)为o(1)。
(四)冒泡排序的优缺点
冒泡排序,作为我们教科书(我当时大学的学的是严老师的数据结构第二版),里面的排序入门算法,就是冒泡排序,我当时虽然知道冒泡排序,需要时间复杂度为O(n²),但是,感觉这个算法特别吊(当时也没有理解透彻),时至今日,才重新温习,发现了冒泡排序是真的没必要作为教科书的入门排序算法,就是因为它效率低,交换次数太多对CPU也不友好,我觉得教科书里面应该要选择简单插入排序作为入门算法(简单入门排序算法稍后会介绍,也会有一篇文章专门对冒泡排序,简单选择排序,简单插入排序做一个比较)。冒泡排序的唯一优点,可能在当数据是有序的情况下,所需要的时间复杂度为o(n),这个会比很多算法都好,但是还是比不上插入排序。据说,在图像处理这方面,有一些算法是用冒泡排序的。
总得来说,冒泡排序名字倒是很好听,但是说真的效率不高,不推荐使用,即使作为一个初学者,写排序算法,也应该先想到用插入排序。以上是个人想法,欢迎大家留言交流。