-
冒泡排序专业解释
冒泡排序是一种简单的排序算法,它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
-
通俗易懂的解释
想象一个水池,里面有许多大小不一的泡泡。每一次我们都从水底开始,比较两个相邻的泡泡的大小。较大的泡泡会上浮,这样每一轮比较后,最大的泡泡都会被推到最上面。重复这个过程,直到所有的泡泡都按照大小有序排列。
-
时间复杂度分析
比较次数:对于一个长度为n的列表,最多需要n-1次外部遍历。
交换次数:在最坏的情况下,即列表完全逆序,需要n(n-1)/2次交换。
时间复杂度:最好情况(已经排序)为O(n),最坏情况(完全逆序)为O(n^2),平均情况也是O(n^2)。
-
例子
考虑一个简单的整数列表:[5, 4, 3, 2, 1],我们进行冒泡排序,步骤如下:
第1轮:
比较5和4,交换他们。得到:[4, 5, 3, 2, 1]
比较5和3,交换他们。得到:[4, 3, 5, 2, 1]
比较5和2,交换他们。得到:[4, 3, 2, 5, 1]
比较5和1,交换他们。得到:[4, 3, 2, 1, 5]
在第1轮结束后,最大的数字5已经在正确的位置上了。
第2轮:
比较4和3,交换他们。得到:[3, 4, 2, 1, 5]
比较4和2,交换他们。得到:[3, 2, 4, 1, 5]
比较4和1,交换他们。得到:[3, 2, 1, 4, 5]
在第2轮结束后,次大的数字4已经在正确的位置上了。
第3轮:
比较3和2,交换他们。得到:[2, 3, 1, 4, 5]
比较3和1,交换他们。得到:[2, 1, 3, 4, 5]
在第3轮结束后,数字3已经在正确的位置上了。
第4轮:
比较2和1,交换他们。得到:[1, 2, 3, 4, 5]
在第4轮结束后,数字2已经在正确的位置上了。
在这四轮之后,数组[5, 4, 3, 2, 1]已经被成功地冒泡排序为[1, 2, 3, 4, 5]。我们发现,要完成对5个数据的排序,需要4轮。那么要完成对n个数据的排序,最多需要(n-1)轮。
下面是java代码的实现方式:
public void bubbleSort(int[] arr) {
int n = arr.length; // 数组的长度
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
以上for循环的时间复杂度分析,和之前讲的有些区别。我们之前讲的双for循环:外层循环每执行一次,内层会执行n次。
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
…
}
}
但是在我们上面实现的冒泡排序代码中,可以发现,内层循环运行的次数会随着外层循环的每次迭代而减少。例如,当i=0时,内循环运行了n-1次;当i=1时,内循环运行了n-2次,依此类推,内层循环并不是固定的n次。
那么该如何计算呢?代码运行了多少次呢?
我们可以把所有的情况都列出来,然后加在一起,就是代码的运行总次数。当i=0时,内循环运行了n-1次;当i=1时,内循环运行了n-2次……
我们把所有的次数加起来:(n-1) + (n-2) + ... + 1 = n(n-1)/2,这实际上是一个算数级数,它的求和公式是n(n-1)/2,所以,最后我们可以得到,这段代码的时间复杂度是O()。当然,这是最坏的情况,进行了n-1次冒泡排序;最好的情况我们只需要进行1次冒泡排序就可以了。
刚刚讲的代码其实还可以优化,当某次冒泡操作已经没有数据交换时,说明已经达到有序,这里有另一个例子,这里面给6个元素排序,只需要三次冒泡操作就可以了。
为了避免没有交换,我们还进行一次排序的操作,那么我们就把代码优化如下:
public void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果在这一轮中没有发生任何交换,说明数组已经排序完毕
if (!swapped) break;
}
}
其中,swapped 变量起到了一个非常重要的作用。
第一:跟踪每一轮是否发生交换:每次元素需要交换时,swapped 被设置为 true。
第二:检查数组是否已经排序完毕:在内循环结束后,如果 swapped 仍然是 false,这意味着在这一轮循环中没有发生任何元素交换。因此,我们可以利用这一特性提前结束排序,从而提高效率。
感谢大家的阅读!