找中位数,找第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 +=1arr[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_thanindex = 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 = iwhile arr[j-1] > temp and j >low:arr[j] = arr[j-1]j -=1arr[j] = temp# 針對已分組的數據塊排序 def insert_sort_node(arr,low,high):for i in range(low+1,high+1):temp = arr[i]j = iwhile arr[j-1][0] > temp[0] and j >low:arr[j] = arr[j-1]j -=1arr[j] = temp# 規約子問題的方法: # 按照每5個一組,在每組中位數里取中位數,然后把小于中位數的元素放在左邊,大于的放在右邊 def partion_group_sort_size_5(arr,left,right):# 我們是在arr上原地操作的# 下面是每5個一組low =lefthigh = 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 +=5high +=5# 假如輸入剛好為5個或者少于5個,直接插入排序,返回最中間的index# 插入排序對已有序大的序列,效率高,在次情況下,比較次數很少# 這種情況下直接返回中位數的標號else:insert_sort(arr,low,high)return (low+high)//2 -1# 對中位數數組排序,取得中位數,也就是分解子問題的pivotinsert_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 =0j =right-left# 先處理左邊的數據,處理左上角for k in range(medium_num):#左上角可以直接放進左邊# 理論上是直接把左上角放在head,右下角放在end,然后再處理左下角和右上角,以及最后的余數# 這樣可以盡量保證有序,減少插入排序的工作量list[i]=arr[medium[k][1]-2]i +=1list[i]=arr[medium[k][1]-1]i +=1list[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 -=1list[j]=arr[medium[k][1]+1]j -=1list[j]=arr[medium[k][1]]j -=1# 處理中位數那一組上邊,上面的放左邊list[i]=arr[medium[medium_num][1]-2]i +=1list[i]=arr[medium[medium_num][1]-1] i +=1# 處理中位數那一組,下面的放右邊,為什么在這里了,因為他在大于pivot里面算是較小的,# 為了保證劃分之后的子問題盡量有序,先下后上list[j] = arr[medium[medium_num][1] +2]j -=1list[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 -=1else:list[i]=arr[medium[k][1] +1]i +=1if arr[medium[k][1] + 2] > medium[medium_num][0]:list[j] = arr[medium[k][1] +2]j -=1else: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 -=1else: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 -=1else: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 -=1else:list[i]=arr[k]i +=1# 把最后的中位數放入 list[i] = medium[medium_num][0]# 把臨時結果放回原來的數組arr[left:right+1] = list[:]# 返回中位數的indexreturn(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 = iwhile arr[j-1] > temp and j >low:arr[j] = arr[j-1]j -=1arr[j] = tempdef group_sort_size_5(arr):# 我們是在arr上原地操作的# 下面是每5個一組low =0high = 4# 保存一下中位數數組,便于求中位數組的中位數# 此法相對于快排的規約,選擇首元素或者隨機選擇,這個pivot是通過計算得出# 每5個分成一組,最后5的余數,特殊處理medium =[]while high < len(arr):insert_sort(arr,low,high)medium.append(arr[low+2])low +=5high +=5 insert_sort(arr,low,len(arr)-1)return arr,mediumdef partion(arr,m_star,medium_num):# 因為沒有足夠的空位,所以臨時放在list里,然后最后復制回去list = [-1]*(len(arr))# 小于pivot的指針,大于pivot的指針i =0j =len(arr)-1if 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 +=1list[i]=arr[5*k + 2-1]i +=1list[i]=arr[5*k + 2]i +=1# 處理中位數后面的分組,處理右下角# 從最后面開始處理,因為這里的數都比較大elif arr[5*k + 2] > m_star:list[j]=arr[5*k + 2+2]j -=1list[j]=arr[5*k + 2+1]j -=1list[j]=arr[5*k + 2]j -=1else:# 處理中位數那一組上邊,上面的放左邊list[i]=arr[5*k + 2-2]i +=1list[i]=arr[5*k + 2-1] i +=1# 處理中位數那一組,下面的放右邊,為什么在這里了,因為他在大于pivot里面算是較小的,# 為了保證劃分之后的子問題盡量有序,先下后上list[j] = arr[5*k + 2 +2]j -=1list[j] = arr[5*k + 2 +1]j -=1 # arr[medium[medium_num][1] 位置還不清楚最后添加# 處理左下角for k in range(medium_num): # 左下角需要比較之后才能決定放左邊還是右邊,先上后下,上面的比較小if arr[5*k + 2] < m_star:if arr[5*k + 2 + 1] > m_star:list[j] = arr[5*k + 2 +1]j -=1else:list[i]=arr[5*k + 2 +1]i +=1if arr[5*k + 2 + 2] > m_star:list[j] = arr[5*k + 2 +2]j -=1else:list[i]=arr[5*k + 2 +2]i +=1 if arr[5*k + 2] > m_star:if arr[5*k + 2 - 1] > m_star:list[j] = arr[5*k + 2 -1]j -=1else:list[i]=arr[5*k + 2 -1]i +=1 if arr[5*k + 2 - 2] > m_star:list[j] = arr[5*k + 2 -2]j -=1else:list[i]=arr[5*k + 2 -2]i +=1 # 處理最后的余數for k in range(5*medium_num,len(arr)):if arr[k] > m_star:list[j] = arr[k]j -=1else:list[i]=arr[k]i +=1# 把最后的中位數放入 list[i] = m_star# 把臨時結果放回原來的數組arr[:] = list[:]left = arr[:i]right =arr[i+1:]return i,left,rightdef select_recursive(arr,k):if len(arr) == 1:return arr[0]if len(arr) >1: arr,medium = group_sort_size_5(arr)medium_num = len(medium) # print(medium_num)m_star = select_recursive(medium,medium_num/2-1)print("m_star",m_star)index,left,right = partion(arr,m_star,medium_num)print(arr[index],left,right)if k == index:return arr[index]elif k < index:return select_recursive(left,k)else:return select_recursive(right,k-index-1)arr = [7,3,66,33,22,66,9,0,1,11,14,17,9,10,13,99,44,33,77,88,99,101,404,87,44,22,11,99,43,22] print(arr) a= sorted(arr) print(a) print(a[len(a)//2-1]) print(select_recursive(arr,len(arr)/2-1))runfile('D:/share/test/common_select_k.py', wdir='D:/share/test') [7, 3, 66, 33, 22, 66, 9, 0, 1, 11, 14, 17, 9, 10, 13, 99, 44, 33, 77, 88, 99, 101, 404, 87, 44, 22, 11, 99, 43, 22] [0, 1, 3, 7, 9, 9, 10, 11, 11, 13, 14, 17, 22, 22, 22, 33, 33, 43, 44, 44, 66, 66, 77, 87, 88, 99, 99, 99, 101, 404] 22 m_star 22 22 [9, 13, 22] [77, 99] m_star None 9 [] [13, 22] m_star None 13 [] [22] m_star 22 22 [3, 7, 0, 1, 9, 9, 10, 13, 11, 22, 11, 14, 17] [-1, 44, 87, 33, 44, 66, 43, 99, 99, 101, 404, 77, 88, 99, 33, 66] m_star None 44 [] [88, 99] m_star None 88 [] [99] m_star None按照如下實現代碼還是有問題,原因在于第一次調用自身時,出棧時劃分時,這時的index,left,right = partion(arr,m_star,medium_num),這個劃分函數arr不是我們期望的arr,他這時是medium,找沒想到解決的辦法
三次遞歸調用自身的方法目前實在是實現不了,經過網上游蕩發現了一種叫BFPRT的算法,這里實在太長了
總結
以上是生活随笔為你收集整理的找中位数,找第k小,还存在问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分治法:关于选择算法,找最大,找最小,同
- 下一篇: 分治法:BFPTR算法找第k小