【问题标题】:How to implement a repeating shuffle that's random - but not too random如何实现随机的重复洗牌 - 但不是太随机
【发布时间】:2011-03-29 02:19:44
【问题描述】:

我有 n 个项目的列表。我想要一个算法让我从该集合中随机选择一个可能无限的项目序列,但有几个限制:

  1. 一旦一个项目被挑选出来,它不应该在接下来的几个项目中再次出现(比如在接下来的 m 个项目中,m 显然严格 n)
  2. 您不必等待太久才能出现任何项目 - 项目平均每 n 次选择就会出现一次
  3. 序列应该是非周期性的

基本上,我想要一种算法来为打开“随机播放”和“重复”的 MP3 播放器生成播放列表,以确保它不会播放同一首歌曲太靠近自己,并确保它播放所有您的歌曲均匀,没有明显的模式。

这些限制消除了两个明显的竞争解决方案:

  • 我们不能简单地选择 rnd(n) 作为下一项的索引,因为那样不能保证不重复;挑选一些物品也可能需要很长时间。
  • 我们不能只使用 Fisher-Yates shuffle 预先对列表进行洗牌,然后反复迭代它,每次到达末尾都重新洗牌;虽然这保证了物品最多在 2n - 1 次挑选后出现,但它并不能完全防止物品重复。

一个天真的解决方案可能是随机挑选,但如果它们发生在最后 m 个挑选中,则拒绝挑选;这意味着保留一个 m 个先前选择的列表,并且每次都根据该列表检查每个选择,这使得该算法同时具有不确定性和缓慢性——双输。除非我遗漏了一些明显的东西..

所以我有一个我现在正在使用的算法,但我有点不满意。我通过一副牌类比得出它,其中我有一个牌堆和一个弃牌堆。我从完整的列表开始,洗牌,在抽牌堆中,弃牌堆是空的。下一个项目从抽牌堆顶部读取,然后放入弃牌堆。一旦弃牌堆达到一定大小(m 个项目),我将其洗牌,并将其移至抽牌堆的底部。

这符合要求,但是每次 m 选择一次的洗牌让我感到困扰。通常是 O(1),但在 m 中是 O(m) 一次。平均而言,这相当于恒定的时间,但必须有一个更清洁的解决方案,可以在丢弃时将丢弃物洗牌。

在我看来,这是一个如此简单、通用和常见的问题,它必须具有其中一种双管算法,例如 Fisher-Yates 或 Boyer-Moore。但是我的 google-fu 显然不强,因为我还没有找到一组术语来定位不可避免的 1973 年 ACM 论文,这可能准确地解释了如何在 O(1) 时间内做到这一点,以及为什么我的算法存在严重缺陷以某种方式。

【问题讨论】:

  • @gradbot - 这可能正是我想要的。我有一种感觉,有一个解决方案可以在数组中使用,将活动(打乱的)和非活动的(最近挑选的)项目分开。现在,将其添加为新答案,在我对其进行调查后,它可能会被接受:)

标签: algorithm random shuffle


【解决方案1】:

要生成您的列表,请执行以下操作。有一个平局和弃牌堆。最初弃牌堆是空的。从抽奖堆中随机选择您的第一个项目。将其添加到播放列表中,然后将其放入弃牌堆。当弃牌堆中有 m 个物品时,从弃牌堆中取出最后一个物品(最近最少使用的)并将其添加到抽牌堆中。播放列表将是随机的,但不需要随机播放。

这里是红宝石:

SONGS = [ "Pink Floyd - Wish You Were Here",
          "Radiohead - Bones",
          "Led Zeppelin - Black Dog",
          "The Cure - A Strange Day",
          "Massive Attack - Teardrop",
          "Depeche Mode - Enjoy the Silence",
          "Wilco - Jesus etc." ]

DONT_REPEAT_FOR = 3

def next_item pick, discard
  result = pick.delete_at(rand(pick.count));
  discard.push result
  pick.push(discard.shift) if (discard.count > DONT_REPEAT_FOR)
  result
end

def playlist_of_length n
    discard = []
    pick = SONGS
    playlist = []
    (0..n).each { playlist.push next_item(pick, discard) }
    playlist
