【问题标题】:to generate random permutation of a array in O(n) time and O(1) space在 O(n) 时间和 O(1) 空间中生成数组的随机排列
【发布时间】:2015-11-17 20:47:00
【问题描述】:

我们必须在O(1)空间中生成数组{1,2,3,..,n}
我可以在O(n) 空间中做到这一点。

我做了O(n) 空间解决方案,首先存储数组,然后将其随机化。但是如何在不将数组存储在O(1) 空间中的情况下做到这一点。

我只是生成随机数,而不是存储它们,我需要打印它们,因为存储需要 O(n) 空间,但我需要在 O(1) 空间中进行,我怀疑如果我们继续生成随机数并打印它们可能有一些介于 1 到 n 之间的数字可能会生成不止一次,而有些可能不会生成。那么如何在 O(1) 空间中只打印一次所有数字呢?

P.S.- 我没有得到任何数组。输入只是“n”,我必须在 O(n) 时间和 O(1) 空间中打印数组 {1,2,3,...,n} 的排列。

【问题讨论】:

  • 在相当自然的模型中证明是不可能的。
  • 如果给定一个数组并且需要对其进行随机播放,则可以使用fisher-yates shuffle。根据定义,在没有额外空间的情况下从头开始生成数组是不可能的,因为输出本身是O(n),您需要生成它。
  • O(1) 空间要求肯定与 额外 存储有关。没有具有 O(1) 空间复杂度的解可以产生 >= 多项式输出(考虑到输出的空间)。
  • 我怀疑你误解了这个任务。 O(1) 空间和 O(1) additional 空间之间存在差异。
  • 我不清楚您是在生成随机元素并将它们存储在数组中,还是对现有数组进行改组。是哪个?

标签: algorithm random probability-theory


【解决方案1】:

我已经构建了一个线性反馈移位寄存器生成器解决方案,我认为它可以满足您的要求。该实现基于斐波那契 LFSR,因此它实现了给定位数的完整周期。我继续输入最多 19 位的多项式系数,并根据N 的指定值选择适当的系数集。大于N 的生成值会被丢弃,但整个周期中的值总数小于2N,因此它将在O(N) 时间产生N 值。 LFSR 保留一个状态字,所以它是O(1) 空格。

这是 Ruby 中的实现:

#!/usr/bin/env ruby -w

# Linear Feedback Shift Register generator which operates on smallest bit
# range possible for a specified integer range, and skips over values outside
# the specified range. Since this attains full cycle length for the specified
# number of bits, and the number of bits is minimized relative to the specified
# N, the total number of iterations is bounded by 2N and is therefore O(N).
class LFSR
  # Polynomials for maximal LFSRs determine shift amounts for up to 19 bits.
  # See https://en.wikipedia.org/wiki/Linear_feedback_shift_register for
  # details. Add more entries if you need more than 19 bits.
  SHIFT_AMT = [
    [], [], [1], [1], [1], [2], [1], [1], [2, 3, 4], [4], [3], [2],
    [1, 2, 8], [1, 2, 5], [1, 2, 12], [1], [2, 3, 5], [3], [7], [1, 2, 5]
  ]

  # Constructor for the LFSR.  Specify the N and seed value.
  def initialize(n, seed)
    @n = n
    @state =  (seed % n) + 1
    @num_bits = Math.log2(n).floor + 1
  end

  # Generate the next iterate of the LFSR.  If it's above the specified N,
  # keep trying until you're done.
  def next_value
    loop do
      bit = @state
      SHIFT_AMT[@num_bits].each { |amt| bit ^= (@state >> amt) }
      @state = ((@state >> 1) | ((bit & 1) << (@num_bits - 1)))
      return @state if @state <= @n
    end
  end
end

N = (ARGV.shift || 100).to_i # Specify the 'N' value on cmd line. Default = 100
SEED = (ARGV.shift || 0x7FFF).to_i # Optionally specify a "seed" for the LFSR
my_lfsr = LFSR.new(N, SEED)  # Instantiate an LFSR object
N.times { p my_lfsr.next_value }   # Invoke it N times, print the results

