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

那誰的技術(shù)博客

感興趣領(lǐng)域:高性能服務(wù)器編程,存儲,算法,Linux內(nèi)核
隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
數(shù)據(jù)加載中……

二分查找學(xué)習(xí)札記

我之前寫過兩篇關(guān)于二分查找算法的文章,這篇算是一個小結(jié).我給這份文檔整理了一份pdf格式的文檔,可以在這里下載.


二分查找算法學(xué)習(xí)札記
說明
作者:那誰
blog: http://www.shnenglu.com/converse
轉(zhuǎn)載請注明出處.
二分查找算法基本思想
二分查找算法的前置條件是,一個已經(jīng)排序好的序列(在本篇文章中為了說明問題的方便,假設(shè)這個序列是升序排列的),這樣在查找所要查找的元素時,首先與序列中間的元素進行比較,如果大于這個元素,就在當(dāng)前序列的后半部分繼續(xù)查找,如果小于這個元素,就在當(dāng)前序列的前半部分繼續(xù)查找,直到找到相同的元素,或者所查找的序列范圍為空為止.

用偽代碼來表示, 二分查找算法大致是這個樣子的:
left = 0, right = n -1
while (left <= right)
    mid 
= (left + right) / 2
    
case
        x[mid] 
< t:    left = mid + 1;
        x[mid] 
= t:    p = mid; break;
        x[mid] 
> t:    right = mid -1;

return -1;


