1. 程式人生 > 其它 >【Python入門基礎】演算法與資料結構

【Python入門基礎】演算法與資料結構

技術標籤:Python學習演算法資料結構python內建模組動態規劃

演算法與資料結構

  • 評價演算法的好壞:漸近時間複雜度和漸近空間複雜度。

時間複雜度

  • 漸近時間複雜度的大O標記:
    • - 常量時間複雜度 - 布隆過濾器 / 雜湊儲存
    • - 對數時間複雜度 - 折半查詢(二分查詢)
    • - 線性時間複雜度 - 順序查詢 / 桶排序
    • - 對數線性時間複雜度 - 高階排序演算法(歸併排序、快速排序)
    • - 平方時間複雜度 - 簡單排序演算法(選擇排序、插入排序、氣泡排序)
    • - 立方時間複雜度 - Floyd演算法 / 矩陣乘法運算
    • - 幾何級數時間複雜度 - 漢諾塔
    • - 階乘時間複雜度 - 旅行經銷商問題 - NP

排序演算法

簡單排序

def select_sort(origin_items, comp=lambda x, y: x < y):
    """簡單選擇排序"""
    items = origin_items[:]
    for i in range(len(items) - 1):
        min_index = i
        for j in range(i + 1, len(items)):
            if comp(items[j], items[min_index]):
                min_index =
j items[i], items[min_index] = items[min_index], items[i] return items

雞尾酒排序(攪拌排序)

def bubble_sort(origin_items, comp=lambda x, y: x > y):
    """高質量氣泡排序(攪拌排序)"""
    items = origin_items[:]
    for i in range(len(items) - 1):
        swapped = False
        # 最大值放在後面
for j in range(i, len(items) - 1 - i): if comp(items[j], items[j + 1]): items[j], items[j + 1] = items[j + 1], items[j] swapped = True if swapped: # 可能已排序好,提前終止遍歷 swapped = False # 最小值放在前面 for j in range(len(items) - 2 - i, i, -1): if comp(items[j - 1], items[j]): items[j], items[j - 1] = items[j - 1], items[j] swapped = True if not swapped: break return items

歸併排序

def merge_sort(items, comp=lambda x, y: x <= y):
    """歸併排序(分治法)"""
    if len(items) < 2:
        return items[:]
    mid = len(items) // 2
    left = merge_sort(items[:mid], comp)
    right = merge_sort(items[mid:], comp)
    return merge(left, right, comp)


def merge(items1, items2, comp):
    """合併(將兩個有序的列表合併成一個有序的列表)"""
    items = []
    index1, index2 = 0, 0
    while index1 < len(items1) and index2 < len(items2):
        if comp(items1[index1], items2[index2]):
            items.append(items1[index1])
            index1 += 1
        else:
            items.append(items2[index2])
            index2 += 1
    items += items1[index1:]
    items += items2[index2:]
    return items

快速排序

"""
快速排序 - 選擇樞軸對元素進行劃分,左邊都比樞軸小右邊都比樞軸大
"""
def quick_sort(origin_items, comp=lambda x, y: x <= y):
    items = origin_items[:]
    _quick_sort(items, 0, len(items) - 1, comp)
    return items


def _quick_sort(items, start, end, comp):
    if start < end:
        pos = _partition(items, start, end, comp)
        _quick_sort(items, start, pos - 1, comp)
        _quick_sort(items, pos + 1, end, comp)


def _partition(items, start, end, comp):
    pivot = items[end]
    i = start - 1
    for j in range(start, end):
        if comp(items[j], pivot):
            i += 1
            items[i], items[j] = items[j], items[i]
    items[i + 1], items[end] = items[end], items[i + 1]
    return i + 1

查詢演算法

順序查詢

def seq_search(items, key):
    """順序查詢"""
    for index, item in enumerate(items):
        if item == key:
            return index
    return -1

折半查詢(二分查詢)

def bin_search(items, key):
    """折半查詢"""
    start, end = 0, len(items) - 1
    while start <= end:
        mid = (start + end) // 2
        if key > items[mid]:
            start = mid + 1
        elif key < items[mid]:
            end = mid - 1
        else:
            return mid
    return -1

內建模組

heapq模組

  一種著名的資料結構是堆(heap),它是一種優先佇列。優先佇列讓你能夠以任意順序新增物件,並隨時(可能是在兩次新增物件之間)找出(並刪除)最小的元素。相比於列表方法min,這樣做的效率要高得多。實際上,Python沒有獨立的堆型別,而只有一個包含一些堆操作函式的模組。這個模組名為heapq(其中的q表示佇列)。
模組heapq中的一些重要函式

函式描述
heappush(heap,x)將x壓入堆中
heappop(heap)從堆中彈出最小的元素
heapify(heap)讓列表具備堆特徵
heapreplace(heap,x)彈出最小的元素,並將x壓入堆中
nlargest(n,iter)返回iter中n個最大的元素
nsmallest(n,iter)返回iter中n給最小的元素
"""
從列表中找出最大的或最小的N個元素
堆結構(大根堆/小根堆)
"""
import heapq

list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
list2 = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]
print(heapq.nlargest(3, list1))
print(heapq.nsmallest(3, list1))
print(heapq.nlargest(2, list2, key=lambda x: x['price']))
print(heapq.nlargest(2, list2, key=lambda x: x['shares']))

在這裡插入圖片描述

itertools模組

"""
迭代工具 - 排列 / 組合 / 笛卡爾積
"""
import itertools

