程式設計師必須掌握的資料結構 1

SpikeKing發表於2019-03-03

無論是任何程式設計師,不論是演算法,還是其他,都需要掌握一定的資料結構。本文以最優雅的方式,基於Python,完成演算法,不要問,背下來就好。程式碼量更少,更好背。

原始碼github.com/SpikeKing/d…

第1篇 查詢和排序:二分查詢、氣泡排序、選擇排序、插入排序、希爾排序、歸併排序、快速排序。


1. 二分查詢

二分查詢,時間複雜度O(logn),排序一次,查詢多次,排序成本可以忽略;只查詢一次,則順序查詢比較好。非遞迴12行,遞迴10行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def binary_search(alist, item):
    """
    二分查詢,非遞迴
    1. 2個引數,待查詢alist和查詢項item
    2. 宣告2個變數,first,last
    3. while條件,first小於等於last
    4. mid是first和last的中值(整除);
    5. 三個if條件,相等alist[mid]=item,小於中值換last,大於中值換first;
    6. 預設返回False,12行
    :param alist: 待查詢alist
    :param item: 待查詢項item
    :return: 是否找到
    """
    first = 0
    last = len(alist) - 1
    while first <= last:
        mid = (first + last) // 2
        if alist[mid] == item:
            return True
        else:
            if alist[mid] > item:
                last = mid - 1
            else:
                first = mid + 1
    return False


def binary_search_re(alist, item):
    """
    二分查詢,遞迴
    1. if終止條件,長度為0,返回False;
    2. 中點是長度除2,中值判斷;
    3. 小於遞迴前半部分,大於遞迴後半部分,返回遞迴函式;
    4. 10行
    :param alist: 待查詢alist
    :param item: 待查詢項item
    :return: 是否找到
    """
    if len(alist) == 0:
        return False
    mid = len(alist) // 2
    if alist[mid] == item:
        return True
    else:
        if alist[mid] > item:
            return binary_search_re(alist[:mid], item)
        else:
            return binary_search_re(alist[mid + 1:], item)


def test_of_binary_search():
    test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42]
    print(binary_search(test_list, 3))
    print(binary_search(test_list, 13))
    print(binary_search_re(test_list, 3))
    print(binary_search_re(test_list, 13))


if __name__ == `__main__`:
    test_of_binary_search()
複製程式碼

2. 氣泡排序

氣泡排序,時間複雜度O(n^2),氣泡排序通常被認為是低效的排序方式。優勢是:識別排序列表,和提前終止排序。短氣泡排序就是提前終止的氣泡排序。氣泡排序4行,短氣泡排序8行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def bubble_sort(alist):
    """
    氣泡排序
    1. 兩次遍歷,第1次遍歷長度,倒序逐漸減1,每遍歷一次,排序一個;
    2. 第2次,正常遍歷,少1個值,因為i和i+1;
    3. 當前位大於下一位,交換當前位和下一位;
    4. 4行
    :param alist: 待排序列表
    :return: None,內部排序
    """
    for p_num in range(len(alist) - 1, 0, -1):
        for i in range(p_num):
            if alist[i] > alist[i + 1]:
                alist[i], alist[i + 1] = alist[i + 1], alist[i]


def bubble_sort_short(alist):
    """
    短氣泡排序,增加exchange,額外終止引數
    1. 初始為True,當為False時終止;
    2. 在第2次迴圈前,設定為False,交換一次就設定為True,一次未交換則觸發終止;
    3. 8行,增加5行的exchange操作
    :param alist:
    :return:
    """
    for p_num in range(len(alist) - 1, 0, -1):
        exchange = False
        for i in range(p_num):
            if alist[i] > alist[i + 1]:
                alist[i], alist[i + 1] = alist[i + 1], alist[i]
                exchange = True
        if not exchange:
            # print(`提前終止`)
            return

                
def test_of_bubble_sort():
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    # bubble_sort(alist)
    # print(alist)
    alist = [17, 20, 26, 93, 77, 31, 44, 55, 54]
    short_bubble_sort(alist)
    print(alist)


if __name__ == `__main__`:
    test_of_bubble_sort()
複製程式碼

3. 選擇排序

選擇排序,時間複雜度O(n^2),比較次數與氣泡排序相同,交換次數小於氣泡排序。
選擇排序6行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def selection_sort(alist):
    """
    選擇排序,即選擇最大值再交換
    1. 依然是2次遍歷,第1次反序,第2次正序,注意起始為1,末尾+1;
    2. max_loc儲存最大值,預設第0位;
    3. 當loc的值大於max_loc的值時,max_loc重新賦值;
    4. 交換loc和max_loc
    5. 6行
    :param alist: 待排序alist
    :return: None
    """
    for p_num in range(len(alist) - 1, 0, -1):
        max_loc = 0
        for i in range(1, p_num + 1):
            if alist[i] > alist[max_loc]:
                max_loc = i
        alist[p_num], alist[max_loc] = alist[max_loc], alist[p_num]


def tes_of_selection_sort():
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    selection_sort(alist)
    print(alist)


if __name__ == `__main__`:
    tes_of_selection_sort()
複製程式碼

4. 插入排序

