排序算法之冒泡排序(关键词:数据结构/算法/排序算法/冒泡排序)

假定:有 1 个乱序的数列 nums ,其中有 n 个数。
要求:排好序之后是 从小到大 的顺序。

冒泡排序算法

代码

def bubble_sort(nums):

	for i in range(len(nums)-1):
		for j in range(0, len(nums)-i-1):
			if nums[j] > nums[j+1]:
				nums[j], nums[j+1] = nums[j+1], nums[j]

	return nums

(对外层循环的循环次数range(len(nums)-1)的解释:
range(n-1)[0, 1, 2, 3, ..., n-2],共 n-1 个数;

如果有 1 个数,
则需要 0 轮外层循环,即无需排序;

如果有 2 个数,
则需要 1 轮外层循环;

如果有 3 个数,比如 [3, 2, 1],
第 0 轮外循环之后,数列为 [2, 1, 3],
第 1 轮外循环之后,数列为 [1, 2, 3],
则需要 2 轮外层循环;

。。。

如果有 n 个数,
则需要 n-1 轮外层循环。

原理

第 i 次循环中,对从第 0 到第 n-i-1 个元素从前往后进行比较,每次比较相邻的两个元素,如果前一个元素大于后一个元素,则两者互换位置,否则保持位置不变。整个过程一共进行 n-1 次循环,直到第 1 个和第 2 个元素完成比较完成,最终剩余最小的元素,留在第 1 个位置上,排序结束。

详细解释

第 0 轮排序:( i 等于 0)
第 0、1 个数比较大小,较大的数放到第 1 个位置;
。。。
第 j、j+1 个数比较大小,较大的数放到第 j+1 个位置;
。。。
第 n-2、n-1 个数比较大小,较大的数放到第 n-1 个位置。
第 0 轮排序完毕,第 1 大的数被放到数列的最后的位置,即第 n-1 个位置。

第 1 轮排序完毕,第 2 大的数被放到数列的最后的位置,即第 n-2 个位置。

第 2 轮排序完毕,第 3 大的数被放到数列的最后的位置,即第 n-3 个位置。

。。。

第 i 轮排序完毕,第 i+1 大的数被放到数列的最后的位置,即第 n-i-1 个位置。

。。。

第 n-1 轮排序:( i 等于 n-1)
。。。
第 n-1 轮排序完毕,第 n 大的数被放到数列的最后的位置,即第 0 个位置。

改进的冒泡排序算法

代码

def bubble_sort_better(nums):

	for i in range(len(nums)-1):
		
		flag = False
		# flag 用于标记内层循环中,是否发生了交换。		
		for j in range(0, len(nums)-i-1):
			if nums[j] > nums[j+1]:
				nums[j], nums[j+1] = nums[j+1], nums[j]
				flag = True	# 发生了交换。

		if flag==False:	break
		# 若未发生交换,则说明数列初始状态是从小到大排列的,即有序的。

	return nums

原理

最初的冒泡排序算法中,如果遇到最好的情况,即数列是有序(从小到大)的,仍然会走完所有的内外循环,时间复杂度为 O(n^2),但显然是不必要的,因此加入了 flag 标志,用于判断是否发生了交换。

如果初始数列就是从小到大排列的,则第 0 次进入外循环时,每 1 个内循环均不会改变 flag 的值,离开内循环后,flag 仍然为 False,break 跳出循环。

算法复杂度

时间复杂度:
最坏情况(初始数列为从大到小,逆序排列)时间复杂度为 O(n^2);
最好情况(初始数列为从小到大,顺序排列),只进入 1 次外循环,内循环约 n 次,时间复杂度为 O(n)。
平均时间复杂度为 O(n^2)。

空间复杂度:
冒泡排序只需要常数个额外空间用于保存中间变量,空间复杂度为 O(1)。

稳定性

从代码中,可以看出,如果 2 个相邻的数是相等的,则不做交换,即相对位置不会发生变化。
因此,冒泡排序是稳定的。

参考文献:

  1. 《数据结构(第 2 版)》 – 浙江大学 – 7.4.1 冒泡排序 – P271,P272。
点赞