三色旗问题

来源:互联网 发布:25岁用什么精华知乎 编辑:程序博客网 时间:2024/04/26 06:26

参考:http://caterpillar.onlyfun.net/Gossip/AlgorithmGossip/ThreeColorsFlags.htm

问题:假设有一根绳子,上面有一些红、白、蓝色的旗子。起初旗子的顺序是任意的,现在要求用最少的次数移动这些旗子,使得它们按照蓝、白、红的顺序排列。注意只能在绳子上操作,并且一次只能调换两个旗子。

解法:从绳子的开头(最左边)开始,遇到蓝色的往前移,遇到白色的不动,遇到红色的往后移。

           :我们这里说的前移往左后移往右

        再来看具体的代码实现,先看下图:

        我们可以定义三个指针 a、b、c 。从上图可以看出,a和b指向第一个元素,c指向最后一个元素。a的作用是确保蓝色的旗子移到左边,而c的作用是确保红色的旗子移到右边。b则用于顺序访问各个旗子。

        思路如下:查看b所指向的旗子的颜色,如果是白色的,则不移动任何旗子(因为要求白色的旗子在中间,所以不去动它),将b往后移一位。

                    如果是红色的,此时将b所指向的旗子和c所指向的旗子交换位置,同时c往前移一位。(交换后c指向的已经是红色的旗子了,所以可以往前移一位了)。

                    如果是蓝色的,此时将b所指向的旗子和a所指向的旗子交换位置,同时a往后移一位。(交换后a指向的已经是蓝色的旗子了,所以可以往后移一位了)。这里要注意的是b也要后移一位。(这里我也没有弄得很清楚。大概是b不能在a的后面)

        那么什么时候停止交换呢?显然是c都跑到b的后面去的时候,即b>c

        根据以上的分析,可以写出如下代码:

import java.util.*;public class ThreeColorFlag {public static void main(String[] args) {           System.out.println("请输入三色旗的顺序(例如 BRRWWB):");  Scanner scanner=new Scanner(System.in);  String s=scanner.next();  ThreeColorFlag tcf=new ThreeColorFlag();  s=tcf.move(s.toUpperCase().toCharArray());  System.out.println("排列好后的顺序:"+s);}//互相交换public void change(char[] flags,int x,int y){System.out.println("交换前:"+new String(flags));char temp=flags[x];flags[x]=flags[y];flags[y]=temp;System.out.println((x+1)+" 号和 "+(y+1)+" 号交换");System.out.println("交换后:"+new String(flags));}public String move(char[] flags){ int a=0,b=0; int c=flags.length-1;  while(b<=c){ switch (flags[b]){   case 'W':b++;break;   case 'B':   change(flags, a, b);   a++;   b++;   break;   case 'R':      change(flags, b, c);   c--;   break; }   } return new String(flags);}}

运行结果:

请输入三色旗的顺序(例如 BRRWWB):
BBRWBWR
交换前:BBRWBWR
1 号和 1 号交换
交换后:BBRWBWR
交换前:BBRWBWR
2 号和 2 号交换
交换后:BBRWBWR
交换前:BBRWBWR
3 号和 7 号交换
交换后:BBRWBWR
交换前:BBRWBWR
3 号和 6 号交换
交换后:BBWWBRR
交换前:BBWWBRR
3 号和 5 号交换
交换后:BBBWWRR
排列好后的顺序:BBBWWRR

        查看以上的结果,我们发现了一些冗余的操作,比如1 号和 1 号交换、2 号和 2 号交换。这显然是多余的,只需要注意一些细节就可以避免它们。

        第一,当b指向的是红色而和c交换时,如果此时c指向的是红色,显然没有必要把两个红色的旗子交换。所以这时应该把c前移,直到c指向的不是红色的时候才和b指向的旗子交换。当然移动时必须确保b<c。

        第二,同样地,当b指向的是蓝色而要和a的指向交换时,如果a指向的不是蓝色,则交换。否则如果a指向的是蓝色,则也没有必要把两个蓝色的旗子交换。所以这时应该把a和b都往后移一位。

        a和b的交换总有一些特殊。大概是因为它们初始都指向第一个元素的缘故吧。如果是从右往左考察每个旗子,也就是说b和c初始指向最后一个元素,那么就该轮到b和c的交换特殊了吧。

        改进后的代码:

import java.util.*;public class ThreeColorFlag {public static void main(String[] args) {           System.out.println("请输入三色旗的顺序(例如 BRRWWB):");  Scanner scanner=new Scanner(System.in);  String s=scanner.next();  ThreeColorFlag tcf=new ThreeColorFlag();  s=tcf.move(s.toUpperCase().toCharArray());  System.out.println("排列好后的顺序:"+s);}//互相交换public void change(char[] flags,int x,int y){System.out.println("交换前:"+new String(flags));char temp=flags[x];flags[x]=flags[y];flags[y]=temp;System.out.println((x+1)+" 号和 "+(y+1)+" 号交换");System.out.println("交换后:"+new String(flags));}public String move(char[] flags){ int a=0,b=0; int c=flags.length-1;  while(b<=c){ switch (flags[b]){   case 'W':b++;break;   case 'B':   if(flags[a]=='B')   {a++;b++;}   else{   change(flags, a, b);   a++;   b++;   }   break;   case 'R':   while(b<c && flags[c]=='R')   c--;   change(flags, b, c);   c--;   break; }   } return new String(flags);}}


运行结果:

请输入三色旗的顺序(例如 BRRWWB):
BBRWBWR
交换前:BBRWBWR
3 号和 6 号交换
交换后:BBWWBRR
交换前:BBWWBRR
3 号和 5 号交换
交换后:BBBWWRR
排列好后的顺序:BBBWWRR

        这就是我们要的最少的交换步骤了。

~完~



 

原创粉丝点击