精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

跳表(SkipList)設計與實現(Java)

開發 前端
跳表是面試常問的一種數據結構,它在很多中間件和語言中得到應用,我們熟知的就有Redis跳表。并且在面試的很多場景可能會問到,偶爾還會讓你手寫試一試(跳表可能會讓手寫,紅黑樹是不可能的).

[[360331]]

 前言

跳表是面試常問的一種數據結構,它在很多中間件和語言中得到應用,我們熟知的就有Redis跳表。并且在面試的很多場景可能會問到,偶爾還會讓你手寫試一試(跳表可能會讓手寫,紅黑樹是不可能的),這不,給大伙復原一個場景:

但你別慌,遇到蘑菇頭這種面試官也別怕,因為你看到這篇文章了(得意),不用像熊貓那樣窘迫。

對于一個數據結構或算法,人群數量從聽過名稱、了解基本原理、清楚執行流程、能夠手寫 呈抖降的趨勢。因為很多數據結構與算法其核心原理可能簡單,但清楚其執行流程就需要動腦子去思考想明白,但是如果能夠把它寫出來,那就要自己一步步去設計和實現。可能要花很久才能真正寫出來,并且還可能要查閱大量的資料。

而本文在前面進行介紹跳表,后面部分詳細介紹跳表的設計和實現,搞懂跳表,這一篇真的就夠了。

快速了解跳表

跳躍表(簡稱跳表)由美國計算機科學家William Pugh發明于1989年。他在論文《Skip lists: a probabilistic alternative to balanced trees》中詳細介紹了跳表的數據結構和插入刪除等操作。

  • 跳表(SkipList,全稱跳躍表)是用于有序元素序列快速搜索查找的一個數據結構,跳表是一個隨機化的數據結構,實質就是一種可以進行二分查找的有序鏈表。跳表在原有的有序鏈表上面增加了多級索引,通過索引來實現快速查找。跳表不僅能提高搜索性能,同時也可以提高插入和刪除操作的性能。它在性能上和紅黑樹,AVL樹不相上下,但是跳表的原理非常簡單,實現也比紅黑樹簡單很多。

在這里你可以看到一些關鍵詞:鏈表(有序鏈表)、索引、二分查找。想必你的腦海中已經有了一個初略的印象,不過你可能還是不清楚這個"會跳的鏈表"有多diao,甚至還可能會產生一點疑慮:跟隨機化有什么關系?你在下文中很快就能得到答案!

回顧鏈表,我們知道鏈表和順序表(數組)通常都是相愛相殺,成對出現,各有優劣。而鏈表的優勢就是更高效的插入、刪除。痛點就是查詢很慢很慢!每次查詢都是一種O(n)復雜度的操作,鏈表估計自己都氣的想哭了。

這是一個帶頭結點的鏈表(頭結點相當于一個固定的入口,不存儲有意義的值),每次查找都需要一個個枚舉,相當的慢,我們能不能稍微優化一下,讓它稍微跳一跳呢?答案是可以的,我們知道很多算法和數據結構以空間換時間,我們在上面加一層索引,讓部分節點在上層能夠直接定位到,這樣鏈表的查詢時間近乎減少一半,鏈表自己雖然沒有開心起來,但收起了它想哭的臉。

這樣,在查詢某個節點的時候,首先會從上一層快速定位節點所在的一個范圍,如果找到具體范圍向下然后查找代價很小,當然在表的結構設計上會增加一個向下的索引(指針)用來查找確定底層節點。平均查找速度平均為O(n/2)。但是當節點數量很大的時候,它依舊很慢很慢。我們都知道二分查找是每次都能折半的去壓縮查找范圍,要是有序鏈表也能這么跳起來那就太完美了。沒錯跳表就能讓鏈表擁有近乎的接近二分查找的效率的一種數據結構,其原理依然是給上面加若干層索引,優化查找速度。

 

通過上圖你可以看到,通過這樣的一個數據結構對有序鏈表進行查找都能近乎二分的性能。就是在上面維護那么多層的索引,首先在最高級索引上查找最后一個小于當前查找元素的位置,然后再跳到次高級索引繼續查找,直到跳到最底層為止,這時候以及十分接近要查找的元素的位置了(如果查找元素存在的話)。由于根據索引可以一次跳過多個元素,所以跳查找的查找速度也就變快了。

