青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 97, 文章 - 22, 評(píng)論 - 81, 引用 - 0
數(shù)據(jù)加載中……

夜深人靜寫(xiě)算法(六) - 最近公共祖先

目錄  
一、引例
      1、樹(shù)-結(jié)點(diǎn)間最短距離
二、LCA(最近公共祖先)
      1、樸素算法
      2、步進(jìn)法
      3、記憶化步進(jìn)法
      4、tarjan算法
      5、doubly算法
三、并查集
       1、"并"和"查"
       2、樸素算法
       3、森林實(shí)現(xiàn)
       4、啟發(fā)式合并
       5、路徑壓縮
       6、元素刪除
四、RMQ
       1、樸素算法
       2、線段樹(shù)(簡(jiǎn)介)
       3、ST算法
五、最近公共祖先相關(guān)題集整理
一、引例
    1、樹(shù)-結(jié)點(diǎn)間最短距離
    【例題1】給定一棵n(n <= 100000)個(gè)結(jié)點(diǎn)的樹(shù),以及m(m <= 100000)條詢問(wèn)(u, v),對(duì)于每條詢問(wèn),求u和v的最短距離?
    我們知道,一個(gè)普通的無(wú)向圖求兩點(diǎn)間最短距離可以采用單源最短路,將時(shí)間復(fù)雜度大致控制在O(nlogn),但是當(dāng)詢問(wèn)足夠多的時(shí)候,這并不是一個(gè)高效的算法。從樹(shù)的性質(zhì)可知,樹(shù)上任意兩點(diǎn)間有且僅有一條簡(jiǎn)單路徑(路徑上沒(méi)有重點(diǎn)和重邊),所以樹(shù)上任意兩點(diǎn)間的最短距離其實(shí)就是這條簡(jiǎn)單路徑的長(zhǎng)度。如圖一-1-1所示,要求u到v的距離,我們需要知道紅色路徑A(u->r),藍(lán)色路徑B(v->r),紅藍(lán)公共路徑C(a->r),那么u->v的路徑顯然就可以通過(guò)A、B、C三者的長(zhǎng)度計(jì)算出來(lái),令dist[x]表示從x到樹(shù)根r的簡(jiǎn)單路徑的長(zhǎng)度,則u到v的距離可以表示成如下:dist[u] + dist[v] - 2*dist[a]。
    那么問(wèn)題來(lái)了,a是什么,我們來(lái)看a->r路徑上的所有結(jié)點(diǎn)既是u的祖先,也是v的祖先,所以我們給它們一個(gè)定義稱為公共祖先(Common Ancestors),而a作為深度最深的祖先,或者說(shuō)離u和v最近的,我們稱它為最近公共祖先(Lowest Common Ancestors),簡(jiǎn)稱LCA。
 
圖一-1-1
二、LCA(最近公共祖先)
    1、樸素算法
    于是求樹(shù)上兩點(diǎn)間的距離轉(zhuǎn)化成了求兩個(gè)結(jié)點(diǎn)的最近公共祖先問(wèn)題上來(lái)了,最容易想到的辦法是將u->r和v->r的兩條路徑通過(guò)遞歸生成出來(lái),并且逆序保存,然后比較兩條路徑的公共前綴路徑,顯然公共前綴路徑的尾結(jié)點(diǎn)就是u和v的最近公共祖先,但是這個(gè)算法在樹(shù)退化成鏈的時(shí)候達(dá)到最壞復(fù)雜度O(n),并不可行。
    2、步進(jìn)法
    對(duì)于兩個(gè)結(jié)點(diǎn)u和v,假設(shè)它們的最近公共祖先為lca(u, v),用depth[x]表示x這個(gè)結(jié)點(diǎn)的深度,pre[x]表示x的父結(jié)點(diǎn)。那么很顯然,有depth[ lca(u, v) ] <= min{depth[u], depth[v]},通過(guò)這樣一個(gè)性質(zhì),我們可以很容易得出一個(gè)算法:
    1) 當(dāng)depth[u] < depth[v]時(shí),lca(u, v) = lca(u, pre[v]);
    2) 當(dāng)depth[u] > depth[v]時(shí),lca(u, v) = lca(pre[u], v);
    利用以上兩個(gè)條件,可以將u和v不斷向根進(jìn)行步進(jìn),遞歸求解,直到u == v時(shí),這里的u或v就是原先要求的(u, v)的最近公共祖先。
    3、記憶化步進(jìn)法
    【例題2】如圖二-3-1的網(wǎng)絡(luò)拓?fù)錁?shù),給定兩個(gè)客戶端(x, y),需要找到一個(gè)離他們最近的服務(wù)器來(lái)處理兩個(gè)客戶端的交互,客戶端和服務(wù)端數(shù)量小于等于1000,但是詢問(wèn)數(shù)Q <= 10^8。
 
圖二-3-1
    這是一個(gè)典型的LCA問(wèn)題,并且詢問(wèn)很多,還有一個(gè)特點(diǎn)是總的樹(shù)結(jié)點(diǎn)樹(shù)并不是很多,所以在步進(jìn)法計(jì)算LCA的時(shí)候,可能會(huì)遇到很多重復(fù)的計(jì)算,具體是指計(jì)算lca(u, v)的時(shí)候可能在某次查詢的時(shí)候已經(jīng)被計(jì)算過(guò)了,那么我們可以把這個(gè)二元組預(yù)先存在數(shù)組中,即lca[u][v],這樣做可以避免多次查詢中遇到的冗余計(jì)算。但同時(shí)也帶來(lái)一個(gè)問(wèn)題,就是空間復(fù)雜度是O(n^2),對(duì)于n = 100000的情況下內(nèi)存已經(jīng)吃不消了,記憶化步進(jìn)法只適用于n在千量級(jí)的情況。
    4、tarjan算法
    tarjan算法采用深度優(yōu)先搜索遞歸計(jì)算結(jié)點(diǎn)(u, v)的LCA。具體的思想是在搜索過(guò)程中將一棵樹(shù)進(jìn)行分類,分類如下:
    a.以結(jié)點(diǎn)x為根的子樹(shù)作為a類結(jié)點(diǎn);
    b.以pre[x]為根的子樹(shù)去掉a類結(jié)點(diǎn),為b類結(jié)點(diǎn);
    c.以pre[ pre[x] ]為根的子樹(shù)并且去掉a、b兩類,為c類結(jié)點(diǎn);依此類推...
 
