分支限界法——对解空间的一种策略搜索(广度优先搜索)

目录

  • 1.分支限界法简介
    1.1 分支限界法的本质——通过限界阻塞子树
    1.2 分支限界法与回溯法的区别
    1.3 下界或者上界估算——贪心
  • 2.单源最短路径问题
    2.1 问题描述
    2.2 分支限界法解决单元最短路径问题
  • 3.装载问题
  • 4.布线问题
  • 5.0-1背包问题
    5.1 问题描述
    5.2 分支限界法解决0-1背包问题
    5.3 一个示例
  • 6.最大团问题
  • 7 作业分配问题
    7.1 问题描述
    7.2 分支限界法步骤描述
  • 8.旅行商问题
    8.1 问题描述
    8.2 分支限界法解决旅行商问题
    8.3 一个示例
  • 9.电路板排列问题
  • 10.批处理作业调度

1.分支限界法简介

1.1 分支限界法的本质——通过限界阻塞子树

  • 分支限界法通常仅关心使给定函数最大化或最小化。
  • 分支限界法的本质)因此,如果算法找到了一个耗费为c的解,并且有一个部分解,它的耗费至少是c,那么就不会有该部分解的扩展生成。
  • 在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
    然后,从活结点表中取下一节点(优先队列中最大或最小值)成为当前扩展结点,并重复上述扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
  • 分支限界法实现的本质)对某个节点进行搜索时,先估算出目标的解,再确定是否向下搜索(选择最小损耗的结点进行搜索)
    在分支结点上,预先分别估算沿着它的各个儿子结点向下搜索的路径中,目标函数可能取得的界,然后把它的这些儿子结点和它们可能取得的界保存在一张结点表中,再从表中选择界最小或最大的结点向下搜索。一般采用优先队列维护这张表。

1.2 分支限界法与回溯法的区别

  • 回溯法
    1)(求解目标)回溯法的求解目标是找出解空间中满足约束条件的一个解或所有解。
    2)(搜索方式深度优先)回溯法会搜索整个解空间,当不满条件时,丢弃,继续搜索下一个儿子结点,如果所有儿子结点都不满足,向上回溯到它的父节点。
  • 分支限界法
    1)(求解目标)分支限界法的目标一般是在满足约束条件的解中找出在某种意义下的最优解,也有找出满足约束条件的一个解。
    2)(搜索方式)分支限界法以广度优先或以最小损耗优先的方式搜索解空间。
    3)常见的两种分支界限法
    a.队列式(FIFO)分支界限法(广度优先):按照队列先进先出原则选取下一个结点为扩展结点
    b.优先队列式分支限界法(最小损耗优先):按照优先队列规定的优先级选取优先级最高的结点成为当前扩展结点

1.3 下界或者上界估算——贪心

  • 分支限界法对下界和上界的估算,总是采用贪心算法,选择当前最优作为界加入到优先队列

2.单源最短路径问题

2.1 问题描述

  • 给定带权重的有向图G=(V, E),图中的每一条边都具有非负的长度,求从源顶点s到目标顶点t的最短路径问题。

2.2 分支限界法解决单元最短路径问题

  • 把源顶点s作为根节点开始进行搜索。对源顶点s的所有邻接顶点,都产生一个分支结点,估计从源点s经该邻接顶点到达目标顶点t的距离作为该分支结点的下界
  • 选择下界最小的分支结点,对该分支结点所对应的顶点的所有邻接顶点继续进行上述的搜索

2.2.1 下界估算方法(贪心)

  • 假定d(node)是搜索树中从根节点到结点node所对应的顶点u的路径长度,顶点u的邻接顶点为v1, v2, ..., vl,而cu,vi为顶点u到其邻接顶点vi(i = 1, ..., l)的距离。
    令h = min(cu,v1, cu,v2, ..., cu,vl)
    则结点node的下界b(node) = d(node) + h
  • 一个例子



    源顶点为a,目标定的为t,把a作为根节点进行搜索:
    a->b->t距离的下界为:1+min{2, 9} = 3
    a->c->t距离的下界为:4+min{3,6,3,4} = 7
    a->d->t距离的下界为:4+min{7} = 11


2.2.2 分支限界法的步骤

