Gray码生成问题(分治策略)
问题描述
n位Gray码是一个长度为2n的序列,序列中的每个元素长度都是为n的(0,1)串,且都是不相同的,相邻元素中只有一位不同。求设计一个算法构造任意的n位的Gray码。
题目源于:王晓东.《计算机算法设计与分析》.第5版习题2-14
例如:当输入n=3,得到如下输出:
000
001
011
010
110
111
101
100
算法思路与代码实现
方法一:分治方法(递归实现)
分治方法的核心思想是把原有问题拆分成若干个规模较小的问题,再将问题的解整合归化到原问题的解。
对问题进行分析,可以发现,格雷码的构造有以下规律:
n位的Gray码可以由n-1位的Gray构造,其顺序排列最高位补零,和镜像排列最高位补零,就得到了n位的Gray码。(如下所示)
所以,得到n位Gray码的问题可以变为得到n-1位Gray码的问题,再将n-1位Gray码处理得到n位Gray,这样就将问题规模减小了。
具体而言:记函数 G r a y ( n ) Gray(n) Gray(n)是求 n n n位格雷码。 G r a y ( n ) Gray(n) Gray(n)的前半部分(共 2 n − 1 2^{n-1} 2n−1项),由 G r a y ( n − 1 ) Gray(n-1) Gray(n−1)顺序排列,每元素首位添"0"得到; G r a y ( n ) Gray(n) Gray(n)的后半部分,由 G r a y ( n − 1 ) Gray(n-1) Gray(n−1)镜像排列并首位添"1"得到。
方法二:异或生成法(二进制转格雷码)
查阅了一些资料,格雷码可以由自然二进制码生成而来。具体做法如下:
对于一个自然二进制数(以5位为例):
B
=
n
4
n
3
n
2
n
1
n
0
B = n_4n_3n_2n_1n_0
B=n4n3n2n1n0 将其对应转换为(5位)格雷码
G
=
g
4
g
3
g
2
g
1
g
0
G=g_4g_3g_2g_1g_0
G=g4g3g2g1g0. 其中:
g 0 = n 0 ⨁ n 1 g0 = n_0\bigoplus n_1 g0=n0⨁n1
g 1 = n 1 ⨁ n 2 g1 = n_1\bigoplus n_2 g1=n1⨁n2
g 2 = n 2 ⨁ n 3 g2 = n_2\bigoplus n_3 g2=n2⨁n3
g 3 = n 3 ⨁ n 4 g3 = n_3\bigoplus n_4 g3=n3⨁n4
g 4 = n 4 ⨁ 0 g4 = n_4\bigoplus 0 g4=n4⨁0
即有:
G
=
b
(
n
)
⨁
b
(
n
+
1
)
G = b(n) \bigoplus b(n+1)
G=b(n)⨁b(n+1) ,
b
(
n
)
b(n)
b(n)表示
B
(
n
)
B(n)
B(n)从右往左的对应位。当
n
n
n为
B
(
n
)
B(n)
B(n)最高位时,
b
(
n
+
1
)
=
0
b(n+1)=0
b(n+1)=0
例如:二进制自然数101, 其对应的格雷码为 111.
以下为算法实现代码:
代码1:分治方法
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int num;
void getGray(int n,char **Gray){ //递归函数
if(n==1){// 边界条件
return;
}
getGray(n-1,Gray);
//Gray(n-1)的数量
int m = 1<<(n-1);//移位计算即m = 2^(n-1)
//Gray(n)的前半部分直接使用Gray(n-1)(首位补0)
//Gray(n)的后半部分使用Gray(n-1)镜像排列,首位补1
for(int i=m;i>0;i--){
strcpy(Gray[++m], Gray[i]);
Gray[m][num-n] = '1';//首位补1
}
return;
}
int main(){
int n;printf("n=");scanf("%d",&n);//输入n
num = n;//记录n
char **Gray = (char**)malloc(sizeof(char*)*((1<<n)+1));//首位不用
for(int i=1;i<(1<<n)+1;i++){
Gray[i]=(char*)malloc(sizeof(char)*(n+1));//最后一位放'\n'
}//添加字符串结束标志,便于输出
//初始化0,1
for(int i=0;i<n-1;i++){
Gray[1][i]='0';
Gray[2][i]='0';
}
Gray[1][n-1]='0';
Gray[2][n-1]='1';
Gray[1][n]='\0';
Gray[2][n]='\0';
getGray(n,Gray);
for(int i=1;i<(1<<n)+1;i++){
printf("%s\n",Gray[i]);
}
return 0;
}
根据此程序和其对应原理,可以很快地得到其非递归代码(用循环替代)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){//非递归程序
int n;
printf("n=");
scanf("%d",&n);
char **Gray = (char**)malloc(sizeof(char*)*((1<<n)+1));//首位不用
for(int i=1;i<(1<<n)+1;i++){
Gray[i]=(char*)malloc(sizeof(char)*(n+1));//最后一位放'\n'
}
//初始化0,1
for(int i=0;i<n-1;i++){
Gray[1][i]='0';
Gray[2][i]='0';
}
Gray[1][n-1]='0';
Gray[2][n-1]='1';
Gray[1][n]='\0';
Gray[2][n]='\0';
for(int i=2;i<n+1;i++){
int m = 1<<(i-1);
for(int j=1;j<m+1;j++){
strcpy(Gray[m+j], Gray[m-j+1]);
Gray[m+j][n-i] = '1';
}
}
//输出
for(int i=1;i<(1<<n)+1;i++){
printf("%s\n",Gray[i]);
}
return 0;
}
代码2:异或生成法
#include<stdio.h>
#include<stdlib.h>
int main(){
int n;printf("n=");scanf("%d",&n);
int t = 0;
char *gray = (char*)malloc(sizeof(char)*(n+1));//保存每次生成的格雷码
gray[n] = '\0';//字符串结束标志,方便输出
//异或生成格雷码
for(int i=0;i<(1<<n);i++){
t = (i>>1)^i;//G(n)=b(n) xor b(n+1)
//将十进制表示的格雷码转换为二进制
for(int i=1;i<n+1;i++){
if(t&1) gray[n-i] = '1';
else gray[n-i] = '0';
t=t>>1;
}
//这里直接输出,也可以保存到相关数组内
printf("%s\n",gray);
}
return 0;
}
代码测试
测试n=4
分享的同时记录学习,有问题欢迎交流指正^ ^