vlambda博客
学习文章列表

你真的懂冒泡排序吗?

冒泡排序是最基础的排序算法之一,不管哪门编程语言,在学习数组的时候,都会遇到冒泡排序。今天就借助冒泡排序来开启我们的数据结构与算法之旅吧!

定义及原理

冒泡排序(Bubble Sort),是一种计算机科学领域的比较简单的排序算法。其原理如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们的位置

  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现

实现原理:
1、外循环控制排序进行的次数。
2、内循环进行每相邻两个元素之间的比较及交换;因为每次冒泡都会把最大的数下沉到最右边,再加上每次下标只需移动到数列的倒数第二个所以内循环range内的范围为n-i-1。

#原始代码
def BubbleSort(arr):
    n = len(arr)
    for i in range(n):                    
        for j in range(n-i-1):
            if arr[j] < arr[j+1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]

    return arr
arr = [3,4,5,6,7,8,1]
print(BubbleSort(arr))

冒泡排序优化一
优化原理:
在每轮排序前添加一个标记,初始值为True,在内循环的比较中,如果没有元素的交换,证明数列已然有序,此时判断该标记为True并跳出循环,排序结束。
优化点:减少外循环次数即排序次数。

def BubbleSort(arr):
    n = len(arr)
    for i in range(n):
        flag = True
        for j in range(n-i-1):
            if arr[j] > arr[j+1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]
                flag = False
        if flag:
            break
    return arr
arr = [3,4,5,6,7,8,1]
print(BubbleSort(arr))

冒泡排序优化二
优化原理:
记录每次最后交换元素的位置,每次内循环结束后,lastExchangeIndex后面的元素都是有序的了,所以下次内循环时不需要再去比较有序区的元素。
优化点:缩短每次内循环比较的数列长度。

def BubbleSort(arr):
    n = len(arr)
    lastExchangeIndex = 0
    sortedBorder = n-1

    for i in range(n):
        flag = True
        for j in range(sortedBorder):
            if arr[j] > arr[j+1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]
                flag = False
                lastExchangeIndex = j
        sortedBorder = lastExchangeIndex
        if flag:
            break
    return arr


arr = [3,4,2,1,5,6,7,8]
print(BubbleSort(arr))

优化三:鸡尾酒排序(双向冒泡)
优化原理:
原本的冒泡每次都是从左往右将最大的数冒泡到最右侧,上述代码在一次从左往右的冒泡后再从右往左将最小数冒泡到左侧依次来更大化地减少循环次数。

def Cocktail_sort(arr):
    n = len(arr)
    for i in range(n//2):
        falg = True
        for j in range(i,n-i-1):
            if arr[j] > arr[j+1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]
                falg = False
        if falg:
            break

        falg = True
        for j in range(n-i-1,i,-1):
            if arr[j] < arr[j-1]:
                arr[j],arr[j-1] = arr[j-1],arr[j]
                falg = False
        if falg:
            break
    return arr

arr = [3,4,5,6,7,8,1]
print('排序前的数组:')
print(arr)

Cocktail_sort(arr)

print('排序后的数组:')
print(arr)

鸡尾酒排序优化

def Cocktail_sort(arr):
    n = len(arr)
    #记录右侧最后一次交换的位置
    lastRightExchangeIndex = 0
    #记录左侧最后一次交换的位置
    lastLeftExchangeIndex = 0
    #无序数列的右边界,每次比较只需要比到这里为止
    rightSortBorder = n - 1
    #无序数列的左边界,每次比较只需要比到这里为止
    leftSortBorder = 0

    for i in range(n//2):
        isSorted = True
        for j in range(leftSortBorder,rightSortBorder):
            if arr[j] > arr[j+1]:
                arr[j],arr[j+1] = arr[j+1],arr[j]
                isSorted = False
                lastRightExchangeIndex = j
        rightSortBorder = lastRightExchangeIndex
        if isSorted:
            break


        isSorted = True
        for j in range(rightSortBorder,leftSortBorder,-1):
            if arr[j] < arr[j-1]:
                arr[j],arr[j-1] = arr[j-1],arr[j]
                isSorted = False
                lastLeftExchangeIndex = j
        leftSortBorder = lastLeftExchangeIndex
        if isSorted:
            break
    return arr

if __name__ == "__main__":
    arr = [7,5,9,6,8,2,1,3,4]
    print(Cocktail_sort(arr))

时间复杂度

最优情况:数据最初是就是有序的,所需比较次数C和记录移动次数M均达到最小值,即:

所以冒泡排序最好的时间复杂度为O(n)(前提是代码至少是优化过的,例如优化一的代码)

若数据是反序的,需要进行n-1趟排序,每趟排序要进行n-i次关键字的比较(1≤i≤-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:

解释:这里之所以乘3,是因为最坏的情况相邻两个元素交换需要执行3条指令:temp = a[i]、arr[i] = arr[i+1]、arr[i+1] = temp

冒泡排序的最坏的时间复杂度为O(n2)

综上,冒泡排序的平均时间复杂度为O(n2)

空间复杂度

可以在循环外面先定义一个临时变量用于元素的交换,由于整个程序都只用到该变量,所以冒泡排序空间复杂度为O(1)

算法稳定性

冒泡算法是把小的元素往前调或把大的元素往后调。比较是相邻的两个元素进行比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,也不会交换位置。所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。