1. 程式人生 > >找中位數,找第k小,還存在問題

找中位數,找第k小,還存在問題

找第k小

上次介紹了找第二大使用的方法時,使用錦標賽的方法,找到最大,在最大的手下敗將裡找第二大,也就是亞軍在冠軍的手下敗將裡產生,亞軍只敗給過冠軍,這種方法比較次數時(n-1) + (logn-1),這個時間複雜度最優的方案了為O(n)
那麼怎麼找第k大了,季軍只能在冠軍和亞軍的手下敗將裡產生,第四名只能在前三名手下敗將裡產生。。。。這個方法也是O(n),但是需要記錄每個選手的手下敗將名單

還有一種分治的方案,思路來源於快排,快排每次劃分子問題,劃分成3個部分,小於pivot,等於pivot,大於pivot,假如我們要找的k,小於pivot的標號,那k肯定在左邊,等於就就是pivot,大於就在右邊,那麼每次都排除了一邊,問題規模縮小了一半,範圍一步步縮小,最後就找到一個pivot他的index就為k。

前面講了,快排在最壞的情況下,每次選擇的都是邊緣上的元素,每次問題規模只縮小了1,那他的時間複雜度還是n^2

隨機快排在一定程度上可以避免最壞的情況,通過隨機選取pivot,可以儘可能讓每次劃分子問題,差不多時均分的,假如我們找第k小的話,下一階段就會落在左邊或者右邊或者pivot,問題規模就會等比縮減。所以這種方法平均時間複雜度是可以達到O(n)

隨機快排的實現方法

import random        
def randomizedPartition(arr,low,high):
    def partition(arr,low,high):
        # 這時另外一種考慮方式,而且他是不需要額外空間的,他只使用一個指標來區分小於基準和大於基準的
# pointer_less_than代表這個指標的左邊全部都是小於基準的(包括自己,不包括首元素) # 然後從左往右掃描,遇到小於基準的元素,就把小於基準元素區域的後面緊接著的一個元素和他交換 # 那麼小於基準元素區域就多了一個元素,。。。就這樣小於基準的元素就連在了一起 # 首元素是基準元素,小於基準元素區域塊,大於基準元素區域塊,現在分成了三個部分 # 把首元素和小於基準元素區域塊最後一個元素交換,那三部分就變成,小於的,基準,大於的 # 剛開始小於基準的元素為0,暫且指向首位值
pointer_less_than = low # 然後一次掃描後面所有元素 for i in range(pointer_less_than +1,high+1): # 遇到小於基準的,就把小於基準元素區域的後面緊接著的一個元素和他交換,小於的塊相當於也更新了 if arr[i] < arr[low] : pointer_less_than +=1 arr[pointer_less_than],arr[i]=arr[i],arr[pointer_less_than] # 把首元素和小於基準元素區域塊最後一個元素交換,那三部分就變成,小於的,基準,大於的 arr[low],arr[pointer_less_than] = arr[pointer_less_than],arr[low] return pointer_less_than index = random.randint(low,high) arr[low],arr[index]=arr[index],arr[low] return partition(arr,low,high) def randomizedQuicksort_for_medium(arr,low,high,k): if low <= high: index = randomizedPartition(arr,low,high) if k == index: return arr[index] elif k < index : return randomizedQuicksort_for_medium(arr,low,index-1,k) else: return randomizedQuicksort_for_medium(arr,index+1,high,k) arr3 = [7,3,66,33,22,66,99,0,1] print(arr3) print(sorted(arr3)) print(randomizedQuicksort_for_medium(arr3,0,len(arr3)-1,4)) [7, 3, 66, 33, 22, 66, 99, 0, 1] [0, 1, 3, 7, 22, 33, 66, 66, 99] 22

還有一種思路,就是那個pivot也不是隨機選擇的,怎麼來了,他應該是在中位數附近,那麼我們是不是可以,計算的來pivot了

下面有一種失敗的方法,參考一下:

# 簡單的插入排序
def insert_sort(arr,low,high):
    
    for i in range(low+1,high+1):
        temp = arr[i]
        j = i
        
        while arr[j-1] > temp and j >low:
            arr[j] = arr[j-1]
            j -=1
        arr[j] = temp

# 針對已分組的資料塊排序
def insert_sort_node(arr,low,high):
    
    for i in range(low+1,high+1):
        temp = arr[i]
        j = i
        
        while arr[j-1][0] > temp[0] and j >low:
            arr[j] = arr[j-1]
            j -=1
        arr[j] = temp

