<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=613&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo-zh / 算法进化历程之“水壶问题”]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=613</link>
		<description><![CDATA[算法进化历程之“水壶问题” 最近发表的帖子。]]></description>
		<lastBuildDate>Fri, 09 Dec 2022 09:04:33 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[算法进化历程之“水壶问题”]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=656#p656</link>
			<description><![CDATA[<p>算法进化历程之“水壶问题”<br />巧若拙（欢迎转载，但请注明出处：http://blog.csdn.net/qiaoruozhuo）</p><p>&#160; &#160;问题描述：假设给定了n个红色的水壶和n个蓝色的水壶，它们的形状和尺寸都不相同。所有红色水壶中所盛水的量都不一样，蓝色水壶也是一样。此外，对于每个红色的水壶，都有一个对应的蓝色水壶，两者所盛的水量是一样的。反之亦然。<br />&#160; 你的任务是将所盛水量一样的红色水壶和蓝色水壶找出来。为了达到这一目的，可以执行如下操作:挑选出一对水壶，其中一个是红色的，另一个是蓝色的：将红色水壶中倒满水；再将水倒入到蓝色的水壶中。通过这个操作，可以判断出来这两只水壶的容量哪一个大，或者是一样大。假设这样的比较需要一个时间单位。你的目标是找出一个算法，它通过执行最少次数的比较，来确定分组和配对问题。记住不能直接比较两个红色的或两个蓝色的水壶。</p><p>&#160; &#160; 思路分析：<br />为了验证配对是否正确，我在设计水壶的数据结构时安排了两个属性：水壶的储水量和水壶的编号。配对结束后，表示红蓝水壶的数组中下标相同的水壶储水量相同，但编号不一定相同。<br />数据结构如下：<br />typedef struct Kettle<br />{<br />&#160; &#160; int value;//水壶的储水量<br />&#160; &#160; int number; //水壶的编号<br />} Kettle;</p><p>&#160; &#160; 为了确保每个红色水壶中所盛水的量都不一样，并且都有一个对应的蓝色水壶，我设计了一个函数为红壶和蓝壶随机初始化各不相同的储水量。代码如下：<br />void CreatKettle(Kettle BlueK[], Kettle RedK[], int n)<br />{<br />&#160; &#160; int i, j, pos, temp;<br />&#160; &#160;<br />&#160; &#160; for (i=0; i&lt;n; i++)<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; BlueK[i ].value = RedK[i ].value = BlueK[i ].number = RedK[i ].number = i + 1;<br />&#160; &#160; }<br />&#160; &#160;<br />&#160; &#160; for (i=0; i&lt;n; i++) //采用插入式洗牌，只改变储水量，不改变序号<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; pos = rand() % n;<br />&#160; &#160; &#160; &#160; temp = BlueK[0].value;<br />&#160; &#160; &#160; &#160; for (j=0; j&lt;pos; j++)<br />&#160; &#160; &#160; &#160; &#160; &#160; BlueK[j].value = BlueK[j+1].value;<br />&#160; &#160; &#160; &#160; BlueK[pos].value = temp;<br />&#160; &#160; &#160; &#160; <br />&#160; &#160; &#160; &#160; pos = rand() % n;<br />&#160; &#160; &#160; &#160; temp = RedK[0].value;<br />&#160; &#160; &#160; &#160; for (j=0; j&lt;pos; j++)<br />&#160; &#160; &#160; &#160; &#160; &#160; RedK[j].value = RedK[j+1].value;<br />&#160; &#160; &#160; &#160; RedK[pos].value = temp;<br />&#160; &#160; }<br />}</p><p>版本一：简明易懂的版本<br />先介绍最简单的方法，拿出第1个红色水壶，然后从n个蓝色水壶中去配对，需要Θ(n)次比较，找到配对的蓝水壶后，将其交换到最左边，使其下标与对应红水壶的下标相同。<br />重复上述操作，直到所有的红色水壶都完成了配对。总的时间复杂度为Θ(n^2)。<br />代码如下：<br />void Match_1(Kettle BlueK[], Kettle RedK[], int n)//最简单的配对算法，时间复杂度为 Θ(n^2)<br />{<br />&#160; &#160; int i, j;<br />&#160; &#160;<br />&#160; &#160; for (i=0; i&lt;n; i++)<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; for (j=i; j&lt;n; j++)<br />&#160; &#160; &#160; &#160; {<br />&#160; &#160; &#160; &#160; &#160; &#160; if (RedK[i ].value == BlueK[j].value)<br />&#160; &#160; &#160; &#160; &#160; &#160; {<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; Swap(&amp;BlueK[j], &amp;BlueK[i ]);<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; break;<br />&#160; &#160; &#160; &#160; &#160; &#160; }<br />&#160; &#160; &#160; &#160; }<br />&#160; &#160; }<br />}</p><p>void Swap(Kettle *a, Kettle *b)<br />{<br />&#160; &#160; Kettle temp = *a;<br />&#160; &#160; *a = *b;<br />&#160; &#160; *b = temp;<br />}</p><p>版本二：在配对某个红壶的同时将蓝壶分成两堆，以便缩小下一次配对的范围<br />&#160; &#160; 在版本一中，每次为红壶配对，都要遍历未配对的所有蓝壶，效率较低。我们可以在配对某个红壶的同时将蓝壶分成两堆，以便缩小下一次配对的范围，提高效率。代码如下：<br />void Match_2(Kettle BlueK[], Kettle RedK[], int n)<br />{<br />&#160; &#160; int i, pos;<br />&#160; &#160;<br />&#160; &#160; pos = Partition(BlueK, RedK[0].value, 0, n-1);//先配对第一个红壶<br />&#160; &#160; Swap(&amp;BlueK[0], &amp;BlueK[pos]);//将已配对水壶交换到最左边<br />&#160; &#160;<br />&#160; &#160; for (i=1; i&lt;n; i++)//依次配对剩下的红壶<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; if (RedK[i ].value &lt; BlueK[i-1].value)<br />&#160; &#160; &#160; &#160; &#160; &#160; pos = Partition(BlueK, RedK[i ].value, i, pos);//在[i,pos]范围内寻找配对<br />&#160; &#160; &#160; &#160; else<br />&#160; &#160; &#160; &#160; &#160; &#160; pos = Partition(BlueK, RedK[i ].value, pos+1, n-1);//在[pos+1, n-1]范围内寻找配对<br />&#160; &#160; &#160; &#160; <br />&#160; &#160; &#160; &#160; Swap(&amp;BlueK[i ], &amp;BlueK[pos]);//将已配对水壶交换到最左边<br />&#160; &#160; }<br />}</p><p>&#160; &#160; Match_2()用到一个分割函数Partition()，这是一个类似快速排序的分割函数，可以以值为x的元素为枢纽元，将数组K[]分成两部分，并返回枢纽元的位置。代码如下：<br />int Partition(Kettle K[], int x, int left, int right)<br />{<br />&#160; &#160; while (left &lt; right)<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; while (K[left].value &lt; x)<br />&#160; &#160; &#160; &#160; &#160; &#160; left++;<br />&#160; &#160; &#160; &#160; while (K[right].value &gt; x)<br />&#160; &#160; &#160; &#160; &#160; &#160; right--;<br />&#160; &#160; &#160; &#160; &#160; &#160; <br />&#160; &#160; &#160; &#160; Swap(&amp;K[left], &amp;K[right]);<br />&#160; &#160; }<br />&#160; &#160;<br />&#160; &#160; return left;<br />}</p><p>版本三：类快速排序算法<br />由于每个红色水壶中所盛水的量都不一样，并且都有一个对应的蓝色水壶，因此我们只需分别将其进行排序即可。但是不能直接比较同种颜色的水壶，只有颜色不一样的水壶才能进行比较，因此需要交叉比较，互为枢纽元素。<br />&#160; &#160; &#160;整个排序过程中利用快速排序的思想，调用了版本二中的分割函数Partition()，采用交叉分割的方法，具体描述如下：<br />1. 从红色水壶序列中随机选择一个作为枢轴元素<br />2. 利用红色水壶枢轴元素RedK[posRedK]对蓝色水壶序列进行分割，并返回对应蓝色水壶的编号posBlueK。<br />3. 利用BlueK[posBlueK]对红色水壶序列进行进行分割，并返回对应红色水壶的编号posRedK，很显然此时posRedK ==posBlueK。<br />4．递归调用函数QuickMatch()，对分割好的序列进行配对。<br />算法的时间复杂度为O(nlgn)，最坏的情况下时间复杂度为O(n^2)。<br />代码如下：</p><p>void Match_3(Kettle BlueK[], Kettle RedK[], int n)//快速配对算法的驱动函数<br />{<br />&#160; &#160; QuickMatch(BlueK, RedK, 0, n-1);<br />}</p><br /><p>void QuickMatch(Kettle BlueK[], Kettle RedK[], int left, int right)//类似快速排序的快速配对算法<br />{<br />&#160; &#160; int posBlueK, posRedK;<br />&#160; &#160;<br />&#160; &#160; if (left &lt; right)<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; posRedK = rand() % (right-left+1) + left; //从红色水壶中随机选择一个作为枢轴元素<br />&#160; &#160; &#160; &#160; posBlueK = Partition(BlueK, RedK[posRedK].value, left, right);//将蓝壶分成两堆，并返回配对蓝壶的编号<br />&#160; &#160; &#160; &#160; posRedK = Partition(RedK, BlueK[posBlueK].value, left, right); //将红壶分成两堆，并返回配对红壶的编号<br />&#160; &#160; &#160; &#160; //递归调用函数QuickMatch()，对分割好的序列进行配对<br />&#160; &#160; &#160; &#160; QuickMatch(BlueK, RedK, left, posRedK-1);<br />&#160; &#160; &#160; &#160; QuickMatch(BlueK, RedK, posRedK+1, right);<br />&#160; &#160; }<br />}</p><p>测试主函数：<br />int main(void)<br />{<br />&#160; &#160; Kettle BlueK[MAXSIZE], RedK[MAXSIZE];<br />&#160; &#160; int i, n = 6;<br />&#160; &#160;<br />&#160; &#160; CreatKettle(BlueK, RedK, n);<br />&#160; &#160; Print(BlueK, RedK, n);<br />Match_1(BlueK, RedK, n);<br />// Match_2(BlueK, RedK, n);<br />// Match_3(BlueK, RedK, n);<br />&#160; &#160; Print(BlueK, RedK, n);<br />&#160; &#160;<br />&#160; &#160; return 0;<br />}</p><p>输出数据函数：<br />void Print(Kettle BlueK[], Kettle RedK[], int n)<br />{<br />&#160; &#160; int i;<br />&#160; &#160;<br />&#160; &#160; for (i=0; i&lt;n; i++)<br />&#160; &#160; {<br />&#160; &#160; &#160; &#160; printf (&quot;BlueK[%d]: %d, %d&#160; RedK[%d]: %d, %d\n&quot;, i, BlueK[i ].number, BlueK[i ].value, i, RedK[i ].number, RedK[i ].value);<br />&#160; &#160; }<br />&#160; &#160; printf(&quot;\n&quot;);<br />}</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Fri, 09 Dec 2022 09:04:33 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=656#p656</guid>
		</item>
	</channel>
</rss>