end

编辑:添加了 playlist_of_length 方法,让您更清楚如何调用 next_item 来生成播放列表

【讨论】:

  • 这有可能一首歌曲可能长时间不播放,但如果其他歌曲一直插入到它之前,则可能性不大。
  • 我想你可能误解了播放列表是如何生成的。我更新了代码以使其更清晰。剧本应均匀分布,至少 DONT_REPEAT_FOR 歌曲不重复。
  • 我的意思是,没有什么可以阻止歌曲长时间不播放。这里我写了一个测试。 pastie.org/1730795 你可以看到只有 7 首歌曲在测试长度为 30,000 的播放列表时,在播放其他 40 首歌曲之前不会重复一首歌曲。
  • 我喜欢这种简单的方法 - 有一个不旋转的项目队列 - 但有几件事仍然困扰着我。一个是物品可以长时间不被挑选,可能会错过“平均每个 n”的要求,尽管我没有统计数据来支持这种感觉。另一个更多是关于从列表中间删除项目,随着列表变大,这可能会开始花费很长时间......
  • 是的,这对你来说是随机的,这很令人惊讶:-)。我稍微修改了您的代码以进行频率计数,它给出:[4264, 4345, 4276, 4277, 4284, 4317, 4238],我在脑海中进行卡方运算并说是均匀分布:-) .如果增加 DONT_REPEAT_FOR 长度,则会降低长时间播放的概率,直到最终达到最大值。
【解决方案2】:

撇开队列算法实现和视觉验证

在数学中:

