數(shù)據(jù)結(jié)構(gòu)和算法--二分查找

二分查找

二分查找的思想

二分查找(Binary Search)算法,也叫折半查找算法屿岂。

二分查找針對(duì)的是一個(gè)有序的數(shù)據(jù)集合缸兔,查找思想有點(diǎn)類似分治思想诗充。每次都通過(guò)跟區(qū)間的中間元素對(duì)比,將待查找的區(qū)間縮小為之前的一半逛腿,直到找到要查找的元素稀并,或者區(qū)間被縮小為0

假設(shè)有1000條訂單數(shù)據(jù)鳄逾,已經(jīng)按照訂單金額從小到大排序稻轨,每個(gè)訂單都不同,并且最小單位是元〉癜迹現(xiàn)在想知道是否存在金額等于19元的訂單殴俱。如果存在,則返回訂單數(shù)據(jù)枚抵,如果不存在則返回null线欲。

利用二分思想,每次都與區(qū)間的中間數(shù)據(jù)比對(duì)大小汽摹,縮小查找區(qū)間的范圍李丰。下圖中,low和high表示待查找區(qū)間的下標(biāo)逼泣,mid表示待查找區(qū)間的中間元素下標(biāo)趴泌。

二分查找的時(shí)間復(fù)雜度為O(logn),被查找區(qū)間的大小變化:
n, n / 2, n / 4, n / 8, ..., n / (2^k), ...

二分查找的實(shí)現(xiàn)

最簡(jiǎn)單的情況就是有序數(shù)組中不存在重復(fù)元素拉庶,java代碼循環(huán)實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    
    while(low <= high){
        int mid = (low + high) / 2;
        if (arr[mid] == value){
            return mid;
        } else if (arr[mid] < value){
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return -1;
}

low嗜憔、high、mid都是指數(shù)組下標(biāo)氏仗,其中l(wèi)ow和high表示當(dāng)前查找的區(qū)間范圍吉捶,初始low = 0,high = n - 1皆尔。mid表示[low, high]的中間位置呐舔。我們通過(guò)對(duì)比arr[mid]與value的大小,來(lái)更新接下來(lái)要查找的區(qū)間范圍慷蠕,知道找到或者區(qū)間縮小為0珊拼,就退出。

需要注意的點(diǎn):

  1. 循環(huán)退出條件是low <= high砌们,而不是low < high杆麸。

  2. mid = (low + high) / 2這種寫法搁进,在low和high比較大時(shí)浪感,兩者之和可能會(huì)溢出昔头。

改進(jìn)的方法是將mid的計(jì)算方式寫成low + (high - low) / 2,轉(zhuǎn)化成位運(yùn)算(low + (high - low) >> 1)更佳影兽。

3.寫成low = mid或者h(yuǎn)igh = mid揭斧,可能會(huì)發(fā)生死循環(huán)。應(yīng)當(dāng)寫成low = mid + 1峻堰,high = mid - 1讹开。

二分查找的java遞歸實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int val){
    return bsearchInternally(arr, 0, n - 1, val);
}

private int bsearchInternally(int[] arr, int low, int high, int value){
    if (low > high){
        return -1;
    }
    
    int mid = low + ((high - low) >> 1);
    if (arr[mid] == value){
        return mid;
    } else if (arr[mid] < value){
        return bsearchInternally(arr, mid + 1, high, value);
    } else {
        return bsearchInternally(arr, low, mid - 1, value);
    }
}

二分查找應(yīng)用場(chǎng)景的局限性

1.二分查找依賴的是順序表結(jié)構(gòu),即數(shù)組捐名。

二分查找算法需要按照下標(biāo)隨機(jī)訪問(wèn)元素旦万,所以不能用鏈表隨機(jī)訪問(wèn)的時(shí)間復(fù)雜度是O(n)。所以镶蹋,如果數(shù)據(jù)使用鏈表存儲(chǔ)成艘,二分查找的時(shí)間復(fù)雜度就會(huì)變得很高。