圖二-4-1   
    如圖二-4-1所示,對(duì)這棵樹(shù)進(jìn)行深度優(yōu)先搜索,深灰色結(jié)點(diǎn)(之所以不用黑色是因?yàn)榫€條也是黑的)表示以該結(jié)點(diǎn)為根的子樹(shù)已經(jīng)盡數(shù)訪問(wèn)完畢;淺灰色結(jié)點(diǎn)表示該結(jié)點(diǎn)為根的子樹(shù)正在進(jìn)行訪問(wèn),且尚未訪問(wèn)完畢;白色結(jié)點(diǎn)表示以該結(jié)點(diǎn)為根的子樹(shù)還沒(méi)開(kāi)始訪問(wèn)。圖中紅色圈出的部分為上文提到的a類結(jié)點(diǎn);黃色圈出的部分為b類結(jié)點(diǎn);藍(lán)色為c類結(jié)點(diǎn);綠色為d類結(jié)點(diǎn),以此類推,不同的顏色屬于不同的集合。所謂一圖在手,勝過(guò)萬(wàn)語(yǔ)千言,從圖中很容易就能看出,x和所有綠色結(jié)點(diǎn)的LCA都為pre[pre[pre[x]]],即x的曾祖父結(jié)點(diǎn);和所有藍(lán)色結(jié)點(diǎn)的LCA都為pre[pre[x]],即x的祖父結(jié)點(diǎn);和所有黃色結(jié)點(diǎn)的LCA都為pre[x],即x的父結(jié)點(diǎn)。
可能有人對(duì)這里集合的概念表示不理解,舉個(gè)例子就能明白了,我們還是沿用上圖,將圖上的結(jié)點(diǎn)進(jìn)行編號(hào),如圖二-4-2所示,可以得到其中三個(gè)集合為:        
    B = {8,13}              C = {4,7,11,12,16}            D = {1,2,3,5,6,9,10,14,15}
    每個(gè)集合對(duì)應(yīng)一棵子樹(shù),按照tarjan算法的思路,當(dāng)給定任意一個(gè)結(jié)點(diǎn),我們需要得到這個(gè)結(jié)點(diǎn)所在集合對(duì)應(yīng)的子樹(shù)的根結(jié)點(diǎn),這里分兩步走:
    1) 找到結(jié)點(diǎn)對(duì)應(yīng)的集合;
    2)找到集合的根; 
    第2)步可以預(yù)先將值保存在數(shù)組中,但是集合不像數(shù)字,不能作為數(shù)組的下標(biāo)。而我們注意到這里的集合都是互不相交的,這一點(diǎn)是非常關(guān)鍵的,這就意味著我們可以用一個(gè)集合中的元素來(lái)作為這個(gè)集合的“代表元”。假設(shè)B的代表元為13,C的代表元為7,D的代表元為5,用ancestor數(shù)組來(lái)存儲(chǔ)集合的根結(jié)點(diǎn),則有ancestor[13] = 8, ancestor[7] = 4, ancestor[5] = 1,于是第2)步就能在O(1)的時(shí)間內(nèi)完成了。
    第1)步其實(shí)可以描述成給定一個(gè)結(jié)點(diǎn),求該結(jié)點(diǎn)所在集合的代表元。這里先不討論實(shí)現(xiàn),因?yàn)檫@個(gè)操作會(huì)在第三節(jié)并查集中花很長(zhǎng)的篇幅來(lái)講。
 
圖二-4-2
    對(duì)集合有一定了解后,讓我們最后總結(jié)下這個(gè)算法究竟是如何實(shí)現(xiàn)的。
    1) 初始化所有結(jié)點(diǎn)顏色colors[i] = 0,對(duì)樹(shù)T進(jìn)行深度優(yōu)先搜索;
    2) 訪問(wèn)到結(jié)點(diǎn)u時(shí),將u單獨(dú)作為一個(gè)集合,并且令ancestor[u] = u,即這個(gè)集合的根結(jié)點(diǎn)為u,然后跳轉(zhuǎn)到3);
    3) 訪問(wèn)u的所有子結(jié)點(diǎn)v,遞歸執(zhí)行2),當(dāng)v所在子樹(shù)結(jié)點(diǎn)全部訪問(wèn)完畢后,將v所在集合和u所在集合合并(執(zhí)行merge(u, v)),并且令新的集合的祖先為u(執(zhí)行ancestor[find(u)] = u);
    4) 當(dāng)u所在子樹(shù)結(jié)點(diǎn)全部訪問(wèn)完畢后,置colors[u] = 1,枚舉所有和u有關(guān)的詢問(wèn):(u, v),如果colors[v]等于1,那么記錄u和v的最近公共祖先為ancestor[ find(v) ];
    這里有兩個(gè)神奇的函數(shù),merge(u, v)和find(u),其中merge(u, v)表示合并u和v所在的兩個(gè)集合,find(u)則表示找到u所在集合的代表元。如果對(duì)這個(gè)算法已經(jīng)有一定的認(rèn)知,那么趁熱打鐵,來(lái)看下偽代碼是如何實(shí)現(xiàn)的。
    void LCA_Tarjan(int u) { 
        make_set(u);                               // 注釋1
        ancestor[ find(u) ] = u;                   // 注釋2
        for(int i = 0; i < edge[u].size(); i++) {  // 注釋3
            int v = edge[u][i].v; 
            LCA_Tarjan(v);                         // 注釋4
            merge(u, v);                           // 注釋5
            ancestor[ find(u) ] = u;               // 注釋6
        }
        colors[u] = 1;                             // 注釋7
        for(int i = 0; i < lcaInfo[u].size(); i++) {
            int v = lcaInfo[u][i].v;
            if(colors[v]) {                        // 注釋8
                lcaDataAns[ lcaInfo[u][i].idx ].lca = ancestor[ find(v) ];
            }
        }
    }
    注釋1:創(chuàng)建一個(gè)集合,集合中只有一個(gè)元素u,即{ u }
    注釋2:因?yàn)閡所在集合只有一個(gè)元素,所以也可以寫(xiě)成ancestor[u] = u
    注釋3:edge[u][0...size-1]存儲(chǔ)的是u的直接子結(jié)點(diǎn)
    注釋4:遞歸計(jì)算u的所有直接子結(jié)點(diǎn)v
    注釋5:回溯的時(shí)候進(jìn)行集合合并,將以v為根的子樹(shù)和u所在集合進(jìn)行合并
    注釋6:對(duì)合并完的集合設(shè)置集合對(duì)應(yīng)子樹(shù)的根結(jié)點(diǎn),find(u)為該集合的代表元
    注釋7:u為根的子樹(shù)訪問(wèn)完畢,設(shè)置結(jié)點(diǎn)顏色
    注釋8:枚舉所有和u相關(guān)的詢問(wèn)(u, v),如果以v為根的子樹(shù)已經(jīng)訪問(wèn)過(guò),那么ancestor[find(v)]肯定已經(jīng)計(jì)算出來(lái),并且必定是u和v的LCA
    tarjan算法的時(shí)間復(fù)雜度為O(n+q),其中n為結(jié)點(diǎn)個(gè)數(shù),q為詢問(wèn)對(duì)(u, v)的個(gè)數(shù)。但是在進(jìn)行深搜之前首先需要知道所有的詢問(wèn)對(duì),所以是一個(gè)離線算法,不能實(shí)時(shí)進(jìn)行計(jì)算,局限性較大。
    【例題3】在一個(gè)可視化的界面上的一棵樹(shù),選中某些結(jié)點(diǎn),然后要求在文件中保存一棵最小的子樹(shù),使得這棵子樹(shù)包含所有這些選中的結(jié)點(diǎn)。
    doubly算這個(gè)是實(shí)際文件保存中比較經(jīng)典的問(wèn)題,我們可以選擇兩個(gè)結(jié)點(diǎn)求出LCA,然后用這個(gè)LCA再和兩一個(gè)點(diǎn)求LCA,以此類推...n個(gè)結(jié)點(diǎn)經(jīng)過(guò)n-1次迭代就能求出n個(gè)結(jié)點(diǎn)的LCA,這個(gè)LCA就是要保存的子樹(shù)的根結(jié)點(diǎn)了。
    5、doubly算法
    doubly算法(倍增法)是在線算法,可以實(shí)時(shí)計(jì)算任意兩點(diǎn)間的LCA,并且每次計(jì)算時(shí)間復(fù)雜度是O(1)的。
    該算法同樣也是基于深度優(yōu)先搜索的,遍歷的時(shí)候從根結(jié)點(diǎn)開(kāi)始遍歷,將遍歷的邊保存下來(lái),對(duì)于一棵n個(gè)結(jié)點(diǎn)的樹(shù)來(lái)說(shuō)總共有n-1條邊,那么遍歷的時(shí)候需要保存2*(n-1)條邊(自頂向下的邊,以及回溯時(shí)的邊,所以總共兩倍的邊),這些邊順序存儲(chǔ)后相鄰兩條邊的首尾結(jié)點(diǎn)必定是一致的,所以可以壓縮到一個(gè)一維數(shù)組E中,數(shù)組的長(zhǎng)度為2*(n-1)+1,然后建立一個(gè)輔助數(shù)組D,長(zhǎng)度和E一致,記錄的是E數(shù)組中對(duì)應(yīng)結(jié)點(diǎn)的深度,這個(gè)在遍歷的時(shí)候可以一并保存,然后再用一個(gè)輔助數(shù)組I[i]來(lái)保存i這個(gè)結(jié)點(diǎn)在E數(shù)組中第一次出現(xiàn)的下標(biāo)。至此,初始化工作就算完畢了。
    那么當(dāng)詢問(wèn)兩個(gè)結(jié)點(diǎn)u和v的LCA時(shí),我們首先通過(guò)輔助數(shù)組I獲取兩個(gè)結(jié)點(diǎn)在D數(shù)組中的位置,即I[u]和I[v],不妨設(shè)I[u] <= I[v],那么在D數(shù)組中的某個(gè)深度序列D[ I[u], I[u]+1, ... I[v]-1, I[v] ],其實(shí)表示的是從u到v的路徑上每個(gè)結(jié)點(diǎn)的深度,而之前我們已經(jīng)知道樹(shù)上任意兩個(gè)結(jié)點(diǎn)的簡(jiǎn)單路徑有且僅有一條,并且路徑上深度最小的點(diǎn)就是u和v的LCA,所以問(wèn)題轉(zhuǎn)化成了求D[ I[u], I[u]+1, ... I[v]-1, I[v] ]的最小值,找到最小值所在下標(biāo)后再到E數(shù)組索引得到結(jié)點(diǎn)的值就是u和v的LCA了。

 

圖二-5-1

    如圖二-5-1為n = 7個(gè)結(jié)點(diǎn)的一棵樹(shù),其中0為根結(jié)點(diǎn),6條邊分別為(0,5)(5,2)(2,4)(0,3)(3,1)(3,6)。注意這里的邊是有向邊,即一定是父結(jié)點(diǎn)指向子結(jié)點(diǎn),如果給定的是無(wú)向邊,需要預(yù)先進(jìn)行處理。如右圖,從根結(jié)點(diǎn)進(jìn)行遍歷,其中紅色的邊為自頂向下的邊,綠色的邊為回溯邊,回溯邊一定是子結(jié)點(diǎn)指向父結(jié)點(diǎn)的,藍(lán)色的小數(shù)字代表邊的遍歷順序,即第幾條邊,將所有的邊串起來(lái)就變成這樣了:
    (0,5) -> (5,2) -> (2,4) -> (4,2) -> (2,5) -> (5,0) -> (0,3) -> (3,1) -> (1,3) -> (3,6) -> (6,3) -> (3,0)
    然后我們將這些邊壓縮到數(shù)組E中,得到:
    E[1 ... 2n-1] = 0 5 2 4 2 5 0 3 1 3 6 3 0
    對(duì)數(shù)組E中對(duì)應(yīng)的結(jié)點(diǎn)在樹(shù)上的深度記錄在數(shù)組D中,得到:
    D[1 ... 2n-1] = 0 1 2 3 2 1 0 1 2 1 2 1 0
    再將每個(gè)結(jié)點(diǎn)在數(shù)組E中第一次出現(xiàn)的位置記錄在數(shù)組I中,得到:
    I[0 ... n-1] = 1 9 3 8 4 2 11
    注意:這里的數(shù)組E和D都是1-based,而I數(shù)組是0-based,這個(gè)是個(gè)人習(xí)慣,可自行約定,不必糾結(jié)。
    根據(jù)LCA的性質(zhì),有LCA(x, y) = E[ Min_Index( D, I[x], I[y] ) ],其中Min_Index(Array, i, j)表示Array數(shù)組中[i, j]區(qū)間內(nèi)的最小值對(duì)應(yīng)的下標(biāo),那么問(wèn)題的難點(diǎn)其實(shí)就是在于求Min_Index(Array, i, j)了,這個(gè)問(wèn)題就是經(jīng)典的區(qū)間最值問(wèn)題,最常見(jiàn)的可以采用線段樹(shù)求解,建好樹(shù)后單次查詢的時(shí)間復(fù)雜度為O(logn),當(dāng)然還有一種更加高效的算法,查詢復(fù)雜度可以達(dá)到嚴(yán)格的O(1),這就是第四節(jié)要討論的RMQ問(wèn)題。
    由于tarjan算法中還有一個(gè)有關(guān)集合操作的遺留問(wèn)題尚未介紹,這里先來(lái)看史上最輕量級(jí)的數(shù)據(jù)結(jié)構(gòu)——并查集。
三、并查集
    1、"并"和"查"
    并查集是一種處理不相交集合的數(shù)據(jù)結(jié)構(gòu),它支持兩種操作:
    1)合并操作merge(x, y)。即合并兩個(gè)原本不相交的集合,此所謂“并”。
    2)查找操作find(x)。即檢索某一個(gè)元素屬于哪個(gè)集合,此所謂“查”。
    講個(gè)簡(jiǎn)單的故事來(lái)加深對(duì)并查集的理解,這個(gè)故事要追溯到北宋年間。話說(shuō)北宋時(shí)期,朝綱敗壞,奸臣當(dāng)?shù)溃涣纳S钟型馕赀|軍大舉南下,于是眾多能人異士群起而反,各大武林門(mén)派同仇敵愾,共抗遼賊,為首的自然是中原武林第一大幫-丐幫,其幫主乃萬(wàn)軍叢中取上將首級(jí)猶如探囊取物、泰山崩于前而面不改色的北喬峰;與其齊名的空有一腔抱負(fù)、壯志未酬的南慕容帶領(lǐng)的慕容世家;當(dāng)然也少不了天下武功的鼻祖-少林,以及一些小幫派,如逍遙派、靈鷲宮、無(wú)量劍、神農(nóng)教等等。我們將每個(gè)門(mén)派(幫派)作為一個(gè)集合,從中選出一個(gè)代表作為這個(gè)集合的標(biāo)識(shí),姑且認(rèn)為門(mén)派(幫派)的掌門(mén)(幫主)就是這個(gè)代表。
    作者有幸成了“抗遼聯(lián)盟”的統(tǒng)計(jì)員,統(tǒng)計(jì)員只有一個(gè)工作,就是接收一條條同門(mén)數(shù)據(jù),然后統(tǒng)計(jì)共有多少個(gè)門(mén)派,好進(jìn)行分派部署。同門(mén)數(shù)據(jù)的格式為(x, y),表示x和y屬于同一個(gè)門(mén)派,接收到一條數(shù)據(jù),需要對(duì)x所在的群體和y的群體進(jìn)行合并,當(dāng)統(tǒng)計(jì)完所有數(shù)據(jù)后有多少個(gè)集合就代表多少個(gè)門(mén)派。
    這個(gè)問(wèn)題其實(shí)隱含了兩個(gè)操作:1、查找a和b是否已經(jīng)在同一個(gè)門(mén)派;2、如果兩個(gè)人的門(mén)派不一致,則合并這兩個(gè)人所在集合的兩堆人。分別對(duì)應(yīng)了并查集的查找和合并操作。
    圖三-1-1所示,分別表示丐幫、少林、逍遙、大理段氏四個(gè)集合。
 
圖三-1-1
    2、樸素算法
    接下來(lái)來(lái)討論下并查集的樸素實(shí)現(xiàn),既然是樸素實(shí)現(xiàn),當(dāng)然是越樸素越好。樸素的只需要一個(gè)數(shù)組就能表示集合,我們用set[i]表示i所在的集合,這樣查找操作的時(shí)間復(fù)雜度就能通過(guò)下標(biāo)索引達(dá)到O(1)(可以把set數(shù)組理解成哈希表);合并x和y的操作需要判斷set[x]和set[y]是否相等,如果不相等,需要將所有滿足set[i]等于set[x]的set[i]變成set[y],由于這里需要遍歷set[i],所以時(shí)間復(fù)雜度為O(n)。圖三-2-1展示了樸素算法的一個(gè)例子,該數(shù)組一共記錄了四個(gè)集合,并且用每個(gè)集合的最小數(shù)字作為該集合的標(biāo)識(shí)。
 
圖三-2-1
    給出樸素算法的偽代碼如下:
    int find (int x) {
        return set[x];
    }
    void merge(int x, int y) {
        int rx = find(x), ry = find(y);
        if(rx != ry) {
            for(int i = 1; i <= n; i++) {
                if( set[i] == rx ) set[i] = ry;
            }
        }
    }
    初始化set[i] = i,每次得到一組數(shù)據(jù)(x, y),就執(zhí)行merge(x, y),統(tǒng)計(jì)完所有數(shù)據(jù)后,對(duì)set數(shù)組進(jìn)行一次線掃,就能統(tǒng)計(jì)出總共有多少個(gè)門(mén)派。
    3、森林實(shí)現(xiàn)
    由于樸素實(shí)現(xiàn)合并操作的時(shí)間復(fù)雜度太高,在人數(shù)很多的情況下,效率上非常吃虧,如果有n次合并操作,那么總的時(shí)間復(fù)雜度就是O(n^2)。所以我們將集合的表示進(jìn)行一定的優(yōu)化,將一個(gè)個(gè)集合用樹(shù)的形式來(lái)組織,多個(gè)集合就組成了一個(gè)森林。用pre[i]表示i在集合樹(shù)上的父結(jié)點(diǎn),當(dāng)pre[i]等于i時(shí),則表示i為這棵集合樹(shù)的根結(jié)點(diǎn)。那么顯而易見(jiàn)的,對(duì)于合并操作(x, y),只需要查找x和y在各自集合樹(shù)的根結(jié)點(diǎn)rx和ry,如果rx和ry不相等,則將rx設(shè)為ry的父結(jié)點(diǎn),即令pre[ry] = rx。查找操作更加簡(jiǎn)單,只要順著父結(jié)點(diǎn)一直找到根結(jié)點(diǎn),就找到了該結(jié)點(diǎn)所在的集合。初始化pre[i] = i,即表示森林中有n棵一個(gè)結(jié)點(diǎn)的樹(shù)。如圖三-3-1為合并x,y所在集合的操作。

 

圖三-3-1

    給出森林實(shí)現(xiàn)的偽代碼如下:

    int find (int x) {
        return x == pre[x] ? x : find(pre[x]);
    }
    void merge(int x, int y) {
        int rx = find(x), ry = find(y);
        pre[ry] = rx;
    }
    代碼量較樸素算法減少了不少,那時(shí)間復(fù)雜度呢?仔細(xì)觀察,不難發(fā)現(xiàn)兩個(gè)操作的時(shí)間復(fù)雜度其實(shí)是一樣的,瓶頸都在查找操作上,來(lái)看一個(gè)簡(jiǎn)單的例子。
    【例題3】因?yàn)樘煜挛涔Τ錾倭郑院芏嗳硕枷爰尤肷倭至?xí)武,令少林寺的編號(hào)為1。然后給定m(m <= 100000)組數(shù)據(jù)(x, y),表示x和y結(jié)成朋友,當(dāng)x或y等于1時(shí),表示另一個(gè)不等于1的人帶領(lǐng)他的朋友一起加入少林,已知總?cè)藬?shù)n,求最后少林寺來(lái)了多少人。
    一個(gè)最基礎(chǔ)的并查集操作,對(duì)于每條數(shù)據(jù)執(zhí)行merge(x, y)即可,最后一次線性掃描統(tǒng)計(jì)1所在集合的人數(shù)的個(gè)數(shù),但是對(duì)于極限情況,還是會(huì)退化成O(n)的查找,如圖三-3-2所示,每次合并都是一條鏈合并到一個(gè)結(jié)點(diǎn)上,使得原本的樹(shù)退化成了鏈,合并本身是O(1)的,但是在合并前的查找根結(jié)點(diǎn)的過(guò)程中已經(jīng)是O(n)的了,為了避免集合成鏈的情況,需要進(jìn)行啟發(fā)式合并。
 