将顶点编号为0, 1, ..., n-1.
每个结点包含如下信息:
  u——该节点所对应的顶点
  path[n]——从源点开始的路径上的顶点编号
  k——当前搜索深度下,路径上的顶点个数
  d——从源点到本节点所对应顶点的路径长度
  b——经本节点到目标顶点最短路径的长度下界
bound——一个可行解的取值,当做剪枝的标准

假定源顶点为s,目标顶点为t。

  • step1.初始化。建立根节点X
    X.u = s
    X.k = 1
    X.path[0] = s
    X.d = 0
    X.b = 0
    当前可行解的最短路径下界bound置位∞。
  • step2.令顶点X.u所对应的顶点为u,对u的所有邻接顶点vi,建立儿子结点Yi,把结点X的数据复制到结点Yi
  • step3.计算Yi的相关值
    Yi.u = vi
    Yi.path[Yi.k] = vi
    Yi.k = Yi.k + 1
    Yi.d = Yi.d + cu,vi
    计算h和Yi.b
  • step4.若Yi.b < bound,转向step5;
    否则剪去结点Yi,转向step6
  • step5.把结点Yi插入优先队列。如果结点Yi.u = t,表明它是问题的一个可行解,用Yi.b更新当前可行解的最短路径长度下界bound。
  • step6.取下优先队列首元素作为子树的根节点X,若X.u = t,表明它是问题的最优解,算法结束,数组X.path存放从源点s到目标顶点t的最短路径上的顶点编号,X.d存放该路径长度,否则,转向step2.

2.2.3 一个示例

  • k=1。
    根节点0对应于源点a,有3个邻接顶点b、c、d,其下界为3,7,11,压入优先队列。
  • k=2。优先队列中下界3最小,对于的顶点b,也即结点1。从顶点b继续进行搜索。
    顶点b的邻接顶点为c和e,其下界为6和11,压入优先队列。
  • k=3。优先队列中下界6最小,对应顶点c,也即结点4.从顶点c继续进行搜索。
    顶点c邻接顶点d、e、f、g,对应的下界为13,10,8,8,压入优先队列。
  • 回溯到了k=2)k=2。优先队列中7最小,对应顶点c,也即结点2。从顶点c进行搜索。
    顶点c邻接顶点d、e、f、g,对应的下界为14,11,9,9,压入优先队列。
  • k=4。优先队列中8最小,对应顶点f,也即结点8。
    顶点f的邻接顶点为e、t,下界分别为9、11,压入栈中。其中11为一个可行解,将bound置为11.
  • 回溯到了k=4)k=4。优先队列中8最小,对应顶点g,也即结点9。
    顶点g的邻接顶点为f、t,下界都是10。其中10为一个可行解,将bound置为10.
  • k=5.优先队列中9最小,对应顶点e,也即结点14.
    顶点e只有一个邻接顶点t,下界为9,从而得到一个可行解,路径长度为9,加入优先队列中。
    优先队列中最小的为9,且最后一个结点为t,因此是最优解。

3.装载问题

4.布线问题

5.0-1背包问题

5.1 问题描述

假定n个商品重量分别为w0, w1, ..., wn-1,价值分别为p0, p1, ..., pn-1,背包载重量为M。怎样选择商品组合,使得价值最高?

5.2 分支限界法解决0-1背包问题

  • 按价值重量比 递减 的顺序,对n个商品进行排序
    排序后商品序号的结合为S = {0, 1, ..., n-1}
  • 将这些商品分为3个集合:
    S1——选择装入背包的商品集合
    S2——不选择装入背包的商品集合
    S3——尚待选择的商品集合
  • S1(k)、S2(k)、S3(k)分别表示在搜索深度为k时的3个集合中的商品。开始时有:
    S1(0) = ∅
    S2(0) = ∅
    S3(0) = {0, 1, ..., n-1}

5.2.1 分支方法(二叉分支)

  • 假设比值pi/wi最大的商品序号为s(s ∈ S3),用s进行分支,一个分支结点表示把商品s装入背包,另一个分支结点表示不把商品s装入背包。
    当商品按照价值重量比递减排序后,s就是集合S3(k)中的第一个元素。特别地,当搜索深度为k时,商品s的序号就是集合S中的元素k。
  • 把商品s装入背包的分支结点
    S1(k+1) = S1(k) ∪ {k}
    S2(k+1) = S2(k)
    S3(k+1) = S3(k) - {k}
  • 不把商品s装入背包的分支结点
    S1(k+1) = S1(k)
    S2(k+1) = S2(k) ∪ {k}
    S3(k+1) = S3(k) - {k}