對于理想的跳表,每向上一層索引節點數量都是下一層的1/2.那么如果n個節點增加的節點數量(1/2+1/4+…)的結構真的存在嗎?大概率不存在的,因為作為一個鏈表,少不了增刪該查的一些操作。而刪除和插入可能會改變整個結構,所以上面的這些都是理想的結構,在插入的時候是否添加上層索引是個概率問題(1>

跳表的增刪改查

上面稍微了解了跳表是個啥,那么在這里就給大家談談跳表的增刪改查過程。在實現本跳表的過程為了便于操作,我們將跳表的頭結點(head)的key設為int的最小值(一定滿足左小右大方便比較)。

對于每個節點的設置,設置成SkipNode類,為了防止初學者將next向下還是向右搞混,直接設置right,down兩個指針。

  1. class SkipNode<T> 
  2.     int key
  3.     T value; 
  4.     SkipNode right,down;//右下個方向的指針 
  5.     public SkipNode (int key,T value) { 
  6.         this.key=key
  7.         this.value=value; 
  8.     } 

 跳表的結構和初始化也很重要,其主要參數和初始化方法為:

  1. public class SkipList <T> { 
  2.  
  3.     SkipNode headNode;//頭節點,入口 
  4.     int highLevel;//當前跳表索引層數 
  5.     Random random;// 用于投擲硬幣 
  6.     final int MAX_LEVEL = 32;//最大的層 
  7.  
  8.     SkipList(){ 
  9.         random=new Random(); 
  10.         headNode=new SkipNode(Integer.MIN_VALUE,null); 
  11.         highLevel=0; 
  12.     } 
  13.     //其他方法 

 查詢操作

很多時候鏈表也可能這樣相連僅僅是某個元素或者key作為有序的標準。所以有可能鏈表內部存在一些value。不過修改和查詢其實都是一個操作,找到關鍵數字(key)。并且查找的流程也很簡單,設置一個臨時節點team=head。當team不為null其流程大致如下:

(1) 從team節點出發,如果當前節點的key與查詢的key相等,那么返回當前節點(如果是修改操作那么一直向下進行修改值即可)。

(2) 如果key不相等,且右側為null,那么證明只能向下(結果可能出現在下右方向),此時team=team.down

(3) 如果key不相等,且右側不為null,且右側節點key小于待查詢的key。那么說明同級還可向右,此時team=team.right

(4)(否則的情況)如果key不相等,且右側不為null,且右側節點key大于待查詢的key 。那么說明如果有結果的話就在這個索引和下個索引之間,此時team=team.down。

最終將按照這個步驟返回正確的節點或者null(說明沒查到)。

例如上圖查詢12節點,首先第一步從head出發發現右側不為空,且7<12,向右;第二步右側為null向下;第三步節點7的右側10<12繼續向右;第四步10右側為null向下;第五步右側12小于等于向右。第六步起始發現相等返回節點結束。

而這塊的代碼也非常容易:

  1. public SkipNode search(int key) { 
  2.     SkipNode team=headNode; 
  3.     while (team!=null) { 
  4.         if(team.key==key
  5.         { 
  6.             return  team; 
  7.         } 
  8.         else if(team.right==null)//右側沒有了,只能下降 
  9.         { 
  10.             team=team.down; 
  11.         } 
  12.         else if(team.right.key>key)//需要下降去尋找 
  13.         { 
  14.             team=team.down; 
  15.         } 
  16.         else //右側比較小向右 
  17.         { 
  18.             team=team.right
  19.         } 
  20.     } 
  21.     return null

 刪除操作

刪除操作比起查詢稍微復雜一丟丟,但是比插入簡單。刪除需要改變鏈表結構所以需要處理好節點之間的聯系。對于刪除操作你需要謹記以下幾點:

(1)刪除當前節點和這個節點的前后節點都有關系

(2)刪除當前層節點之后,下一層該key的節點也要刪除,一直刪除到最底層

根據這兩點分析一下:如果找到當前節點了,它的前面一個節點怎么查找呢?這個總不能再遍歷一遍吧!有的使用四個方向的指針(上下左右)用來找到左側節點。是可以的,但是這里可以特殊處理一下 ,不直接判斷和操作節點,先找到待刪除節點的左側節點。通過這個節點即可完成刪除,然后這個節點直接向下去找下一層待刪除的左側節點。設置一個臨時節點team=head,當team不為null具體循環流程為:

(1)如果team右側為null,那么team=team.down(之所以敢直接這么判斷是因為左側有頭結點在左側,不用擔心特殊情況)

(2)如果team右側不 為null,并且右側的key等于待刪除的key,那么先刪除節點,再team向下team=team.down為了刪除下層節點。

(3)如果team右側不 為null,并且右側key小于待刪除的key,那么team向右team=team.right。

(4)如果team右側不 為null,并且右側key大于待刪除的key,那么team向下team=team.down,在下層繼續查找刪除節點。

例如上圖刪除10節點,首先team=head從team出發,7<10向右(team=team.right后面省略);第二步右側為null只能向下;第三部右側為10在當前層刪除10節點然后向下繼續查找下一層10節點;第四步8<10向右;第五步右側為10刪除該節點并且team向下。team為null說明刪除完畢退出循環。

刪除操作實現的代碼如下:

  1. public void delete(int key)//刪除不需要考慮層數 
  2.     SkipNode team=headNode; 
  3.     while (team!=null) { 
  4.         if (team.right == null) {//右側沒有了,說明這一層找到,沒有只能下降 
  5.             team=team.down; 
  6.         } 
  7.         else if(team.right.key==key)//找到節點,右側即為待刪除節點 
  8.         { 
  9.             team.right=team.right.right;//刪除右側節點 
  10.             team=team.down;//向下繼續查找刪除 
  11.         } 
  12.         else if(team.right.key>key)//右側已經不可能了,向下 
  13.         { 
  14.             team=team.down; 
  15.         } 
  16.         else { //節點還在右側 
  17.             team=team.right
  18.         } 
  19.     } 

 

插入操作

插入操作在實現起來是最麻煩的,需要的考慮的東西最多。回顧查詢,不需要動索引;回顧刪除,每層索引如果有刪除就是了。但是插入不一樣了,插入需要考慮是否插入索引,插入幾層等問題。由于需要插入刪除所以我們肯定無法維護一個完全理想的索引結構,因為它耗費的代價太高。但我們使用隨機化的方法去判斷是否向上層插入索引。即產生一個[0-1]的隨機數如果小于0.5就向上插入索引,插入完畢后再次使用隨機數判斷是否向上插入索引。運氣好這個值可能是多層索引,運氣不好只插入最底層(這是100%插入的)。但是索引也不能不限制高度,我們一般會設置索引最高值如果大于這個值就不往上繼續添加索引了。

我們一步步剖析該怎么做,其流程為

(1)首先通過上面查找的方式,找到待插入的左節點。插入的話最底層肯定是需要插入的,所以通過鏈表插入節點(需要考慮是否為末尾節點)

(2)插入完這一層,需要考慮上一層是否插入,首先判斷當前索引層級,如果大于最大值那么就停止(比如已經到最高索引層了)。否則設置一個隨機數1/2的概率向上插入一層索引(因為理想狀態下的就是每2個向上建一個索引節點)。

(3)繼續(2)的操作,直到概率退出或者索引層數大于最大索引層。

具體向上插入的時候,實質上還有非常重要的細節需要考慮。首先如何找到上層的待插入節點 ?

這個各個實現方法可能不同,如果有左、上指向的指針那么可以向左向上找到上層需要插入的節點,但是如果只有右指向和下指向的我們也可以巧妙的借助查詢過程中記錄下降的節點。因為曾經下降的節點倒序就是需要插入的節點,最底層也不例外(因為沒有匹配值會下降為null結束循環)。在這里我使用這個數據結構進行存儲,當然使用List也可以。下圖就是給了一個插入示意圖。

其次如果該層是目前的最高層索引,需要繼續向上建立索引應該怎么辦?

首先跳表最初肯定是沒索引的,然后慢慢添加節點才有一層、二層索引,但是如果這個節點添加的索引突破當前最高層,該怎么辦呢?

這時候需要注意了,跳表的head需要改變了,新建一個ListNode節點作為新的head,將它的down指向老head,將這個head節點加入棧中(也就是這個節點作為下次后面要插入的節點),就比如上面的9節點如果運氣夠好再往上建立一層節點,會是這樣的。

插入上層的時候注意所有節點要新建(拷貝),除了right的指向down的指向也不能忘記,down指向上一個節點可以用一個臨時節點作為前驅節點。如果層數突破當前最高層,頭head節點(入口)需要改變。

這部分更多的細節在代碼中注釋解釋了,詳細代碼為:

  1. public void add(SkipNode node) 
  2.  
  3.     int key=node.key
  4.     SkipNode findNode=search(key); 
  5.     if(findNode!=null)//如果存在這個key的節點 
  6.     { 
  7.         findNode.value=node.value; 
  8.         return
  9.     } 
  10.     Stack<SkipNode>stack=new Stack<SkipNode>();//存儲向下的節點,這些節點可能在右側插入節點 
  11.     SkipNode team=headNode;//查找待插入的節點   找到最底層的哪個節點。 
  12.     while (team!=null) {//進行查找操作  
  13.         if(team.right==null)//右側沒有了,只能下降 
  14.         { 
  15.             stack.add(team);//將曾經向下的節點記錄一下 
  16.             team=team.down; 
  17.         } 
  18.         else if(team.right.key>key)//需要下降去尋找 
  19.         { 
  20.             stack.add(team);//將曾經向下的節點記錄一下 
  21.             team=team.down; 
  22.         } 
  23.         else //向右 
  24.         { 
  25.             team=team.right
  26.         } 
  27.     } 
  28.     int level=1;//當前層數,從第一層添加(第一層必須添加,先添加再判斷) 
  29.     SkipNode downNode=null;//保持前驅節點(即down的指向,初始為null
  30.     while (!stack.isEmpty()) { 
  31.         //在該層插入node 
  32.         team=stack.pop();//拋出待插入的左側節點 
  33.         SkipNode nodeTeam=new SkipNode(node.key, node.value);//節點需要重新創建 
  34.         nodeTeam.down=downNode;//處理豎方向 
  35.         downNode=nodeTeam;//標記新的節點下次使用 
  36.         if(team.right==null) {//右側為null 說明插入在末尾 
  37.             team.right=nodeTeam; 
  38.         } 
  39.         //水平方向處理 
  40.         else {//右側還有節點,插入在兩者之間 
  41.             nodeTeam.right=team.right
  42.             team.right=nodeTeam; 
  43.         } 
  44.         //考慮是否需要向上 
  45.         if(level>MAX_LEVEL)//已經到達最高級的節點啦 
  46.             break; 
  47.         double num=random.nextDouble();//[0-1]隨機數 
  48.         if(num>0.5)//運氣不好結束 
  49.             break; 
  50.         level++; 
  51.         if(level>highLevel)//比當前最大高度要高但是依然在允許范圍內 需要改變head節點 
  52.         { 
  53.             highLevel=level
  54.             //需要創建一個新的節點 
  55.             SkipNode highHeadNode=new SkipNode(Integer.MIN_VALUE, null); 
  56.             highHeadNode.down=headNode; 
  57.             headNode=highHeadNode;//改變head 
  58.             stack.add(headNode);//下次拋出head 
  59.         } 
  60.     } 

 總結

對于上面,跳表完整分析就結束啦,當然,你可能看到不同品種跳表的實現,還有的用數組方式表示上下層的關系這樣也可以,但本文只定義right和down兩個方向的鏈表更純正化的講解跳表。

對于跳表以及跳表的同類競爭產品:紅黑樹,為啥Redis的有序集合(zset) 使用跳表呢?因為跳表除了查找插入維護和紅黑樹有著差不多的效率,它是個鏈表,能確定范圍區間,而區間問題在樹上可能就沒那么方便查詢啦。而JDK中跳躍表ConcurrentSkipListSet和ConcurrentSkipListMap。 有興趣的也可以查閱一下源碼。

對于學習,完整的代碼是非常重要的,這里我把完整代碼貼出來,需要的自取。

  1. import java.util.Random; 
  2. import java.util.Stack; 
  3. class SkipNode<T> 
  4.     int key
  5.     T value; 
  6.     SkipNode right,down;//左右上下四個方向的指針 
  7.     public SkipNode (int key,T value) { 
  8.         this.key=key
  9.         this.value=value; 
  10.     } 
  11.  
  12. public class SkipList <T> { 
  13.  
  14.     SkipNode headNode;//頭節點,入口 
  15.     int highLevel;//層數 
  16.     Random random;// 用于投擲硬幣 
  17.     final int MAX_LEVEL = 32;//最大的層 
  18.     SkipList(){ 
  19.         random=new Random(); 
  20.         headNode=new SkipNode(Integer.MIN_VALUE,null); 
  21.         highLevel=0; 
  22.     } 
  23.     public SkipNode search(int key) { 
  24.         SkipNode team=headNode; 
  25.         while (team!=null) { 
  26.             if(team.key==key
  27.             { 
  28.                 return  team; 
  29.             } 
  30.             else if(team.right==null)//右側沒有了,只能下降 
  31.             { 
  32.                 team=team.down; 
  33.             } 
  34.             else if(team.right.key>key)//需要下降去尋找 
  35.             { 
  36.                 team=team.down; 
  37.             } 
  38.             else //右側比較小向右 
  39.             { 
  40.                 team=team.right
  41.             } 
  42.         } 
  43.         return null
  44.     } 
  45.  
  46.     public void delete(int key)//刪除不需要考慮層數 
  47.     { 
  48.         SkipNode team=headNode; 
  49.         while (team!=null) { 
  50.             if (team.right == null) {//右側沒有了,說明這一層找到,沒有只能下降 
  51.                 team=team.down; 
  52.             } 
  53.             else if(team.right.key==key)//找到節點,右側即為待刪除節點 
  54.             { 
  55.                 team.right=team.right.right;//刪除右側節點 
  56.                 team=team.down;//向下繼續查找刪除 
  57.             } 
  58.             else if(team.right.key>key)//右側已經不可能了,向下 
  59.             { 
  60.                 team=team.down; 
  61.             } 
  62.             else { //節點還在右側 
  63.                 team=team.right
  64.             } 
  65.         } 
  66.     } 
  67.     public void add(SkipNode node) 
  68.     { 
  69.  
  70.         int key=node.key
  71.         SkipNode findNode=search(key); 
  72.         if(findNode!=null)//如果存在這個key的節點 
  73.         { 
  74.             findNode.value=node.value; 
  75.             return
  76.         } 
  77.  
  78.         Stack<SkipNode>stack=new Stack<SkipNode>();//存儲向下的節點,這些節點可能在右側插入節點 
  79.         SkipNode team=headNode;//查找待插入的節點   找到最底層的哪個節點。 
  80.         while (team!=null) {//進行查找操作 
  81.             if(team.right==null)//右側沒有了,只能下降 
  82.             { 
  83.                 stack.add(team);//將曾經向下的節點記錄一下 
  84.                 team=team.down; 
  85.             } 
  86.             else if(team.right.key>key)//需要下降去尋找 
  87.             { 
  88.                 stack.add(team);//將曾經向下的節點記錄一下 
  89.                 team=team.down; 
  90.             } 
  91.             else //向右 
  92.             { 
  93.                 team=team.right
  94.             } 
  95.         } 
  96.  
  97.         int level=1;//當前層數,從第一層添加(第一層必須添加,先添加再判斷) 
  98.         SkipNode downNode=null;//保持前驅節點(即down的指向,初始為null
  99.         while (!stack.isEmpty()) { 
  100.             //在該層插入node 
  101.             team=stack.pop();//拋出待插入的左側節點 
  102.             SkipNode nodeTeam=new SkipNode(node.key, node.value);//節點需要重新創建 
  103.             nodeTeam.down=downNode;//處理豎方向 
  104.             downNode=nodeTeam;//標記新的節點下次使用 
  105.             if(team.right==null) {//右側為null 說明插入在末尾 
  106.                 team.right=nodeTeam; 
  107.             } 
  108.             //水平方向處理 
  109.             else {//右側還有節點,插入在兩者之間 
  110.                 nodeTeam.right=team.right
  111.                 team.right=nodeTeam; 
  112.             } 
  113.             //考慮是否需要向上 
  114.             if(level>MAX_LEVEL)//已經到達最高級的節點啦 
  115.                 break; 
  116.             double num=random.nextDouble();//[0-1]隨機數 
  117.             if(num>0.5)//運氣不好結束 
  118.                 break; 
  119.             level++; 
  120.             if(level>highLevel)//比當前最大高度要高但是依然在允許范圍內 需要改變head節點 
  121.             { 
  122.                 highLevel=level
  123.                 //需要創建一個新的節點 
  124.                 SkipNode highHeadNode=new SkipNode(Integer.MIN_VALUE, null); 
  125.                 highHeadNode.down=headNode; 
  126.                 headNode=highHeadNode;//改變head 
  127.                 stack.add(headNode);//下次拋出head 
  128.             } 
  129.         } 
  130.  
  131.     } 
  132.     public void printList() { 
  133.         SkipNode teamNode=headNode; 
  134.         int index=1; 
  135.         SkipNode last=teamNode; 
  136.         while (last.down!=null){ 
  137.             last=last.down; 
  138.         } 
  139.         while (teamNode!=null) { 
  140.             SkipNode enumNode=teamNode.right
  141.             SkipNode enumLast=last.right
  142.             System.out.printf("%-8s","head->"); 
  143.             while (enumLast!=null&&enumNode!=null) { 
  144.                 if(enumLast.key==enumNode.key
  145.                 { 
  146.                     System.out.printf("%-5s",enumLast.key+"->"); 
  147.                     enumLast=enumLast.right
  148.                     enumNode=enumNode.right
  149.                 } 
  150.                 else
  151.                     enumLast=enumLast.right
  152.                     System.out.printf("%-5s",""); 
  153.                 } 
  154.  
  155.             } 
  156.             teamNode=teamNode.down; 
  157.             index++; 
  158.             System.out.println(); 
  159.         } 
  160.     } 
  161.     public static void main(String[] args) { 
  162.         SkipList<Integer>list=new SkipList<Integer>(); 
  163.         for(int i=1;i<20;i++) 
  164.         { 
  165.             list.add(new SkipNode(i,666)); 
  166.         } 
  167.         list.printList(); 
  168.         list.delete(4); 
  169.         list.delete(8); 
  170.         list.printList(); 
  171.     } 

 進行測試一下可以發現跳表還是挺完美的(自夸一下)。

 

責任編輯:姜華 來源: bigsai
相關推薦

2024-12-13 16:28:43

2025-01-06 08:10:00

Redis跳表索引

2023-09-27 09:39:08

Java優化

2010-02-26 13:14:39

Java日志系統

2025-06-23 10:13:00

FutureTask線程開發

2010-03-02 09:38:16

Java熱替換

2009-06-14 22:09:24

Java界面布局DSL

2023-08-10 10:13:35

轉轉短鏈平臺

2015-11-03 09:28:52

Hybrid技術設計實現

2022-09-12 07:17:20

redis命令redissynce

2023-05-26 08:24:17

短信渠道模型

2015-06-30 11:05:11

flexibleWebAPP設計

2020-10-23 08:31:15

Nodejs-Ipc設計實現

2022-09-14 09:37:22

數據系統

2023-05-17 00:15:11

TCCXA模式

2020-10-19 10:01:12

Nodejs線程池設計

2022-05-25 11:24:25

CalendarNutUI移動端

2024-06-27 10:48:48

2022-12-28 08:31:38

平臺設計應用

2017-03-20 17:49:21

Java Web模板代碼
點贊
收藏

51CTO技術棧公眾號

日本韩国精品在线| 国产在线精品一区二区| 亚洲欧美日韩第一区| 奇米影音第四色| 男女在线观看视频| 国产视频一区二区在线观看| 成人免费网站在线| 香蕉免费毛片视频| 久久精品国产www456c0m| 日韩视频一区二区在线观看| 黄色免费观看视频网站| 免费黄色在线| 高清在线不卡av| 国产精品久久久久久婷婷天堂| 乱h高h女3p含苞待放| 色婷婷精品视频| 欧美一区二区三区影视| 黄色片久久久久| 欧美xxxx做受欧美88bbw| 久久精品一区二区| 国产一区二区三区四区hd| 97人妻精品一区二区三区| 999在线观看精品免费不卡网站| 日韩中文综合网| 久久只有这里有精品| 99久久婷婷国产综合精品青牛牛| 欧美日韩国产综合一区二区 | 久久99精品久久| 久久免费美女视频| 久久99影院| 好男人www在线视频| 狠狠色狠狠色合久久伊人| 国产精品成人国产乱一区| 国产成人亚洲精品自产在线| 欧美在线资源| zzjj国产精品一区二区| 免费在线观看污| 欧美日韩一区二区三区不卡视频| 日韩精品在线看片z| 亚洲无在线观看| 看片一区二区| 欧美日韩精品一区二区三区四区| 欧美两根一起进3p做受视频| 一区二区三区电影大全| 午夜激情一区二区| 日本韩国欧美在线观看| 国产丝袜在线观看视频| 亚洲午夜一二三区视频| 久久综合久久久久| caoporn-草棚在线视频最| 亚洲综合视频在线| 91动漫在线看| 中文一区一区三区高中清不卡免费| 亚洲一区二区3| 国产免费一区二区视频| 男人av在线播放| 日韩欧美国产高清91| 麻豆av免费在线| 亚洲高清黄色| 欧美三区在线观看| 亚洲国产高清av| 91视频成人| 日韩欧美国产一区二区三区| 国产成人av片| 欧美理伦片在线播放| 亚洲男人av在线| 欧美激情久久久久久久| 久久一级电影| 久久99久久99精品免观看粉嫩| 久草视频免费在线| 在线一区视频| 国产精品视频免费观看www| 一级淫片免费看| 国产+成+人+亚洲欧洲自线| 国产在线精品一区二区三区》| 三级在线播放| 国产精品久久久久久久岛一牛影视| 亚洲在线播放电影| 日本电影在线观看| 欧美天堂在线观看| 手机免费av片| 国产精品45p| 夜夜嗨av一区二区三区免费区| 亚洲区一区二区三| 亚洲巨乳在线| 成人av资源在线播放| 好吊色视频一区二区| 国产视频一区不卡| 日本三级中文字幕在线观看| 三级在线看中文字幕完整版| 欧美日本视频在线| 麻豆精品国产传媒av| 日韩精品dvd| 久久全球大尺度高清视频| 波多野结衣理论片| 国产成人一区在线| 日韩电影免费观看在| av免费看在线| 欧美在线免费视屏| 人妻 日韩 欧美 综合 制服| 日韩精品永久网址| 97香蕉超级碰碰久久免费软件| 懂色av蜜臀av粉嫩av喷吹 | 国产精品伦子伦| 久久大综合网| 45www国产精品网站| 国产免费视频一区二区三区| 久久亚洲一区二区三区明星换脸 | 91麻豆精品91久久久久久清纯| 国产黑丝在线观看| 欧美激情1区2区| 国产欧美精品日韩| 巨骚激情综合| 欧美日韩免费网站| 欧美日韩一区二区区| 成人激情诱惑| 欧美亚洲视频一区二区| 成人黄色免费视频| 最新日韩在线视频| 天天爱天天操天天干| 丝袜连裤袜欧美激情日韩| 欧美多人爱爱视频网站| 91亚洲精品国偷拍自产在线观看| 久久久蜜桃精品| 自拍日韩亚洲一区在线| 一区二区三区四区高清视频 | 精品国产露脸精彩对白| 午夜精品一区二区三区视频| 男人的天堂久久精品| 国内精品久久国产| 韩国日本一区| 日韩美女一区二区三区四区| 女人18毛片毛片毛片毛片区二 | 国产精品大片wwwwww| 天天色天天操天天射| 亚洲国产cao| 国内精品免费视频| 精品福利电影| 国产精品夜夜夜一区二区三区尤| av网站网址在线观看| 欧美精品日日鲁夜夜添| 国产精品18在线| 久久精品国产久精国产| 亚洲国产精品一区二区第四页av| 日韩精品一区二区三区| 亚洲欧美日韩精品久久亚洲区 | 人禽交欧美网站免费| 日韩精品一区二区三区| 亚洲午夜精品久久久久久性色 | 性做爰过程免费播放| 亚洲香蕉久久| 成年无码av片在线| 亚洲av无码国产综合专区| 亚洲综合丝袜美腿| 国产黄色三级网站| 久久这里只有| 亚洲一区二区在| 国产精品视频首页| 久久91精品国产91久久跳| 性欧美18一19性猛交| 亚洲在线成人精品| 久久久久久久久免费看无码| 久久国产成人| 亚洲永久激情精品| 成人搞黄视频| 日韩美女视频在线观看| 97电影在线看视频| 日韩一卡二卡三卡四卡| 国产视频91在线| 国产无遮挡一区二区三区毛片日本| 中文字幕第100页| 亚洲破处大片| 久久99精品久久久久久久久久| 久久uomeier| 精品国产视频在线| 免费看日韩av| 在线观看亚洲精品视频| 国产大片免费看| 91欧美一区二区| 国内国产精品天干天干| 在线日本高清免费不卡| 色综合影院在线观看| 日本一区二区三区电影免费观看 | 国产资源第一页| 亚洲精品**不卡在线播he| 国产精品亚洲片夜色在线| 午夜成年人在线免费视频| 亚洲欧美日韩精品久久奇米色影视 | 天天揉久久久久亚洲精品| 国产精品亚洲综合| 国产精品久久久久久久久免费高清 | 精品免费国产二区三区| 波多野结衣高清在线| 亚洲综合一区二区三区| 黄大色黄女片18免费| 懂色av一区二区夜夜嗨| 爱情岛论坛亚洲首页入口章节| 中文视频一区| 亚洲精品国产系列| 美女av一区| 91精品久久久久久久久不口人| 1234区中文字幕在线观看| 日韩亚洲一区二区| 欧美日韩视频精品二区| 日韩视频不卡中文| 最近中文字幕在线视频| 性做久久久久久久免费看| 中日韩一级黄色片| 国产亚洲精品中文字幕| 国产精品一区二区在线免费观看| 美女一区二区视频| 国产精品动漫网站| 亚洲国产导航| 欧美极品少妇无套实战| 三级电影一区| 日本高清不卡一区二区三| 国产精品45p| 成人看片视频| 日韩精品三级| 成人国内精品久久久久一区| 成人午夜精品| 日本成人激情视频| sm久久捆绑调教精品一区| 久久伊人免费视频| 日本韩国在线视频爽| 尤物yw午夜国产精品视频| 男人av在线| 亚洲欧美另类自拍| 青青色在线视频| 日韩激情片免费| 性感美女一级片| 亚洲成人999| 黄色av一区二区三区| 欧美精品一区二| 少妇喷水在线观看| 日韩极品精品视频免费观看| 色呦呦中文字幕| 亚洲第一黄色网| 天堂网2014av| 日韩精品极品在线观看| 香蕉视频免费看| 日韩精品在线播放| 欧美一区二区少妇| 亚洲性日韩精品一区二区| 免费观看成年在线视频网站| 亚洲人午夜精品| www.亚洲免费| 久久综合伊人77777蜜臀| 菠萝蜜视频国产在线播放| 免费av一区二区| 黄网站在线观| 欧美有码在线观看视频| 日韩电影av| 国产日韩欧美中文| 国产免费区一区二区三视频免费| 亚洲永久在线观看| 最新国产精品精品视频| 国产一区高清视频| 美女少妇全过程你懂的久久| 日韩免费电影一区二区三区| 欧洲杯什么时候开赛| 亚洲精品高清视频| 亚洲成av人电影| 国产一二三在线视频| 六月丁香综合| 亚洲欧美日本一区二区| 成人av免费观看| 加勒比一区二区| 亚洲国产精品黑人久久久| 亚洲精品自拍视频在线观看| 一区二区三区蜜桃网| 久草手机在线观看| 欧美日韩午夜影院| 亚洲成人一级片| 亚洲色图av在线| 一二三四区在线观看| 97在线观看免费| 日韩大陆av| 国产一区二区久久久| 国产成人一区二区三区影院| 不卡中文字幕在线| 日韩一级精品| 亚洲理论中文字幕| 久久亚洲影视婷婷| 欧洲猛交xxxx乱大交3| 欧美日韩在线第一页| 91精品视频免费在线观看| 精品少妇一区二区三区在线播放| 青青草观看免费视频在线| 久久久av网站| jk漫画禁漫成人入口| 亚洲jizzjizz日本少妇| 亚洲自拍电影| 永久免费看av| 蜜桃av一区二区| 亚洲自拍偷拍一区二区| 亚洲精品国产一区二区三区四区在线| 免费污污视频在线观看| 日韩欧美国产三级电影视频| 男女av在线| 欧美激情一区二区三区在线视频观看| 欧美日韩精品免费观看视欧美高清免费大片| 91入口在线观看| 清纯唯美亚洲综合一区| 夜夜添无码一区二区三区| 国产一区视频导航| 亚洲一二三精品| 日韩欧美国产骚| 三级网站在线看| 欧美人在线观看| 日韩三级一区| 日本不卡一区| 久久九九国产| 欧美做受喷浆在线观看| 亚洲免费观看高清| 亚洲综合视频在线播放| 国产一区二区成人| 欧美成人h版| 精品国产区在线| 在线欧美一区| 久久久久久久穴| 一区二区三区欧美久久| 一级特黄色大片| 日韩在线视频线视频免费网站| 日本成人伦理电影| 久久精品丝袜高跟鞋| aa国产精品| 一区二区免费在线观看视频| 亚洲综合一二三区| 亚洲xxxx天美| 欧美人交a欧美精品| 日韩中文一区二区| 日本精品福利视频| 国产盗摄视频一区二区三区| 久久福利免费视频| 91超碰这里只有精品国产| 欧美三级理伦电影| 91久久精品在线| 你懂的国产精品永久在线| 国产伦精品一区二区三区妓女下载 | 久久视频在线观看中文字幕| 99在线观看免费视频精品观看| 又黄又爽的网站| 欧美性猛交xxxxx水多| 日韩欧美电影在线观看| 亲子乱一区二区三区电影| 欧美精品第一区| 天天干天天玩天天操| **性色生活片久久毛片| 国产sm主人调教女m视频| 欧美老少配视频| 国偷自产av一区二区三区| 青青青青在线视频| 国产成人免费网站| 国产成人一区二区三区影院在线| 亚洲国产精品久久91精品| 正在播放日韩精品| 亚洲高清视频一区二区| 久久 天天综合| 久久在线视频精品| 日韩精品视频免费在线观看| 成人软件在线观看| 伊人色综合久久天天五月婷| 国产精品一级黄| 日韩欧美三级在线观看| 亚洲人成自拍网站| 成人乱码手机视频| 国产精品69久久久| 国产日韩一级二级三级| 国产精品热久久| 亚洲91精品在线| 日韩aaaa| 美女被爆操网站| 色婷婷一区二区三区四区| 免费在线观看黄色网| 国产精品av一区| 日韩成人精品在线观看| 久热这里有精品| 亚洲欧美一区二区三区情侣bbw| 先锋影音网一区二区| 国产欧美日韩小视频| 国产清纯在线一区二区www| 国产人妖在线播放| 欧美影院久久久| 欧美日本二区| 少妇av片在线观看| 欧美mv日韩mv国产| 精品乱码一区二区三区四区| 国产美女永久无遮挡| 国产精品午夜在线| 亚洲av成人无码久久精品老人| 国产精品视频免费在线观看| 狠狠爱综合网| 特级西西人体高清大胆| 亚洲激情在线观看| 成人影院网站ww555久久精品| www.浪潮av.com| 一区二区三区精品久久久| se在线电影| 久久精品久久精品国产大片|