無序數組求中位數——小根堆法原理(附python代碼)

小根堆法:

定義中位數爲一個有序數組(len(array)+1)//2處的元素,“//”代表下取整。我知道中位數的定義分按照數組長度的奇偶性分兩種,但是面試官會告訴你這裏求的中位數就是有序數組(len(array)+1)//2處的元素。
如:
1 2 3 4 5 6 7 8 9 的中位數是(9+1)//2 = 5
1 2 3 4 5 6 7 8 9 10 的中位數是(10+1)//2=5
現在來求無序數組的中位數:
步驟 1 :如果數組長度爲奇數則取數組的前 (len(array)+1)//2 個元素建立 一個最小堆,如果爲偶數則取(len(array)+1)//2 +1個元素建立 一個最小堆。
步驟 2 :遍歷剩餘元素,如果該元素小於堆頂元素,則丟棄或不作處理;如果該元素大於堆頂元素,則將其取代堆頂元素,並且重新調整當前堆爲最小堆。
步驟 3 :遍歷結束後,返回堆頂元素,它就是所要尋找的中位數。
原理:
(建立大根堆也是行得通,這裏選小根堆來解釋)
這裏假設數組長度爲奇數,步驟1中說是從前往後取(len(array)+1)//2 個元素建立 一個最小堆,其實任意取(len(array)+1)//2 個元素都可以,所取的這(len(array)+1)//2 個元素有兩種可能的情況。
情況1:這(len(array)+1)//2 個元素中包含了中位數。
情況2:這(len(array)+1)//2 個元素沒有包含中位數。
對於情況1:
中位數可能位於堆頂或者堆內部。
(1)當中位數位於堆頂的時候,因爲是小根堆,所以數組內所有比中位數大的元素都在小根堆內,此時遍歷數組剩餘元素,根本不可能找到比堆頂大的元素,所以遍歷完數組剩餘元素堆頂保持不變。
如1 2 3 4 5 6 7 8 9 任取(9+1)//2=5個元素建立小根堆,如果5是堆頂,那堆內的其餘元素只能是6 7 8 9了。遍歷剩餘元素1 2 3 4 ,堆頂保持不變。
(2)當中位數位於堆內的時候,堆頂元素比中位數小,則數組剩餘元素中必然含有比中位數大的元素。如果用數組剩餘元素中比堆頂小的來替換堆頂,那小根堆根本不用調整,這誰頂得住啊,小根堆不調整那中位數什麼時候能到堆頂。所以要用數組剩餘元素中比堆頂大的來替換堆頂,上面說了數組剩餘元素中必然含有比中位數大的元素,現記它爲T,那T必然比堆頂要大,所以用T替換堆頂,當T成了堆頂,而且它比中位數要大,所以T必然下沉,中位數必然上升,只要經過若干次這樣的替換與調整,中位數肯定會到達堆頂。
對於情況2:
這(len(array)+1)//2 個元素沒有包含中位數,那這些元素中肯定包含比中位數大的和比中位數小的,並且比中位數小的必然在堆頂,此時中位數位於數組的剩餘元素中。與情況1描述的操作一樣,多次替換與調整後中位數肯定會替換掉比它小的堆頂元素,此時中位數又到了小根堆內,這就和情況1一模一樣了。

代碼如下:

# 調整自頂向下調整小根堆
def changed_top_bot(array):
    flag = len(array)//2  # 最後一個分支節點的索引
    for i in range(1, flag+1):  # 從堆頂開始到最後一個分支節點
        if i == flag:
            if (2 * i + 1) > len(array):  # 判斷最後一個分支節點是否有右孩子
                if array[i-1] > array[2 * i-1]:
                    temp = array[i-1]
                    array[i-1] = array[2 * i-1]
                    array[2 * i-1] = temp
                continue

        if not (array[i-1] <= array[2*i-1] and array[i-1] <= array[2*i]):
            temp = array[i-1]
            if array[2*i-1] >= array[2*i]:
                array[i-1] = array[2*i]
                array[2*i] = temp
            else:
                array[i-1] = array[2*i-1]
                array[2*i-1] = temp
    return array


# 調整自底向上調整小根堆
def changed_bot_top(array):
    flag_branch = len(array) // 2  # 數組長度除以2下取整表示最後一個分支節點
    # print 'flag_branch: ', flag_branch
    while flag_branch != 0:  # 從最後一個分支結點開始自底向上調整該二叉樹
        # print '執行了'
        if flag_branch == len(array)//2:
            if (2 * flag_branch + 1) > len(array):  # 因爲是完全二叉樹,最後一個分支結點可能沒有右孩子
                # print '執行了'
                if array[flag_branch - 1] >= array[2 * flag_branch - 1]:  # 數組中索引要減1
                    temp = array[flag_branch - 1]
                    array[flag_branch - 1] = array[2 * flag_branch - 1]
                    array[2 * flag_branch - 1] = temp
                flag_branch -= 1
                continue
        # print array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]
        if not (array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]):
            # 該分支結點的孩子節點中有比它小的,所以選擇小的替換該分支節點
            temp = array[flag_branch-1]
            if array[2 * flag_branch-1] >= array[2 * flag_branch]:
                array[flag_branch-1] = array[2 * flag_branch]
                array[2 * flag_branch] = temp
            else:
                array[flag_branch-1] = array[2 * flag_branch-1]
                array[2 * flag_branch-1] = temp
        flag_branch -= 1
        # print 'flag_branch: ', flag_branch
        # print array
    return array


# 實現小根堆找無序數組中位數
def find_mid_num(array):
    # 根據數組長度奇偶性取部分元素
    extract_array = []
    remain_array = []
    if not len(array) % 2:
        extract_array += array[:(len(array)+1)//2+1]
        remain_array += array[(len(array)+1)//2+1:]
    else:
        extract_array += array[:(len(array)+1)//2]
        remain_array += array[(len(array)+1)//2:]
    # print 'extract_array: \n', extract_array
    # print remain_array
    # 利用所取的部分元素建立完全二叉樹
    # 根據完全二叉樹在數組的中的存儲,現在調整該完全二叉樹使其成爲小根堆
    # 將完全二叉樹調整爲小根堆需要先自低向上調整再自頂向下調整
    extract_array = changed_bot_top(extract_array)
    # 自底向上調整完,必須在自頂向下調整一次纔可以完成初始小根堆
    extract_array = changed_top_bot(extract_array)
    # print extract_array
    small_root_array = extract_array  # 得到了初始小根堆

    for remain_item in remain_array:  # 遍歷剩餘元素數組
        if remain_item < small_root_array[0]:  # 剩餘元素小於堆頂元素
            continue
        else:
            small_root_array[0] = remain_item  # 替換
            small_root_array = changed_top_bot(small_root_array)  # 只要建立了初始小根堆,後面的調整隻需自定向下)
    return small_root_array[0]  # 堆頂元素就是中位數

if __name__ == '__main__':
    array = [5, 2, 4, 1, 3, 6, 7, 8, 9]
    # array = [5, 2, 4, 1, 3, 6, 7, 8, 9, 10]
    mid_num = find_mid_num(array)
    print mid_num
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章