算法进化历程之“水壶问题”
来源:互联网 发布:重庆网络事件营销 编辑:程序博客网 时间:2024/06/08 14:28
算法进化历程之“水壶问题”
巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo)
问题描述:假设给定了n个红色的水壶和n个蓝色的水壶,它们的形状和尺寸都不相同。所有红色水壶中所盛水的量都不一样,蓝色水壶也是一样。此外,对于每个红色的水壶,都有一个对应的蓝色水壶,两者所盛的水量是一样的。反之亦然。
你的任务是将所盛水量一样的红色水壶和蓝色水壶找出来。为了达到这一目的,可以执行如下操作:挑选出一对水壶,其中一个是红色的,另一个是蓝色的:将红色水壶中倒满水;再将水倒入到蓝色的水壶中。通过这个操作,可以判断出来这两只水壶的容量哪一个大,或者是一样大。假设这样的比较需要一个时间单位。你的目标是找出一个算法,它通过执行最少次数的比较,来确定分组和配对问题。记住不能直接比较两个红色的或两个蓝色的水壶。
思路分析:
为了验证配对是否正确,我在设计水壶的数据结构时安排了两个属性:水壶的储水量和水壶的编号。配对结束后,表示红蓝水壶的数组中下标相同的水壶储水量相同,但编号不一定相同。
数据结构如下:
typedef structKettle
{
int value;//水壶的储水量
int number; //水壶的编号
} Kettle;
为了确保每个红色水壶中所盛水的量都不一样,并且都有一个对应的蓝色水壶,我设计了一个函数为红壶和蓝壶随机初始化各不相同的储水量。代码如下:
void CreatKettle(Kettle BlueK[], KettleRedK[], int n)
{
inti, j, pos, temp;
for(i=0; i<n; i++)
{
BlueK[i].value = RedK[i].value =BlueK[i].number = RedK[i].number = i + 1;
}
for (i=0; i<n; i++) //采用插入式洗牌,只改变储水量,不改变序号
{
pos = rand() % n;
temp = BlueK[0].value;
for (j=0; j<pos; j++)
BlueK[j].value = BlueK[j+1].value;
BlueK[pos].value = temp;
pos = rand() % n;
temp = RedK[0].value;
for (j=0; j<pos; j++)
RedK[j].value = RedK[j+1].value;
RedK[pos].value = temp;
}
}
版本一:简明易懂的版本
先介绍最简单的方法,拿出第1个红色水壶,然后从n个蓝色水壶中去配对,需要Θ(n)次比较,找到配对的蓝水壶后,将其交换到最左边,使其下标与对应红水壶的下标相同。
重复上述操作,直到所有的红色水壶都完成了配对。总的时间复杂度为Θ(n^2)。
代码如下:
void Match_1(Kettle BlueK[], Kettle RedK[],int n)//最简单的配对算法,时间复杂度为 Θ(n^2)
{
inti, j;
for(i=0; i<n; i++)
{
for(j=i; j<n; j++)
{
if(RedK[i].value == BlueK[j].value)
{
Swap(&BlueK[j],&BlueK[i]);
break;
}
}
}
}
void Swap(Kettle *a, Kettle *b)
{
Kettletemp = *a;
*a= *b;
*b= temp;
}
版本二:在配对某个红壶的同时将蓝壶分成两堆,以便缩小下一次配对的范围
在版本一中,每次为红壶配对,都要遍历未配对的所有蓝壶,效率较低。我们可以在配对某个红壶的同时将蓝壶分成两堆,以便缩小下一次配对的范围,提高效率。代码如下:
void Match_2(Kettle BlueK[], Kettle RedK[],int n)
{
inti, pos;
pos= Partition(BlueK, RedK[0].value, 0, n-1);//先配对第一个红壶
Swap(&BlueK[0],&BlueK[pos]);//将已配对水壶交换到最左边
for(i=1; i<n; i++)//依次配对剩下的红壶
{
if(RedK[i].value < BlueK[i-1].value)
pos= Partition(BlueK, RedK[i].value, i, pos);//在[i,pos]范围内寻找配对
else
pos= Partition(BlueK, RedK[i].value, pos+1, n-1);//在[pos+1, n-1]范围内寻找配对
Swap(&BlueK[i],&BlueK[pos]);//将已配对水壶交换到最左边
}
}
Match_2()用到一个分割函数Partition(),这是一个类似快速排序的分割函数,可以以值为x的元素为枢纽元,将数组K[]分成两部分,并返回枢纽元的位置。代码如下:
int Partition(Kettle K[], int x, int left,int right)
{
while(left < right)
{
while(K[left].value < x)
left++;
while(K[right].value > x)
right--;
Swap(&K[left],&K[right]);
}
returnleft;
}
版本三:类快速排序算法
由于每个红色水壶中所盛水的量都不一样,并且都有一个对应的蓝色水壶,因此我们只需分别将其进行排序即可。但是不能直接比较同种颜色的水壶,只有颜色不一样的水壶才能进行比较,因此需要交叉比较,互为枢纽元素。
整个排序过程中利用快速排序的思想,调用了版本二中的分割函数Partition(),采用交叉分割的方法,具体描述如下:
1. 从红色水壶序列中随机选择一个作为枢轴元素
2. 利用红色水壶枢轴元素RedK[posRedK]对蓝色水壶序列进行分割,并返回对应蓝色水壶的编号posBlueK。
3. 利用BlueK[posBlueK]对红色水壶序列进行进行分割,并返回对应红色水壶的编号posRedK,很显然此时posRedK ==posBlueK。
4.递归调用函数QuickMatch(),对分割好的序列进行配对。
算法的时间复杂度为O(nlgn),最坏的情况下时间复杂度为O(n^2)。
代码如下:
void Match_3(Kettle BlueK[], Kettle RedK[],int n)//快速配对算法的驱动函数
{
QuickMatch(BlueK,RedK, 0, n-1);
}
void QuickMatch(Kettle BlueK[], KettleRedK[], int left, int right)//类似快速排序的快速配对算法
{
intposBlueK, posRedK;
if(left < right)
{
posRedK= rand() % (right-left+1) + left; //从红色水壶中随机选择一个作为枢轴元素
posBlueK= Partition(BlueK, RedK[posRedK].value, left, right);//将蓝壶分成两堆,并返回配对蓝壶的编号
posRedK= Partition(RedK, BlueK[posBlueK].value, left, right); //将红壶分成两堆,并返回配对红壶的编号
//递归调用函数QuickMatch(),对分割好的序列进行配对
QuickMatch(BlueK,RedK, left, posRedK-1);
QuickMatch(BlueK,RedK, posRedK+1, right);
}
}
测试主函数:
int main(void)
{
Kettle BlueK[MAXSIZE], RedK[MAXSIZE];
int i, n = 6;
CreatKettle(BlueK, RedK, n);
Print(BlueK, RedK, n);
Match_1(BlueK,RedK, n);
// Match_2(BlueK,RedK, n);
// Match_3(BlueK,RedK, n);
Print(BlueK, RedK, n);
return 0;
}
输出数据函数:
void Print(Kettle BlueK[], Kettle RedK[],int n)
{
inti;
for(i=0; i<n; i++)
{
printf("BlueK[%d]: %d, %d RedK[%d]: %d,%d\n", i, BlueK[i].number, BlueK[i].value, i, RedK[i].number, RedK[i].value);
}
printf("\n");
}
- 算法进化历程之“水壶问题”
- 算法进化历程之“n皇后问题”
- 算法进化历程之“快速排序”
- 算法进化历程之“归并排序”
- 算法进化历程之相亲数
- 最大m子段和问题算法进化历程
- 0-1背包问题(算法进化历程)
- 算法进化历程之“最大连续子序列之和”
- [进化算法] 进化算法之标准进化规划(EP)
- 算法谜题——三个水壶问题
- 算法进化历程之“根据二叉树的先序和中序序列输出后序序列”
- 智能算法之微分进化算法(差分进化算法)
- 最大连续子序列之和算法进化历程
- 遗传算法之花朵进化
- 水壶
- 进化算法之粒子群(PSO)
- 进化算法之神秘的二进制
- 进化算法
- 文件系统writeback
- Unity3d NGUI的使用(六)(UIInput&多个UICheckBox单选的使用)
- 特权级的意味
- Android常用UI组件 - TextView
- 软考考点---CPU
- 算法进化历程之“水壶问题”
- 数据库优化(超级详细),转文,值得一读
- 常用三方库
- leetcode - Merge k Sorted Lists
- 【重要博文收藏】
- Linux ALSA声卡驱动:ALSA架构简介
- 第9周项目4乘法口诀表
- Servlet多线程安全问题
- POJ 2051 Argus(按周期输出)