在学习算法的初期,我们肯定会学习排序算法。提到排序算法,在比较排序的方式中,有一种算法的名字叫做冒泡排序(Bubble Sort),相信大家都非常了解。

冒泡排序是一种简单的排序算法,它的代码非常简洁,实现难度也很低,因此会成为我们学习算法的时候的当头炮。它是一种比较暴力的算法,即通过比较进而调整相邻两个对象的位置,每进行一次内循环,就会确定一个最大值的位置。废话不多说,直接上代码:

def bubble(arr):
    for i in range(0, len(arr)-1):
        print(f'Round {i+1}:')
        for j in range(0, len(arr)-i-1):
            print(f'before {j}: {arr[j]},{arr[j+1]}')
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
            print(f'after {j}: {arr[j]},{arr[j+1]}')
    return arr

很容易看出它的时间复杂度是O(n²)。经过对几个不同数组排序测试后,我们看到输出的before和after的两组信息常常在排序后期就不再改变,也就意味着数组已经排序成功,但遍历比较一直在进行着,做着无谓的浪费。基于这个问题,我们设置一个flag,初始值为False,即当进行一次内循环时,如果两个对象进行了交换,则设flag为True。在一次内循环后,如果flag仍为False, 则直接跳出循环,不再进行剩下的遍历比较。

def bubble_flag(arr):
    for i in range(0, len(arr)-1):
        flag = False   # wether swapped
        print(f'Round: {i+1}')
        for j in range(0, len(arr)-i-1):
            print(f'before {j}: {arr[j]},{arr[j+1]}')
            if arr[j]>arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                flag = True
            print(f'after {j}: {arr[j]},{arr[j+1]}')
        if not flag:
            break
    return arr

尽管,这种方式仍为O(n²)的时间复杂度,但无疑在很多时候会节省不少时间。

相比较下,梳排序(Comb Sort)的知名度低很多,并且它进行到最后,行为与冒泡排序完全一致。

首先,我们要设定一个递减率s为1.3 (由先人大量实验所得出的最好数值)。递减的数是数组的长度(n-1),直到递减到1。举个例子,待排序数组 A = [3, 6, 2, 9, 1, 7, 5, 11, 4, 8]

计算 j = max([j/s, 1]) = 9/1.3 = 6,因此我们将数组A的前四个对象和后四个对象一一比较并调整位置(其index差均为6)。

遍历调整过后,数组 A = [3, 6, 2, 8, 1, 7, 5, 11, 4, 9]。

再次计算 j = 6/1.3 = 4, 比较调整前六个对象和后六个对象(index差4),得数组 A = [1, 6, 2, 8, 3, 7, 5, 11, 4, 9].

j = 3, A = [1, 3, 2, 5, 6, 4, 8, 11, 7, 9]

j = 2, A = [1, 3, 2, 4, 6, 5, 7, 9, 8, 11]

j = 1,即进行一次与冒泡排序一样的操作(相邻两个对象比较并调整),A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]

在以上遍历中,因为一直都有位置调整并且除最后一次j均大于1,flag一直为True,因此j = 1的情况会再次进行。此时,由于没有位置调整,flag会是False跳出循环,排序结束。

可能通过我拙劣的文字描述你无法完全明白,我们再看一个wiki的gif, 加速一下对其的理解

Comb

示例代码:

def comb(arr):
    j = len(arr) - 1
    s = 1.3
    flag = False

    while j > 1 or flag == True:
        i = 0
        j = max([int(j/s), 1])
        flag = False
        while i + j <= len(arr)-1:
            if a[i] > a[i+j]:
                arr[i], arr[i+j] = arr[i+j],arr[i]
                flag = True
            i += 1
        print(f'{arr}:{flag}')

    return arr

经过上面的举例与代码,我们可以清楚知道,j值递减的每一次遍历中,比较的次数不会达到n次。由于递减率为1.3, 每次j值都会除以1.3, 直到j值为1,得出一共遍历得次数不会超过logn次。因此,梳排序得时间复杂度为O(n·logn),要快于冒泡排序很多,并且代码也很简洁。在下次选择排序算法时,梳排序不失为一个优先考虑的方法。

(代码均为Python实现)

如果有什么疑问,欢迎大家评论,一起探讨!!!祝愿大家在算法研究的道路上一帆风顺!