数据结构与算法--排序算法:冒泡排序 多种方法让你彻底搞懂冒泡排序

排序的相关概念

排序算法(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)): 【备注3for 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]

'''
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值