1. 程式人生 > >nowcoder basic algorithm chapter 2

nowcoder basic algorithm chapter 2

lar not 排序算法 chapter ima 所有 adding 穩定性 min

劃分問題和快排

普通劃分

給定一個數組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