nowcoder basic algorithm chapter 2
劃分問題和快排
普通劃分
給定一個數組arr, 和一個數num, 請把小於等於num的數放在數組的左邊, 大於num的數放在數組的右邊。要求額外空間復雜度O(1), 時間復雜度O(N)
想法是這樣的,維持一個index,表示在index以及以前的數字都是小於等於num的。
def split_array(arr, num): """維持一個小於等於區域的index, 如果一個數小於等於num,則和index位置交換 如果一個數大於num,則直接跳下一個 """ i = -1 for j in range(len(arr)):if arr[j] > num: pass else: i += 1 arr[j], arr[i] = arr[i], arr[j] return arr
荷蘭國旗問題
給定一個數組arr, 和一個數num, 請把小於num的數放在數組的左邊, 等於num的數放在數組的中間, 大於num的數放在數組的右邊。 要求額外空間復雜度O(1), 時間復雜度O(N)
思路是這樣的,維持兩個index,一個less,一個more
def split_array(arr, num):"""總體的策略是,遇到小的數字,那麽把它往前面移動,遇到大的數字,把它讓後面移動,遇到相等的數字,不動 維持兩個三個變量來進行交換 less , more , current 其中more的值應該為len(arr),這樣在判斷的時候多判斷一次,並且j-=1寫在後面 more要等於len(arr),並且運算的時候先要減一,否則會出現這種情況: [7,6,4,1,2,5,4] 變為[2, 4, 4, 1, 5, 6, 7],因為這種情況下面並沒有對中間的1進行排序,少了一次排序的次數 """ current = 0 less= -1 more = len(arr) while(current < more): if arr[current] < num: # 如果小於,那麽less和current要進行交換,並且都推進一位 less += 1 arr[less], arr[current] = arr[current], arr[less] current += 1 elif arr[current] == num: # 如果等於,那麽直接向下進行 current += 1 else: more -= 1 arr[current], arr[more] = arr[more], arr[current] return arr
快排算法
由劃分問題引出來的快排算法,用一個數將數組劃分為兩部分,然後剩下的兩部分繼續劃分
import random def quick_sort(arr): """快排散發 時間復雜度:隨機快排是一種隨機算法,使用期望來求解,時間復雜度為O(NlogN) 空間復雜度: O(logN) 穩定性: 非穩定的,由於是隨機選擇, 比如有兩個相等的數,可能選擇左邊的,然後右邊的會放到它的左邊 """ sort_range(arr, 0, len(arr)-1) def sort_range(arr, left, right): if left < right: position = split_array(arr, left, right) sort_range(arr, left, position[0]-1) sort_range(arr, position[1]+1, right) def split_array(arr, left, right): """遇到小與num的數,那麽把它往前面移動,遇到大的數字,把它讓後面移動,遇到相等的數字,繼續向後走。 less = left-1 與 more = right +1 在less下標左邊的數字(包含less)都是比num要小的, 在more右邊的數字(包含more)都是比more要大的 隨機快排的時間復雜度: O(nlogn) 空間復雜度: O(logn) 使用隨機算法,這個復雜度是求隨機的期望得到的 Args: left 表示需要排序的左區間,包含left right 需要排序的由區間 包含right Returns: [less+1, more-1] 一個含有兩個數的裏列表,表示劃分的num的坐下標和右下標 如排序後的[2,1,4,4,5,6,7]返回[2,3] """ num = arr[random.randint(left, right)] current = left less = left -1 more = right + 1 while(current < more): if arr[current] < num: # 如果小於,那麽less和current要進行交換,並且都推進一位 less += 1 arr[less], arr[current] = arr[current], arr[less] current += 1 elif arr[current] == num: # 如果等於,那麽直接向下進行 current += 1 else: more -= 1 arr[current], arr[more] = arr[more], arr[current] return [less+1 , more-1]
堆結構
一個數組可以看做是一顆完全二叉樹。 節點標號是從0開始的,其中父節點和子節點的關系:如果父節點為i,那麽左子節點為 2*i+1, 右子節點為2*i+2。 若果子節點為i,那麽父節點為(i-1)/2,對於0也使用,前提是除法是向0舍入。
大根堆和小根堆:大根堆是一個節點的值總是比它下面節點所有的值都大,這樣根節點的值為最大值。小根堆則剛好相反。
如何將數組轉換為大根堆:
思路是這樣的,對於數組節點都做一次調整,如果它比父節點大,那麽和父節點交換,直到交換到根節點。
def heap_insert(arr, current_index): father_index = int((current_index - 1) / 2) while(arr[father_index] < arr[current_index]): arr[father_index], arr[current_index] = arr[current_index], arr[father_index] current_index = father_index father_index = int((current_index - 1) / 2) x = [2, 1, 3, 6, 0, 4] for i in range(len(x)): heap_insert(x, i)
大根堆的下沈操作:
假如有一個節點的值變小了,那麽如何調整這個數組,使得它依然是一個堆,這個過程叫做下沈。思路是這樣的:如果是左子樹不存在,那麽它是葉子節點,不做調整。判斷右子樹存在嗎?然後找出左子樹和右子樹的最大值,如果當前值比最大值大,那麽退出,否則進行交換,然後重復這樣的操作。
def heapipy(arr, current_index, heap_size): """在大根堆的情況下,如果一個節點變小情況下的 向下調整過程 Args: arr: 要調整的數組 current_index: 要替換的下標 heap_size: 數組的長度(堆的大小) """ left_index = 2 * current_index + 1 while(left_index < heap_size): right_index = left_index + 1 if right_index < heap_size: largest = left_index if arr[left_index] >= arr[right_index] else right_index else: largest = left_index largest = largest if arr[largest] > arr[current_index] else current_index if largest == current_index: break else: arr[largest], arr[current_index] = arr[current_index], arr[largest] current_index = largest left_index = 2*current_index + 1
堆排序:
利用上面兩個特性,首先來構造根,然後是將末尾的數和第一個數交換,然後第一個數下沈一下。
def heapipy(arr, current_index, heap_size): """在大根堆的情況下,如果一個節點變小情況下的 向下調整過程 Args: arr: 要調整的數組 current_index: 要替換的下標 heap_size: 數組的長度(堆的大小) """ left_index = 2 * current_index + 1 while(left_index < heap_size): right_index = left_index + 1 if right_index < heap_size: largest = left_index if arr[left_index] >= arr[right_index] else right_index else: largest = left_index largest = largest if arr[largest] > arr[current_index] else current_index if largest == current_index: break else: arr[largest], arr[current_index] = arr[current_index], arr[largest] current_index = largest left_index = 2*current_index + 1 def heap_insert(arr, current_index): """在數組指定的位置插入值,一般是末尾值,這樣能夠不斷的構建大堆""" father_index = int((current_index - 1) / 2) while(arr[father_index] < arr[current_index]): arr[father_index], arr[current_index] = arr[current_index], arr[father_index] current_index = father_index father_index = int((current_index - 1) / 2) def heap_sort(arr): """堆排序 時間復雜度:建堆的時間復雜度為O(n), 調整過程的時間復雜度為O(nlogn),所以總體復雜度為O(nlogn) 空間復雜度: O(1) 只是交換的時候用了一下額外空間 穩定性: 不穩定, 比如[4a,4b,4c,5]建堆的時候發生錯誤,變為【5,4a,4c,4b],排序後變為【5,4b,4c,4a】 [9,5A,7,5B]在放入堆的時候會發生錯誤 """ # 建立堆棧的過程 for i in range(len(arr)): heap_insert(arr, i) arr_length = len(arr) arr[0], arr[-1] = arr[-1], arr[0] arr_length -= 1 while(arr_length > 0): heapipy(arr, 0, arr_length) arr[0], arr[arr_length-1] = arr[arr_length-1], arr[0] arr_length -= 1
堆的應用:求不斷產生數的中位數
由一個數組產生器不斷的產生一組數,在產生的過程當中求這些數的中位數。
暴力解法:每求一個數,然後將數組進行一些排序,求中位數,時間復雜度為O(NlogN)
使用堆結構:使用一個大根堆和一個小根堆,兩個堆存放的數量相差不會超過1,這樣大根堆所有的數小於小根堆所有的數,中位數在大根堆的堆頂或者小根堆的堆頂。
【關於如何彈出一個堆的最大值,或者最小值】,有一個很好的方法是,【1:第一個數和最後一個數交換,2彈出最後一個數,3:向下調整第一個數】
代碼如下:
import random def heapipy(arr, current_index, heap_size): """在大根堆的情況下,如果一個節點變小情況下的 向下調整過程 Args: arr: 要調整的數組 current_index: 要替換的下標 heap_size: 數組的長度(堆的大小) """ left_index = 2 * current_index + 1 while(left_index < heap_size): right_index = left_index + 1 if right_index < heap_size: largest = left_index if arr[left_index] >= arr[right_index] else right_index else: largest = left_index largest = largest if arr[largest] > arr[current_index] else current_index if largest == current_index: break else: arr[largest], arr[current_index] = arr[current_index], arr[largest] current_index = largest left_index = 2*current_index + 1 def small_heapipy(arr, current_index, heap_size): """在大根堆的情況下,如果一個節點變小情況下的 向下調整過程 Args: arr: 要調整的數組 current_index: 要替換的下標 heap_size: 數組的長度(堆的大小) """ left_index = 2 * current_index + 1 while(left_index < heap_size): right_index = left_index + 1 if right_index < heap_size: small = left_index if arr[left_index] <= arr[right_index] else right_index else: small = left_index small = small if arr[small] < arr[current_index] else current_index if small == current_index: break else: arr[small], arr[current_index] = arr[current_index], arr[small] current_index = small left_index = 2*current_index + 1 def big_heap_insert(arr, current_index): father_index = int((current_index - 1) / 2) while(arr[father_index] < arr[current_index]): arr[father_index], arr[current_index] = arr[current_index], arr[father_index] current_index = father_index father_index = int((current_index - 1) / 2) def small_heap_insert(arr, current_index): father_index = int((current_index - 1) / 2) while(arr[father_index] > arr[current_index]): arr[father_index], arr[current_index] = arr[current_index], arr[father_index] current_index = father_index father_index = int((current_index - 1) / 2) small_heap = [] big_heap = [] for i in range(100): x = random.randint(1,100) if not big_heap: big_heap.append(x) else: if x <= big_heap[0]: big_heap.append(x) big_heap_insert(big_heap,len(big_heap)-1) else: small_heap.append(x) small_heap_insert(small_heap, len(small_heap)-1) if len(big_heap) - len(small_heap) == 2: big_heap[0], big_heap[-1] = big_heap[-1], big_heap[0] tmp = big_heap.pop() heapipy(big_heap, 0, len(big_heap)) small_heap.append(tmp) # 有代碼重復的意思在裏面 small_heap_insert(small_heap, len(small_heap)-1) elif len(small_heap) - len(big_heap) == 2: small_heap[0], small_heap[-1] = small_heap[-1], small_heap[0] tmp = small_heap.pop() small_heapipy(small_heap,0, len(small_heap)) big_heap.append(tmp) big_heap_insert(big_heap,len(big_heap)-1)
桶排序:
計數排序
def counting_sort(arr, arr_size): """計數排序 它是非比較的排序算法 N=len(arr) K = arr_size 時間復雜度: O(N+K) 空間復雜度: O(N+K) 穩定性: 穩定性排序 """ buck_count = [0] * arr_size for i in arr: buck_count[i-1] += 1 result = [] for i in range(arr_size): result += [i+1] * buck_count[i] return result
基數排序
def radix_sort(arr, digit): """基數排序 時間復雜度: O(N) 其實是O(digit*n)一般digit很小,所以為O(N) 空間復雜度: O(N) 用了兩個數組,bucket和result(下面程序當中arr=[],直接將值賦給了arr,而不是result) 穩定性:穩定性算法 """ for i in range(digit): bucket = [[] for i in range(10)] for x in arr: number_of_digit = (x % (10**(i+1))) // (10 ** i) # 得到第i位的數字 bucket[number_of_digit].append(x) arr = [] for i in range(10): arr += bucket[i] return arr
求相鄰數的最大差值,時間復雜度O(N)
給定一個數組, 求如果排序之後, 相鄰兩數的最大差值, 要求時間復雜度O(N), 且要求不能用非基於比較的排序
思路是利用桶的思想來進行解決,將相差範圍內的數字放入一個桶當中
def get_index_by_num(num, arr_length, max_value, min_value): """將一個數劃分到桶中 將arr_length 切分成 (max_value - min_value)份 然後在 min_value開始,分配這些份 """ return int( arr_length / (max_value - min_value) * (num-min_value) ) def get_max_sub_min(arr): max_value = float(‘-inf‘) min_value = float(‘inf‘) for i in arr: max_value = i if i > max_value else max_value min_value = i if i < min_value else min_value if max_value == min_value: return 0 arr_length = len(arr) max = [float(‘-inf‘)] * (arr_length + 1) min = [float(‘inf‘)] * (arr_length + 1) has_num = [False] * (arr_length + 1) for i in arr: index = get_index_by_num(i, arr_length, max_value, min_value) has_num[index] = True min[index] = i if i < min[index] else min[index] max[index] = i if i > max[index] else max[index] print(min) print(max) print(has_num) #最大值不會出現在一個桶的內部,只會出現在兩個桶之間,但並不一定是空桶之間 res = 0 last_max = max[0] for index in range(1,arr_length + 1): if has_num[index]: res = min[index] - last_max if (min[index] - last_max) > res else res last_max = max[index] return res
nowcoder basic algorithm chapter 2