二分查找只能用在數(shù)據(jù)是通過(guò)順序表來(lái)存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)上贺归。如果你的數(shù)據(jù)是通過(guò)其他數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)的淆两,則無(wú)法應(yīng)用二分查找。

2.二分查找要求數(shù)據(jù)必須是有序的拂酣,或者無(wú)序但沒(méi)有頻繁的插入和刪除操作秋冰。

數(shù)據(jù)沒(méi)有序,進(jìn)行一次排序婶熬,多次二分查找剑勾。這樣排序的成本可被均攤,二分查找的邊際成本就會(huì)比較低赵颅。

但如果數(shù)據(jù)集合有頻繁的插入和刪除操作虽另,要想用二分查找,要么每次插入性含、刪除操作之后保證數(shù)據(jù)仍然有序洲赵,要么在每次二分查找之前都先進(jìn)行排序。針對(duì)這種動(dòng)態(tài)數(shù)據(jù)集合商蕴,無(wú)論哪種方法叠萍,維護(hù)有序的成本都是很高的。

所以绪商,二分查找只能用在插入苛谷、刪除操作不頻繁,一次排序多次查找的場(chǎng)景中格郁。

3.數(shù)據(jù)量太小查找性能提升不大

但如果數(shù)據(jù)之間的比較操作非常耗時(shí)腹殿,比較次數(shù)的減少會(huì)大大提高性能独悴,這個(gè)時(shí)候二分查找就比較順序遍歷更有優(yōu)勢(shì)。

4.數(shù)據(jù)量太大不適合二分查找

只有數(shù)據(jù)量比較大的時(shí)候锣尉,二分查找的優(yōu)勢(shì)才會(huì)比較明顯刻炒。

不過(guò),這里有一個(gè)例外自沧。如果數(shù)據(jù)之間的比較操作非常耗時(shí)坟奥,不管數(shù)據(jù)量大小,都推薦使用二分查找拇厢。比如爱谁,數(shù)組中存儲(chǔ)的都是長(zhǎng)度超過(guò)300的字符串,如此長(zhǎng)的兩個(gè)字符之間比對(duì)大小孝偎,就會(huì)非常耗時(shí)访敌。我們需要盡可能地減少比較次數(shù),而比較次數(shù)的減少會(huì)大大提高性能衣盾,這個(gè)時(shí)候二分查找就比順序遍歷更有優(yōu)勢(shì)寺旺。

二分查找的底層需要依賴數(shù)組這種數(shù)據(jù)結(jié)構(gòu),而數(shù)組為了支持隨機(jī)訪問(wèn)的特性雨效,要求內(nèi)存空間連續(xù)迅涮,對(duì)內(nèi)存的要求比較苛刻。比如有1G大小的數(shù)據(jù)徽龟,用數(shù)組來(lái)存儲(chǔ)叮姑,就有需要1G的連續(xù)內(nèi)存空間。

“連續(xù)”意味著即便有2G的內(nèi)存空間剩余据悔,但是如果這剩余的2G內(nèi)存空間都是零散的传透,沒(méi)有連續(xù)的1G大小的內(nèi)存空間,那就無(wú)法申請(qǐng)一個(gè)1G大小的數(shù)組极颓。

二分查找的三種變體

變體一:查找第一個(gè)值等于給定值的元素

如果有序數(shù)據(jù)集合中存在重復(fù)的數(shù)據(jù)朱盐,要找到第一個(gè)值等于給定值的數(shù)據(jù)。

比如下面這樣一個(gè)有序數(shù)組菠隆,其中兵琳,a[5], a[6], a[7]的值都等于8,是重復(fù)的數(shù)據(jù)骇径。我們希望查找第一個(gè)等于8的數(shù)據(jù)躯肌,也就是下標(biāo)是5的元素。

簡(jiǎn)潔實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if (arr[mid] >= value){
            high = mid - 1;
        } else {
            low = mid + 1;
        }
    }
    if (low < n && arr[low] == value){
        return low;
    } else {
        return -1;
    }
}

容易理解的java實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if (arr[mid] > value){
            high = mid - 1;
        } else if (arr[mid] < value){
            low = mid + 1;
        } else {
            if ((mid == 0) || (arr[mid - 1] != value)){
                return mid;
            } else {
                high = mid - 1;
            }
        }
    }
    return -1;
}