圖三-3-2
    4、啟發(fā)式合并
    啟發(fā)式合并是為了解決合并過(guò)程中樹(shù)退化成鏈的情況,用depth[i]表示根為i的樹(shù)的最大深度,合并ra和rb時(shí),采用最大深度小的向最大深度大的進(jìn)行合并,如果兩棵樹(shù)的最大深度一樣,則隨便選擇一個(gè)作為根,并且將根的最大深度depth自增1,這樣做的好處是在n次操作后,任何一棵集合樹(shù)的最大深度都不會(huì)超過(guò)log(n),所以使得查找的復(fù)雜度降為O( log(n) )。
    給出啟發(fā)式合并的偽代碼如下:
    int find (int x) {
        return x == pre[x] ? x : find(pre[x]);
    }
    int merge(int x, int y) {
        int rx = find(x), ry = find(y);
        if(rx != ry) {
            if( depth[rx] == depth[ry] ) {
                pre[ry] = rx;
                depth[rx]++;
            }else if( depth[rx] < depth[ry] ) {
                pre[rx] = ry;
            }else {
                pre[ry] = rx;
            }
        }
    }
    啟發(fā)式合并的查找操作不變,合并操作引入了depth數(shù)組,并且在合并過(guò)程中即時(shí)更新。
    5、路徑壓縮
    【例題4】n(n <= 100000)個(gè)門(mén)派編號(hào)為1-n,經(jīng)過(guò)m(m <= 100000)次江湖上的血雨腥風(fēng),不斷產(chǎn)生門(mén)派吞并,每次吞并可以表示成(x, y),即x吞并了y,最后問(wèn)從前往后數(shù)還存在的編號(hào)第k大的那個(gè)門(mén)派的編號(hào)。
    啟發(fā)式合并通過(guò)改善合并操作提高了效率,但是這個(gè)問(wèn)題的合并是有向的,即一定是y向x合并,所以無(wú)法采用按深度的啟發(fā)式合并,那么是否有辦法優(yōu)化查找操作來(lái)改善效率呢?答案是一定的,我們可以在結(jié)點(diǎn)x找到樹(shù)根rx的時(shí)候,將x到rx路徑上的點(diǎn)的父結(jié)點(diǎn)都設(shè)置為rx,這樣做并不會(huì)改變?cè)械募详P(guān)系,如圖三-5-1所示。

 

圖三-5-1

    由于每次查找過(guò)程中都對(duì)路徑進(jìn)行了壓縮,使得任何時(shí)候樹(shù)的深度都是小于4的,從而查找操作可以認(rèn)為是常數(shù)時(shí)間。
    當(dāng)然,如果合并是無(wú)向的同樣可以采用路徑壓縮,這樣做會(huì)導(dǎo)致樹(shù)的深度可能時(shí)刻在變化,所以啟發(fā)式合并的啟發(fā)值不能采用樹(shù)的深度,可以通過(guò)一個(gè)rank值來(lái)進(jìn)行合并,rank值初始為0,當(dāng)兩棵樹(shù)進(jìn)行合并時(shí),如果rank值相同,隨便選一棵樹(shù)的根作為新根進(jìn)行合并,rank值+1;否則rank小的向大的合并,rank值保持不變。
    給出路徑壓縮的偽代碼如下:
    int find(int x) {
        return x == pre[x] ? x : pre[x] = find(pre[x]);
    }
    int merge(int x, int y) {
        int rx = find(x), ry = find(y);
        if(rx != ry) {
            if( rank[rx] == rank[ry] ) {
                pre[ry] = rx;
                rank[rx]++;
            }else if( rank[rx] < rank[ry] ) {
                pre[rx] = ry;
            }else {
                pre[ry] = rx;
            }
        }
    }
    仔細(xì)觀察不難發(fā)現(xiàn),路徑壓縮版本的find和merge操作與啟發(fā)式合并的版本除了紅色標(biāo)注部分,其它完全相同(只是merge函數(shù)中depth變量名換成了rank),find函數(shù)中的賦值操作(紅色部分代碼)很好地詮釋了深度優(yōu)先搜索在回溯時(shí)的完美表現(xiàn),find函數(shù)的返回值一定是這棵集合樹(shù)的根結(jié)點(diǎn)root,回溯的時(shí)候會(huì)經(jīng)過(guò)從x到root的路徑,通過(guò)這一步賦值可以很輕松的將該路徑上所有結(jié)點(diǎn)的父結(jié)點(diǎn)都設(shè)為根結(jié)點(diǎn)root。
    6、元素刪除
    【例題5】話說(shuō)有人加入少林,當(dāng)然也有人還俗,虛竹就是個(gè)很好的例子,還俗后加入了逍遙派,這就是所謂的集合元素的刪除。
    并查集的刪除操作可以描述成:在某個(gè)集合中,將某個(gè)元素孤立出來(lái)成為一個(gè)只有它本身的新的集合。這步操作不能破壞原有的樹(shù)結(jié)構(gòu),單純“拆”樹(shù)是不現(xiàn)實(shí)的,如圖三-6-1所示,是我們的美好預(yù)期,但是事實(shí)上并做不到,因?yàn)樵诓⒉榧臄?shù)據(jù)結(jié)構(gòu)中只記錄了x的父親,而未記錄x的子結(jié)點(diǎn),沒(méi)辦法改變x子結(jié)點(diǎn)的父結(jié)點(diǎn)。

 