插入排序,時間複雜度O(n^2),用移位代替交換,移位操作的時間大約是交換時間的1/3。插入排序7行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def insert_sort(alist):
    """
    插入排序,子序列逐漸有序
    1. 遍歷列表,儲存當前值cur_val,設定遊標pos
    2. 遊標大於0和遊標的值大於當前值,則移位,同時遊標減1;
    3. 將當前值賦予遊標的終止位置;
    4. 7行
    :param alist: 待排序alist
    :return: None
    """
    for i in range(1, len(alist)):
        cur_val = alist[i]
        pos = i  # 遊標
        while pos > 0 and alist[pos - 1] > cur_val:
            alist[pos] = alist[pos - 1]
            pos -= 1
        alist[pos] = cur_val  # 最後遊標的位置


def test_of_insert_sort():
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    insert_sort(alist)
    print(alist)


if __name__ == `__main__`:
    test_of_insert_sort()
複製程式碼

5. 希爾排序

希爾排序,時間複雜度,介於O(n)~O(n^2),也可以認為是O(n^3/2),插入排序的改進,比較和移位較少,每次遍歷都會生成一個”更有序”的子列表。12行,增量部分5行,插入部分7行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def shell_sort(alist):
    """
    希爾排序
    1. 兩部分,第1部分,計算增量gap和起始位置s_pos;
    2. 增量是累除2,s_pos是增量的遍歷
    3. 增量插入排序,額外傳入起始位置和增量;
    4. range的起始由起始位置+增量;
    5. 迴圈條件為大於等於增量,差值為增量
    6. 12行,增量部分5行,插入部分7行
    :param alist: 待排序alist
    :return: None
    """
    gap = len(alist) // 2
    while gap > 0:
        for s_pos in range(gap):
            insert_sort_gap(alist, s_pos, gap)
        gap = gap // 2


def insert_sort_gap(alist, s_pos, gap):
    """
    帶增量的插入排序
    :param alist: 待排序alist
    :param s_pos: 起始位置
    :param gap: 增量
    :return: None
    """
    for i in range(s_pos + gap, len(alist), gap):
        cur_val = alist[i]
        pos = i
        while pos >= gap and alist[pos - gap] > cur_val:
            alist[pos] = alist[pos - gap]
            pos -= gap
        alist[pos] = cur_val


def test_of_shell_sort():
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    shell_sort(alist)
    print(alist)


if __name__ == `__main__`:
    test_of_shell_sort()
複製程式碼

6. 歸併排序

歸併排序,時間複雜度O(nlogn)。20行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def merge_sort(alist):
    """
    歸併排序
    1. 遞迴,結束條件,只有1個元素,return;
    2. mid中心,左右兩部分,遞迴呼叫merge_sort;
    3. 遍歷左右,新增較小的值;遍歷其餘部分;
    4. 20行
    :param alist:
    :return:
    """
    if len(alist) < 2:
        return
    mid = len(alist) // 2
    left = alist[:mid]
    right = alist[mid:]
    merge_sort(left)
    merge_sort(right)
    i, j, k = 0, 0, 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            alist[k] = left[i]
            i += 1
        else:
            alist[k] = right[j]
            j += 1
        k += 1
    if i < len(left):
        alist[k:] = left[i:]
    if j < len(right):
        alist[k:] = right[j:]


def test_of_merge_sort():
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    merge_sort(alist)
    print(alist)


if __name__ == `__main__`:
    test_of_merge_sort()
複製程式碼

7. 快速排序

快速排序,時間複雜度O(nlogn)。不需要額外空間14行,需要額外空間7行。

# 原始碼: https://github.com/SpikeKing/data_structure_with_python
def quick_sort(alist, fst, lst):
    """
    快速排序
    1. 確定終止條件,起始大於等於終止;
    2. 起始fst和終止lst的位置,樞軸值pivot是第1個值;
    3. 遍歷i和j,i是第2個,j是最後一個;
    4. 迴圈交換,直到i和j交叉;
    5. 樞軸索引取i和j最小的1個;
    6. 交換樞軸位置的值與起始位置的值;
    7. 遞迴呼叫左右兩部分;
    8. 16行
    :param alist: 待排序列表
    :param fst: 起始idx
    :param lst: 終止idx
    :return: None
    """
    if fst >= lst:
        return
    pivot = alist[fst]
    i, j = fst + 1, lst
    while i < j:
        while alist[i] < pivot:
            i += 1
        while alist[j] > pivot:
            j -= 1
        if i < j:
            alist[i], alist[j] = alist[j], alist[i]
            i, j = i + 1, j - 1
    p_idx = min(i, j)  # 樞軸索引
    alist[fst], alist[p_idx] = alist[p_idx], alist[fst]
    quick_sort(alist, fst, p_idx - 1)
    quick_sort(alist, p_idx + 1, lst)


def quick_sort_v2(alist):
    """
    快速排序,需要額外空間
    :param alist: 待排序列表
    :return: 排序列表
    """
    if len(alist) <= 1:
        return alist
    pivot = alist[0]
    small = [i for i in alist if i < pivot]
    mid = [i for i in alist if i == pivot]
    large = [i for i in alist if i > pivot]
    return quick_sort_v2(small) + mid + quick_sort_v2(large)
複製程式碼

OK, that`s all! Enjoy it!

相關文章