算法进化历程之“水壶问题”

来源:互联网 发布:重庆网络事件营销 编辑:程序博客网 时间: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");

}

0 0