【Python入門基礎】演算法與資料結構
阿新 • • 發佈:2020-12-20
技術標籤: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()