冒泡排序(Bubble Sort):
输入:乱序n长数组
输出:排序好的n长数组
时间复杂度:O(n^2)
空间复杂度:O(1)
冒泡排序的原理是对数组多次扫描,每次扫描都对相邻的两个元素的顺序进行调整。假设我们按升序排列数组,那么相邻两个元素中如果左边的元素大于右边的元素,就交换这两个元素位置,否则不交换。依次扫描过数组中全部元素。可见第一次扫描就使得数组的最大值排在了数组的最后一个位置,第二次扫描使得数组中第二大的元素排在了数组的倒数第二个位置。。。以此类推。这样,最多需要进行n-1次扫描就能完成排序。我们来看一个具体的例子:
数组[6, 5, 3, 1, 8, 7, 2, 4]的第一次扫描过程如下:
6>5:交换位置->[5, 6, 3, 1, 8, 7, 2, 4]
6>3:交换位置->[5, 3, 6, 1, 8, 7, 2, 4]
6>1:交换位置->[5, 3, 1, 6, 8, 7, 2, 4]
6<8:不交换->[5, 3, 1, 6, 8, 7, 2, 4]
8>7:交换位置->[5, 3, 1, 6, 7, 8, 2, 4]
8>2:交换位置->[5, 3, 1, 6, 7, 2, 8, 4]
8>4:交换位置->[5, 3, 1, 6, 7, 2, 4, 8]
可以看出,经过第一次扫描,数组的最大值8移动到了数组的最后一个位置,扫描的过程就是依次比较相邻元素的大小,再做交换。大的元素像气泡一样最终“升至”数组“顶端”。
同理,第二次扫描我们只需要对数组的前n-1个元素做相同的扫描即可,使得前n-1个元素的最大值(也就是整个数组的第二大元素)排在数组的倒数第二的位置。
可见,最多只需要n-1次扫描就能排列整个数组。这里,之所以说“最多”,是因为,假如第x次扫描没有发生元素交换位置(也就是说当前所扫描的数组是排好序的),那么,很显然,我们当然没有必要再进行之后的扫描。一个简单的例子是:数组[2, 1, 3, 4],经过第一次扫描,变成[1, 2, 3, 4];第二次扫描没有发生元素交换,说明已经排好序,就没有必要再进行第三次扫描了。
输入:乱序n长数组
输出:排序好的n长数组
时间复杂度:O(n^2)
空间复杂度:O(1)
冒泡排序的原理是对数组多次扫描,每次扫描都对相邻的两个元素的顺序进行调整。假设我们按升序排列数组,那么相邻两个元素中如果左边的元素大于右边的元素,就交换这两个元素位置,否则不交换。依次扫描过数组中全部元素。可见第一次扫描就使得数组的最大值排在了数组的最后一个位置,第二次扫描使得数组中第二大的元素排在了数组的倒数第二个位置。。。以此类推。这样,最多需要进行n-1次扫描就能完成排序。我们来看一个具体的例子:
数组[6, 5, 3, 1, 8, 7, 2, 4]的第一次扫描过程如下:
6>5:交换位置->[5, 6, 3, 1, 8, 7, 2, 4]
6>3:交换位置->[5, 3, 6, 1, 8, 7, 2, 4]
6>1:交换位置->[5, 3, 1, 6, 8, 7, 2, 4]
6<8:不交换->[5, 3, 1, 6, 8, 7, 2, 4]
8>7:交换位置->[5, 3, 1, 6, 7, 8, 2, 4]
8>2:交换位置->[5, 3, 1, 6, 7, 2, 8, 4]
8>4:交换位置->[5, 3, 1, 6, 7, 2, 4, 8]
可以看出,经过第一次扫描,数组的最大值8移动到了数组的最后一个位置,扫描的过程就是依次比较相邻元素的大小,再做交换。大的元素像气泡一样最终“升至”数组“顶端”。
同理,第二次扫描我们只需要对数组的前n-1个元素做相同的扫描即可,使得前n-1个元素的最大值(也就是整个数组的第二大元素)排在数组的倒数第二的位置。
可见,最多只需要n-1次扫描就能排列整个数组。这里,之所以说“最多”,是因为,假如第x次扫描没有发生元素交换位置(也就是说当前所扫描的数组是排好序的),那么,很显然,我们当然没有必要再进行之后的扫描。一个简单的例子是:数组[2, 1, 3, 4],经过第一次扫描,变成[1, 2, 3, 4];第二次扫描没有发生元素交换,说明已经排好序,就没有必要再进行第三次扫描了。
代码可以这样写:
def bubble_sort(aList):
# 用bool值Next判断是否还需要进行下一轮扫描
Next = True
n = len(aList)
while Next:
Next = False
for i in range(0, n - 1):
if aList[i] > aList[i + 1]:
# 交换位置
aList[i], aList[i + 1] = aList[i + 1], aList[i]
Next = True
n -= 1
选择排序(Selection Sort):
输入:乱序n长数组
输出:排序好的n长数组
时间复杂度:O(n^2)
空间复杂度:O(1)
从原理上讲,选择排序与冒泡排序是一样的,都是通过扫描数组找出数组的“最值”,然后将这些挑选出的元素依次排列。不同的是,冒泡是通过比较相邻元素的方法选出最大值,而选择排序是用更“简单粗暴”的方式选出最小值,将每次选出的最小值依次排列,从而完成排序。
举个简单例子:数组[3, 2, 1]。经过第一次扫描“选择”出最小值1,与数组的首元素3交换位置,变成[1, 2, 3],之后第二次扫描数组[2, 3]部分(显然,第一个元素已经安置好,不用扫描了),"选择"出[2, 3]部分最小元素(也是整个数组第二小元素)2,与2交换位置(这里是它本身,相当于不交换)。对于这个有三个元素的数组而言,需经过两次扫描,这一点与冒泡排序一样,都是对于n长数组需要进行n-1次排序,大家也就可以通过这一点计算出时间复杂度(过程我略了)。
那么,思路很清楚了,我们就能写出代码:
def selection_sort(aList):
begin = 0
n = len(aList)
# begin表示开始扫描的位置,也是放置每次选出的最小值的位置
while begin < n - 1:
# min_index代表最小值所在的位置
min_index = begin
for i in range(begin, n):
if aList[i] < aList[min_index]:
min_index = i
# 交换位置
aList[begin], aList[min_index] = aList[min_index], aList[begin]
begin += 1
这里,一样是个没有返回值的函数。其实,这两种排序算法都不难,但是细节处还是得搞清楚,否则,也不容易一遍就写对。
下一节,我们就来看看另一种排序方法:插入排序