5.2.2 上界估算方法(贪心)

  • 假定b(k)表示在搜索深度为k时,某个分支结点的背包中商品的价值上界。
    此时S3(k) = {k, k+1, ..., n-1}。用如下方法计算两种分支结点背包中商品价值的上界:
  • 上述公式的理解
    1)按照一个商品是否加入到S1集合,总共有2n个叶子节点,每个叶子节点对应一种情况
    2)当一层一层向下搜索是,如果当前S1集合中的总重量超过了载重量M,则直接将b(k)置为0,该分支终止。
    为什么这样做?因为在搜索上一层时,该商品不应该加入到S1集合,这种不加入该商品情况对应于另一个分支。加入该商品的此分支已经不满足要求了,所以剪枝。

5.2.3 分支限界法求解步骤

每个结点都包含如下信息:
  S1——当前选择装入背包的商品集合
  S2——当前不选择装入背包的商品集合
  S3——当前尚待选择的商品集合
  k——搜索深度
  b——上界
bound——一个可行解的取值,当做剪枝的标准

  • step1.bound = 0,把商品按价值重量比递减排序
  • step2.建立根节点X
    X.b = 0
    X.k = 0
    X.S1 = ∅
    X.S2 = ∅
    X.S3 = S
  • step3. 若X.k == n,算法结束,X.S1即为装入背包中的物体,X.b即为装入背包中物体的最大价值;
    否则转向step4
  • 分支1)step4.建立结点Y
    Y.S1 = X.S1 ∪ {X.k}
    Y.S2 = X.S2
    Y.S3 = X.S3 - {X.k}
    Y.k = X.k + 1
    计算Y.b,将Y.b与bound进行比较,据此判定是否插入优先队列;当S3为空时,找到一个可行解,判定是否更新bound。
  • 分支2)step5.建立结点Z
    Z.S1 = X.S1
    Z.S2 = X.S2 ∪ {X.k}
    Z.S3 = X.S3 - {X.k}
    Z.k = Z.k + 1
    计算Z.b,将Z.b与bound进行比较,据此判定是否插入优先队列;当S3为空时,找到一个可行解,判定是否更新bound。
  • step6.取出优先队列首元素作为结点X,转向step3

5.3 一个示例

  • 有5个商品,重量分别为8,16,21,17,12,价值分别为8,14,16,11,7,背包的载重量为37,求装入背包的商品及其价值。


6.最大团问题

7 作业分配问题

7.1 问题描述

  • n个操作员(编号:0, 1, ..., n-1)以n种不同时间完成n项不同作业(编号:0, 1, ..., n-1),要求分配每位操作完成一项作业,使完成n项作业的时间总和最少。
  • cij表示第i位操作员完成第j号作业所需的时间。
  • 用向量x来描述分配给操作员的作业编号,如分量xi表示分配给第i位操作员的作业编号。

7.2 分支限界法步骤描述

  • (估算下界,放到优先队列)从根节点开始向下搜索,在整个搜索过程中,每遇到一个结点,对其所有儿子结点计算它们的下界,把它们记录在结点表中。
  • (从优先队列取最值,重复操作)从表中选取最小的结点,并重复上述过程。
  • (叶节点是否是最优解的判定)当搜索到一个叶子节点,如果该节点的下界是结点表中最小的,那么该节点就是问题的最优解。
    否则,对下界最小的结点继续进行扩展。

7.2.1 下界估算方法(类似于贪心,是一种最小估算方法)

  • 假定k表示搜索深度,当k = 0,从根节点开始向下搜索。若将0号作业(j = 0)分配给第i位操作员(0 ≤ i ≤ n-1),其余作业分配给其余操作员,则所需时间至少为:第i位操作员完成第0号作业的时间 + 其余n-1项作业分配给其余n-1位操作员单独完成时所需最短时间之和。(下式中l是任意的,只要不等于i即可)

    下图中,若将第0号作业分配给第0位操作员,c00 = 3,此时所需时间下界为: 3 + 7(1号作业给其余三位操作员完成最短时间) + 6(2号作业给其余三位操作员完成最短时间) + 3(3号作业给其余三位操作员完成最短时间) = 19
    一个示例
  • 一般的,当搜索深度为k,前面第0, 1, ..., k-1号作业已经分别分配给编号i0, i1, ..., ik-1的操作员。令S = {0, 1, ..., n-1}表示所有操作员的编号集合;mk-1 = {i0, i1, ..., ik-1}表示作业已分配的操作员的编号集合。当把k号作业分配给编号为ik的操作员时,ik ∈ S - mk-1。显然,其下界为:

7.2.2 分支限界法求解作业分配问题

每个结点包含如下信息:
  m——已分配作业的操作员编号集合
  S——未分配作业的操作员编号集合
  x——操作的分配方案向量x,分量xi表示分配给第i位操作员的作业编号。
  k——搜索深度(表示分配的第k号作业)
  t——所需时间的下界
bound——一个可行解的取值,当做剪枝的标准

  • step1.开始步骤。建立根节点X
    X.k = 0
    X.S = {0, 1, ..., n-1}
    X.m = ∅
    把当前问题的可行解的最优时间下界bound置位∞。
  • step2.对所有操作员i(i ∈ X.S),建立儿子结点Yi,把结点X的数据复制到Yi
  • step3. 修改Yi的值
    Yi.m = Yi.m ∪ {i}
    Yi.s = Yi.S - {i}
    Yi.xi= Yi.k (表示将第k号作业分给第i号操作员)
    Yi.k= Yi.k + 1
    并计算Yi.t
  • step4.若Yi.t < bound,转step5;
    否则剪去结点Yi,转step6
  • step5.把结点Yi插入优先队列,如果结点Yi是叶节点,表明它是问题的一个可行解,用Yi.t更新当前可行解的最优时间下界bound。
  • step6.取下队列首元素作为子树的根节点X,若X.k = n,则该节点是叶节点,表明它是问题的最优解,算法结束,向量X.x便是作业最优分配方案;否则转向step2.

7.2.3 一个实例

考虑如图所示的4个操作员的作业最优分配方案


令tik表示在某个搜索深度k下,把作业k分配给操作员i的时间下界。

  • 当k = 0时,有
    t00 = 3 + 7 + 6 + 3 = 19
    t10 = 9 + 7 + 4 + 3 = 23
    t20 = 8 + 7 + 4 + 5 = 24
    t30 = 12 + 7 + 4 + 3 = 26
    将这四个结点插入优先队列
  • 当k = 1时,结点2(把0号作业分给0号操作员)的下界做小,将其取出,并由它向下继续搜索
    t11 = 3 + 12 + 6 + 3 = 24
    t21 = 3 + 7 + 6 + 5 = 21
    t31 = 3 + 7 + 9 + 3 = 22
    将这三个结点插入优先队列
  • 当k =2时,结点7(把1号作业分给2号操作员)的下界最小,将其取出,并由它向下继续搜索
    t12 = 3 + 7 + 13 + 8 = 31
    t32 = 3 + 7 + 6 + 5 = 21
    将这两个结点插入优先队列
  • 当k = 3时,结点10(将2号作业分给3号操作员)的下界最小,将其取出,并由它向下继续搜索
    t13 = 3 + 7 + 6 + 5 = 21
    将其插入队列中。
    因为它是队列中最小的结点,所以将其取出,又因为是叶子节点,所以是最优解。(分量xi表示分配给第i位操作员的作业编号。)
    x0 = 0, x1 = 3, x2 = 1, x3 = 2

8.旅行商问题

8.1 问题描述

8.1.1 问题描述

  • 一个售货员必须访问n个城市,恰好访问每个城市一次,并最终回到出发城市。
    售货员从城市i到城市j的旅行费用是一个整数,旅行所需的全部费用是他旅行经过的的各边费用之和,而售货员希望使整个旅行费用最低。
  • (等价于求图的最短哈密尔顿回路问题)令G=(V, E)是一个带权重的有向图,顶点集V=(v0, v1, ..., vn-1)。从图中任一顶点vi出发,经图中所有其他顶点一次且只有一次,最后回到同一顶点vi的最短路径。
  • c——费用矩阵(邻接矩阵),cij表示顶点vi到顶点vj的关联边的长度。

