{"componentChunkName":"component---src-templates-blog-post-js","path":"/shuffle/","result":{"data":{"site":{"siteMetadata":{"title":"😏","author":"thoamsy"}},"markdownRemark":{"id":"abbf2535-b00b-52a3-b4ea-38340c2536bf","html":"<blockquote>\n<p>这篇文章其实在大学的时候就写好了, 不过今天突然忘记怎么写, 就重新看了一遍, 然后调整了一下.</p>\n</blockquote>\n<p>虽然以前一直知道如何写一个随机数算法，但是从来不知道从理论上为什么能证明这个.\n直到看了 <a href=\"https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle\">WiKi 的介绍</a>，终于明白了原因。</p>\n<h2 id=\"算法起源\" style=\"position:relative;\"><a href=\"#%E7%AE%97%E6%B3%95%E8%B5%B7%E6%BA%90\" aria-label=\"算法起源 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>算法起源</h2>\n<p>这个算法的刚开始的起源是在 1938 年，也就是说早于<strong>第一台计算机</strong>问世的时间。而它的思路是这样的：</p>\n<ol>\n<li>刚开始整个数组都是未标记状态，记未标记的个数为 k</li>\n<li>随机选一个在 1…k 的范围的数 n，从左往右找到第 n 为未标记的数。</li>\n<li>将这个数标记，放入随机序列中。k -= 1</li>\n<li>重复第二步，直到全部被标记为止</li>\n</ol>\n<p>通过这段代码的描述我们可以确定，肯定不会生成重复的随机数。因为一个数只被使用一次。但是为了减少对 k 的计算，于是我们平时常见的算法都是，<strong>通过交换将标记的数放在尾部</strong></p>\n<h2 id=\"现代方法\" style=\"position:relative;\"><a href=\"#%E7%8E%B0%E4%BB%A3%E6%96%B9%E6%B3%95\" aria-label=\"现代方法 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>现代方法</h2>\n<p>一个常见的算法是下面这个样子.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">for i in n - 1 down to 1:\n\tj = rand(i)\n\tswap(arr[i], arr[j])\n}</code></pre></div>\n<p>可以这样看这个算法。j 的值就是相当于上面算法的第二步，而显然 arr[j] 就是将要找到的那个数。因为 i 也是从尾部开始递减的，所以说，可以通过交换将这个被选取出来的数字和尾部交换，这样相当于将被标记的数字放到了尾部。之后 i 的递减直接帮我们完成了第三步的过程。所以这个算法不会产生重复的。\n值得注意的是, 我们不是从 n - 1 到 0 而是 1, 因为最后最后一个数不需要再调整了.</p>\n<p>这里还有另外一种形式的，只不过 i 是递增的.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">let arr\nfor i in 0 to n - 2:\n\tj = rand(i, n)\n\tswap(arr[i], arr[j])</code></pre></div>\n<p>但是这个算法是 mutate 的，也就说会直接修改数组。虽然<strong>immutable</strong>的算法也能直接通过上面的方法实现，但是可以通过一点小小的优化来将 swap 的三次赋值改成两次, 这种优化和插入排序的优化非常类似(其实我个人觉得没啥必要啦, 用解构赋值就行 🌚)</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">let suffled = arr\nfor i from n - 1 down to 1:\n\tj = rand(i)\n\tif i != j\n\t\tsuffled[i] = suffled[j]\n  \tsuffled[j] = arr[i]</code></pre></div>\n<h2 id=\"js-实现\" style=\"position:relative;\"><a href=\"#js-%E5%AE%9E%E7%8E%B0\" aria-label=\"js 实现 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>JS 实现</h2>\n<p>我们可以调一个简单的例子来实现最终的 JS 版本代码</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">function</span> <span class=\"token function\">shuffle</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">array</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> copy <span class=\"token operator\">=</span> array<span class=\"token punctuation\">.</span><span class=\"token function\">slice</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> lower <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> copy<span class=\"token punctuation\">.</span>length <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">>=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">-=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">let</span> upper <span class=\"token operator\">=</span> i<span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// 加 1 是因为, 要能取到 upper - lower</span>\n    <span class=\"token keyword\">let</span> j <span class=\"token operator\">=</span> <span class=\"token operator\">~</span><span class=\"token operator\">~</span><span class=\"token punctuation\">(</span>Math<span class=\"token punctuation\">.</span><span class=\"token function\">random</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token punctuation\">(</span>upper <span class=\"token operator\">-</span> lower <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> lower<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">[</span>copy<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> copy<span class=\"token punctuation\">[</span>j<span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>copy<span class=\"token punctuation\">[</span>j<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> copy<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">return</span> copy<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>值得一提的是, 大学的时候好像看过上面的随机数方法不能在让 <code class=\"language-text\">[lower,upper]</code> 在概率上的分布是一样的, 但是具体的做法我忘记了. <a href=\"http://www.ituring.com.cn/book/987\">计算机程序设计艺术 卷 2：半数值算法</a> 可能描述了更为正确的方式</p>","frontmatter":{"title":"Shuffle 算法","spoiler":"随机算法的基本实现","image":null,"date":"2019/07/29"}}},"pageContext":{"slug":"/shuffle/","previous":{"fields":{"slug":"/first-look-of-swiftui/first look of swiftui/"},"frontmatter":{"title":"对于 SwiftUI 的一些认识","image":null,"spoiler":"在写了一些 SwiftUI 代码后的初级看法"}},"next":{"fields":{"slug":"/how-to-add/"},"frontmatter":{"title":"如何实现加法和减法","image":null,"spoiler":"你知道 -1 的补码是多少吗？"}}}}}