#  規約子問題的方法:
# 按照每5個一組,在每組中位數裡取中位數,然後把小於中位數的元素放在左邊,大於的放在右邊
def partion_group_sort_size_5(arr,left,right):
    
    # 我們是在arr上原地操作的
    # 下面是每5個一組
    low =left
    high = left +4
    
    # 儲存一下中位數陣列,便於求中位陣列的中位數
    # 此法相對於快排的規約,選擇首元素或者隨機選擇,這個pivot是通過計算得出
    # 每5個分成一組,最後5的餘數,特殊處理
    medium =[]
    if right -left > 4:
        while high <= right:
            insert_sort(arr,low,high)
            medium.append((arr[low+2],low+2))
            low +=5
            high +=5
    
    # 假如輸入剛好為5個或者少於5個,直接插入排序,返回最中間的index
    # 插入排序對已有序大的序列,效率高,在次情況下,比較次數很少
    # 這種情況下直接返回中位數的標號
    else:
        insert_sort(arr,low,high)
        return (low+high)//2 -1
    

    # 對中位數陣列排序,取得中位數,也就是分解子問題的pivot
    insert_sort_node(medium,0,len(medium)-1)

    # 把小於pivot的資料放左邊,把大於pivot資料放右邊
    # 分組裡面的左上角可以直接放在前後和右下角的資料可以直接放在後面
    # 左下角和右上角,以及最後的餘數需要比較之後,再決定放左邊還是右邊
    medium_num = (len(medium) -1)//2
    # 現在中位數為medium[medium_num],把小於medium[medium_num]放到左邊
    
    # 因為沒有足夠的空位,所以臨時放在list裡,然後最後複製回去
    list = [-1]*(right-left +1)
    # 小於pivot的指標,大於pivot的指標
    i =0
    j =right-left
    
    # 先處理左邊的資料,處理左上角
    for k in range(medium_num):
        #左上角可以直接放進左邊
        # 理論上是直接把左上角放在head,右下角放在end,然後再處理左下角和右上角,以及最後的餘數
        # 這樣可以儘量保證有序,減少插入排序的工作量
        list[i]=arr[medium[k][1]-2]
        i +=1
        list[i]=arr[medium[k][1]-1]
        i +=1
        list[i]=arr[medium[k][1]]
        i +=1

    # 處理中位數後面的分組,處理右下角
    for k in range(len(medium)-1,medium_num,-1):
        # 從最後面開始處理,因為這裡的數都比較大
        list[j]=arr[medium[k][1]+2]
        j -=1
        list[j]=arr[medium[k][1]+1]
        j -=1
        list[j]=arr[medium[k][1]]
        j -=1

    # 處理中位數那一組上邊,上面的放左邊
    list[i]=arr[medium[medium_num][1]-2]
    i +=1
    list[i]=arr[medium[medium_num][1]-1]    
    i +=1

    # 處理中位數那一組,下面的放右邊,為什麼在這裡了,因為他在大於pivot裡面算是較小的,
    # 為了保證劃分之後的子問題儘量有序,先下後上
    list[j] = arr[medium[medium_num][1] +2]
    j -=1
    list[j] = arr[medium[medium_num][1] +1]
    j -=1  
    
    # arr[medium[medium_num][1] 位置還不清楚最後新增
    
    # 處理左下角
    for k in range(medium_num):        
        # 左下角需要比較之後才能決定放左邊還是右邊,先上後下,上面的比較小
        if arr[medium[k][1] + 1] > medium[medium_num][0]:
            list[j] = arr[medium[k][1] +1]
            j -=1
        else:
            list[i]=arr[medium[k][1] +1]
            i +=1
            
        if arr[medium[k][1] + 2] > medium[medium_num][0]:
            list[j] = arr[medium[k][1] +2]
            j -=1
        else:
            list[i]=arr[medium[k][1] +2]
            i +=1            
        
    # 處理右上角        
    for k in range(len(medium)-1,medium_num,-1):        
        if arr[medium[k][1] - 1] > medium[medium_num][0]:
            list[j] = arr[medium[k][1] -1]
            j -=1
        else:
            list[i]=arr[medium[k][1] -1]
            i +=1 
            
        if arr[medium[k][1] - 2] > medium[medium_num][0]:
            list[j] = arr[medium[k][1] -2]
            j -=1
        else:
            list[i]=arr[medium[k][1] -2]
            i +=1 

    # 處理最後的餘數
    for k in range(low,right+1):

        if arr[k] > medium[medium_num][0]:
            list[j] = arr[k]
            j -=1
        else:
            list[i]=arr[k]
            i +=1
            
    # 把最後的中位數放入       
    list[i] = medium[medium_num][0]
    # 把臨時結果放回原來的陣列
    arr[left:right+1] = list[:]
    # 返回中位數的index
    return(left+i)

