一开始可能没有什么思路,那么先从字母比较少数
的情况开始观察:假设只有A B C三个字母,那么他们的所有子集是什么?
{A},{B},{C},{A,B},{A,C},{B,C},{A,B,C},空集
根据上面的列举我们可以画出下面的表:
A B C
1 0 0
0 1 0
0 0 1
1 1 0
1 0 1
0 1 1
1 1 1
0 0 0
能看出这是一种特殊的编码吧?如果不行,我们换一下排列的顺序,就更加直观了:
000
001
011
010
110
111
101
100
相邻的编码有且只有一位不同,其实这就是一个格雷码表
格雷码
关于构造格雷码的算法,在网上看到最经典的一种方法是递归。
仔细观察上面的格雷码表,存在一个对称的规律,比如000和100,除了最高位,其余位完全相同,而001和101同理….
因此我们可以使用递归的方法,当需要构造n位格雷码时,先构造n-1位格雷码,最后对生成的n-1位格雷码轮流在最高位加上0和1,那么就生成了n位格雷码了。
递归结束条件
递归结束的条件是什么呢?自然就是构造1位格雷码了,1位格雷码就是“0”和“1”
存储格雷码的数据结构
要怎么存储格雷码?这里我们可以用vector<string>
比如上面列出的三位格雷码,总共有8个string(包含空集)
递归实现N位格雷码的构造
typedef vector<string> gray;
gray gernalGrayCode(int n){
gray code(pow(2,n));//code用于存储构造好的n位格雷码,对n位格雷码而言,总共有2^n种编码
int len = code.size();
if(n == 1){//递归结束条件,开始构造1位格雷码
code[0] = "0";
code[1] = "1";
return code;
}
gray g_code = gernalGrayCode(n - 1);//构造n-1位格雷码
for(int i = 0; i < g_code.size(); i++){//给构造好的n-1位格雷码轮流在最高位(最左边)加上"0"和"1"
code[i] = "0" + g_code[i];
code[len - 1 - i] = "1" + g_code[i];//存储位置由刚才上面讲的对称决定
}
return code;
}
转换为字母打印
解决了构造格雷码的问题,那么怎么转换为字母打印出来呢?
在上面根据字母构造格雷码的推导过程中,可以看到1表示字母出现,0表示字母不出现,三位格雷码里,可以用任何一位分别代表A、B、C,根据我们的日常习惯,就用最低位代表A,以此类推。
因此,当我们得到N位格雷码时,只要对应的编码某一位置为1,则输出对应的字母。
怎么知道对应的字母是多少?这个也很容易,构造一个查询表就可以了,把26个字母按顺序存储在一个字符串里,那么当第n位为1时,我们就打印出对应的字母。
char a[27] = "abcdefghijklmnopqrstuvwxyz";
gray code = gernalGrayCode(n);//得到了n位格雷码
gray::iterator iter = code.begin();
iter++;
while(iter != code.end()){//遍历格雷码表
string get = *iter++;
for(int i = 0; i < get.size(); i++){
if(get[i] == '1'){//检查每一个格雷码的每一位,如果为1,打印出在数组a中对应位置的字母
cout << a[i];
}
}
cout << endl;
}
完整代码
#include <string.h>
#include <malloc.h>
#include <iostream>
#include <vector>
#include <math.h>
using namespace std;
typedef vector<string> gray;
char a[27] = "abcdefghijklmnopqrstuvwxyz";
gray gernalGrayCode(int n){
gray code(pow(2,n));
int len = code.size();
if(n == 1){
code[0] = "0";
code[1] = "1";
return code;
}
gray g_code = gernalGrayCode(n - 1);
for(int i = 0; i < g_code.size(); i++){
code[i] = "0" + g_code[i];
code[len - 1 - i] = "1" + g_code[i];
}
return code;
}
int main(){
gray code = gernalGrayCode(4);
gray::iterator iter = code.begin();
iter++;
while(iter != code.end()){
string get = *iter++;
for(int i = 0; i < get.size(); i++){
if(get[i] == '1'){
cout << a[i];
}
}
cout << endl;
}
}
下面是运行程序得到4个字母的所有子集:
非递归方法构造格雷码
在网上还搜到了一种利用非递归的方式构造格雷码,非常简洁的代码,这里记录下来作为累积
vector<int> grayCode(int n) {
vector<int>res;
for(int i=0;i<(1<<n);i++){//执行2^n次,因为n位格雷码总共有2^n个编码
res.push_back(i^(i>>1));//关键步骤,利用异或运算产生和当前编码有且只有一位不同的格雷码
}
return res;
}