第一個正確的程序
根據(jù)前面給出的算法思想和偽代碼, 我們給出第一個正確的程序,但是,它還有一些小的問題,后面會講到
int search(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n - 1;

    
while (left <= right)
    {
        middle 
= (left + right) / 2;
        
if (array[middle] > v)
        {
            right 
= middle;
        }
        
else if (array[middle] < v)
        {
            left 
= middle;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}


下面,講講在編寫二分查找算法時可能出現(xiàn)的一些問題.

邊界錯誤造成的問題
二分查找算法的邊界,一般來說分兩種情況,一種是左閉右開區(qū)間,類似于[left, right),一種是左閉右閉區(qū)間,類似于[left, right].需要注意的是, 循環(huán)體外的初始化條件,與循環(huán)體內(nèi)的迭代步驟, 都必須遵守一致的區(qū)間規(guī)則,也就是說,如果循環(huán)體初始化時,是以左閉右開區(qū)間為邊界的,那么循環(huán)體內(nèi)部的迭代也應(yīng)該如此.如果兩者不一致,會造成程序的錯誤.比如下面就是錯誤的二分查找算法:
int search_bad(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n;

    
while (left < right)
    {
        middle 
= (left + right) / 2;
        
if (array[middle] > v)
        {
            right 
= middle - 1;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}

這個算法的錯誤在于, 在循環(huán)初始化的時候,初始化right=n,也就是采用的是左閉右開區(qū)間,而當(dāng)滿足array[middle] > v的條件是, v如果存在的話應(yīng)該在[left, middle)區(qū)間中,但是這里卻把right賦值為middle - 1了,這樣,如果恰巧middle-1就是查找的元素,那么就會找不到這個元素.
下面給出兩個算法, 分別是正確的左閉右閉和左閉右開區(qū)間算法,可以與上面的進行比較:
int search2(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n - 1;

    
while (left <= right)
    {
        middle 
= (left + right) / 2;
        
if (array[middle] > v)
        {
            right 
= middle - 1;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}

int search3(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n;

    
while (left < right)
    {
        middle 
= (left + right) / 2;

        
if (array[middle] > v)
        {
            right 
= middle;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}


死循環(huán)
上面的情況還只是把邊界的其中一個寫錯, 也就是右邊的邊界值寫錯, 如果兩者同時都寫錯的話,可能會造成死循環(huán),比如下面的這個程序:
int search_bad2(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n - 1;

    
while (left <= right)
    {
        middle 
= (left + right) / 2;
        
if (array[middle] > v)
        {
            right 
= middle;
        }
        
else if (array[middle] < v)
        {
            left 
= middle;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}

這個程序采用的是左閉右閉的區(qū)間.但是,當(dāng)array[middle] > v的時候,那么下一次查找的區(qū)間應(yīng)該為[middle + 1, right], 而這里變成了[middle, right];當(dāng)array[middle] < v的時候,那么下一次查找的區(qū)間應(yīng)該為[left, middle - 1], 而這里變成了[left, middle].兩個邊界的選擇都出現(xiàn)了問題, 因此,有可能出現(xiàn)某次查找時始終在這兩個范圍中輪換,造成了程序的死循環(huán).

溢出
前面解決了邊界選擇時可能出現(xiàn)的問題, 下面來解決另一個問題,其實這個問題嚴(yán)格的說不屬于算法問題,不過我注意到很多地方都沒有提到,我覺得還是提一下比較好.
在循環(huán)體內(nèi),計算中間位置的時候,使用的是這個表達式:
middle = (left + right) / 2;

假如,left與right之和超過了所在類型的表示范圍的話,那么middle就不會得到正確的值.
所以,更穩(wěn)妥的做法應(yīng)該是這樣的:
middle = left + (right - left) / 2;

更完善的算法
前面我們說了,給出的第一個算法是一個"正確"的程序, 但是還有一些小的問題.
首先, 如果序列中有多個相同的元素時,查找的時候不見得每次都會返回第一個元素的位置, 比如考慮一種極端情況:序列中都只有一個相同的元素,那么去查找這個元素時,顯然返回的是中間元素的位置.
其次, 前面給出的算法中,每次循環(huán)體中都有三次情況,兩次比較,有沒有辦法減少比較的數(shù)量進一步的優(yōu)化程序?
<<編程珠璣>>中給出了解決這兩個問題的算法,結(jié)合前面提到溢出問題我對middle的計算也做了修改:
int search4(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= -1, right = n;

    
while (left + 1 != right)
    {
        middle 
= left + (right - left) / 2;

        
if (array[middle] < v)
        {
            left 
= middle;
        }
        
else
        {
            right 
= middle;
        }
    }

    
if (right >= n || array[right] != v)
    {
        right
= -1;
    }

    
return right;
}

這個算法是所有這里給出的算法中最完善的一個,正確,精確且效率高.
參考資料
1.<<編程珠璣>>
2.wiki上關(guān)于二分查找的說明:http://en.wikipedia.org/wiki/Binary_search




posted on 2009-10-05 21:53 那誰 閱讀(14343) 評論(22)  編輯 收藏 引用 所屬分類: 算法與數(shù)據(jù)結(jié)構(gòu)

評論

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

哈哈,這個總結(jié)真好~
2009-10-06 00:35 | 唐僧

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

orz..我就經(jīng)常2分的時候orz
2009-10-06 12:08 | Vincent

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

再orz一個..第一次來到您的blog..超贊啊
2009-10-06 12:09 | Vincent

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

真這么神奇?我一直以為二分查找很簡單啊!太打擊我了!我簡直是菜鳥中再也不能菜的了...........
2009-10-06 16:52 | 浮生如夢

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

受教了 O(∩_∩)O哈!
2009-10-07 08:19 | 遲到的愛

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

測試了下。。。。最后的代碼效率不高。
2009-10-07 10:31 | 飯中淹

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@飯中淹
哦?怎么測試的?
2009-10-07 10:59 | 那誰

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@那誰
大量隨機數(shù)值的查找。
用我自己的二分算法和最后的算法進行比較測試。

結(jié)果是最后的算法比我的算法慢個幾百毫秒。

是10萬int數(shù)組,1萬預(yù)生成的待查數(shù)據(jù)。

重復(fù)1000次后得出的時間。
我的算法大概是1.8S
你文中的算法是2.XS
RELEASE版,在2.6GHZ的INTEL CPU上。

為了排除緩沖區(qū)干擾等,我用自己的算法的變種進行過測試,相差不到10毫秒。



2009-10-08 07:51 | 飯中淹

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@飯中淹
可以把你的算法貼出來嗎?
2009-10-08 10:09 | 那誰

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@那誰
http://www.shnenglu.com/johndragon/archive/2008/04/17/47345.html
在這里。
2009-10-08 14:58 | 飯中淹

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@飯中淹
我這邊測試的結(jié)果是我的稍好一些的,測試的數(shù)據(jù)是這樣的:

#define LEN 400000

int main()
{
int array[LEN];
int i;

for (i = 0; i < LEN; ++i)
{
array[i] = i;
}

xbinary_search<int, int> search;

for (i = 0; i < LEN / 1000; ++i)
{
search.search_value(array, LEN, i * 10);
}
printf("done123\n");

return 0;
}
這個測試數(shù)據(jù), 你的表現(xiàn)是0.176s,我的是0.047.

另外,你的代碼在我的cygwin上面的g++上不能直接編譯過去,我稍作了一些修改.
2009-10-08 15:51 | 那誰

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@飯中淹
2-way和3-way的對比,通常和輸入有關(guān)。
3-way一旦命中,就可以提前退出循環(huán), 但每次循環(huán)要多一次compare。
2-way總是執(zhí)行l(wèi)gN次比較后才退出循環(huán),再測試是否命中。
嚴(yán)格計算需要概率統(tǒng)計知識。
這種蠻力測試我也做過, 3-way通常表現(xiàn)得比2-way更優(yōu)秀。


但2-way有一個決定性的優(yōu)勢,是3-way無法相比的。
"存在相同鍵的有序區(qū)間中查找鍵屬于某個區(qū)間的所有值",對這種需求,2-way可以繼續(xù)保持O(lgN)的復(fù)雜度, 而3-way會退化至O(N)。


stl中使用2-way而不是3-way,可能就是基于這種考慮吧 —— 即使在最壞情況下也要足夠好, 在最好的情況也不算壞。
2009-10-08 16:23 | OwnWaterloo

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@OwnWaterloo
是的,你這么分析有道理.所以最后的那個算法"精確"(都找到相同元素的第一個)的代價就是在一些情況下不如3-way高效.
2009-10-08 16:30 | 那誰

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

綜上,我想這篇文章還需要做一些說明.稍后我會補充上,謝謝幾位.
2009-10-08 16:35 | 那誰

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

好久沒來了,貌似這回的論題是算法效率了。
2-way/3-way的二分算法都是基于無冗余的比較運算的,理論比較次數(shù)的下限不會達到log(2,n)。而
”3-way一旦命中,就可以提前退出循環(huán), 但每次循環(huán)要多一次compare。
2-way總是執(zhí)行l(wèi)gN次比較后才退出循環(huán),再測試是否命中。“
考慮到測試數(shù)據(jù)的生成方法,那么3-way的表現(xiàn)會好一些,結(jié)果就是大量的數(shù)據(jù)面前差距會被會放大。
但是如果附加大量無法命中的搜索,那么理論上二者差距會趨近于零。
我印象中數(shù)學(xué)方法分析算法效率的時候大概的過程是:寫出匯編代碼,看單次表現(xiàn)的指令數(shù),數(shù)學(xué)求和。感覺不是很困難的事情,無非就是給一個所在位置n所需要搜索次數(shù)s的函數(shù),對0..max求和看平均,但是運算量上看卻是很麻煩的事情,特別是當(dāng)這個函數(shù)比較復(fù)雜的時候。。

ps,在上次討論的時候那個uniform二分的表現(xiàn)要好于3-way很多,我當(dāng)時測了一萬左右的數(shù)據(jù),因為”動態(tài)“尋找二分點要比預(yù)先生成靜態(tài)二分點多增加很多運算,而且這種增加是隨著搜索空間的增大而增長的(這句話我不太確定,直覺上一倍左右的差距)。這也從另一方面說明了二分算法本身比較運算的運算量已經(jīng)沒有提高的空間了,優(yōu)化僅僅是從周邊入手,比如3-way的用一次比較的代價來減少循環(huán)的次數(shù)。

再ps,剛才寫上一段的時候想起來通用指令集cpu的設(shè)計,尤其以intel為首,通過加長流水線,提高分支預(yù)測的效率來提高性能,一旦預(yù)測失敗就會有比較大的性能損失。這和精簡指令集的cpu的區(qū)別有點像3-way和2-way的區(qū)別,一個強調(diào)命中,一個強調(diào)穩(wěn)定的結(jié)果。理念非常接近。
2009-10-09 08:11 | 唐僧

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

再補充一點,翻了一下編程藝術(shù),mix結(jié)構(gòu)上的匯編
3-way的每個循環(huán)的操作數(shù)大概是15個
uniform在12個左右
3個的差距在于取二分點的操作上面,占到了1/4。
沒有2-way的實現(xiàn),不過可以猜測每個循環(huán)的操作在13或者14個上。
那么這對于最終算法的影響確實是相當(dāng)大的,因為純粹用于比較兩數(shù)大小的操作在一個循環(huán)中不過1/2左右。
2009-10-09 08:21 | 唐僧

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@那誰
我是這樣測試的,不知道有么有錯。
第一步:將你的算法嵌入到我的binarysearch里面
static inline int _search( const T1 * pTArray, int nArraySize, const T2 & v, int & insertpoint )
{
if( pTArray == NULL )
{
insertpoint = 0;
return -1;
}

int left, right, middle;

left = -1, right = nArraySize;

while (left + 1 != right)
{
middle = left + (right-left) / 2;

if (_cmp( pTArray[middle], v ) < 0)
{
left = middle;
}
else
{
right = middle;
}
}

if (right >= nArraySize || _cmp( pTArray[right], v ) != 0)
{
right = -1;
}

return right;
//}

}
第二步:測試函數(shù)
#define MAX_DATA_COUNT 100000
int gDatas[MAX_DATA_COUNT];
#define MAX_FIND_DATA_COUNT 10000
int gFindDatas[MAX_FIND_DATA_COUNT];
#define MAX_TEST_TIMES 1000
void Test1()
{
typedef xbinary_search<int, int> INT_SEARCH;
for( int t = 0;t < MAX_TEST_TIMES; ++t )
{
for( int i = 0;i < MAX_FIND_DATA_COUNT; ++i )
{
int nPos = INT_SEARCH::search_value( gDatas, MAX_DATA_COUNT, gFindDatas[i] );
if( nPos != gFindDatas[i] )printf( "error !\n" );
}
}
}

void Test2()
{
typedef xbinary_search2<int, int> INT_SEARCH;
for( int t = 0;t < MAX_TEST_TIMES; ++t )
{
for( int i = 0;i < MAX_FIND_DATA_COUNT; ++i )
{
int nPos = INT_SEARCH::search_value( gDatas, MAX_DATA_COUNT, gFindDatas[i] );
if( nPos != gFindDatas[i] )printf( "error !\n" );
}
}
}

void Test3()
{
typedef xbinary_search3<int, int> INT_SEARCH;
for( int t = 0;t < MAX_TEST_TIMES; ++t )
{
for( int i = 0;i < MAX_FIND_DATA_COUNT; ++i )
{
int nPos = INT_SEARCH::search_value( gDatas, MAX_DATA_COUNT, gFindDatas[i] );
if( nPos != gFindDatas[i] )printf( "error !\n" );
}
}
}

xbinary_search3就是你的函數(shù)嵌入的

第三步:數(shù)據(jù)生成和測試代碼
srand( 312431 );
for( int i = 0;i < MAX_DATA_COUNT; ++i )
{
gDatas[i] = i;
}

for( int i = 0;i < MAX_FIND_DATA_COUNT; ++i )
{
gFindDatas[i] = (rand() % 10000) * MAX_DATA_COUNT / 10000;
}
timeBeginPeriod(1);
DWORD dwT1 = timeGetTime();
Test1();
dwT1 = timeGetTime() - dwT1;
printf( "t1 = %u\n", dwT1 );
DWORD dwT2 = timeGetTime();
Test2();
dwT2 = timeGetTime() - dwT2;
printf( "t2 = %u\n", dwT2 );
DWORD dwT3 = timeGetTime();
Test3();
dwT3 = timeGetTime() - dwT3;
printf( "t3 = %u\n", dwT3 );
timeEndPeriod(1);

最后結(jié)果


t1 = 1828
t2 = 1822
t3 = 2155


2009-10-09 10:03 | 飯中淹

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

查找i*10的結(jié)果是:

t1 = 1159
t2 = 1160
t3 = 1515

你的算法的消耗時間仍然多幾百毫秒

2009-10-09 10:07 | 飯中淹

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@唐僧

對對,我忘了說了,如果查找目標(biāo)不在區(qū)間中,2-way肯定比3-way高效。


-------- 關(guān)于 uniform --------
"因為”動態(tài)“尋找二分點要比預(yù)先生成靜態(tài)二分點多增加很多運算,而且這種增加是隨著搜索空間的增大而增長的(這句話我不太確定,直覺上一倍左右的差距)。"

上次我就說了嘛, 關(guān)于uniform這個詞。
如果只從源代碼(維基上的那份)入手uniform優(yōu)化的實質(zhì)就預(yù)處理一個delta,每次免去除法運算。
不管高爺爺是怎么一步一步想到這里來的(那本書我沒看……), 源代碼中已經(jīng)不怎么能體現(xiàn)出來了。


但實際上,效果并不一定很顯著, 我瞎猜的啊。
因為除數(shù)是2, 實際上并不需要做除法, 只是移位。

-------- 注意 --------
一次移位的寫法有(但不限于)以下2種:
int m = lower + ( (upper - lower) >> 1);
int m = lower + (upper - lower) / 2u; /* 這個u很關(guān)鍵 */

第1種使用sar,第2種使用shr。 只要upper>=lower, sar,shr都是正確的。

而樓主和飯中淹使用的代碼
int m = lower + (upper - lower) /2;
會被翻譯為3條指令。
飯中淹的代碼還沒有考慮溢出的情況。

-------- 注意完畢 --------


那么,不使用uniform的方式, 每次計算middle的消耗就是:
減法,移位,加法。
lower, upper都是緩存在寄存器中的。
具體可以見上一篇文章的評論中我發(fā)的匯編代碼。


而uniform的方式:
i += delta[++d];
大概就是
mov r1, delta[r2(d)*4+4]
add r3(i),r1

一次加法;一次內(nèi)存訪問;d需要多占用一個寄存器(否則更慢),++d還需要要一次加法。


這個靜態(tài)存儲區(qū)的內(nèi)存訪問很討厭。
非uniform方式lower,upper所在頁面肯定是被加載的,因為它們是函數(shù)參數(shù),處在棧頂。
uniform的方式,delta就不一定在頁面中,有可能被置換出去了。這種情況一旦發(fā)生,說不定效率就是數(shù)量級的下降,和非uniform完全沒得比。
并且,這種情況,通過這種小的測試程序還不一定能測得出來 —— 怎么模擬一個大的、長期運行的、偶爾調(diào)用二分查找的程序,它已經(jīng)將delta所在頁面置換出去了?

從本質(zhì)上來說, uniform的方式的一個直接缺陷就是造成數(shù)據(jù)的局部性不如非uniform的方式。而且這缺陷是無法修正的。 缺陷造成的影響可能會被uniform的其他優(yōu)勢所掩蓋, 但缺陷始終存在。


當(dāng)然, 上面全都是臆測,臆測。 需要實際測試一下。
我覺得只要非uniform的注意移位問題, 效率應(yīng)該不會比uniform的差太多。即使不缺頁,內(nèi)存訪問也是比較傷的。
一旦缺頁,我覺得uniform就沒什么可比性的了。



ps:
關(guān)于兩種方式的對比:
1. 增加happy path的效率,不管unfortunate的效率
2. 兼顧所有情況的效率

除了uniform和非uniform,我還想起一個……
不知道hash表和紅黑樹算不算這種對比的例子之一……
2009-10-09 15:27 | OwnWaterloo

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@OwnWaterloo
我確實理解錯了,因為mix結(jié)構(gòu)上不能實現(xiàn)右移這樣的操作……所以有了uniform。
而且我也沒有考慮到實際實際內(nèi)存的分配情況。如果仔細考慮一下就能想到,這么多年來保持下來的,成為標(biāo)準(zhǔn)的,大量應(yīng)用的是非uniform的二分,說明了實際應(yīng)用要好的多。
stl里面的set/map都應(yīng)該用的是紅黑樹吧,還有2-way的二分,都是兼顧所有情況的。hash表也挺極端,這個例子也挺像。呵呵,合適最好。先搞實現(xiàn),在追求優(yōu)化。
2009-10-09 22:37 | 唐僧

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

@唐僧

mix沒有右移?
不過嘛…… 右移是除法的子集…… 沒有也挺合理的…

嗯,在你介紹之前, 我完全不知道有uniform這回事……
高爺爺?shù)臅袝r間一定得去看看……

sgi的stl中有非公開的紅黑樹實現(xiàn)。4中關(guān)聯(lián)容器其實是紅黑樹的adapter...
就像std::stack,queue,priority_queue一樣…… 尷尬……

我覺得吧,二分只要寫正確就行了…… 復(fù)雜度肯定是lgN的。
lgN已經(jīng)很猛了…… 常數(shù)稍微差點也壞不到哪去…… 優(yōu)化是無底洞……
不記得在哪本書上看到過這么一句話"只需要讓軟件足夠快"
2009-10-10 14:16 | OwnWaterloo

# re: 二分查找學(xué)習(xí)札記  回復(fù)  更多評論   

沒有一個人認真看樓主的貼的算法,search()和search_bad2()是一模一樣的錯誤算法,我跪了!!!
2013-04-08 10:21 | quatrejuin
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            国产精品久久久久久久久久直播 | 欧美伊久线香蕉线新在线| 欧美日韩国产成人在线| 这里只有精品视频在线| 日韩天天综合| 国产麻豆精品久久一二三| 久久精品道一区二区三区| 久久久国产成人精品| 亚洲国产成人久久综合一区| 亚洲黄色成人| 欧美视频在线观看 亚洲欧| 亚洲欧美伊人| 久久久久久综合网天天| 亚洲美女av电影| 亚洲一区视频| 亚洲福利电影| 亚洲私拍自拍| 亚洲黄色视屏| 亚洲伊人色欲综合网| 揄拍成人国产精品视频| 美女黄网久久| 日韩午夜电影av| 国产精品一区免费观看| 裸体素人女欧美日韩| 欧美三级乱码| 久久中文欧美| 国产精品久99| 欧美激情精品久久久六区热门| 欧美日韩在线观看视频| 免费观看久久久4p| 国产精品久久久久久影视 | 欧美 日韩 国产精品免费观看| 欧美巨乳在线| 欧美成人免费在线视频| 国产欧美日韩亚洲| 亚洲麻豆国产自偷在线| 一区二区在线看| 亚洲一区二区在线| 99re热这里只有精品免费视频| 久久av一区| 亚洲欧美一区二区视频| 欧美日韩国产免费| 欧美黄色aaaa| 精品成人一区二区| 欧美中文在线观看国产| 新片速递亚洲合集欧美合集| 欧美激情中文字幕乱码免费| 免费91麻豆精品国产自产在线观看| 国产精品高潮呻吟久久| 亚洲精品国产系列| 亚洲国产欧美久久| 久久这里只有| 噜噜爱69成人精品| 国语自产偷拍精品视频偷| 亚洲一二三区视频在线观看| 亚洲尤物在线| 欧美性大战久久久久久久| 亚洲美女视频在线免费观看| 99re视频这里只有精品| 欧美精品国产| 日韩一二三在线视频播| 一区二区精品在线观看| 欧美日韩免费看| av成人免费观看| 亚洲午夜在线观看| 国产精品国色综合久久| 亚洲最新色图| 午夜欧美视频| 国产一区二区观看| 久久久久一区二区三区四区| 蜜臀a∨国产成人精品| 亚洲国内自拍| 欧美日韩视频一区二区| 亚洲无线一线二线三线区别av| 亚洲欧美激情一区| 国产揄拍国内精品对白| 久久久久久色| 亚洲人成在线观看| 亚洲影院色在线观看免费| 国产美女一区二区| 久久久久久亚洲精品中文字幕| 欧美黄污视频| 亚洲专区一区二区三区| 国产一区二区三区在线观看精品 | 国产精品videossex久久发布| 中文在线一区| 麻豆国产va免费精品高清在线| 噜噜噜噜噜久久久久久91| 91久久夜色精品国产九色| 欧美日韩国产黄| 午夜精品福利电影| 欧美激情网友自拍| 亚洲一区二区三区四区中文| 国产日韩在线不卡| 欧美va亚洲va国产综合| 亚洲最新色图| 蜜桃av综合| 亚洲系列中文字幕| 黄色成人av| 欧美日韩蜜桃| 久久久久国产精品www | 99精品视频一区| 国产精品亚洲а∨天堂免在线| 久久亚洲国产精品一区二区| 一本色道久久综合| 欧美aaa级| 午夜精品福利一区二区三区av | 欧美日韩八区| 久久久精品动漫| 亚洲视频在线观看| 欧美大片免费| 欧美中文在线观看| 亚洲一区二区三区精品视频| 在线成人激情视频| 国产精品一二三四| 欧美日韩国产精品一区二区亚洲| 久久久999精品视频| 亚洲免费中文| 99成人在线| 亚洲二区三区四区| 久久免费精品日本久久中文字幕| 亚洲一区三区在线观看| 亚洲黄色小视频| 国内精品免费在线观看| 国产精品日韩在线播放| 欧美日韩一区二区三区在线观看免| 久久久综合精品| 欧美在线观看视频一区二区三区| 亚洲国产你懂的| 蜜臀久久99精品久久久久久9 | 日韩一级在线观看| 亚洲高清不卡在线| 欧美成人精品h版在线观看| 久久精品国内一区二区三区| 性欧美激情精品| 午夜视频在线观看一区| 亚洲综合色噜噜狠狠| 亚洲综合丁香| 亚洲综合色丁香婷婷六月图片| 亚洲一卡久久| 亚洲男人的天堂在线aⅴ视频| 亚洲视频自拍偷拍| 亚洲一区www| 国产精品美女久久久久aⅴ国产馆| 亚洲香蕉在线观看| 一本色道久久综合亚洲精品不卡 | 国产精品成人免费| 国产精品网站在线播放| 国产精品一区二区a| 国产日韩欧美一区二区三区四区 | 欧美日韩在线三区| 国产精品国产亚洲精品看不卡15| 欧美日韩一卡二卡| 国产乱肥老妇国产一区二 | 黑丝一区二区| 亚洲国产精品一区二区尤物区| 在线观看91精品国产麻豆| 亚洲国产精品激情在线观看| 亚洲精品国产拍免费91在线| 日韩视频中文| 欧美一区二区久久久| 毛片基地黄久久久久久天堂| 欧美国产亚洲精品久久久8v| 亚洲片在线资源| 午夜久久电影网| 久久综合国产精品| 欧美日韩综合一区| 韩日欧美一区| 一本一本久久a久久精品综合妖精| 亚洲欧美国产精品桃花| 久久天天躁狠狠躁夜夜av| 欧美电影在线播放| 这里是久久伊人| 久久免费视频在线| 欧美日韩三级| 在线观看av一区| 亚洲欧美激情一区| 欧美寡妇偷汉性猛交| 亚洲视频在线观看三级| 麻豆av福利av久久av| 国产精品国产成人国产三级| 在线观看欧美一区| 亚洲欧美激情在线视频| 欧美黄色成人网| 亚洲欧美一区二区激情| 欧美高清视频在线| 国内精品99| 亚洲男女毛片无遮挡| 亚洲国产精品传媒在线观看| 午夜电影亚洲| 欧美三级视频在线观看| 亚洲国产综合在线看不卡| 久久精品理论片| 在线亚洲精品| 欧美日韩国产高清视频| 91久久国产综合久久91精品网站| 欧美综合国产| 亚洲欧美成人一区二区三区| 欧美日韩国产综合视频在线观看 | 国产精品v欧美精品v日本精品动漫 |