# 使用分治獲取中位數
def partion_group_sort_size_5_for_medium(arr,low,high,k):
        
    # 遞迴出口,當左右指標重合時,便是找到了第k小的陣列
    if low <= high:
        # 取經過計算的pivot分組,這個pivot 的index應該接近中位數的index,這樣就可以很快的收斂
        index = partion_group_sort_size_5(arr,low,high)
        # 這個index恰好為中位數的index時,就可以直接返回中位數大小
        if k == index:
            return arr[index]
        # 當index>k時,代表在左半部分
        elif k < index :            
            return partion_group_sort_size_5_for_medium(arr,low,index-1,k)
        # 否則就在右半部分
        else:
            return partion_group_sort_size_5_for_medium(arr,index+1,high,k)        
    
            
    
arr3 = [7,3,66,33,22,66,9,0,1,11,14,17,15,22,88,91,10,5,11,77,88,45,990,1]
print(arr3)
print(partion_group_sort_size_5_for_medium(arr3,0,len(arr3)-1,len(arr3)//2-1))

a =sorted(arr3)
print(a)
print(a[len(arr3)//2-1])  

[7, 3, 66, 33, 22, 66, 9, 0, 1, 11, 14, 17, 15, 22, 88, 91, 10, 5, 11, 77, 88, 45, 990, 1]
15
[0, 1, 1, 3, 5, 7, 9, 10, 11, 11, 14, 15, 17, 22, 22, 33, 45, 66, 66, 77, 88, 88, 91, 990]
15

為什麼是失敗的方法了?這裡給中位數陣列求中位數的方法是插入排序?你是沒睡醒嗎?n/5的規模使用插入排序,你說雞肋不雞肋,雖然後面的陣列基本都是有序的,但是第一次的工作量就有O(n^2)的工作量。

我們的目標是求中位數,劃分子問題中位數劃分最均衡,所以用分治求中位數效率比較高,那麼我們求中位數陣列時就應該使用分治的方法,正確的方法是:求中位數陣列的中位數,遞迴呼叫自身,得到pivot後,左半邊要呼叫自身,右半邊也要呼叫自身,也就是三個地方都需要呼叫自身。

# -*- coding: utf-8 -*-

# 簡單的插入排序
def insert_sort(arr,low,high):
    
    for i in range(low+1,high+1):
        temp = arr[i]
        j = i
        
        while arr[j-1] > temp and j >low:
            arr[j] = arr[j-1]
            j -=1
        arr[j] = temp
        
        
def group_sort_size_5(arr):
    
    # 我們是在arr上原地操作的
    # 下面是每5個一組
    low =0
    high = 4
    
    # 儲存一下中位數陣列,便於求中位陣列的中位數
    # 此法相對於快排的規約,選擇首元素或者隨機選擇,這個pivot是通過計算得出
    # 每5個分成一組,最後5的餘數,特殊處理
    medium =[]
    while high < len(arr):
        insert_sort(arr,low,high)
        medium.append(arr[low+2])
        low +=5
        high +=5 
        
    insert_sort(arr,low,len(arr)-1)
    
    return arr,medium

def partion(arr,m_star,medium_num):
    
    # 因為沒有足夠的空位,所以臨時放在list裡,然後最後複製回去
    list = [-1]*(len(arr))
    # 小於pivot的指標,大於pivot的指標
    i =0
    j =len(arr)-1
    
    if medium_num == 0:
        left = arr[:len(arr)//2-1]
        right =arr[len(arr)//2:]
        return len(arr)//2-1,left,right
        
    
    # 先處理左邊的資料,處理左上角
    for k in range(medium_num):
        #左上角可以直接放進左邊
        # 理論上是直接把左上角放在head,右下角放在end,然後再處理左下角和右上角,以及最後的餘數
        # 這樣可以儘量保證有序,減少插入排序的工作量
        if arr[5*k + 2] < m_star:
            list[i]=arr[5*k + 2-2]
            i +=1
            list[i]=arr[5*k + 2-1]
            i +=1
            list[i]=arr[5*k + 2]
            i +=1

    # 處理中位數後面的分組,處理右下角
        # 從最後面開始處理,因為這裡的數都比較大
        elif  arr[5*k + 2] > m_star:
            list[j]=arr[5*k + 2+2]
            j -=1
            list[j]=arr[5*k + 2+1]
            j -=1
            list[j]=arr[5*k + 2]
            j -=1
            
        else:
            # 處理中位數那一組上邊,上面的放左邊
            list[i]