求解的是第一個(gè)值等于給定值的元素破衔,當(dāng)arr[mid]等于要查找的值清女,就需要確認(rèn)一下這個(gè)arr[mid]是不是第一個(gè)值等于給定值的元素。

如果mid等于0晰筛,那這個(gè)元素已經(jīng)是數(shù)組的第一個(gè)元素嫡丙,那它肯定是我們要找的拴袭;

如果mid不等于0,但arr[mid]的前一個(gè)元素arr[mid - 1]不等于value曙博,那也說(shuō)明arr[mid]就是要找的第一個(gè)值等于給定值的元素拥刻。

如果經(jīng)過(guò)檢查之后發(fā)現(xiàn)arr[mid]前面的一個(gè)元素arr[mid - 1]也等于value,那說(shuō)明此時(shí)的arr[mid]肯定不是要查找的第一個(gè)值等于給定值的元素羊瘩。那就更新high = mid - 1泰佳,因?yàn)橐业脑乜隙ǔ霈F(xiàn)在[low, mid - 1]之間盼砍。

變體二:查找最后一個(gè)值等于給定值的元素

java實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if (arr[mid] > value){
            high = mid - 1;
        } else if (arr[mid] < value){
            low = mid + 1;
        } else {
            if ((mid == n - 1) || (arr[mid + 1] != value)){
                return mid;
            } else {
                low = mid + 1;
            }
        }
    }
}

如果arr[mid]這個(gè)元素已經(jīng)是數(shù)組中的最后一個(gè)元素了尘吗,那它肯定是我們要找的;

如果arr[mid]的后一個(gè)元素arr[mid + 1]不等于value浇坐,那也說(shuō)明arr[mid]就是我們要找的最后一個(gè)值等于給定值的元素睬捶。

如果arr[mid]后面的一個(gè)元素arr[mid + 1]也等于value,那說(shuō)明當(dāng)前的這個(gè)arr[mid]并不是最后一個(gè)值等于給定值的元素近刘。我們就更新low = mid + 1擒贸,因?yàn)橐业脑乜隙ǔ霈F(xiàn)在[mid + 1, high]之間。

變體三:查找第一個(gè)大于等于給定值的元素

java代碼實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if (arr[mid] >= value){
            if ((mid == 0) || (arr[mid - 1] < value)) {
                return mid;
            } else {
                high = mid - 1;
            }
        } else {
            low = mid + 1;
        }
    }
    return -1;
}

如果arr[mid]小于要查找的值value觉渴,那要查找的值肯定在[mid + 1, high]之間介劫,更新low = high + 1。

對(duì)于arr[mid]大于等于給定值value的情況案淋,如果arr[mid]前面已經(jīng)沒(méi)有元素座韵,或者前面一個(gè)元素arr[mid - 1]小于要查找的值value,那arr[mid]就是目標(biāo)元素踢京。

如果arr[mid - 1]也大于等于要查找的值value誉碴,那說(shuō)明要查找的元素在[low, mid - 1]之間,所以瓣距,將high更新為mid - 1黔帕。

變體四:查找最后一個(gè)小于等于給定值的元素

java代碼實(shí)現(xiàn):

public int bsearch(int[] arr, int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if (arr[mid] > value){
            high = mid - 1;
        } else {
            if ((mid == n - 1) || (arr[mid + 1] > value)){
                return mid;
            } else {
                low = mid + 1;
            }
        }
    }
    return -1;
}

二分查找相關(guān)問(wèn)題

如何快速判斷某個(gè)整數(shù)是否出現(xiàn)在這1000萬(wàn)數(shù)據(jù)中?

假設(shè)有1000萬(wàn)個(gè)整數(shù)蹈丸,每個(gè)數(shù)據(jù)占8個(gè)字節(jié)成黄,內(nèi)存限制是100MB,如何設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)和算法逻杖,快速判斷某個(gè)整數(shù)是否出現(xiàn)在者1000萬(wàn)數(shù)據(jù)中奋岁?