itertools.permutations('ABCD')
itertools.combinations('ABCDE', 3)
itertools.product('ABCD', '123')

collections模組

Python3 collections模組使用詳解包括Counter,deque,defaultdict,namedtuple,OrderedDict等

常用演算法

窮舉法

又稱為暴力破解法,對所有的可能性進行驗證,直到找到正確答案。如百錢百雞,五人分魚等。

     # 公雞5元一隻 母雞3元一隻 小雞1元三隻
     # 用100元買100只雞 問公雞/母雞/小雞各多少隻
     for x in range(20):
         for y in range(33):
             z = 100 - x - y
             if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0:
                 print(x, y, z)
     
     # A、B、C、D、E五人在某天夜裡合夥捕魚 最後疲憊不堪各自睡覺
     # 第二天A第一個醒來 他將魚分為5份 扔掉多餘的1條 拿走自己的一份
     # B第二個醒來 也將魚分為5份 扔掉多餘的1條 拿走自己的一份
     # 然後C、D、E依次醒來也按同樣的方式分魚 問他們至少捕了多少條魚
     fish = 6
     while True:
         total = fish
         enough = True
         for _ in range(5):
             if (total - 1) % 5 == 0:
                 total = (total - 1) // 5 * 4
             else:
                 enough = False
                 break
         if enough:
             print(fish)
             break
         fish += 5

貪心演算法(貪婪法)

在對問題求解時,總是做出在當前看來最好的選擇,不追求最優解,快速找到滿意解。典型例子是揹包問題

     """
     貪婪法:在對問題求解時,總是做出在當前看來是最好的選擇,不追求最優解,快速找到滿意解。
     輸入:
     20 6
     電腦 200 20
     收音機 20 4
     鍾 175 10
     花瓶 50 2
     書 10 1
     油畫 90 9
     """
     class Thing(object):
         """物品"""
     
         def __init__(self, name, price, weight):
             self.name = name
             self.price = price
             self.weight = weight
     
         @property
         def value(self):
             """價格重量比"""
             return self.price / self.weight
     
     
     def input_thing():
         """輸入物品資訊"""
         name_str, price_str, weight_str = input().split()
         return name_str, int(price_str), int(weight_str)
     
     
     def main():
         """主函式"""
         max_weight, num_of_things = map(int, input().split())
         all_things = []
         for _ in range(num_of_things):
             all_things.append(Thing(*input_thing()))
         all_things.sort(key=lambda x: x.value, reverse=True)
         total_weight = 0
         total_price = 0
         for thing in all_things:
             if total_weight + thing.weight <= max_weight:
                 print(f'小偷拿走了{thing.name}')
                 total_weight += thing.weight
                 total_price += thing.price
         print(f'總價值: {total_price}美元')
     
     
     if __name__ == '__main__':
         main()

分治法

把一個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題,直到可以直接求解的程度,最後將子問題的解進行合併得到原問題的解。如上面講到的歸併排序,以及快速排序等。

回溯法

回溯法又稱為試探法,按選優條件向前搜尋,當搜尋到某一步發現原先選擇並不優或達不到目標時,就退回一步重新選擇。
騎士巡邏,八皇后,迷宮尋路

"""
     遞歸回溯法;騎士巡邏
     """
     import sys
     import time
     
     SIZE = 5
     total = 0
     
     
     def print_board(board):
         for row in board:
             for col in row:
                 print(str(col).center(4), end='')
             print()
     
     
     def patrol(board, row, col, step=1):
         if row >= 0 and row < SIZE and \
             col >= 0 and col < SIZE and \
             board[row][col] == 0:
             board[row][col] = step
             if step == SIZE * SIZE:
                 global total
                 total += 1
                 print(f'第{total}種走法: ')
                 print_board(board)
             patrol(board, row - 2, col - 1, step + 1)
             patrol(board, row - 1, col - 2, step + 1)
             patrol(board, row + 1, col - 2, step + 1)
             patrol(board, row + 2, col - 1, step + 1)
             patrol(board, row + 2, col + 1, step + 1)
             patrol(board, row + 1, col + 2, step + 1)
             patrol(board, row - 1, col + 2, step + 1)
             patrol(board, row - 2, col + 1, step + 1)
             board[row][col] = 0
     
     
     def main():
         board = [[0] * SIZE for _ in range(SIZE)]
         patrol(board, SIZE - 1, SIZE - 1)
     
     
     if __name__ == '__main__':
         main()

動態規劃

基本思想也是將待求解問題分解成若干個子問題,先求解並儲存這些子問題的解,避免產生大量的重複運算。

"""
     斐波拉切數列,不使用動態規劃將會是幾何級數複雜度
     """
     def fib(num, temp={}):
         """用遞迴計算Fibonacci數"""
         if num in (1, 2):
             return 1
         try:
             return temp[num]
         except KeyError:
             temp[num] = fib(num - 1) + fib(num - 2)
             return temp[num]

子列表元素之和的最大值

說明:子列表指的是列表中索引(下標)連續的元素構成的列表;列表中的元素是int型別,可能包含正整數、0、負整數;程式輸入列表中的元素,輸出子列表元素求和的最大值,例如:

def main():
    items = list(map(int, input().split()))
    size = len(items)
    overall, partial = {}, {}
    overall[size - 1] = partial[size - 1] = items[size - 1]
    for i in range(size - 2, -1, -1):
        partial[i] = max(items[i], partial[i + 1] + items[i])
        overall[i] = max(partial[i], overall[i + 1])
    print(overall[0])


if __name__ == '__main__':
    main()