# 時間複雜度/二分搜 # 排序/資料結構 sprout-py 2021 --- 請問這個程式會執行多久? ```py= for i in range(n): sum = sum + i print(sum) ``` --- ```py= for i in range(n): sum = sum + i print(sum) ``` | 指令 | 次數 | | ------------- | ----- | | next(i) | n - 1 | | sum = sum + i | n | | print(sum) | 1 | --- | 指令 | 次數 | 花費時間 | | ------------- | ----- | --- | | next(i) | n - 1 | c1 | | sum = sum + i | n | c2 | | print(sum) | 1 | c3 | 總共:$c1 \times (n - 1) + c2 \times n + c3$ --- 每個指令的執行速度不盡相同 且需要考量的點很多 (不同架構、快取...) 因此在評估程式的效能時 通常可以無視這之間的差距 --- | 指令 | 次數 | 花費時間 | | ------------- | ----- | --- | | next(i) | n - 1 | c1 | | sum = sum + i | n | c2 | | print(sum) | 1 | c3 | 總共:$(n - 1) + n + 1 = 2n$ --- 請問這個程式會執行多久? ```py= for i in range(n): for j in range(n): sum = sum + i sum = sum + j ``` --- | 指令 | 次數 | | ------------- | ----------- | | next(i) | n - 1 | | next(j) | n * (n - 1) | | sum = sum + i | n * n | | sum = sum + j | n * n | --- 總共執行:$(n - 1) + n \times (n - 1) + n \times n + n \times n$ 也就是 $3n^2 - 1$ --- 比較一下這兩個多項式 $2n$ vs $3n^2 - 1$ 哪一個看起來比較大? n 我要帶多少進去? --- $100n + 1000$ vs $2^n + n$ n = 5? n = 12? --- 我們需要比較兩個多項式的漸進行為 因此衍生出了 Big-O 符號 --- ## Big-O $f(n) = O(g(n))$ 代表 $g(n)$ 是 $f(n)$ 的漸進上界 講白話一點就是 $f(n) = O(g(n))$ 代表當n夠大時 我們能找到一個常數 $c$ 使得 $c \times g(n)$ 大於 $f(n)$ --- 對於一個式子 我們取用次方數最高、n 很大時影響最大的項 並且將常數移除 當作時間複雜度 --- $3n + 2 => O(n)$ $3n^2 + 5n + 1 => O(n^2)$ $2^n + 5 => O(2^n)$ --- $2n => O(n)$ $3n^2 - 1 => O(n^2)$ --- How about this? ```py= for i in range(1, n + 1): for j in range(1, n - i + 2): sum = sum + i + j ``` --- ```py= for i in range(1, n + 1): for j in range(1, n - i + 2): sum = sum + i + j ``` $n + (n - 1) + (n - 2) + .... + 1 = \frac{(1 + n) \times n}{2} = \frac{n^2+n}{2}$ 時間複雜度:$O(n^2)$ --- How about this? ```py= for i in range(n + 1): sum = sum + i ``` vs ```py= sum = (1 + n) * n / 2 ``` --- 較差的時間複雜度在 $n$ 較小時 可能有較好的執行時間 ![](https://social.msdn.microsoft.com/Forums/getfile/329411) --- 電腦一秒約可以做 $10^8$ 次基本運算 因此可以將程式的時間複雜度算出來 帶入數字判斷量級是否小於 $10^8$ --- 有經驗的人可以透過測資大小直接推算 題目要求的程式的時間複雜度大概是什麼 | N | 複雜度 | | --- | ------ | | $10$ | $O(n!)$ | | $20$ | $O(2^n\times n)$ | | $2000$ | $O(n^2)$ | | $10^5$ | $O(nlogn)$ | | $10^7$ | $O(n)$ | | $10^{18}$ | $O(1)$ | --- 嘗試估估看以前寫過的題目中 你的程式碼的時間複雜度是多少呢 neoj 3029 --- ```python= ls = list(map(int, input().split())) k = int(input()) for i in range(k): num = int(input()) if num in ls: print("YES") else: print("NO") ``` --- ```python= ls = input().split() k = int(input()) for i in range(k): num = input() if num in ls: print("YES") else: print("NO") ``` --- ## 基礎排序 --- ## 生活中的例子 - 一堆散落的紙有頁碼,我們想把它重新排成一本書。 - 要把學生的成績由大到小排序。 - ........ --- 給你一個長度為n的序列 你要把他們由小到大排序 --- ## Swap 先介紹基於比較、交換的算法。 ```python= a, b = b, a ``` --- ## Selection Sort - 在序列中找到最小的元素,並將他與第一個元素交換。 - 在序列中找到第二小的元素(也就是撇除掉第一個元素後的最小的元素),並將他與第二個交換。 - 重複以上操作,直到排序好。 --- ## Selection Sort ```cpp def selection_sort(l): n = len(l) for i in range(n): mn = i for j in range(i+1, n): if l[j] < l[mn]: mn = j l[i], l[mn] = l[mn], l[i] ``` --- ## Bubble Sort - 從序列的最前面開始,一次比較陣列中兩兩相鄰的元素,然後根據將大的移到後面。 - 當我們比較過所有元素一次後,可以確保數值最大的元素在最後面。 - 接著扣掉陣列中的最後一個元素,接著重複上面的步驟進行兩兩比較。 --- ## Bubble Sort ```cpp def bubble_sorted(l): n = len(l) for i in range(n): for j in range(n - i - 1): if l[j] > l[j + 1]: l[j], l[j + 1] = l[j + 1], l[j] ``` --- ## Insertion Sort - 想像序列中有一部分是已經排序好的,那每次新增一個元素就去看他要插在排序好的序列的哪裡。 - 對於陣列中第 i 個值,假設 0 ~ i - 1 都排序好了,把 i 往左比較找到第一個小於他的元素並插在他右邊。 --- ## Insertion Sort ```python= def insertionSort(l): n = len(l) for i in range(n): key = l[i] j = i - 1 while j >= 0 and key < l[j]: l[j], l[j + 1] = l[j + 1], l[j] j -= 1 ``` --- ## Quick Sort [Quick Sort](http://alrightchiu.github.io/SecondRound/comparison-sort-quick-sortkuai-su-pai-xu-fa.html) --- ## Merge Sort [Merge Sort](http://alrightchiu.github.io/SecondRound/comparison-sort-merge-sorthe-bing-pai-xu-fa.html) --- ## 複雜度分析 - Bubble Sort - Best: $O(n^2)$, Worst $O(n^2)$ - Selection Sort - Best: $O(n^2)$, Worst $O(n^2)$ - Insertion Sort - Best: $O(n)$, Worst $O(n^2)$ - Quick Sort - Worst $O(n^2)$, Average $O(nlogn)$ - Merge Sort - $O(nlogn)$ --- 有複雜度比 $O(nlogn)$ 更好的排序方法嗎? --- 基於比較的排序演算法最好就是 $O(nlogn)$ [優質文章](https://tmt514.github.io/algorithm-analysis/sorting/comparison-based-sorting.html#定理-18) --- n = 100 時 Bubble sort vs quick sort ? --- ## 不基於比較的排序方法 - Counting Sort - Bucket sort - radix sort --- ## Counting Sort - 假如數值介於 0 ~ 999,我們就開一個長度為1000的陣列去記每一種數字出現幾次。 - 最後由小到大輸出 --- ## (stabled) Counting Sort ```python= def countingSort(l): n = len(l) tmp = [0] * n cnt = [0] * 100 for i in range(0, n): cnt[l[i]] += 1 for i in range(100): cnt[i] += cnt[i - 1] i = n - 1 while i >= 0: tmp[cnt[l[i]] - 1] = l[i] cnt[l[i]] -= 1 i -= 1 for i in range(0, n): l[i] = tmp[i] ``` --- ## Counting Sort 時間複雜度是 $O(n)$ 嗎? --- ## Counting Sort k 為數字範圍 時間複雜度 $O(n + k)$ --- {%youtube kPRA0W1kECg %} --- ## 二分搜 --- 猜數字 1 ~ 100 中猜我想的數字 我會告訴你我想的比你猜多還大或小 請問我最小猜幾次能猜到 --- 猜數字 1 ~ 100 中猜我想的數字 我會告訴你我想的比你猜多還大或小 請問我最小猜幾次能保證猜到? --- 100, 50, 25, 13, 7, 4, 2, 1 --- 給你遞增數列 $[0, 1, 2, 3, 6, 8, 9, 10]$ 求 $2$ 在哪 從左到右掃過陣列是 $O(N)$ 沒有用到遞增數列的性質! --- ### 觀察 $[0, 1, 2, 3, 6, 8, 9, 10]$ * 數字是升序的 --- $[?, ?, ?, [3], ?, ?, ?, ?]$ * 先問中間的數字 * 2 一定在 3 的左邊! --- $[?, ?, ?, 3, X, X, X, X]$ --- $[?, [1], ?, 3, X, X, X, X]$ * 再問中間的數字 * 2 一定在 1 的右邊! --- $[X, 1, ?, 3, X, X, X, X]$ --- $[X, 1, [2], 3, X, X, X, X]$ * 再問中間的數字... --- ```python def binary_search(arr, left, right, hkey): while left <= right: mid = left + (right - left) // 2 if arr[mid] == hkey: return mid elif arr[mid] < hkey: left = mid + 1 elif arr[mid] > hkey: right = mid - 1 return -1 } ``` --- 複雜度分析 $N$ 為序列長度 因為每一次可能的解答的集合都會減少一半 $N$ 一直除以$2$要除幾次才能等於 $1$ 因此複雜度為 $O(logN)$ --- ```python= from bisect import bisect_left def BinarySearch(a, x): i = bisect_left(a, x) if i != len(a) and a[i] == x: return i else: return -1 ``` --- 我們也可以利用二分搜的概念去逼近答案 Ex: 找一個數字開根號的值為多少 --- ```python= eps = 1e-9 def sqrt(n): l = 0 r = n while r - l >= eps: mid = (l + r) / 2 if mid * mid > n: r = mid else : l = mid return (l + r) / 2 ``` --- # 資料結構 --- ```python L = [1,2,3....] S = set(L) a in L a in S # 這兩個執行時間是一樣的嗎? ``` --- ```python res = [i for i in range(100000000)] S = set(res) L = list(res) a = 99999999 a in S a in L ``` --- https://wiki.python.org/moin/TimeComplexity --- ## Queue - FIFO(First In First Out) - enqueue 放入 queue 的尾端 - dequeue 把 queue 中最前面的取出來 ![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Data_Queue.svg/1200px-Data_Queue.svg.png =50%x) --- ```py= class Queue: def __init__(self): self.queue = [] def dequeue(self): char = self.queue[0] self.queue = self.queue[1:] return char def enqueue(self, char): self.queue.append(char) ``` --- 任何有排隊順序性的都有queue的框架在 - process queue - 訂單系統的 queue - ... --- ## Stack - FILO (First In Last Out) - push 把元素放在stack的最上面 - pop 把stack中最上面的元素取出來 ![](https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Data_stack.svg/1200px-Data_stack.svg.png =50%x) --- ```python= class Stack: def __init__(self): self.stack = [] def pop(self): return self.stack.pop() def push(self, char): self.stack.append(char) ``` --- 遞迴 vs stack ```python= def fib(n): if n == 1 or n == 2: return 1 return fib(n - 1) + fib(n - 2) ``` --- | stack | | -------- | | x | | x | | x | | x | | x | | x | --- | stack | | -------- | | x | | x | | x | | x | | x | | fib(5) | --- | stack | | -------- | | x | | x | | x | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | fib(1) | | fib(2) | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | fib(2) | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | x | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | fib(2) | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | fib(1) | | fib(2) | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | fib(3) | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | x | | fib(4) | | fib(5) | --- | stack | | -------- | | x | | x | | x | | x | | x | | fib(5) | --- | stack | | -------- | | x | | x | | x | | x | | x | | x | --- ## Linked-list ![](https://miro.medium.com/max/970/1*ZsQoOXgnSpaEDlUEVOoKgA.png) --- ![](https://miro.medium.com/max/1838/1*nDwDbeHwOz_Kl4zRIMbe_A.png) --- linked list vs array | Operation | linked list | Array | | --------- | ----------- | ----- | | i-th | O(n) | O(1) | | insert afther i-th | O(1) | O(n) | | delete i-th | O(1) | O(n) | --- {%youtube pKO9UjSeLew %} ---
{"metaMigratedAt":"2023-06-16T03:42:56.387Z","metaMigratedFrom":"Content","title":"時間複雜度/二分搜","breaks":true,"contributors":"[{\"id\":\"7692497a-be9a-4cb4-81b9-afb37e7453a8\",\"add\":14205,\"del\":3973}]"}
    440 views