每個(gè)數(shù)據(jù)大小是8字節(jié),將數(shù)據(jù)存儲(chǔ)在數(shù)組中弧腥,內(nèi)存占用差不多是80MB厦取,符合內(nèi)存的限制。對(duì)這1000萬(wàn)數(shù)據(jù)利用原地排序算法排序管搪,然后再利用二分查找算法虾攻,就可以快速地查找想要的數(shù)據(jù)了铡买。

用二分查找求一個(gè)數(shù)的平方根

如何編程實(shí)現(xiàn)“求一個(gè)數(shù)據(jù)的平方根”?要求精確到小數(shù)點(diǎn)后6位霎箍。
python代碼實(shí)現(xiàn):

def bsearch_sqrt(num: float) -> float:
    if num < 0
        raise ValueError
    if num ** 2 == num:
        return num
    if num < 1:
        low, high = num, 1
    else:
        low, high = 0, num
    while high - low > 0.0000001:
        mid = (high + low) / 2
        mid_v2 = mid ** 2
        if mid_v2 > num:
            high = mid
        elif mid_v2 < num:
            low = mid
        elif mid_v2 == num:
            return mid
    return round((high + low) / 2, 6)

二分查找快速定位IP對(duì)應(yīng)的省份地址

IP地址查找IP歸屬地是通過(guò)維護(hù)一個(gè)很大的IP地址庫(kù)來(lái)實(shí)現(xiàn)的奇钞。地址庫(kù)中包括IP地址范圍和歸屬地的對(duì)應(yīng)關(guān)系。

當(dāng)查詢202.102.133.13這個(gè)IP地址的歸屬地時(shí)漂坏,就在地址庫(kù)中搜索景埃,發(fā)現(xiàn)這個(gè)IP地址落在[202.102.133.0,202.102.133.255]這個(gè)地址范圍內(nèi)顶别,那我們就可以將這個(gè)IP地址范圍對(duì)應(yīng)的歸屬地“山東東營(yíng)市”顯示給用戶了谷徙。

202.102.133.0|202.102.133.63|山東東營(yíng)聯(lián)通
202.102.133.64|202.102.133.255|山東濟(jì)南聯(lián)通
202.102.134.0|202.102.134.255|山東青島聯(lián)通
202.102.135.0|202.102.135.231|山東煙臺(tái)聯(lián)通
202.102.48.0|202.102.48.255|江蘇宿遷電信
202.102.49.0|202.102.49.255|江蘇泰州電信
202.102.50.0|202.102.50.255|江蘇蘇州電信
202.102.51.0|202.102.101.255|江蘇南京電信

假設(shè)有12萬(wàn)條這樣的IP區(qū)間與歸屬地的對(duì)應(yīng)關(guān)系,如何快速定位出一個(gè)IP地址的歸屬地呢驯绎?

思路:
可以先預(yù)處理這12萬(wàn)條數(shù)據(jù)完慧,存儲(chǔ)在數(shù)組中,讓其按照起始IP(按照地址對(duì)應(yīng)的整型值)從小到大排序剩失。

查詢某個(gè)IP歸屬地時(shí)屈尼,先通過(guò)二分查找找到最后一個(gè)起始IP小于等于這個(gè)IP的IP區(qū)間,然后檢查這個(gè)IP是否在這個(gè)IP區(qū)間內(nèi)拴孤,如果在就取出對(duì)應(yīng)的歸屬地顯示脾歧;如果不在就返回未查找到。

代碼實(shí)現(xiàn):

ip_rule_list = []
with open("ip.txt", encoding = "utf-8") as f:
    for line in f:
        start_ip, end_ip, location = line.rstrp().split("|", 2)
        ip_rule_list.append((start_ip, end_ip, location))
        
def ip2int(ip_str: str) -> int:
    result = 0
    for i in ip_str.split(",")
        result = (result << 8) + int(i)
    return result
    
ip_rule_list.sort(key = lambda x: ip2int(x[0]))

