参考: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
这就是我们要的最少的交换步骤了。
~完~