圖三-6-1

    而且就算是記錄了子結(jié)點(diǎn),每次刪除操作最壞情況會(huì)更新n個(gè)子結(jié)點(diǎn)的父結(jié)點(diǎn),使得刪除操作的復(fù)雜度退化成O(n),還有一個(gè)問(wèn)題就是x本身如果就是根結(jié)點(diǎn),那么一旦x刪除,它的子結(jié)點(diǎn)就會(huì)妻離子散,家破人亡,需要重新建立關(guān)系,越想越復(fù)雜。
    所以,及時(shí)制止記錄子結(jié)點(diǎn)的想法,采用一種新的思維——二次哈希法(ReHash),對(duì)于每個(gè)結(jié)點(diǎn)都有一個(gè)哈希值,在進(jìn)行查找之前需要將x轉(zhuǎn)化成它的哈希值HASH[x],那么在進(jìn)行刪除的時(shí)候,只要將x的哈希值進(jìn)行改變,變成一個(gè)從來(lái)沒(méi)有出現(xiàn)過(guò)的值(可以采用一個(gè)計(jì)數(shù)器來(lái)實(shí)現(xiàn)這一步),然后對(duì)新的值建立集合,因?yàn)橹挥兴粋€(gè)元素,所以必定是一個(gè)新的集合。這樣做可以保證每次刪除操作的時(shí)間復(fù)雜度都是O(1)的,而且不會(huì)破壞原有樹(shù)結(jié)構(gòu),唯一的一個(gè)缺點(diǎn)就是每刪除一個(gè)結(jié)點(diǎn)其實(shí)是多申請(qǐng)了一塊內(nèi)存,如果刪除操作無(wú)限制,那么內(nèi)存會(huì)無(wú)限增長(zhǎng)。
四、RMQ
    1、樸素算法
    RMQ (Range Minimum/Maximum Query)問(wèn)題是指:對(duì)于長(zhǎng)度為n的數(shù)列Array,回答若干詢問(wèn)RMQ(Array,i,j)(i,j<=n),返回?cái)?shù)列Array中下標(biāo)在i,j里的最小(大)值(或者最小(大)值所在的下標(biāo)),也就是說(shuō),RMQ問(wèn)題是指求區(qū)間最值的問(wèn)題。
    樸素算法就是枚舉區(qū)間的值進(jìn)行大小判定,如果長(zhǎng)度為n的數(shù)組,詢問(wèn)個(gè)數(shù)為q,那么算法的時(shí)間復(fù)雜度為O(qn)。
    2、線段樹(shù)(簡(jiǎn)介)
    線段樹(shù)是一種二叉搜索樹(shù),它的每個(gè)結(jié)點(diǎn)都保存一個(gè)區(qū)間[l, r],如果當(dāng)l等r時(shí),表示該結(jié)點(diǎn)是一個(gè)葉子結(jié)點(diǎn);否則它必定有兩個(gè)子結(jié)點(diǎn),兩個(gè)子結(jié)點(diǎn)保存的區(qū)間分別為[l, mid]和[mid+1, r],其中mid = (l+r)/2的下整,利用二分(分治)的思想將一個(gè)長(zhǎng)度為n的區(qū)間分割成一系列單位區(qū)間(葉子區(qū)間)。
    線段樹(shù)的每個(gè)結(jié)點(diǎn)都可以保存一些信息,最經(jīng)典的應(yīng)用就是區(qū)間最值,可以在結(jié)點(diǎn)上保存該結(jié)點(diǎn)對(duì)應(yīng)區(qū)間[l, r]上的最值,每次詢問(wèn)[A, B]區(qū)間最值時(shí)將區(qū)間[A, B]進(jìn)行劃分,劃分成一些區(qū)間的并集,并且集合中的區(qū)間必定都能在線段樹(shù)結(jié)點(diǎn)上一一對(duì)應(yīng),可以證明一定存在這樣一個(gè)劃分,并且劃分的集合個(gè)數(shù)不會(huì)超過(guò)logn,從而使得每次查詢的時(shí)間復(fù)雜度能夠保證在O(logn)。
    由于線段樹(shù)還有很多經(jīng)典應(yīng)用,不想在這篇文章快要結(jié)束的時(shí)候草草了事,所以在標(biāo)題上加了“簡(jiǎn)介”二字,等到日后有時(shí)間再詳細(xì)介紹吧。
    3、ST算法
    ST(Sparse Table)算法是基于動(dòng)態(tài)規(guī)劃的,
用f[i][j]表示區(qū)間起點(diǎn)為j長(zhǎng)度為2^i的區(qū)間內(nèi)的最小值所在下標(biāo),通俗的說(shuō),就是區(qū)間[j, j + 2^i)的區(qū)間內(nèi)的最小值的下標(biāo)。
    從定義可知,這種表示法的區(qū)間長(zhǎng)度一定是2的冪,所以除了單位區(qū)間(長(zhǎng)度為1的區(qū)間)以外,任意一個(gè)區(qū)間都能夠分成兩份,并且同樣可以用這種表示法進(jìn)行表示,
[j, j + 2^i)的區(qū)間可以分成
[j, j+2^(i-1))和[j + 2^i),于是
可以列出狀態(tài)轉(zhuǎn)移方程為: f[i][j] = RMQ( f[i-1][j], f[i-1][j+2^(i-1)] )。
    f[m][n]的狀態(tài)數(shù)目為n*m = nlogn,每次狀態(tài)轉(zhuǎn)移耗時(shí)O(1),所以預(yù)處理總時(shí)間為O(nlogn)。
    原數(shù)組長(zhǎng)度為n,當(dāng)[j, j + 2^i)區(qū)間右端點(diǎn) j + 2^i - 1 > n時(shí)如何處理?在狀態(tài)轉(zhuǎn)移方程中只有一個(gè)地方會(huì)下標(biāo)越界,所以當(dāng)越界的時(shí)候狀態(tài)轉(zhuǎn)移只有一個(gè)方向,即當(dāng)j + 2^(i-1) > n 時(shí),f[i][j] = f[i-1][j]。
    求解f[i][j]的代碼就不給出了,只需要兩層循環(huán)的狀態(tài)轉(zhuǎn)移就搞定了。
    f[i][j]的計(jì)算只是做了一步預(yù)處理,但是我們?cè)谠儐?wèn)的時(shí)候,不能保證每個(gè)詢問(wèn)區(qū)間長(zhǎng)度都是2的冪,如何利用預(yù)處理出來(lái)的值計(jì)算任何長(zhǎng)度區(qū)間的值就是我們接下來(lái)要解決的問(wèn)題。
    首先只考慮區(qū)間長(zhǎng)度大于1的情況(區(qū)間長(zhǎng)度為1的情況最小值就等于它本身),給定任意區(qū)間[a, b] (1 <= a < b <= n),必定可以找到兩個(gè)區(qū)間X和Y,它們的并是[a, b],并且區(qū)間X的左端點(diǎn)是a,區(qū)間Y的右端點(diǎn)是b,而且兩個(gè)區(qū)間長(zhǎng)度相當(dāng),且都是2的冪,如圖所示:

 