def ip_to_location(ip: str) -> str:
    low, high = 0, len(ip_rule_list) - 1
    ip_int = ip2int(ip)
    while low <= high:
        mid = (low + high) >> 1
        #查找最后一個(gè)起始IP小于等于這個(gè)IP的區(qū)間
        if ip2int(ip_rule_list[mid][0]) < ip_int:
            low = mid + 1
        else:
            high = mid - 1
    
    if high >= 0 and ip2int(ip_rule_list[high][0] <= ip_int <= ip2int(ip_rule_list[high][1])):
        return ip_rule_list[high][2]
    else:
        return ""

循環(huán)有序數(shù)組的二分查找問(wèn)題

如果有序數(shù)組是一個(gè)循環(huán)有序數(shù)組演熟,比如4鞭执、5、6绽媒、1蚕冬、2、3是辕。針對(duì)這種情況囤热,如何實(shí)現(xiàn)一個(gè)求“值等于給定值”的二分查找算法呢?

思路:
對(duì)于數(shù)組[1获三,2旁蔼,3,4疙教,5棺聊,6]共有下列6中旋轉(zhuǎn)方法:
1  2  3  4  5  6

6  1  2  3  4  5

5  6  1  2  3  4

4  5  6  1  2  3

3  4  5  6  1  2

2  3  4  5  6  1

以數(shù)組中間點(diǎn)為分區(qū),會(huì)將數(shù)組分成升序和非升序兩部分贞谓。

由此可得:

  • 若中間數(shù)小于最右邊數(shù)限佩,則右半段是升序區(qū)間
  • 若中間數(shù)大于最右邊數(shù),則左半段是升序區(qū)間

然后根據(jù)目標(biāo)值是否存在與升序區(qū)間,確定保留哪一區(qū)間祟同,進(jìn)行二分查找即可

python代碼實(shí)現(xiàn):

def binary_search_circle_array(arr: list, value) -> int:
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) >> 1
        if arr[mid] < arr[high]: return mid
        # 上面沒(méi)有返回作喘,說(shuō)明中間值不是目標(biāo)值
        if arr[mid] < arr[high]: # 若中間數(shù)小于最右邊數(shù),則右半段是升序區(qū)間
            if arr[mid] < value and value <= arr[high]: # 在右半段有序區(qū)間
                low = mid + 1
            else:
                high = mid - 1
        elif arr[mid] > arr[high]: # 若中間數(shù)大于最右邊數(shù)晕城,則左半段是升序區(qū)間
            if arr[low] <= value and value < arr[mid]: # 在左半段有序區(qū)間
                high = mid - 1
            else:
                low = mid + 1
        else: # 若中間數(shù)等于最右邊數(shù)泞坦,則目標(biāo)不在右半段
            low = mid + 1
    return -1
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砖顷,隨后出現(xiàn)的幾起案子贰锁,更是在濱河造成了極大的恐慌,老刑警劉巖滤蝠,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豌熄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡几睛,警方通過(guò)查閱死者的電腦和手機(jī)房轿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)所森,“玉大人,你說(shuō)我怎么就攤上這事夯接』兰茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵盔几,是天一觀的道長(zhǎng)晴弃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)逊拍,這世上最難降的妖魔是什么上鞠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮芯丧,結(jié)果婚禮上芍阎,老公的妹妹穿的比我還像新娘。我一直安慰自己缨恒,他們只是感情好谴咸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著骗露,像睡著了一般岭佳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萧锉,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天珊随,我揣著相機(jī)與錄音,去河邊找鬼。 笑死叶洞,一個(gè)胖子當(dāng)著我的面吹牛辨赐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播京办,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼掀序,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了惭婿?” 一聲冷哼從身側(cè)響起不恭,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎财饥,沒(méi)想到半個(gè)月后换吧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钥星,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年沾瓦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谦炒。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贯莺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宁改,到底是詐尸還是另有隱情缕探,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布还蹲,位于F島的核電站爹耗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谜喊。R本人自食惡果不足惜潭兽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斗遏。 院中可真熱鬧山卦,春花似錦、人聲如沸最易。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)藻懒。三九已至剔猿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嬉荆,已是汗流浹背归敬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汪茧。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓椅亚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親舱污。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呀舔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355