排序算法(Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法
排序算法的稳定性
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。
也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,
在排序过的列表中R也将会是在S之前。
当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,
假设以下的数对将要以他们的第一个数字来排序。
(4, 1) (3, 1) (3, 7)(5, 6)
在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,
而另外一个则没有:
(3, 1) (3, 7) (4, 1) (5, 6) (维持次序)稳定
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)不稳定
不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。
不稳定排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,
如此在其他方面相同键值的两个对象间之比较,(比如上面的比较中加入第二个标准:第二个键值的大小)
就会被决定使用在原先数据次序中的条目,当作一个同分决赛。
然而,要记住这种次序通常牵涉到额外的空间负担。
十大排序算法的总结
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。
它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。
这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
那么我们需要进行n-1次冒泡过程,每次对应的比较次数如下图所示:
法一:通常方法
def bubble_sort(li):
for j in range(len(li) - 1): #【备注1】写成 for j in range(len(li)) 也行
# 取(0,n-1),产生 0~n-2 总共n-1个数,只需比较前一个和后一个的关系
for i in range(len(li) - 1 - j): # 需要两两比较的次数
if li[i] > li[i + 1]:
li[i], li[i + 1] = li[i + 1], li[i]
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
法二:过程和次数 联动
def bubble_sort(alist):
for j in range(len(alist) - 1, 0, -1): # 【备注2】写成 for j in range(len(alist) - 1, -1, -1) 也行
# j表示每次遍历需要比较的次数,是逐渐减小的
for i in range(j):
if alist[i] > alist[i + 1]:
alist[i], alist[i + 1] = alist[i + 1], alist[i]
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
法三:从最后一个数和它前面的数依次比较
def bubble_sort(array):
for i in range(len(array)): 【备注3】 for i in range(len(array-1)) 也行
flag = False
for j in range(len(array) - 1, i, -1): # 注意 每次 都到 一个 i
if array[j] < array[j - 1]:
array[j - 1], array[j] = array[j], array[j - 1]
flag = True
print('测试本行是否执行')
if flag is False: # if not flag:
return
array = [1, 5, 3, 4, 8, 6, 7]
bubble_sort(array)
print(array)
解释上面两个【备注】对于这个外层界限,其实是关于 当数列只有两个 元素时,需不需要比较都是可以的,两个元素没有严格意义的存在排序,这是对于 过程数来说,多一次和少一次是不重要的;重要的是:内层最后比较的两个数,一定是要到 最尾端 前一个数,这样才存在 前后关系
最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束)
最坏时间复杂度:O(n2)
稳定性:稳定
冒泡排序对n个数据操作n-1轮,每轮找出一个最大(小)值。
操作只对相邻两个数比较与交换,每轮会将一个最值交换到数据列首(尾),像冒泡一样。
每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。
额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)
过程示意图
'''
每次比较的次数都递减,整个过程总次数是要进行最坏程度时的次数
0 1 2 3 4 5 6 7 1
0 1 2 3 4 5 6 2
0 1 2 3 4 5 .
0 1 2 3 4 .
0 1 2 3
0 1 2
0 1
0 n-1
for i in range(n-1)
for j in range(n-1-j)
或者:
for i in range(n-1,0,-1)
for j in range(i)
'''
未优化时,当检测一次从头走到尾,发现不需要进行任何交换,说明已经是有序了,
此时时间复杂度为O(n)
冒泡排序优化一
def bubble_sort(li):
for j in range(len(li) - 1):
count = 0 # 记录交换的次数
for i in range(len(li) - 1 - j):
if li[i] > li[i + 1]:
li[i], li[i + 1] = li[i + 1], li[i]
count += 1 # 只有在满足判断条件才会被计数
if count == 0:
return
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
'''
说明:上述,因为是在给列表排序,列表只要内部改变了,外部也会跟着改变;但是上述给的终止条件,直接return,虽然能正常排序,但是存在错误!不妨 试一下这样写也可以!
def bubble_sort(li):
for j in range(len(li) - 1):
count = 0 # 记录交换的次数
for i in range(len(li) - 1 - j):
if li[i] > li[i + 1]:
li[i], li[i + 1] = li[i + 1], li[i]
count += 1 # 只有在满足判断条件才会被计数
if count == 0:
break
# return li 或者同时在外层循环加上返回列表也行
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
【不妨尝试做一下这题,就能发现出错的情况】
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,
并保证奇数和奇数,偶数和偶数之间的相对位置不变。
这道题可以用冒泡排序的思维解答:
def reorder(array):
for i in range(len(array)):
flag = False # 设置一个falg
for j in range(len(array) - 1, i, -1):
# 后奇前偶再交换
if array[j] % 2 == 1 and array[j - 1] % 2 == 0:
array[j - 1], array[j] = array[j], array[j - 1]
flag = True # 如果发生的交换,置为True
if flag is False: # 如果一次过程下来,还是False,说明已经排好,后序可以不用进行了
break # 跳出外层for循环,不可以用return,return会直接退出函数
return array
array = [10, 8, 5, 2, 3, 4, 6]
print(reorder(array))
'''
# 规范写法:
def bubble_sort(li):
for j in range(len(li) - 1):
count = 0 # 记录交换的次数
for i in range(len(li) - 1 - j):
if li[i] > li[i + 1]:
li[i], li[i + 1] = li[i + 1], li[i]
count += 1 # 只有在满足判断条件才会被计数
if count == 0: # 判断一个过程后,发现都没有发生变化,那么后序也不用判断了,跳出循环即可
break
return li # 返回排序好的列表
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
'''
# 用 flag 是一样的道理
def bubble_sort(items):
for i in range(len(items) - 1):
flag = False
for j in range(len(items) - 1 - i):
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
flag = True
if not flag: # if flag is False:
break
return items
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
'''
冒泡排序优化 二:搅拌排序/鸡尾酒排序
上面这种写法还有一个问题,就是每次都是从左边到右边进行比较,这样效率不高,
你要考虑当最大值和最小值分别在两端的情况。写成双向排序提高效率,
即当一次从左向右的排序比较结束后,立马从右向左来一次排序比较。
def bubble_sort(items):
for i in range(len(items) - 1):
flag = False
for j in range(len(items) - 1 - i):
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
flag = True
if flag:
flag = False
for j in range(len(items) - 2 - i, 0, -1):
if items[j - 1] > items[j]:
items[j], items[j - 1] = items[j - 1], items[j]
flag = True
if not flag:
break
return items
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print(li)
bubble_sort(li)
print(li)
冒泡排序优化三
最后要考虑的情况就是,如果给你的不是列表,而是对象,或者列表里面都是字符串,
那么上述的代码也就没有用了,这时候你就要自定义函数了,并将其当成参数传入bubble_sort函数
输出结果:[‘pear’, ‘apple’, ‘pitaya’, ‘waxberry’, ‘watermelon’]
类似的,当给一个类对象排序时,也可以传入lambda 自定义函数
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'{self.name}: {self.age}'
items1 = [
Student('Wang Dachui', 25),
Student('Di ren jie', 38),
Student('Zhang Sanfeng', 120),
Student('Bai yuanfang', 18)
]
print(bubble_sort(items1, lambda s1, s2: s1.age > s2.age))
'''
输出结果:按照年龄从小到大排序
[Bai yuanfang: 18, Wang Dachui: 25, Di ren jie: 38, Zhang Sanfeng: 120]
'''