| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 719 人关注过本帖
标题:算法进化历程之“水壶问题”
只看楼主 加入收藏
巧若拙
Rank: 4
来 自:宁波余姚
等 级:业余侠客
威 望:1
帖 子:159
专家分:273
注 册:2014-8-24
结帖率:46.15%
收藏
 问题点数:0 回复次数:0 
算法进化历程之“水壶问题”
算法进化历程之“水壶问题”
巧若拙(欢迎转载,但请注明出处:http://blog.

   问题描述:假设给定了n个红色的水壶和n个蓝色的水壶,它们的形状和尺寸都不相同。所有红色水壶中所盛水的量都不一样,蓝色水壶也是一样。此外,对于每个红色的水壶,都有一个对应的蓝色水壶,两者所盛的水量是一样的。反之亦然。
  你的任务是将所盛水量一样的红色水壶和蓝色水壶找出来。为了达到这一目的,可以执行如下操作:挑选出一对水壶,其中一个是红色的,另一个是蓝色的:将红色水壶中倒满水;再将水倒入到蓝色的水壶中。通过这个操作,可以判断出来这两只水壶的容量哪一个大,或者是一样大。假设这样的比较需要一个时间单位。你的目标是找出一个算法,它通过执行最少次数的比较,来确定分组和配对问题。记住不能直接比较两个红色的或两个蓝色的水壶。
 
    思路分析:
为了验证配对是否正确,我在设计水壶的数据结构时安排了两个属性:水壶的储水量和水壶的编号。配对结束后,表示红蓝水壶的数组中下标相同的水壶储水量相同,但编号不一定相同。
数据结构如下:
typedef struct Kettle
{
    int value;//水壶的储水量
    int number; //水壶的编号
} Kettle;

    为了确保每个红色水壶中所盛水的量都不一样,并且都有一个对应的蓝色水壶,我设计了一个函数为红壶和蓝壶随机初始化各不相同的储水量。代码如下:
void CreatKettle(Kettle BlueK[], Kettle RedK[], int n)
{
    int i, 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)
{
    int i, 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)
{
    Kettle temp = *a;
    *a = *b;
    *b = temp;
}

版本二:在配对某个红壶的同时将蓝壶分成两堆,以便缩小下一次配对的范围
    在版本一中,每次为红壶配对,都要遍历未配对的所有蓝壶,效率较低。我们可以在配对某个红壶的同时将蓝壶分成两堆,以便缩小下一次配对的范围,提高效率。代码如下:
void Match_2(Kettle BlueK[], Kettle RedK[], int n)
{
    int i, 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]);
    }
   
    return left;
}

版本三:类快速排序算法
由于每个红色水壶中所盛水的量都不一样,并且都有一个对应的蓝色水壶,因此我们只需分别将其进行排序即可。但是不能直接比较同种颜色的水壶,只有颜色不一样的水壶才能进行比较,因此需要交叉比较,互为枢纽元素。
     整个排序过程中利用快速排序的思想,调用了版本二中的分割函数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[], Kettle RedK[], int left, int right)//类似快速排序的快速配对算法
{
    int posBlueK, 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)
{
    int i;
   
    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");
}
搜索更多相关主题的帖子: 联系人 粤语 珠海 电话 
2014-10-25 18:11
快速回复:算法进化历程之“水壶问题”
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.015564 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved