[當算法遇上數學]元芳,你怎么能隨即生成m個數,讓其和等於n?(加強版)


今天看到了一個比較有意思的算法題,其實更有意思的是其解法,讓人頓時有一種耳目一新的感覺,愛不釋手,拿來分享一下。

題目:假設生成26個非負隨即數,要求其和是301,求程序生成此列數字

哈哈,元芳,你如何看?

解法一: 關於此種算法原理,我們可以假想是一根長301單位的繩子,然后隨即在其上面截25個點,於是得到26根子繩,這26根子繩之和恰好就是繩子總長301。

           於是,我們可以:

  1. 初始化27的數組
  2. 該數組0位和26位分別為0和301,其他位置填充0到301之間的隨即數字
  3. 對數組排序
  4. 數組相鄰數字相減,所得的26個差值即為所求數列。
 class Random301
    {
        static void Main(string[] args)
        {
        int[] arryTemp=new int[27];
        int[] arryRandom = new int[26];
        //get random number,from 0 to 301
        Random ran=new Random((int)DateTime.Now.Ticks);
        arryTemp[0] = 0;
        arryTemp[26] = 301;
        for (int i = 1; i < 26; i++)
        {
            arryTemp[i] = ran.Next(0,301);
        }
        
        //sort the arry
        int temp;
        for (int m = arryTemp.Length-1; m > 0; m--) {
            for (int n = 0; n < m;n++ ) {
                if (arryTemp[m] < arryTemp[n]) { 
                     temp=arryTemp[n];
                     arryTemp[n] = arryTemp[m];
                     arryTemp[m] = temp;
                }
            }
        }
        //get the lastest random arry
        for (int j = 0; j < arryRandom.Length;j++) {
            arryRandom[j] = arryTemp[j + 1] - arryTemp[j];
        }
       
        //check the arry
        int sum = 0;
        for (int k = 0; k < arryRandom.Length; k++) {
            sum = sum + arryRandom[k];
        }
        Console.WriteLine(sum);
        Console.ReadKey();
    }      
    }

 解決方案二,這種方案利用了非負這個不顯眼的條件,思路非常簡單,就是將1隨即加到一個26位數組中,隨即301次,有點劍走偏鋒,另辟蹊徑,讓人耳目一新阿,有謀有啊有謀有!

  class Radom301Arry
    {
        static void Main(string[] args) {
            int[] arryRandom = new int[26];
            Random ran = new Random();
            //add 1 301times into the arry
            for (int i = 0; i <301;i++ ) {
                arryRandom[ran.Next(0, 26)]++;
            }
            //chenck the arry
            int sum = 0;
            for (int j = 0; j < arryRandom.Length; j++) {
                Console.WriteLine(arryRandom[j]);
                sum = sum + arryRandom[j];
            }
            Console.WriteLine("sum:"+sum);
            Console.ReadKey();
        }
    }

 多謝@另一個石頭,@八字和尚,@firstrose等等朋友的質疑指證,我測試了一下,如果將數字增大,i <3000001,確實得出來的數字比較的平均,看來我扔了我這塊石頭確實引來了不少玉啊,嘿嘿,我仔細的看了下,第二種算法確實隱藏着缺陷,此方法的意圖是通過利用概率的隨機性,等於將301個1按照等概率分布在26個位置,random函數均勻拋出數字,所以其分布應該大概按照301/26分布,在301次的時候其實已經有表現了,當數字變大此種現象更加明顯。

不過由於此種算法確實太過奇妙,所以我覺得用於小數字的隨機還是可以的,元芳,你怎么看呢?  

 

第三種解法:下象棋的朋友都知道一種常見的開局方式,兵三進一或者兵七進一,即仙人指路局,此種着法能進能退,能起馬能飛炮,算起來中規中矩,其實也不乏一種方法,於是我也中規中矩的按照正常的思路又寫了一種所謂的“中規中矩”的算法:

 class List301
    {
        static void Main(string[] args) {
            //store the 26 random numbers
            List<int> templist = new List<int>();
            Random ran = new Random((int)DateTime.Now.Ticks);
            int temp = 0;
            int total = 301;
            for (int i = 0; i < 26; i++)
            {
                if (25 != i)
                    temp = ran.Next(0, total);
                else
                    temp = total;
                total = total - temp;
                templist.Add(temp);
            }  
              int sum = 0;
            for (int m = 0; m <templist.Count; m++)
            {
                sum = sum + templist[m];
                Console.WriteLine(m+":"+templist[m]);
            }
            Console.WriteLine(sum);
            Console.ReadKey();
        }
    }

 這種方法就是先從0-301之間random出來一個數字,然后301減去此數字得出目前需要拋出數字的總和,然后再從0-目前總合中再random出來一個數字。。。如此知道第26個數字,不再random,直接賦值為最后剩下的目前數字之和,測試后發現這個方法最后會拋出大串的0,也在意料之中,因為隨着random次數的增加,random的震盪范圍越來越小,最終會在大於0附近徘徊。

鑒於此種現象,稍微改進了一下方案,控制了一下random的震盪范圍:

 class List301
    {
        static void Main(string[] args) {
            //store the 26 random numbers
            List<int> templist = new List<int>();
            Random ran = new Random((int)DateTime.Now.Ticks);
            int temp = 0;
            int total = 301;
            for (int i = 0; i < 26; i++)
            {
                if (25 != i)
                {
                    int avg = total / (26 - i);//控制震盪范圍在動態平均值附近
                    temp = ran.Next(0, avg * 2);
                    total = total - temp;
                }
                else
                    temp = total;
                templist.Add(temp);
            }

            //check
            int sum = 0;
            for (int m = 0; m <templist.Count; m++)
            {
                sum = sum + templist[m];
                Console.WriteLine(m+":"+templist[m]);
            }            
            Console.WriteLine(sum);
            Console.ReadKey();
        }
    }

 但是覺得這樣並不好,控制了震盪范圍,就等於間接控制了隨機數字的出現概率,算來算去還是第一種方法和第二種方法好,元芳,你認為呢?

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM