【C++】扩展格雷码生成算法

942 阅读2分钟

定义

一组mm进制的nn位格雷码,是一组有序的、无重复的、mm进制nn位数。这组数中一共有mmnn次方个数。相邻的(因有序,所以有相邻的概念)两个数之间有且只有一位是不同的。这组数的第一个和最后一个数,也被视为相邻的数。

输入

一行nnmm,分别表示格雷码位数和进制,其中2n12,2m10,mn50000002\le n\le 12, 2\le m \le 10, m^n\le 5000000

输出

任意一种格雷码编码方案,每个编码一行,相邻两行格雷码相差一位,第一行和最后一行也算相邻。

样例

输入

2 3

输出

00
01
02
12
10
11
21
22
20

生成算法

算法思想

nnmm进制格雷码的编码结果存储于字符串数组ss,构造kk位长度时字符串数组长度为lenlen

  1. 初始化k=1,len=mk=1,len=m,首先构造kkmm进制格雷码的字符串数组,将0(m1)0\sim (m-1)依次插入数组ss,如1133进制:0,1,20,1,2
  2. 构造k+1k+1位编码,首先将kk位的编码结果复制成mm倍存于数组中,如:0,1,20,1,2,0,1,2,0,1,20,1,2\to0,1,2,0,1,2,0,1,2
  3. 对于kk位编码,长度为len=mn1len=m^{n-1},将扩充后的数组按顺序分为lenlen组,如1133进制:0,1,2,0,1,2,0,1,2(0,1,2),(0,1,2),(0,1,2)0,1,2,0,1,2,0,1,2\to(0,1,2),(0,1,2),(0,1,2),第ii组循环右移ii位(计数从00开始):(0,1,2),(0,1,2),(0,1,2)(0,1,2),(2,0,1),(1,2,0)(0,1,2),(0,1,2),(0,1,2)\to (0,1,2),(2,0,1),(1,2,0)
  4. 对于第ii组,在每个元素前插入数字ii,构成nnmm进制格雷码的编码结果,如:(00,01,02),(12,10,11),(21,22,20)(00,01,02),(12,10,11),(21,22,20)
  5. 更新k=k+1,len=lenmk=k+1,len=len*m,重复第22步直到k=nk=n

回溯实现

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string code[50001];//编码结果 
int n,m;
int len;
void dfs(int k){
	if(k==n+1){//搜索结束 
		return;
	}
	for(int i=len;i<m*len;i++){//复制为循环右移 
		code[i]=code[(i-i/len+len)%len];
	}
	for(int i=0;i<m*len;i++){//i组插入i前缀 
		code[i]=char('0'+(i/len%m))+code[i];
	}
	len*=m;//更新长度 
	dfs(k+1);
}
int main(){
	cin>>n>>m;
	len=1;//乘法不能用0作为初始值 
	dfs(1);//从1开始搜索 
	for(int i=0;i<len;i++){
		cout<<code[i]<<endl;//输出结果 
	}
	return 0;
}

循环实现

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string code[50001];//编码结果 
int n,m,k,len;
int main(){
	cin>>n>>m;
	len=1;//乘法不能用0作为初始值
	k=1;
	while(k<=n){
		for(int j=len;j<m*len;j++){//复制为循环右移 
			code[j]=code[(j-j/len+len)%len];
		}
		for(int i=0;i<m*len;i++){//i组插入i前缀 
			code[i]=char('0'+(i/len%m))+code[i];
		}
		len*=m;//更新长度 
		k++;
	}
	for(int i=0;i<len;i++){
		cout<<code[i]<<endl;//输出结果 
	}
	return 0;
}