Play[themes_, minCycle_, iterations_] :=
 Module[{l, queue, played},
  l = Range[themes]; 
  queue = {};
  played = {}; (*just for accounting*)

  While [  Length@played < iterations,
   (AppendTo[queue, #]; l = DeleteCases[l, #]) &@RandomChoice[l];
   If[Length[queue] > minCycle, (AppendTo[l, First@queue]; queue = Rest@queue)];
   AppendTo[played, Last@queue]
   ];
  Return[played];
  ]

MatrixPlot[Partition[Play[100, 50, 20000], 100], ColorFunction -> Hue]

我们看看有没有明显的重复模式:

比较不同的周期长度:

【讨论】:

  • 我有兴趣查看最大周期的图形。 :)
  • 我对mathematica 语法不是很熟悉,但这看起来与Dean Povey 的算法相似——对吗?
  • 另外,'aside queue' 是这个的艺术术语吗?谷歌搜索显示,该表格的大多数用法是在拥塞管理中的“预留队列”的上下文中......
  • @James 我的英语水平肯定不是最好的。随意编辑任何拼写错误。
  • @James 关于与 Ruby 答案(Dean's)的相似性,我不确定,但这可能是因为我看不懂 Ruby :(。无论如何我更感兴趣的是看在设计算法时产生的模式。
【解决方案3】:

播放给定歌曲后,使用伪追加将其放置在列表末尾附近。您可能希望真正附加大约 1/2 到 2/3,而其他 1/3 到 1/2 分布在列表中的最后 5 个左右的项目中。

显然这不适用于非常短的列表,但对于 10 个或更多的列表应该没问题。


编辑(提供有关“PseudoAppend”的更多详细信息):

以下伪代码使用了多种语言结构,但应该很容易变成真正的代码。

给定列表[歌曲]

While(PLAY)
    Play(List[0])
    PseudoAppend(List[], 0)

def PseudoAppend(List[], index)
    # test to verify length of list, valid index, etc.
    song = List[index].delete    # < not safe
    List.append(song)
    target = -1

    While( (random() < (1/3)) && (target > -3) )
        Swap(List[target], List[target-1])
        target -= 1

在没有备份列表的情况下从列表中删除刚刚完成的歌曲可能会导致信息丢失,但这只是用于传达想法的伪代码。

如您所见,刚播放的歌曲有 2/3 的时间会移到列表的后面,有 1/3 的时间会移到最后一首歌曲的前面。

歌曲有 1/3 的机会向前移动,有 2/3 的时间它只会向前移动一首歌曲,另外 1/3 的时间它会向前移动两首或更多歌曲。歌曲移动到最后位置的几率=66%,倒数第二位=22%,倒数第三位=12%。

PseudoAppend 的实际行为都受@​​987654322@ 语句的条件控制。您可以更改值以与random 数字生成器进行比较,以使歌曲或多或少地移动到其他歌曲之前,您可以更改与target 比较的值以调整刚刚完成的歌曲的距离可以在列表中前进。


编辑 II(Python 3 代码和 11 项列表的示例输出)
songlist=[0,1,2,3,4,5,6,7,8,9,10]

import random

def pseudoappend(locallist, index):
    song=locallist[index]
    del(locallist[index])
    locallist.append(song)
    target=-1
    while (random.randint(1,3)==1) and (target> -3):
        locallist[target],locallist[target-1] = locallist[target-1],locallist[target]
        target-=1

for x in range(len(songlist)*9):
    print("%3d" % x, ': ', "%2d" % songlist[0], ': ', songlist)
    pseudoappend(songlist, 0)

print( 'end : ', "%2d" % songlist[0], ': ', songlist)

这是一个在列表中运行约 9 次的示例输出。第一列只是一个运行索引,第二列显示当前选择的歌曲,第三列显示当前列表的顺序:

>>> ================================ RESTART ================================
>>> 
  0 :   0 :  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  1 :   1 :  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
  2 :   2 :  [2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1]
  3 :   3 :  [3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2]
  4 :   4 :  [4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3]
  5 :   5 :  [5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4]
  6 :   6 :  [6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5]
  7 :   7 :  [7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6]
  8 :   8 :  [8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7]
  9 :   9 :  [9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8]
 10 :  10 :  [10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 11 :   0 :  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 12 :   1 :  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
 13 :   2 :  [2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 0]
 14 :   3 :  [3, 4, 5, 6, 7, 8, 9, 10, 1, 0, 2]
 15 :   4 :  [4, 5, 6, 7, 8, 9, 10, 1, 0, 2, 3]
 16 :   5 :  [5, 6, 7, 8, 9, 10, 1, 0, 2, 3, 4]
 17 :   6 :  [6, 7, 8, 9, 10, 1, 0, 2, 3, 4, 5]
 18 :   7 :  [7, 8, 9, 10, 1, 0, 2, 3, 4, 6, 5]
 19 :   8 :  [8, 9, 10, 1, 0, 2, 3, 4, 6, 7, 5]
 20 :   9 :  [9, 10, 1, 0, 2, 3, 4, 6, 7, 5, 8]
 21 :  10 :  [10, 1, 0, 2, 3, 4, 6, 7, 5, 8, 9]
 22 :   1 :  [1, 0, 2, 3, 4, 6, 7, 5, 10, 8, 9]
 23 :   0 :  [0, 2, 3, 4, 6, 7, 5, 10, 8, 9, 1]
 24 :   2 :  [2, 3, 4, 6, 7, 5, 10, 8, 9, 1, 0]
 25 :   3 :  [3, 4, 6, 7, 5, 10, 8, 9, 2, 1, 0]
 26 :   4 :  [4, 6, 7, 5, 10, 8, 9, 2, 1, 0, 3]
 27 :   6 :  [6, 7, 5, 10, 8, 9, 2, 1, 0, 3, 4]
 28 :   7 :  [7, 5, 10, 8, 9, 2, 1, 0, 3, 4, 6]
 29 :   5 :  [5, 10, 8, 9, 2, 1, 0, 3, 4, 6, 7]
 30 :  10 :  [10, 8, 9, 2, 1, 0, 3, 4, 5, 6, 7]
 31 :   8 :  [8, 9, 2, 1, 0, 3, 4, 5, 10, 6, 7]
 32 :   9 :  [9, 2, 1, 0, 3, 4, 5, 10, 6, 7, 8]
 33 :   2 :  [2, 1, 0, 3, 4, 5, 10, 6, 7, 9, 8]
 34 :   1 :  [1, 0, 3, 4, 5, 10, 6, 7, 9, 8, 2]
 35 :   0 :  [0, 3, 4, 5, 10, 6, 7, 9, 8, 2, 1]
 36 :   3 :  [3, 4, 5, 10, 6, 7, 9, 8, 2, 1, 0]
 37 :   4 :  [4, 5, 10, 6, 7, 9, 8, 2, 1, 0, 3]
 38 :   5 :  [5, 10, 6, 7, 9, 8, 2, 1, 0, 3, 4]
 39 :  10 :  [10, 6, 7, 9, 8, 2, 1, 0, 3, 4, 5]
 40 :   6 :  [6, 7, 9, 8, 2, 1, 0, 3, 4, 5, 10]
 41 :   7 :  [7, 9, 8, 2, 1, 0, 3, 4, 5, 10, 6]
 42 :   9 :  [9, 8, 2, 1, 0, 3, 4, 5, 7, 10, 6]
 43 :   8 :  [8, 2, 1, 0, 3, 4, 5, 7, 10, 6, 9]
 44 :   2 :  [2, 1, 0, 3, 4, 5, 7, 10, 6, 9, 8]
 45 :   1 :  [1, 0, 3, 4, 5, 7, 10, 6, 2, 9, 8]
 46 :   0 :  [0, 3, 4, 5, 7, 10, 6, 2, 9, 8, 1]
 47 :   3 :  [3, 4, 5, 7, 10, 6, 2, 9, 8, 1, 0]
 48 :   4 :  [4, 5, 7, 10, 6, 2, 9, 8, 1, 3, 0]
 49 :   5 :  [5, 7, 10, 6, 2, 9, 8, 1, 3, 0, 4]
 50 :   7 :  [7, 10, 6, 2, 9, 8, 1, 3, 5, 0, 4]
 51 :  10 :  [10, 6, 2, 9, 8, 1, 3, 5, 0, 7, 4]
 52 :   6 :  [6, 2, 9, 8, 1, 3, 5, 0, 7, 4, 10]
 53 :   2 :  [2, 9, 8, 1, 3, 5, 0, 7, 6, 4, 10]
 54 :   9 :  [9, 8, 1, 3, 5, 0, 7, 6, 4, 10, 2]
 55 :   8 :  [8, 1, 3, 5, 0, 7, 6, 4, 10, 2, 9]
 56 :   1 :  [1, 3, 5, 0, 7, 6, 4, 10, 2, 9, 8]
 57 :   3 :  [3, 5, 0, 7, 6, 4, 10, 2, 9, 1, 8]
 58 :   5 :  [5, 0, 7, 6, 4, 10, 2, 9, 3, 1, 8]
 59 :   0 :  [0, 7, 6, 4, 10, 2, 9, 3, 1, 8, 5]
 60 :   7 :  [7, 6, 4, 10, 2, 9, 3, 1, 8, 5, 0]
 61 :   6 :  [6, 4, 10, 2, 9, 3, 1, 8, 5, 0, 7]
 62 :   4 :  [4, 10, 2, 9, 3, 1, 8, 5, 0, 7, 6]
 63 :  10 :  [10, 2, 9, 3, 1, 8, 5, 0, 7, 6, 4]
 64 :   2 :  [2, 9, 3, 1, 8, 5, 0, 7, 6, 4, 10]
 65 :   9 :  [9, 3, 1, 8, 5, 0, 7, 6, 4, 10, 2]
 66 :   3 :  [3, 1, 8, 5, 0, 7, 6, 4, 10, 2, 9]
 67 :   1 :  [1, 8, 5, 0, 7, 6, 4, 10, 2, 9, 3]
 68 :   8 :  [8, 5, 0, 7, 6, 4, 10, 2, 9, 3, 1]
 69 :   5 :  [5, 0, 7, 6, 4, 10, 2, 9, 8, 3, 1]
 70 :   0 :  [0, 7, 6, 4, 10, 2, 9, 8, 3, 1, 5]
 71 :   7 :  [7, 6, 4, 10, 2, 9, 8, 3, 0, 1, 5]
 72 :   6 :  [6, 4, 10, 2, 9, 8, 3, 0, 1, 5, 7]
 73 :   4 :  [4, 10, 2, 9, 8, 3, 0, 1, 5, 7, 6]
 74 :  10 :  [10, 2, 9, 8, 3, 0, 1, 5, 7, 6, 4]
 75 :   2 :  [2, 9, 8, 3, 0, 1, 5, 7, 6, 4, 10]
 76 :   9 :  [9, 8, 3, 0, 1, 5, 7, 6, 4, 10, 2]
 77 :   8 :  [8, 3, 0, 1, 5, 7, 6, 4, 10, 2, 9]
 78 :   3 :  [3, 0, 1, 5, 7, 6, 4, 10, 2, 9, 8]
 79 :   0 :  [0, 1, 5, 7, 6, 4, 10, 2, 3, 9, 8]
 80 :   1 :  [1, 5, 7, 6, 4, 10, 2, 3, 9, 8, 0]
 81 :   5 :  [5, 7, 6, 4, 10, 2, 3, 9, 8, 1, 0]
 82 :   7 :  [7, 6, 4, 10, 2, 3, 9, 8, 1, 0, 5]
 83 :   6 :  [6, 4, 10, 2, 3, 9, 8, 1, 0, 7, 5]
 84 :   4 :  [4, 10, 2, 3, 9, 8, 1, 0, 7, 5, 6]
 85 :  10 :  [10, 2, 3, 9, 8, 1, 0, 7, 5, 6, 4]
 86 :   2 :  [2, 3, 9, 8, 1, 0, 7, 5, 6, 4, 10]
 87 :   3 :  [3, 9, 8, 1, 0, 7, 5, 6, 4, 2, 10]
 88 :   9 :  [9, 8, 1, 0, 7, 5, 6, 4, 2, 10, 3]
 89 :   8 :  [8, 1, 0, 7, 5, 6, 4, 2, 10, 3, 9]
 90 :   1 :  [1, 0, 7, 5, 6, 4, 2, 10, 8, 3, 9]
 91 :   0 :  [0, 7, 5, 6, 4, 2, 10, 8, 3, 1, 9]
 92 :   7 :  [7, 5, 6, 4, 2, 10, 8, 3, 1, 9, 0]
 93 :   5 :  [5, 6, 4, 2, 10, 8, 3, 1, 9, 0, 7]
 94 :   6 :  [6, 4, 2, 10, 8, 3, 1, 9, 0, 7, 5]
 95 :   4 :  [4, 2, 10, 8, 3, 1, 9, 0, 7, 6, 5]
 96 :   2 :  [2, 10, 8, 3, 1, 9, 0, 7, 6, 4, 5]
 97 :  10 :  [10, 8, 3, 1, 9, 0, 7, 6, 4, 5, 2]
 98 :   8 :  [8, 3, 1, 9, 0, 7, 6, 4, 5, 2, 10]
end :   3 :  [3, 1, 9, 0, 7, 6, 4, 5, 2, 10, 8]

【讨论】:

  • 我喜欢“伪附加”的想法。我担心如果一首歌曲从打乱列表的末尾开始,这些伪附加通常会使其接近结尾,可能会持续很长时间,使其永远不会出现在前面。我想这就是为什么您要确保“真正附加”一些内容的原因。看看调整这些比例对最终分布的影响会很有趣。
  • @James Hart:接近尾声的歌曲会迅速前进到不再有被抢先风险的地方。当我有机会时,我会更新我的答案以提供更多细节......
  • @James Hart:我添加了工作 Python 代码和示例运行。您可以在运行中看到,即使在接近尾声时“附加”刚刚播放的歌曲,也没有歌曲在列表后面停留很长时间。
【解决方案4】:

我的想法是有一系列要玩的牌。队列被打乱,然后一次播放一个,直到清空。在打出每张牌时,如果打出的牌少于 m 回合,则将其添加到队列的末尾并选择下一张牌。一旦队列被清空,它可以再次被填充并重新洗牌。数组可用于跟踪一张牌最后一次打出的回合。这平均每首歌曲运行 O(1)。

这是我在 F# 中的解决方案。

let deal (deck : _[]) m =
    let played = Array.create (deck.Length) (-m)

    let rec subDeal (cards : Queue<_>) i = 
        seq {
            if cards.Count = 0 then
                yield! subDeal (shuffledQueue deck) i
            else
                let card = cards.Dequeue()

                if i - played.[card] > m then
                    played.[card] <- i
                    yield card
                else
                    cards.Enqueue card

                yield! subDeal cards (i + 1)
        }

    subDeal (shuffledQueue deck) 1

0 .. 7 与 m = 4 的交易的一些测试数据。

[|3; 1; 4; 0; 2; 6; 5; 4; 0; 2; 3; 6; 1; 5; 0; 1; 2; 6; 4; 3; 5; 2; 0; 6; 3; 4;
  5; 1; 6; 0; 3; 2; 5; 4; 1; 3; 5; 2; 0; 6; 1; 4; 2; 5; 3; 4; 0; 1; 6; 5; 2; 4;
  3; 0; 6; 1; 3; 5; 6; 2; 4; 1; 0; 5; 2; 6; 3; 1; 4; 0; 2; 6; 1; 4; 0; 5; 3; 2;
  1; 0; 5; 6; 4; 3; 2; 1; 3; 0; 5; 6; 4; 3; 1; 2; 0; 5; 6; 4; 3; 0; ...|]

// card number and the number of occurrences of said card
[|(3, 286); (6, 286); (5, 285); (0, 286); (1, 285); (4, 286); (2, 286)|]

// longest time before each card is repeated
[|11; 11; 11; 11; 12; 11; 11|]

完整的测试程序。

open System
open System.Collections.Generic

let rnd = new Random()

let shuffle cards =
    let swap (a: _[]) x y =
        let tmp = a.[x]
        a.[x] <- a.[y]
        a.[y] <- tmp

    Array.iteri (fun i _ -> swap cards i (rnd.Next(i, Array.length cards))) cards
    cards

let shuffledQueue cards =
    let queue = new Queue<_>()

    cards 
    |> shuffle 
    |> Array.iter (fun x -> queue.Enqueue x)
    queue

let deal (deck : _[]) m =
    let played = Array.create (deck.Length) (-m)

    let rec subDeal (cards : Queue<_>) i = 
        seq {
            if cards.Count = 0 then
                yield! subDeal (shuffledQueue deck) i
            else
                let card = cards.Dequeue()

                if i - played.[card] > m then
                    played.[card] <- i
                    yield card
                else
                    cards.Enqueue card

                yield! subDeal cards (i + 1)
        }

    subDeal (shuffledQueue deck) 1

let size = 7
let deck = Array.init size (fun i -> i)
let cards = deal deck 4

let getMaxWait seq value =
    Seq.fold (fun (last, count) test -> 
        if test = value then 
            (0, count) 
        else 
            (last + 1, max (last+1) count)
    ) (0, 0) seq
    |> snd

let test = cards |> Seq.take 2000

test
|> Seq.take 200
|> Seq.toArray
|> printfn "%A"

test
|> Seq.countBy (fun x -> x)
|> Seq.toArray
|> printfn "%A"

deck
|> Seq.map (fun x -> getMaxWait test x)
|> Seq.toArray
|> printfn "%A"

Console.ReadLine() |> ignore

【讨论】:

  • 我喜欢这种明确地保持项目向前发展的方式,这给人一种强烈的感觉,你最终会看到它们——但要明确地检查警察,它们不会很快出现。那么,这种方法的优点是可以证明是正确的。但是......嗯 - 可能平均 O(1),但 O(n) 在 n 中一次,这 - 例如,一个大型 MP3 收藏(而不是一副 52 张扑克牌) - 可能是一个漂亮的昂贵的操作。当然还有 O(n) 额外的存储空间......
  • 由于随机播放的性质,我没有看到解决 O(n) 行为的方法。您可以使用字典来替换播放的数组。然后慢慢地用玩过的字典中不存在的随机卡片填充卡片队列以获得初始随机卡片集,但随机挑选最后几张卡片会变得昂贵。我发现了一个相关的问题stackoverflow.com/questions/1816534/random-playlist-algorithm
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 2016-04-26
  • 1970-01-01
相关资源
最近更新 更多