日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解RMQ LCA

發布時間:2025/6/15 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解RMQ LCA 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第一節?RMQLCA概述

?

?????? LCA:Lowest?Common?Ancestor,譯為最近公共祖先。其解釋就是說:在有根樹中,找出樹中任意兩個節點最近的公共祖先,或者說找到任意兩個節點離樹根最遠的公共祖先。

?

?????? RMQRange?Minimum?Query,譯為區間最小值查詢。其解釋就是說:對于含有N個元素的數列A,在數列中找到兩個指定索引之間的最小值及最小值的位置。

?

第二節?RMQ Algorithm

?

?????? 首先我們來看RQM算法,我將會根據預處理和查詢的速度介紹幾種解決該問題的方法。

???

??? 設有數組A[N],其表示如下:

??? 要求求得區間(2,7)的最小元素,如下圖所示:

解法一:直接遍歷區間

?

?????? 看到這個問題之后,我們最先想到的就是對區間的這些數進行一次遍歷,就可以找到區間的最值,因此查詢的時間為O(M)。但是,當數據量非常大并且查詢很頻繁時,直接遍歷序列的效果就不是那么理想了。因為每查詢一次就得對序列做一次遍歷,對于大數據量這顯然不能滿足要求了。不過對于小數據量,這種算法倒是不錯的選擇!??

???????查詢:O(M)

算法的代碼如下:

