一、文字解说:
为待随机抽取的集合每个项加一个权值,这个权值就是随机几率,比如正常被抽正的几率为1,那么将希望被抽中几率更大的项的权值设置为3或5,然后随机抽取集合中的项,并将随机数乘以每个项对应的权值,然后排序!!提取前N个项即可!大家可以发现权值更高被乘之后有更高几率排在前面而被抽中,如果将权值设为0将永远也不会被抽中!
二、应用场景:
1. 随机抽题:如果题A去年考过了,那么我希望今年出现的几率更小或者不出现,那么我将题A的权值设置为0,这道题将在以后的考试随机抽题中永远不会被随机抽中;而另外题B是本院今年模拟考试中的一道题目,我将这道题权值增加到5,根据算法,那么这道题目在下次随机抽题抽中率将比普通题目提高数倍!
2. 赌博机:大家知道游戏厅里面的赌博机是可以调的,被人调了之后出彩率明显提高或者降低,我觉得本算法适合解释。假设赌博机有24个赌项可供选择,分别是A-Z各个字母,按正常几率的话每个项的权值都是1,调机师可以通过动态改变权值来达到提高或降低中奖率。假如你投三个币,分别选了A、B、C,赌博机根据调机师的设置动态改变了A、B、C的权值,让灯转3-4圈后更大的几率停留在这三个选择中奖金较少的一个。
3. 俄罗斯方块:大家在打QQ俄罗斯方块对打的时候,有时候明显感觉堆得越高,出的东西反而不顺意,我觉得本算法也可以达到这个效果。计算机能算得出下一个最优方案是出条还是出角最好,所以可以通过调整权值来打破平均出现的几率来达到这个目的!
......
三、代码实现(C#实现):
RandomController.cs
// ======================================================================== // // 作 者:小唐 // 邮 箱:over140@gmail.com // 博 客: http://over140.cnblogs.com/ // 时 间:2009-2-10 // 描 述:控制随机抽中几率。 // // ======================================================================== using System; using System.Collections.Generic; public class RandomController { #region Member Variables // 待随机抽取数据集合 public List < char > datas = new List < char > ( new char []{ ' A ' , ' B ' , ' C ' , ' D ' , ' E ' , ' F ' , ' G ' , ' H ' , ' I ' , ' J ' , ' K ' , ' L ' , ' M ' , ' N ' , ' O ' , ' P ' , ' Q ' , ' R ' , ' S ' , ' T ' , ' U ' , ' V ' , ' W ' , ' X ' , ' Y ' , ' Z ' }); // 权值 public List < ushort > weights = new List < ushort > ( new ushort []{ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 }); #endregion #region Contructors /// <summary> /// 构造函数 /// </summary> /// <param name="count"> 随机抽取个数 </param> public RandomController( ushort count) { if (count > 26 ) throw new Exception( " 抽取个数不能超过数据集合大小!! " ); _Count = count; } #endregion #region Method #region 普通随机抽取 /// <summary> /// 随机抽取 /// </summary> /// <param name="rand"> 随机数生成器 </param> /// <returns></returns> public char [] RandomExtract(Random rand) { List < char > result = new List < char > (); if (rand != null ) { for ( int i = Count; i > 0 ; ) { char item = datas[rand.Next( 25 )]; if (result.Contains(item)) continue ; else { result.Add(item); i -- ; } } } return result.ToArray(); } #endregion #region 受控随机抽取 /// <summary> /// 随机抽取 /// </summary> /// <param name="rand"> 随机数生成器 </param> /// <returns></returns> public char [] ControllerRandomExtract(Random rand) { List < char > result = new List < char > (); if (rand != null ) { // 临时变量 Dictionary < char , int > dict = new Dictionary < char , int > ( 26 ); // 为每个项算一个随机数并乘以相应的权值 for ( int i = datas.Count - 1 ; i >= 0 ; i -- ) { dict.Add(datas[i], rand.Next( 100 ) * weights[i]); } // 排序 List < KeyValuePair < char , int >> listDict = SortByValue(dict); // 拷贝抽取权值最大的前Count项 foreach (KeyValuePair < char , int > kvp in listDict.GetRange( 0 , Count)) { result.Add(kvp.Key); } } return result.ToArray(); } #endregion #region Tools /// <summary> /// 排序集合 /// </summary> /// <param name="dict"></param> /// <returns></returns> private List < KeyValuePair < char , int >> SortByValue(Dictionary < char , int > dict) { List < KeyValuePair < char , int >> list = new List < KeyValuePair < char , int >> (); if (dict != null ) { list.AddRange(dict); list.Sort( delegate (KeyValuePair < char , int > kvp1, KeyValuePair < char , int > kvp2) { return kvp2.Value - kvp1.Value; }); } return list; } #endregion #endregion #region Properties private int _Count; /// <summary> /// 随机抽取个数 /// </summary> public int Count { get { return _Count; } set { _Count = value; } } #endregion } ----------------------------------------------------------------------------------------
调用测试代码:
static void Main( string [] args) { // 从集合中随机抽取个数 const ushort COUNT = 6 ; // 循环次数 const int FOR_COUNT = 1000 ; // 10000 #region 1000、10000次随机抽取,每次抽取6个 RandomController rc = new RandomController(COUNT); // 累积器 Dictionary < char , int > result = new Dictionary < char , int > (); // 随机数生成器 Random rand = new Random(); // 循环生成随机数 for ( int i = 0 ; i < FOR_COUNT; i ++ ) { char [] rands = rc.RandomExtract(rand); for ( int j = 0 ; j < COUNT; j ++ ) { char item = rands[j]; if (result.ContainsKey(item)) result[item] += 1 ; else result.Add(item, 1 ); } Thread.Sleep( 5 ); } Console.WriteLine( " \t\t出现次数\t占总共出现次数百分比 " ); foreach (KeyValuePair < char , int > item in result) { Console.WriteLine(item.Key + " \t\t " + item.Value.ToString() + " \t\t " + (( double )item.Value / ( double )(FOR_COUNT * COUNT)).ToString( " 0.00% " )); } } 普通随机抽取分别进行1000次和10000次测试显示:
1000次
![](https://images.cnblogs.com/cnblogs_com/over140/2009/2/2009-2-12C6x1000.png)
10000次
![](https://images.cnblogs.com/cnblogs_com/over140/2009/2/2009-2-12C6x10000.png)
控制随机几率随机抽取分别进行1000次和10000次代码修改:
1. 将rc.RandomExtract(rand)改为rc.ControllerRandomExtract(rand)
2. 注释掉上面输出部分代码,加上以下代码:
Dictionary < char , ushort > items = new Dictionary < char , ushort > (); for ( int i = 0 ,j = rc.datas.Count; i < j; i ++ ) { items.Add(rc.datas[i],rc.weights[i]); } Console.WriteLine( " \t\t出现次数\t占总共出现次数百分比\t权值 " ); foreach (KeyValuePair < char , int > item in result) { Console.WriteLine(item.Key + " \t\t " + item.Value.ToString() + " \t\t " + (( double )item.Value / ( double )(FOR_COUNT * COUNT)).ToString( " 0.00% " ) + " \t\t\t " + items[item.Key]); } 测试结果:
1000次
![](https://images.cnblogs.com/cnblogs_com/over140/2009/2/2009-2-12C6x1000qq.png)
10000次
![](https://images.cnblogs.com/cnblogs_com/over140/2009/2/2009-2-12C6x10000qq.png)
小结
从上面统计结果可以看出,普通随机数分布比较均匀,随机抽中的几率相对持平;但是经过控制随机抽中几率,权值高的明显抽中几率要高,另外需要注意的是这里只输出了25个字母,也就是还有一个字母没有被抽中过,因为按算法他是始终不会出现的,除非一次抽26个!!
需要注意的是:
1. 合理的调配权值和随机数生成的大小也很有关系,大家可以看到权值5的和权值1的出现几率相差不是5倍,而是30-50倍。
2. 如果数据源随机的数据大,比如上千上万条,按现在的程序是不可行的,可以先随机抽取比所需抽取个数多2-5倍的数据,然后直接按权值排序然后抽取前N位来达到目的。
3. 最重要的一点就是注意随机性,这个算法如果不是建立在随机的机制上是毫无价值的!!
本文转自博客园农民伯伯的博客,原文链接:,如需转载请自行联系原博主。