在C#中实现蒙特卡罗模拟时的奇怪行为

时间:2016-07-27 16:19:44

标签: c# winforms algorithm montecarlo

我尝试使用加权快速联合路径压缩方法在C#窗体应用程序中实现蒙特卡罗模拟。首先,我采用gridSize,然后生成一个等于gridSize * gridSize的网格,并由代表网站的黑色背景标签组成,然后我随机联合 一些网站并将其背景设为白色。问题是每当我运行项目时,我都会将整个网站设置为黑色 - 无论我尝试多少次 - ,但如果我添加了断点并逐步进行,我会得到一些白色网站意!!!

以下是代码:

public partial class Form1 : Form
{
    int gridSize = 0;
    byte siteSize = 50;
    FlowLayoutPanel flowLayoutPanel;
    UnionFind uf;
    int randomFirstNumber;
    int randomSecondNumber;
    string labelName = string.Empty;
    Random rnd = new Random();
    int numberOfAttempts;
    Label label;
    HashSet<int> excludeHashSet = new HashSet<int>();

    public Form1()
    {
        InitializeComponent();
    }

    private void setGridSizeButton_Click(object sender, EventArgs e)
    {
        if (!string.IsNullOrEmpty(gridSizeTextbox.Text) && int.TryParse(gridSizeTextbox.Text, out gridSize))
        {
            if (gridSize > 0)
            {
                var flowLayoutPanel = createFlowLayoutPanelAndLabels();

                setGridSizeButton.Enabled = false;
                gridSizeTextbox.Enabled = false;

                randomlyUnionSitesAndWhiteThem();
            }
        }
    }

    private void reset_Click(object sender, EventArgs e)
    {
        setGridSizeButton.Enabled = true;
        gridSizeTextbox.Enabled = true;
        gridSizeTextbox.Text = string.Empty;
        flowLayoutPanel.Dispose();
        excludeHashSet = new HashSet<int>();
        gridSizeTextbox.Focus();
    }

    private FlowLayoutPanel createFlowLayoutPanelAndLabels()
    {
        flowLayoutPanel = new FlowLayoutPanel();
        flowLayoutPanel.Name = "flowLayoutPanel";
        flowLayoutPanel.Location = new Point(30, 60);
        flowLayoutPanel.Size = new Size(gridSize, gridSize);
        flowLayoutPanel.BorderStyle = BorderStyle.Fixed3D;
        flowLayoutPanel.Width = siteSize * gridSize + 10 * gridSize;
        flowLayoutPanel.Height = siteSize * gridSize + 10 * gridSize;

        for (int i = 0; i < gridSize * gridSize; i++)
        {
            var label = new Label();
            label.Text = "";
            label.Name = i.ToString();
            label.Width = siteSize;
            label.Height = siteSize;
            label.BackColor = Color.Black;
            label.Margin = new Padding(3);
            label.BorderStyle = BorderStyle.FixedSingle;

            flowLayoutPanel.Controls.Add(label);
        }
        this.Controls.Add(flowLayoutPanel);

        return flowLayoutPanel;
    }

    private void randomlyUnionSitesAndWhiteThem()
    {
        uf = new UnionFind(gridSize * gridSize);

        numberOfAttempts = rnd.Next(0, gridSize * gridSize);
        for (int i = 0; i < numberOfAttempts; i++)
        {
            randomFirstNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);
            randomSecondNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);

            labelName = uf.union(randomFirstNumber, randomSecondNumber).ToString();
            if (Convert.ToInt16(labelName) > -1)
            {
                label = (Label)flowLayoutPanel.Controls.Find(labelName, false).First();
                label.BackColor = Color.White;
                excludeHashSet.Add(Convert.ToInt16(labelName));
            }
        }
    }

    private int getRandomNumber(int min, int max, HashSet<int> excludes)
    {
        int randomNumber;
        do
        {
            var range = Enumerable.Range(min, max).Where(i =>  !excludes.Contains(i));

            var rand = new Random();
            int index = rand.Next(0, max - excludes.Count);

            randomNumber = range.ElementAt(index);
        }
        while (excludes.Any(x => x == randomNumber));

        return randomNumber;
    }
}

这是我的Union方法:

class UnionFind
{
    .....
    public int union(int firstNumber, int secondNumber)
    {
        firstNumber = getRoot(firstNumber);
        secondNumber = getRoot(secondNumber);

        if (firstNumber == secondNumber)
        {
            return -1;
        }

        if (sizes[firstNumber] < sizes[secondNumber])
        {
            array[firstNumber] = secondNumber;
            sizes[secondNumber] += sizes[firstNumber];

            return array[firstNumber];
        }
        else
        {
            array[secondNumber] = firstNumber;
            sizes[firstNumber] += sizes[secondNumber];

            return array[secondNumber];
        }
    }
    .....
}

3 个答案:

答案 0 :(得分:1)

您已在getRandomNumber方法中声明了一个新的Random对象,该对象始终为您提供相同的值;如果你使用你的属性rnd而不是它的工作。

Here is the explanation

答案 1 :(得分:0)

你只需要在randomUnionSitesAndWhiteThem()方法的末尾调用this.Refresh()方法;)

答案 2 :(得分:0)

我弄清楚这种奇怪行为的原因。在循环中多次使用Random类时会发生这种情况。这可以通过多种方法解决。

例如System.Threading.Thread.Sleep(10);。这里为 n 毫秒制作循环 sleep

private int getRandomNumber(int min, int max, HashSet<int> excludes)
{
    int randomNumber;
    do
    {
        System.Threading.Thread.Sleep(10);
        ...

这也可以使用System.Timers.Timer来实现,这将产生相同的结果。此方法也更适合此类问题。

System.Timers.Timer myTimer = new System.Timers.Timer();

private void percolateEverySecond()
{
    myTimer.Elapsed += new ElapsedEventHandler(percolateForTimer);   //this is the function that will be executed every myTimer.Interval
    myTimer.Interval = 1000;
    myTimer.Enabled = true;
}

private void percolateForTimer(object source, ElapsedEventArgs e)
{
    //randomly white site, then union it with surrounding sites
    ...
}

private void Start_Click(object sender, EventArgs e)
{
    ...
    percolateEverySecond();
}

有关System.Threading.Thread.Sleep() vs System.Timers.Timer的详细信息,请参阅this

另请注意,问题中的上述代码有很大的改进空间,但作为一般规则,您应该让每个函数只负责一个特定任务“Single Responsibility Principle”。