選擇問題最多見的問題有:算法
選擇算法
統一描述:設L是n個算法的集合,從L中選出第k小的元素,1<=k<=n,當L中元素按從小到大排好序後,排在第k個位置的數,就是第k小的數。
下面介紹 順序比較法
算法Findmax
輸入:n個數的數組L
輸出:max,k數組
max <- L[1]; k <- 1 for i <- 2 to n do //for循環執行n-1次 if max < L[i] then max <- L[i] k <- i return max, k
算法Findmax第二行,for循環執行n-1次,因此 \(W(n)=n-1\)
這個算法是選最大問題在時間上最優的算法(對於選最小問題,只需對算法稍加改動,就能夠獲得順序比較的Findmin算法)性能
設計思想:先選最大,而後把最大的從L中刪除,接着選最小。
算法:(利用Findmax和Findmin)
輸入:n個數的數組L
輸出:max,minspa
if n=1 then return L[1]做爲max和min else Findmax 從L中刪除max Findmin
算法執行的比較次數:\(W(n)=n-1+n-2=2n-3\)設計
分組比賽的方法
基本思想:首先將L中的元素兩兩一組,分紅\(\lfloor n/2 \rfloor\)組(當n是奇數時有一個元素輪空)。每組中的2個元素進行比較,獲得組內較大和較少數,把至多\(\lfloor n/2 \rfloor + 1\)(當n爲奇數時,把被輪空的元素加進來)個小組中較大的元素放在一塊兒,運行Findmax,獲得L中的最大元素,同理獲得L中的最小元素。
算法FindMaxMin3d
將n個元素兩兩一組分紅n/2(下取整)組 每組比較,獲得n/2(下取整)個較小和n/2(下取整)個較大的數 //比較n/2(下取整)次 在n/2(下取整)個(n爲奇數時,是n/2(下取整)+1)較小中找最小min 在n/2(下取整)個(n爲奇數時,是n/2(下取整)+1)較大中找最大max
行3和行4都執行\(\lceil n/2 \rceil -1\)次
因此\(W(n)=\lfloor n/2 \rfloor +2 \lceil n/2 \rceil-2=n+\lceil n/2 \rceil-2=\lceil 3n/2 \rceil-2\)
此算法效率更高,是全部同時找最大和最小算法中事件複雜度最低的算法。指針
2次調用Findmax算法
\(W(n)=n-1+n-2=2n-3\)code
錦標賽算法
把數組中的元素兩兩一組,劃分爲\(\lfloor n/2 \rfloor\)組(n爲奇數時1個元素輪空),每組組內兩個元素比大小,大的進入下一輪(n爲奇數時,輪空的元素也進入下一輪)。
因此下一輪有\(\lceil n/2 \rceil\)個元素。繼續每組組內比大小,而後大的進入下一輪,直至找出max。篩掉\(n-1\)個元素,比較\(n-1\)次。blog
找第二大,不可能再用Findmax了,若是用Findmax,就又比較n-2次了,和上面的算法兩次調用Findmax同樣。
因此,咱們能夠利用找max時比較所產生的記錄幫咱們減小比較次數。在比賽前爲每一個元素設定一個指針,指向一個鏈表,把比較後比它小的元素都記錄進它的鏈表中,找出max後,在max的鏈表中用Findmax找出整個數組第二大的元素。
算法FindSecond
輸入:n個數的數組L
輸出:second排序
k <- n 將k個元素兩兩一組,分紅k/2(下取整)組 每組的兩個數比較,找到較大的 將被淘汰的較小的數在淘汰它的數所指向的鏈表中作記錄 if k爲奇數 then k <- k/2(下取整)+1 else k <- k/2(下取整) if k>1 then goto 2 max <- 剩下的一個數 second <- max的鏈表中的最大
此時,第一輪兩兩比較的比較次數是\(n-1\),但還不知道max的鏈表中有多少個元素,
因此接下來求解max所淘汰掉的元素個數(這部分的工做量)
設本輪參與比較的有\(t\)個元素,通過分組淘汰後進入下一輪的元素數至可能是\(\lceil t/2 \rceil\),下下一輪就是\(\lceil \lceil t/2 \rceil /2 \rceil = \lceil t/2^2 \rceil\)。
假設k輪淘汰後只剩max,則\(\lceil n/2^k \rceil = 1\).
若\(n=2^d\),那麼\(k=d=logn=\lceil logn \rceil\)
因此max進行了\(d\)次比較,\(W(n)=n-1+\lceil logn \rceil\)。
對於找第二大的問題,算法Findmax是時間複雜度最低的算法。
上面,咱們只討論了一些特例狀況,基本上使用順序比較或分組比較的方法,沒有明確的分治算法的特徵。下面考慮通常性的選第k小問題的算法,用到分治策略。
輸入:數組S,S的長度n,正整數k,1<=k<=n
輸出:第k小的數
選第k小,可使用排序算法,從小到大排好序後,選擇第k個便可,最好的排序算法的時間複雜度是\(O(nlogn)\)。
下面考慮性能更好的分治算法,就是\(O(n)\)時間的算法,方便起見,假設數組S中元素彼此不等。
以S中的某個元素\(m^*\)做爲劃分標準,將S劃分爲兩個子數組S1和S2,把這個數組中比\(m^*\)小的都放入\(S_1\)的數組中,數組\(S_1\)的元素個數是\(|S_1|\)個;把這個數組中比\(m^*\)大的都放入\(S_2\)的數組中,數組\(S_2\)的元素個數是\(|S_2|\)個。
將S劃分紅5個一組,共n/5(上取整)個組 每組中找一箇中位數,把這些中位數放到集合M中 m* <- Select(M,|M|/2) //選M中的中位數m*,將S中的數組劃分紅A,B,C,D四個集合 把A和D中的每一個元素與m*比較,小的構成S1,大的構成S2 S1 <- S1並C ; S2 <- S2並B if k=|S1|+1 then 輸出m* else if k<=|S1| then Select(S1,k) else Select(S2,k-|S1|-1)
左下方的集合\(C\)中因此元素所有小於\(m^*\),右上角的集合B中的全部元素所有大於\(m^*\)。僅僅對於\(A\),\(D\)中的元素,咱們不能肯定它們是否大於或小於\(m^*\),因此在算法的行4加以比較,完成\(S1\)和\(S2\)的劃分。
下面分析時間複雜度
假設n是5的倍數,且\(n/5是奇數\),即\(n/5=2r+1\)
因此\(|A|=|D|=2r\),\(|B|=|C|=3r+2\),\(n=10r+5\)。
若A,D的元素都小於\(m^*\),那麼它們都加入至S1中,且下一步算法又在這個子問題上遞歸調用,這對應了歸約後子問題的規模的上界,也正好是時間複雜度最壞的狀況。相似的,若是A,D的元素都大於\(m^*\),也會出現相似的狀況。
之前者爲例時,子問題的大小是
\(|A|+|C|+|D|=7r+2=7\frac{n-5}{10}+2=7\frac{n}{10}-1.5<\frac{7n}{10}\)
上式代表子問題規模最大不超過原問題的\(7/10\),這個參數\(7/10\)就是前文特徵(2)中的\(d\)。
因此最壞狀況下的時間複雜度的遞推式:
\(W(n)<=W(\frac{n}{5})+W(\frac{7n}{10})+cn\)
因此從上面的分析能夠看出,這樣找的\(m^*\)徹底知足算法要求,兩次遞歸調用的參數分別是\(c=0.2\),\(d=0.7\),\(c+d=0.9<1\)。
因此Select算法能夠把時間複雜度降至線性時間。
分組時也能夠選\(3\)個或\(7\)個元素,元素數的改變可能會改變\(m^*\)的特徵,從而使得針對某些分組方法獲得的\(c+d\)的值再也不小於\(1\),這就會增長運算時間。