圖四-3-1

    設(shè)區(qū)間長(zhǎng)度為2^k,則X表示的區(qū)間為[a, a + 2^k),Y表示的區(qū)間為(b - 2^k, b],則需要滿足一個(gè)條件就是X的右端點(diǎn)必須大于等于Y的左端點(diǎn)減一,即 a+2^k-1 >= b-2^k,則2^(k+1) >= (b-a+1), 兩邊取對(duì)數(shù)(以2為底),得 k+1 >= lg(b-a+1),則k >= lg(b-a+1) - 1,k只要需要取最小的滿足條件的整數(shù)即可( lg(x)代表以2為底x的對(duì)數(shù) )。
    仔細(xì)觀察發(fā)現(xiàn)b-a+1正好為區(qū)間[a, b]的長(zhǎng)度len,所以只要區(qū)間長(zhǎng)度一定,k就能在常數(shù)時(shí)間內(nèi)求出來(lái)。而區(qū)間長(zhǎng)度只有n種情況,所以k可以通過(guò)預(yù)處理進(jìn)行預(yù)存。
    當(dāng)lg(len)為整數(shù)時(shí),k 取 lg(len)-1,否則k為 lg(len)-1 的上整(并且只有當(dāng)len為2的冪時(shí),lg(len)才為整數(shù))。
    代碼如下:
    for(i = 1, k = 0; i <= n; i++) {
        lg2K[i] = k - 1;
        if((1<<k) == i) k++;
    }
    int RMQ_Query(ValueType val[], int (*f)[MAXN], int a, int b) {
        if(a == b) return a;
        int k = lg2K[ abs(b-a) + 1 ];
        int x = f[k][a], y = f[k][b-(1<<k)+1];
        return val[x] < val[y] ? x : y;
    }
    val數(shù)組代表原數(shù)組,f是個(gè)二維數(shù)組,即上文提到的動(dòng)態(tài)規(guī)劃數(shù)組,x和y分別對(duì)應(yīng)了圖四-3-1中X區(qū)間和Y區(qū)間的最小值所在的下標(biāo)。將這兩部分在原數(shù)組中的數(shù)值進(jìn)行比較就能得到整個(gè)[a, b]區(qū)間內(nèi)的最小值所在下標(biāo)了。
    ST算法可以擴(kuò)展到二維,用四維的數(shù)組來(lái)保存狀態(tài),每個(gè)狀態(tài)表示的是一個(gè)矩形區(qū)域中的最值,可以用來(lái)求解矩形區(qū)域內(nèi)的最值問(wèn)題。
五、最近公共祖先相關(guān)題集整理
    并查集
Is It A Tree?                ★☆☆☆☆     并查集判樹(shù)
Farm Irrigation              ★☆☆☆☆     圖的連通性問(wèn)題
Virtual Friends              ★★☆☆☆     基礎(chǔ)并查集 + 字符串HASH
More is better               ★★★☆☆     基礎(chǔ)并查集
Building Block               ★★★☆☆     并查集(路徑壓縮)
Corporative Network          ★★★☆☆     并查集(路徑壓縮)
Dragon Balls                 ★★★☆☆     并查集(路徑壓縮變形)
Connect the Cities           ★★★☆☆     并查集求解最小生成樹(shù)
How Many Answers Are Wrong   ★★★☆☆     并查集 擴(kuò)展
Warfare                      ★★★☆☆     并查集 + HASH
Junk-Mail Filter             ★★★☆☆     并查集(擴(kuò)展操作:刪除)
The k-th Largest Group       ★★★☆     并查集 + 樹(shù)狀數(shù)組
Regroup                      ★★★★☆     并查集(路徑壓縮)
D-City                       ★★★★☆     并查集(離線應(yīng)用)
Pseudoforest                 ★★★★☆     并查集求最大單環(huán)森林
Code Lock                    ★★★★☆     并查集 + 排列組合
Exclusive-OR                 ★★★★☆     并查集 + BFS
    RMQ
Balanced Lineup              ★☆☆☆☆    裸RMQ
Frequent values              ★★☆☆☆    區(qū)間頻率統(tǒng)計(jì)
Sticks Problem               ★★☆☆☆    二分+RMQ
Cartesian Tree               ★★☆☆    RMQ 構(gòu)造笛卡爾樹(shù)
A Famous City                ★★☆☆    RMQ
WorstWeather Ever            ★★★★☆    二分+RMQ
Matrix Searching             ★★☆☆    二維RMQ
Check Corners                ★★    二維RMQ
    LCA
Nearest Common Ancestors     ★☆☆☆☆
    
LCA(小數(shù)據(jù)) 
Closest Common Ancestors     ★☆☆☆☆    
LCA(小數(shù)據(jù))
Distance Queries             
★★☆☆☆    
LCA求解樹(shù)上結(jié)點(diǎn)間距離
Connections between cities   ★★☆☆☆
    LCA求解樹(shù)上結(jié)點(diǎn)間距離
How far away                 ★★☆☆☆
    LCA求解樹(shù)上結(jié)點(diǎn)間距離
Tree                         ★★★☆☆    LCA(RMQ) + 正負(fù)tag統(tǒng)計(jì)
One and One Story            ★★★☆☆    LCA + 并查集    
CD Opertaion                 ★★★☆☆    LCA(RMQ) + 字符串HASH
Parent and son               ★★★☆☆    無(wú)根樹(shù)LCA
Dylans loves tree            ★★★☆☆    LCA + 線段樹(shù)
Tri-war                      ★★★★☆    三個(gè)點(diǎn)的LCA問(wèn)題
Annoying problem             ★★★★☆    LCA
Checkers                     ★★★★☆    轉(zhuǎn)化成二叉樹(shù)后二分+ LCA
Traffic Time Query System    ★★★★☆    點(diǎn)雙連通 + LCA
Network                      ★★★★☆    邊雙連通 + LCA
An Easy Problem for Elfness  ★★★★☆    主席樹(shù) + LCA
Paths on the tree            ★★★★☆    貪心 + LCA

posted on 2015-12-10 00:14 英雄哪里出來(lái) 閱讀(31159) 評(píng)論(3)  編輯 收藏 引用 所屬分類: 算法專輯

評(píng)論

# re: 夜深人靜寫(xiě)算法(六) - 最近公共祖先  回復(fù)  更多評(píng)論   

dist[u] + dist[v] - dist[a]

這里不應(yīng)該減兩個(gè)dist[a]嗎?
2015-12-22 16:45 | 張晴川

# re: 夜深人靜寫(xiě)算法(六) - 最近公共祖先  回復(fù)  更多評(píng)論   

@張晴川
哈哈是的,筆誤了,多謝指正~~
2015-12-26 14:11 | 英雄哪里出來(lái)

# re: 夜深人靜寫(xiě)算法(六) - 最近公共祖先  回復(fù)  更多評(píng)論   