【讨论】:

  • n 数组所需的空间量在多大程度上取决于SHIFT_AMT
  • @גלעדברקן 如您所见,几乎没有。提供的SHIFT_AMT 集最多为 19 位,最大的子数组为 3 个元素。就我而言,如果我们将 N 限制为 32 位或 64 位整数,它仍然是一个固定大小的表,它只会有 45 个更多的条目进入 64 位。
  • n 的平均迭代次数对next_value 找到范围内的迭代所需的平均迭代次数有多依赖?这个平均值是多少?
  • @גלעדברקן 您将枚举从 1 到 2 的最小幂的所有整数的完整循环,其中包含 N,它小于 2N,并且清楚地包含从 1 到 @ 的所有值987654336@。因此,平均每次调用next_value 的迭代次数少于两次。这就是我声称生成它们是O(N) 的基础,因为它以2N 为界。
  • 哦,对了,你确实提到了,这听起来很有趣,值得进一步了解。
【解决方案2】:

严格来说,O(1) 解决方案是不可能的,因为数字 n 本身需要 log(n) 位来存储。

另一方面,如果练习的目标是避免使用 n 整数数组,并且您愿意牺牲一些严谨性 - 即假设 n! 可以 表示为O(1) 内存 - 解决方案是在 [0, n!) 范围内生成一个随机数 k,并计算 k-th permutation,在计算时打印数字。

【讨论】:

  • 有趣的想法。 n 最好保持小,嗯?
  • @ErickG.Hagstrom 你打赌
【解决方案3】:

一般来说,这是一个不可能的问题。虽然可以在没有任何内存的情况下重组一个列表并留下一个统计上的随机列表,但真正的随机列表是不可能的。

如果将问题解释为数字流过滤器,则在任何时间点我们都只能看到流的一个元素。我们可以看到流中尚未处理的部分,这很有帮助,但如果我们无法更改该部分,我们就会陷入困境。

完美混合列表的两种基本方法(给定一个完美的随机数生成器):

for each element in the list:
  select a random element later in the list.
  swap.

for each element in the list:
  select a random element earlier in the list.
  swap.

一个真正随机的列表可以简化为这两种方法之一。不幸的是,这两种方法都不能与没有随机写入权限的单通道过滤器一起使用。第一个需要修改列表中尚未创建的部分。第二个需要修改已经打印出来的部分列表。

【讨论】:

    【解决方案4】:

    如果 n 是 2 的幂,则可以使用块大小为 n 位的块密码来生成 n 个元素的排列 - 只需写出 Encrypt(0), Encrypt(1)... Encrypt(n -1)。

    如果 n 不是 2 的幂,则令 m 是 n 的 2 的第一个幂。加密 0..n-1,如果结果 >= n,则再次加密,直到得到一个范围内的值。这相当于将 m 个元素的排列写成一个循环,然后删除 >= n 个元素。

    如果您没有所需大小的标准分组密码,您可以使用 Luby-Rackoff aka Feistel 构造来创建一个使用散列函数作为 https://en.wikipedia.org/wiki/Feistel_cipher 中的 F 操作的密码。 Feistel 网络的一个特点是 F() 产生多于一位,被视为排列,它们从不产生奇数排列:如果 Feistel 输出为 k 位宽,则每轮产生 2^(k-1) 的某个倍数 2-循环,这会产生 k > 1 的均匀排列,因此您可能需要稍微考虑一下和/或使用具有不同反馈风格的多个 Fe​​istel 轮来从中获得合理的随机排列。具有 1 位 Feistel 输出的适当精细的 Fiestel 轮次系统可以被视为交换网络的隐式构造,可用于实现网络中的任意排列。

    【讨论】:

      猜你喜欢
      • 2015-06-25
      • 2015-05-25
      • 2020-01-28
      • 2011-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 1970-01-01
      相关资源
      最近更新 更多