8.1.2 费用矩阵的特性及规约

  • 令G=(V, E)是一个带权重的有向图,l是图G的一条哈密尔顿回路,c是图G的费用矩阵,则回路上的边对应于费用矩阵c中每行每列各一个元素。
    证明:因为l上面的每个顶点vi有且仅有一条入边和出边,入边表示费用矩阵第i列仅有一个元素对应,出边表示费用矩阵第i行仅有一个元素对应。
  • 费用矩阵c的第i行(或第j列)中的每个元素减去一个整数lhi(或chj),得到一个新的费用矩阵c'。使得c'中第i行(或第j列)中的最小元素为0,称为费用矩阵的行归约(或列归约)。称lhi为行归约常数,chj为列归约常数。
  • 对费用矩阵c的每一行和每一列都进行行归约和列归约,得到一个新的费用矩阵c',使得c'中每一行和每一列至少都有一个元素0,称为费用矩阵的归约。矩阵c'称为费用矩阵c的归约矩阵,称常数h为矩阵c的归约常数。


  • 令G=(V, E)是一个带权重的有向图,l是图G的一条哈密尔顿回路,c是G的费用矩阵,w(l)是以费用矩阵c计算的这条回来的费用。如果c'是费用矩阵c的归约矩阵,归约常数为h,w'(l)是以费用矩阵c'计算的这条回路的费用。则有:
    w(l) = w'(l) + h
  • 令G=(V, E)是一个带权重的有向图,l是图G的一条最短哈密尔顿回路,c是G的费用矩阵,c'是c的归约矩阵,G'是与c'对应的图,c'是G'的费用矩阵,则l是G'的一条最短的哈密尔顿回路。

8.2 分支限界法解决旅行商问题

8.2.1 分支方法(二叉分支)

  • (分支1)选取沿着某一条边出发的路径,作为进行搜索的一个分支结点
  • (分支2)不沿这条边的其他所有路径集合,作为进行搜索的另一个分支结点

8.2.2 下界确定

  • 假定父亲结点为X, w(X)是父亲结点的下界。
    现在,选择沿vivj边向下搜索作为其一个分支结点Y;
    不沿vivj边向下搜索作为另一个分支结点Y'。
  • 分支Y:
    费用矩阵被降阶和归约,归约常数为h,则w(Y) = w(X) + h
  • 分支Y':
    将cij置位∞。
    同时它必然包含费用矩阵中第i行的某个元素和第j列的某个元素,令dij为第第i行和第j列中除cij之外的最小元素之和。
    如果这两个最小元素不为零,那么也即按照这两个元素进行归约。本质上dij也是矩阵进一步的归约常数。

    因此有w(Y') = w(X) + dij

8.2.3 分支的选择(贪心)

  • 1)沿cij 为0的方向选择,使所选择的路线尽可能短。
  • 2)在多个cij 为0的方向中,沿dij最大的方向选择,使w(Y')尽可能大
    令S是费用矩阵中cij 为0的元素集合,Dkl是S中使dij达到最大的元素,即:

    即vkl就是所要选择的分支方向。

8.2.4 分支限界法的求解步骤

每个结点包含如下信息:
  c——归约过后的费用矩阵
  k——费用矩阵的阶数
  w——下界
  ad——顶点邻接表
bound——一个可行解的取值,当做剪枝的标准

  • step1.bound = ∞
  • step2.建立父亲结点X
    X.c 为费用矩阵,并进行归约,归约常数为h
    X.k = n
    X.w = h(下界)
    X.ad顶点邻接表
  • step3.由X.c中所有为0的cij,计算dij
  • step4.选择使dij最大的元素dkl,选择边vkl作为分支方向。
    step5是分支Y'的处理
  • step5.(分支1)建立儿子结点Y'
    Y'.c = X.c,将Y'.c中元素ckl置为∞,归约Y'.c
    Y'.ad = X.ad
    Y'.k = X.k
    计算下界Y'.w,并与bound进行比较,根据比较决定是否插入优先队列。
    step6-step9是分支Y的处理
  • step6.(分支2)建立儿子结点Y
    Y.c = X.c,将clk置为∞
    Y.ad = X.ad
    Y.k = X.k
  • step7.删除Y.c的第k行与第l列元素
    Y.k = Y.k -1
    归约Y.c
    计算Y.w
  • step8.Y.k为2,直接判断最短回路的两条边,并登记Y.ad,使Y.k = 0
  • step9. Y.w与bound进行比较,处理是否插入优先队列和更新bound
  • step10.取下优先队列元素作为结点X,若X.k为0,算法结束;否则转向step3.

8.3 一个示例


9.电路板排列问题

10.批处理作业调度

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容