[cpp]?view plaincopy
  • int?MaxNum?=?0;??
  • for(i?=?0;?i?<?range;?i++)??
  • {??
  • ???/**查找最大值**/??
  • ???if(array[i]?>?MaxNum)??
  • ???{??
  • ??????MaxNum?=?array[i];??
  • ???}??
  • }??
  • 解法二:切割法

    ?

    ?????? 解法一中查詢的速度為O(M),如果每次查詢都這樣的話,那真就成了龜速了。于是我們對解法一做了預處理,這就是該節要講的:切割法。

    ??? 首先,我們將序列分成sqrt(N)個部分,用數組M[sqrt(N)?]來表示每個部分中最小的值的下標,即這個最小數的位置。對于數組M,我們只需對原序列進行一次遍歷就可以得到M。如下圖所示:

    ?????? 接下來我們來求RMQ[27]。為了得到區間[27]的最小值,我們需比較A[2]A[M[1]]A[6],以及A[7],并得到他們中最小值的下標。

    ?

    ??? 分析:其實,這種方法較第一種方法而言并沒有實質的改進,甚至還不如方法一。至于為什么這樣做,我的解釋是:我們是基于查詢快慢的角度上來比較的,說白了,就是我們追求的是查詢速度,所以說只要查的快了,先做一些預處理也是值得的(解法四正是基于這種思想)現在我們根據上面的例子來看看法二,當做完預處理之后,得到了數組M,此時我們要求區間的最值,那么我們只需將在區間內,包含數組M的值以及包含兩個邊界的值作比較就行,這樣的話,查詢的次數:O(M)?<=?查詢次數?<?O(M)?+?K,其中K?<?sqrt(N)。

    ?

    解法三:排序

    ?

    ?????? 解法二已經提到我們的目的是查得快,那么我們可對選擇區間的這M個數據進行排序,然后就可以直接得到最小值。但是如果做排序的話,會有很大的缺陷。我們來看看。

    ?

    ?????? 分析:我們選擇快速排序,O(M?*?LogM),但是快速排序會改變序列中數的相對位置,因此用快排的話,為了保證原數據的順序不變,我們還得用O(M)的空間來維護原序列,因此這樣的消耗是很大的。附注:復雜度為O(M?*?M)的排序算法在這就不啰嗦了!你懂得!

    ????查詢:O(1)

    OK,我們來實現我們的想法,代碼如下:

    [cpp]?view plaincopy
  • 快速排序??
  • int?partition(int?*array,?int?low,?int?high)??
  • {??
  • ????int?key?=?array[high];??
  • ????int?i?=?low;??
  • ????int?j?=?high;??
  • ??
  • ????while(i?<?j)??
  • ????{??
  • ????????while(array[i]?<=?key?&&?i?<?j)??
  • ????????{??
  • ????????????i++;??
  • ????????}??
  • ????????array[j]?=?array[i];??
  • ??
  • ????????while(array[j]?>=?key?&&?i?<?j)??
  • ????????{??
  • ????????????j--;??
  • ????????}??
  • ????????array[i]?=?array[j];??
  • ????}??
  • ????array[i]?=?key;??
  • ??????
  • ????return?i;??
  • }??
  • ??
  • void?quicksort(int?*array,?int?low,?int?high)??
  • {??
  • ????int?index;??
  • ????int?i?=?low;??
  • ????int?j?=?high;??
  • ??
  • ????if(i?<?j)??
  • ????{??
  • ????????index?=?partition(array,?low,?high);??
  • ??????????
  • ????????quicksort(array,?low,?index?-?1);??
  • ????????quicksort(array,?index?+?1,?high);??
  • ????}??
  • }??
  • 排完序之后就可以直接得到最值了!

    ?

    解法四:Sparse?Table(ST)?algorithm

    ?

    ?????? ST算法是一種比較高效的在線處理RMQ問題的算法,所謂在線算法,是指每輸入一個查詢就會馬上處理這個查詢。ST算法首先會對序列做預處理,完成之后就可以對查詢做回答了。

    ?

    ?????? 分析:

    ?????????????? 預處理:O(N * LogN)。

    ???????????????查詢:O(1),這樣的查詢正是我們想要的

    ?

    好了,我來詳細講述一下ST算法:

    ?

    ?????? 預處理:首先用維護一個數組M[N][LogN]M[i][j]的值是從原序列Ai位置開始,連續2j?個元素的最小值的下標,如下所示:

    ?????? 那么,我們如何計算M[i][j]呢?

    ?????? 我們采用DP的思想將區間分成兩部分,即M[i][j?-?1]M[i][2^(j?-?1)]。現在我們只需比較這兩個子區間就可以得到M[i][j]了。比較規則如下:

    于是乎,就可按照此寫出代碼: [cpp]?view plaincopy
  • void?Proprocessing(int?M[N][logN],?int?*A,?int?N)??
  • {??
  • ????int?i,?j;??
  • ??????
  • ????for(j?=?1;?(1?<<?j)?<?N;?j++)??
  • ????{??
  • ????????for(i?=?0;?(i?+?(1?<<?j)?-?1)?<?N;?i++)??
  • ????????{??
  • ????????????if(A[?M[i][j?-?1]?]?<?A[?M[i?+?(1?<<?(j?-?1))][i?-?1]])??
  • ????????????{??
  • ????????????????M[i][j]?=?M[i][j?-?1];??
  • ????????????}??
  • ????????????else??
  • ????????????{??
  • ????????????????M[i][j]?=?A[?M[i?+?(1?<<?(j?-?1))][i?-?1]];??
  • ????????????}??
  • ????????}??
  • ????}??
  • }??
  • 解法五:線段樹

    ?????? 我們也可用線段樹來解決RMQ問題,如需了解線段樹,請到此一游: ?????? 線段樹:http://en.wikipedia.org/wiki/Segment_tree 線段樹的構造口訣:

    ok,我們根據口訣,并用上面的例子構造了線段樹,如下:

    ?????? 那么將線段樹應用到RMQ問題中,首先,維護一個有2^([logN]?+?1?+?1)?元素,名為M的數組,即M[2^([logN]?+?1?+?1)],我先來解釋一些數組M的意義:M[i]表示已劃分節點區間的最小值的位置(下標)。 ? ??? 知道了這些,那我們就通過代碼來實現線段樹的構造,并通過節點所代表的值來計算得到數組M。代碼如下: [cpp]?view plaincopy
  • void?init_tree(int?node,?int?low,?int?high,?int?*array,?int?*M)??
  • {??
  • /***node:表示線段樹中的某個節點?
  • ****low?:表示低索引?
  • ****high:表示高索引?
  • ****array:表示原數組?
  • ****M:??表示維護下標的數組?
  • ***/??
  • ????????if(low?==?high)?//為葉子節點??
  • ????????{??
  • ????????????????M[node]?=?low;??
  • ????????}??
  • ????????else??
  • ????????{??
  • ????????????????init_tree(2?*?node,?low,?(low?+?high)/2,?array,?M);??
  • ????????????????init_tree(2?*?node?+?1,?(low?+?high)/2?+?1,?high,?array,?M);??
  • ??
  • ????????????????if(array[?M[2?*?node]?]?<=?array[?M[2?*?node?+?1]?])?//拿到較小值的下標??
  • ????????????????{??
  • ????????????????????????M[node]?=?M[2?*?node];??
  • ????????????????}??
  • ????????????????else??
  • ????????????????{??
  • ????????????????????????M[node]?=?M[2?*?node?+?1];??
  • ????????????????}??
  • ????????}??
  • }??
  • 通過代碼,可得到構造線段樹的復雜度為O(N)。 ? ?????? 線段樹構造成功,接下來就是查詢了。我們知道,線段樹查詢所需的時間為O(LogN)。因為我們在前面已經了解了線段樹的幾種操作,所以查詢在這就不贅述了,直接看代碼吧! [cpp]?view plaincopy
  • int?query(int?node,?int?low,?int?high,?int?*a,?int?*b,?int?i,?int?j)??
  • {??
  • /***node:表示線段樹中的某個節點?
  • ****low?:表示低索引?
  • ****high:表示高索引?
  • ****array:表示原數組?
  • ****M:??表示維護下標的數組?
  • ****i,?j:表示要查詢的區間?
  • ***/??
  • ????int?s,?t;??
  • ????if(i?>?high?||?j?<?low)??
  • ????????return?-1;??
  • ??
  • ????if(low?>=?i?&&?high?<=?j)??
  • ????????return?b[node];?//返回最小值的下標??
  • ??
  • ????s?=?query(2?*?node,?low,?(low?+?high)/2,?a,?b,?i,?j);??
  • ????t?=?query(2?*?node?+?1,?(low?+?high)/2?+?1,?high,?a,?b,?i,?j);??
  • ??
  • ????if(s?==?-1)??
  • ????????return?b[node]?=?t;??
  • ????if(t?==?-1)??
  • ????????return?b[node]?=?s;??
  • ??
  • ????if(a[s]?<=?a[t])??
  • ????????return?b[node]?=?s;??
  • ????else??
  • ????????return?b[node]?=?t;??
  • }??

  • 第三節?LCA?Algorithm

    ?????? LCA算法的概念我們已經知道了,那我們就來看看它的實現過程吧! ? ?????? 對于一棵樹,在這我用二叉樹,如下圖所示。我們要找節點8和節點9的最近公共祖先,即節點2。 ???????附注:有些朋友說這個問題可以當做兩條鏈表是否相交的問題來解決,我們只需分別得到兩個節點到根節點的路徑,而這兩條路徑就是兩條鏈表,問題就迎刃而解了。顯然這是可行的。

    戰前準備:

    ?????? 數組T[i]:表示樹中某個節點i的父節點;

    ?????? 數組L[i]:表示樹中的某個節點i。

    ?????? 維護數組:P[N][LogN]:其中,P[i][j]表示樹中i節點的第j個祖先。

    ?

    實現的過程如下:

    ?

    利用二分檢索判斷節點p和節點q是否在樹的同一層:

    ???????如果在同一層,那么我們通過DP思想,不斷地求LCA(p?=?P[p][j]q?=?P[q][j]),一旦?p?=?q就停止,因為此時pq的父節點是一樣的,也就是說我們找到了最近公共祖先。

    ????如果不在同一層,如果p?>?q,也就是說p相對與qp在樹的更深層。此時,我們仍然通過DP思想來找到qp的祖先在同一層的節點,即q?=?p_祖先。接下來就可按照在同一層的做法做了。

    ?

    實現就是這么簡單。

    首先是預處理得到維護數組P[N][LogN]:

    [cpp]?view plaincopy
  • void?preprocessing(int?*t,?int?n,?int?p[][max])??
  • {??
  • ????int?i,?j;??
  • ??
  • ????for(i?=?0;?i?<?n;?i++)??
  • ????????p[i][0]?=?t[i];??
  • ??
  • ????for(j?=?1;?(1?<<?j)?<=?n;?j++)??
  • ????{??
  • ????????for(i?=?0;?i?<?n;?i++)??
  • ????????{??
  • ????????????if(p[i][j?-?1]?!=?-1)??
  • ????????????????p[i][j]?=?p[p[i][j?-?1]][j?-?1];??
  • ????????}??
  • ????}??
  • }??
  • 接下來就是查詢了,如下:

    [cpp]?view plaincopy
  • int?query(int?*t,?int?*l,?int?s,?int?t,?int?n,?int?p[][max])??
  • {??
  • ????int?tmp,?lg,?i;??
  • ????if(l[s]?<?l[t])??
  • ????{??
  • ????????tmp?=?s;s?=?t;t?=?tmp;??
  • ????}??
  • ??
  • ????for(lg?=?1;?(1?<<?lg)?<=?l[s];?lg++);??
  • ??
  • ????for(i?=?lg;?i?>=?0;?i--)??
  • ????{??
  • ????????if((l[s]?-?(1?<<?i))?>=?l[t])??
  • ????????????s?=?p[s][i];??
  • ????}??
  • ??
  • ????if(s?==?t)??
  • ????????return?s;??
  • ??
  • ????for(i?=?lg;?i?>=?0;?i--)??
  • ????{??
  • ????????if(p[s][i]?!=?-1?&&?p[s][i]?!=?p[t][i])??
  • ????????{??
  • ????????????s?=?p[s][i];??
  • ????????????t?=?p[t][i];??
  • ????????}??
  • ????}??
  • ????return?t[s];??
  • }??
  • ?????? 上面說的LCA的這種算法應該是最容易想到的,預處理過程O(NLogN),查詢O(LogN)。還有一種類似于RMQ分割法德算法,我先就不在這贅述了,以后有時間一定補上。

    ?

    第四節 結束語

    ?

    ?????? 想想、寫寫、畫畫.......

    ?

    后續:本文后半部分拖得周期較長,因此寫的比較匆忙。如果本文的內容有任何不妥之處,請指正!

    總結

    以上是生活随笔為你收集整理的详解RMQ LCA的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。