丐幫、少林、逍遙、大理段氏四個(gè)集合

有意思
2016-01-06 15:41 | bns
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            免费成人美女女| 久久免费精品视频| 欧美三级韩国三级日本三斤| 亚洲伦理网站| 一本色道久久| 国产欧美精品一区二区色综合| 欧美一二区视频| 校园春色综合网| 136国产福利精品导航网址应用 | 国产日韩欧美制服另类| 欧美伊久线香蕉线新在线| 欧美一区二区日韩| 在线播放中文一区| 亚洲精华国产欧美| 欧美午夜片欧美片在线观看| 久久大逼视频| 欧美mv日韩mv国产网站| 亚洲愉拍自拍另类高清精品| 欧美一区二区三区免费看| 亚洲第一毛片| 中日韩视频在线观看| 激情久久久久久| 亚洲美女淫视频| 亚洲一区二区精品视频| 国产一区成人| 亚洲精选视频在线| 精品动漫3d一区二区三区免费版| 亚洲国产影院| 国产欧美日韩综合| 亚洲人成绝费网站色www| 国产精品久久久久aaaa| 欧美福利在线观看| 国产日韩欧美成人| 日韩天堂av| 1024成人| 欧美专区在线观看一区| 亚洲一区二区精品在线| 久久夜色精品一区| 欧美尤物巨大精品爽| 欧美精品在线一区| 卡通动漫国产精品| 国产精品网站在线| 日韩小视频在线观看| 亚洲国产精品va在线看黑人动漫| 亚洲欧美成人一区二区三区| 亚洲免费黄色| 欧美 日韩 国产一区二区在线视频| 香蕉久久夜色精品国产使用方法| 免费视频一区二区三区在线观看| 久久精品视频导航| 国产精品国产三级国产aⅴ无密码| 亚洲国产精选| 亚洲国产影院| 六月婷婷久久| 免费在线观看一区二区| 国产一区二区| 欧美一区二区三区四区在线观看地址| 亚洲一区二区三区四区视频| 欧美激情精品久久久六区热门| 久久久久久久久岛国免费| 国产欧美日韩一区二区三区| 一本大道久久精品懂色aⅴ| 日韩午夜精品| 欧美精品免费视频| 亚洲第一毛片| 亚洲三级视频| 欧美日韩一区二区视频在线观看| 亚洲国产一成人久久精品| 亚洲人在线视频| 欧美激情欧美狂野欧美精品| 亚洲风情亚aⅴ在线发布| 亚洲激情婷婷| 欧美日本国产精品| 亚洲作爱视频| 欧美一区二区| 好吊色欧美一区二区三区四区 | 另类天堂av| 亚洲国产精品一区二区第一页| 亚洲精品久久久久久久久久久久| 欧美国产日韩免费| 在线一区二区三区四区五区| 性色一区二区三区| 伊人成人在线视频| 欧美成人xxx| 一本色道88久久加勒比精品| 午夜精品婷婷| 在线看片日韩| 欧美午夜在线观看| 欧美国产日韩亚洲一区| 中文精品一区二区三区| 欧美午夜宅男影院在线观看| 欧美在线网站| 亚洲欧洲综合另类| 欧美一区二区视频免费观看| 韩日精品在线| 欧美日韩国产综合一区二区| 亚洲欧美精品中文字幕在线| 欧美大尺度在线观看| 亚洲综合日韩在线| 狠狠色丁香久久综合频道 | 亚洲你懂的在线视频| 欧美99久久| 亚洲欧美日韩国产成人精品影院 | 欧美成人69av| 欧美一区二区三区喷汁尤物| 亚洲二区在线| 久久人91精品久久久久久不卡| 99这里有精品| 激情91久久| 国产精品久久999| 麻豆久久精品| 欧美一区二区三区四区夜夜大片| 最近看过的日韩成人| 久久久青草婷婷精品综合日韩| 99国产精品视频免费观看| 韩国成人精品a∨在线观看| 欧美美女日韩| 能在线观看的日韩av| 欧美中文字幕| 亚洲女同性videos| 一区二区日韩| 亚洲精品国产精品乱码不99按摩 | 99re8这里有精品热视频免费| 国产在线精品一区二区夜色| 欧美日韩影院| 欧美日韩国产综合新一区| 美女视频网站黄色亚洲| 欧美在线看片| 欧美一区二区三区久久精品 | 久久在线免费观看视频| 亚洲欧美日韩一区二区三区在线| 一本色道久久综合亚洲精品小说 | 久久久久久久网站| 欧美与欧洲交xxxx免费观看| 亚洲男女自偷自拍| 亚洲一区二区免费视频| 99亚洲伊人久久精品影院红桃| 亚洲娇小video精品| 亚洲国产精品一区在线观看不卡| 欧美黄色免费| 亚洲高清视频一区二区| 欧美二区在线播放| 亚洲国产99| 欧美激情在线狂野欧美精品| 欧美电影免费观看大全| 欧美 日韩 国产 一区| 欧美大片专区| 亚洲国产日韩欧美综合久久| 亚洲狠狠婷婷| 亚洲伦理自拍| 亚洲最新视频在线| 亚洲在线免费视频| 久久国产一二区| 久久人人97超碰精品888| 久久午夜国产精品| 欧美激情精品久久久久久黑人| 欧美日韩大陆在线| 国产精品久久999| 国产一区二区三区黄| 麻豆国产精品777777在线| 欧美成年人在线观看| 欧美日韩精品一本二本三本| 国产精品乱码一区二三区小蝌蚪| 国产麻豆91精品| 影音欧美亚洲| 日韩视频免费观看高清在线视频| 亚洲性色视频| 久久久99爱| 亚洲国产高清在线观看视频| 一区二区三区久久久| 欧美在线中文字幕| 欧美激情视频给我| 国产精品一区二区久久| 亚洲激情在线激情| 亚洲在线免费视频| 免费人成网站在线观看欧美高清| 亚洲黄色一区| 欧美一区二区高清在线观看| 欧美sm视频| 国精产品99永久一区一区| 亚洲欧洲视频| 久久另类ts人妖一区二区| 亚洲人成在线观看| 欧美在线影院在线视频| 欧美日韩裸体免费视频| 在线视频国产日韩| 午夜久久影院| 亚洲人成欧美中文字幕| 欧美在线免费| 国产精品国产精品国产专区不蜜| 有码中文亚洲精品| 亚洲欧美日本伦理| 最新中文字幕亚洲| 久久久噜噜噜久久中文字幕色伊伊| 欧美人与禽猛交乱配视频| 精品999久久久| 欧美在线影院在线视频| 日韩午夜在线电影| 欧美激情精品久久久久久久变态| 韩国av一区二区三区在线观看|