百度一道笔试题解法探讨

题目:

给你一个长度为N的链表。N很大,但你不知道N有多大。你的任务是从这N个元素中随机取出k个元素。你只能遍历这个链表一次。你的算法必须保证取出的元素恰好有k个,且它们是完全随机的(出现概率均等)。

解法1:

先选中前k个, 从第k+1个元素到最后一个元素为止, 1/i (i=k+1, k+2,...,N) 的概率选中第i个元素,并且随机替换掉一个原先选中的元素, 这样遍历一次得到k个元素, 可以保证完全随机选取。这个算法叫做蓄水池抽样,在某门课上听到的,证明起来也不是很复杂。

可以参考编程珠玑问题12.10:如何从n个排序的对象中选择一个,但实现不知道n的大小?

解答:总是选择第一个对象,并使用1/2的概率选择第二个对象,使用1/3的概率选择第三个对象,以此类推。在过程结束时,每个对像被选中的概率都是1/n。伪码如下:

i = 0;

while( more objects)

{

      with probability 1.0/i++

               choice = this object

      print choice

}

解法2:

首先扫描一遍链表,对每个节点赋予一个随机的值(譬如一个随机整数);然后使用一种Top K算法(譬如最大K个整数)得到需要的K个节点。

解法3:

首先,可以这样考虑:考虑第一个节点,其以K/N的概率被选中;如果该节点被选中,则从剩下的(N-1)个节点中选出(K-1)个节点;如果没有被选中,则从剩下的(N-1)个节点中选出K个节点。

Ruby代码如下:
-----------------
require 'set'

# 链表的节点类型;为了简化,只包含next属性
class Node
attr_reader :next     
attr_writer :next
end

# 以k/n的概率返回'true';其中rand(max)返回[0,max)范围内的一个随机整数
def Hit(n, k)
return rand(n) < k
end

# +head_node+ 链表的首节点
# +n+ 链表长度
# +k+ 选出的节点个数
def Choose(head_node, n, k)
if k == 0
    Set.new()
elsif !Hit(n,k)
    Choose(head_node.next, n-1, k)
else
    Choose(head_node.next, n-1, k-1).add(head_node)
end
end
-----------------

另外,这个问题还可以引申到随机取样的问题。从另一个考虑:先从{1,2,...,N}中随机取样K个不同整数,然后从首节点(编号为1)开始遍历链表,如果所在节点的编号出现在样本中,则该节点被选中。

关键问题是怎么进行随机取样。

先假设我们已经有一个RandInt(int lower, int upper)函数,该函数返回区间[lower,upper]上均匀分布的一个整数。
在Ruby中已经存在一个rand(max)函数,返回[0,max)范围里的随机数。则我们可以这样实现RandInt():
-----------------
def RandInt(lower,upper)
if lower>upper
    return 0
else
    return lower+rand(upper+1-lower)
end
end
-----------------

http://hi.baidu.com/sunxiangwei/blog/item/a7839b51bdbf522e42a75b45.html

随机采样算法RandomSampling(n,k)的一种简单实现是:
-----------------
def RandomSampling(n,k)
s = Set.new     # Init set as null
while s.size<k
t = RandInt(1,n)
if !s.include?(t)
s.add(t)
end 
end
return s
end
-----------------

但是以上算法存在缺陷。假设取n=k=100,当s.size=99时,算法闭着眼睛乱猜整数,知道碰到那个正确整数为止。这个过程可能持续很长时间,甚至可能不会停止(在随机数发生器不是真正随机的时候)。

还好,
Robert W Floyd提出了另一种巧妙的算法,保证只调用RandInt()函数k次就可以完成取样。其基本思想很容易理解:为了从1..10中产生5个元素的样本,首先从 1..9中产生一个4元素样本,然后加上第5个元素(注意,该元素可能是数字10,也可能不是)。可以证明该算法以等概率产生N元素的任一K样本。

其递归形式如下:
-----------------
def RandomSampling(n,k)
if k==0
    return Set.new   # Return null set
else
    s = RandomSampling(n-1,k-1)
    t = RandInt(1,n)
    if !s.include?(t)
      s.add(t)
    else
      s.add(n)
    end
end
-----------------

将递归形式改写成迭代形式:
-----------------
def RandomSampling(n,k)
s = Set.new
for j in n-k+1..n
    t = RandInt(1,j)
    if !s.include?(t)
      s.add(t)
    else
      s.add(j)
    end
end
return s
end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值