簡介
在一組數的編碼中,若任意兩個相鄰的代碼只有一位二進制數不同,則稱這種編碼為格雷碼(Gray Code),另外由於最大數與最小數之間也僅一位數不同,即“首尾相連”,因此又稱循環碼或反射碼。在數字系統中,常要求代碼按一定順序變化。例如,按自然數遞增計數,若采用8421碼,則數0111變到1000時四位均要變化,而在實際電路中,4位的變化不可能絕對同時發生,則計數中可能出現短暫的其它代碼(1100、1111等)。在特定情況下可能導致電路狀態錯誤或輸入錯誤。使用格雷碼可以避免這種錯誤。格雷碼有多種編碼形式。
格雷碼(Gray Code)曾用過Grey Code、葛萊碼、格萊碼、戈萊碼、循環碼、反射二進制碼、最小差錯碼等名字,它們有的不對,有的易與其它名稱混淆,建議不要再使用這些曾用名。
格雷碼是一種具有反射特性和循環特性的單步自補碼,其循環和單步特性消除了隨機取數時出現重大錯誤的可能,其反射和自補特性使得對其進行求反操作也非常方便,所以,格雷碼屬於一種可靠性編碼,是一種錯誤最小化的編碼方式,因此格雷碼在通信和測量技術中得到廣泛應用。
生成格雷碼
格雷碼(Gray Code)是一個數列集合,每個數使用二進位來表示,假設使用n位元來表示每個數字,任兩個數之間只有一個位元值不同。
例如以下為3位元的格雷碼: 000 001 011 010 110 111 101 100 。
如果要產生
n位元的格雷碼,那么
格雷碼的個數為2^n.
假設原始的值從0開始,格雷碼
產生的規律是:
第一步,改變最右邊的位元值;
第二步,改變右起第一個為1的位元的左邊位元;
第三步,第四步重復第一步和第二步,直到所有的格雷碼產生完畢(換句話說,已經走了(2^n) - 1 步)。
用一個
例子來說明:
假設產生3位元的格雷碼,原始值位 000
第一步:改變最右邊的位元值: 001
第二步:改變右起第一個為1的位元的左邊位元: 011
第三步:改變最右邊的位元值: 010
第四步:改變右起第一個為1的位元的左邊位元: 110
第五步:改變最右邊的位元值: 111
第六步:改變右起第一個為1的位元的左邊位元: 101
第七步:改變最右邊的位元值: 100
如果按照這個規則來生成格雷碼,是沒有問題的,但是這樣做太復雜了。如果仔細觀察格雷碼的結構,我們會有以下發現:
1、除了最高位(左邊第一位),格雷碼的位元完全上下對稱(看下面列表)。比如第一個格雷碼與最后一個格雷碼對稱(除了第一位),第二個格雷碼與倒數第二個對稱,以此類推。
2、
最小的重復單元是 0 , 1。
0 00
0 01
0 11
0 10
1 10
1 11
1 01
1 00
所以,在實現的時候,我們完全可以利用遞歸,在每一層前面加上0或者1,然后就可以列出所有的格雷碼。
比如:
第一步:產生 0, 1 兩個字符串。
第二步:在第一步的基礎上,每一個字符串都加上0和1,但是每次只能加一個,所以得做兩次。這樣就變成了 00,01,11,10 (注意對稱)。
第三步:在第二步的基礎上,再給每個字符串都加上0和1,同樣,每次只能加一個,這樣就變成了 000,001,011,010,110,111,101,100。
好了,這樣就把3位元格雷碼生成好了。
如果要生成4位元格雷碼,我們只需要在3位元格雷碼上再加一層0,1就可以了: 0000,0001,0011,0010,0110,0111,0101,0100,1100,1101,1110,1010,0111,1001,1000.
也就是說,
n位元格雷碼是基於n-1位元格雷碼產生的。
算法實現
1、遞歸實現
/** * 遞歸生成二進制格雷碼 * 思路:1、獲得n-1位生成格雷碼的數組 * 2、由於n位生成的格雷碼位數是n-1的兩倍,故只要在n為格雷碼的前半部分加0,后半部分加1即可。 * @param n 格雷碼的位數 * @return 生成的格雷碼數組 */ public static String[] GrayCode(int n) { //數組的大小是2的n次方,因為n位的格雷碼有2的n次方種排列 String[] grayCodeArr = new String[(int)Math.pow(2, n)]; if(n < 1) { System.out.println("你輸入的格雷碼位數有誤!"); } if(1 == n) { grayCodeArr[0] = "0"; grayCodeArr[1] = "1"; return grayCodeArr; } //n-1 位格雷碼的生成方式 String[] before = GrayCode(n-1); for(int i = 0 ; i < before.length ; i++){ grayCodeArr[i] = "0" + before[i]; grayCodeArr[grayCodeArr.length -1 - i] = "1" + before[i]; } return grayCodeArr; }
2、非遞歸實現
/** * 非遞歸生成二進制格雷碼 * 思路:1、獲得n-1位生成格雷碼的數組 * 2、由於n位生成的格雷碼位數是n-1的兩倍,故只要在n為格雷碼的前半部分加0,后半部分加1即可。 * @param n 格雷碼的位數 * @return 生成的格雷碼數組 */ public static String[] GrayCode2(int n) { int num = (int)Math.pow(2, n);//根據輸入的整數,計算出此Gray序列大小 String[] s1 = {"0","1"};//第一個Gray序列 if(n < 1) { System.out.println("你輸入的格雷碼位數有誤!"); } for(int i=2;i<=n;i++){//循環根據第一個Gray序列,來一個一個的求 int p = (int)Math.pow(2, i);//到了第幾個的時候,來計算出此Gray序列大小 String[] si = new String[p]; for(int j=0;j<p;j++){//循環根據某個Gray序列,來一個一個的求此序列 if(j<(p/2)){ si[j] = "0" + s1[j];//原始序列前面加上"0" }else{ si[j] = "1" + s1[p-j-1];//原始序列反序,前面加上"1" } } s1 = si;//把求得的si,附給s1,以便求下一個Gray序列 } return s1; }
3、測試
public static void main(String[] args) { System.out.println("————————————————————遞歸實現————————————————"); String[] strArr = GrayCode(4); for(int i = 0 ; i < strArr.length ; i++) { System.out.println(strArr[i]); } System.out.println("——————————————————非遞歸實現————————————————"); String[] strArr2 = GrayCode2(4); for(int i = 0 ; i < strArr2.length ; i++) { System.out.println(strArr2[i]); } }
4、結果:
————————————————————遞歸實現———————————————— 0000 0001 0011 0010 0110 0111 0101 0100 1100 1101 1111 1110 1010 1011 1001 1000 ——————————————————非遞歸實現———————————————— 0000 0001 0011 0010 0110 0111 0101 0100 1100 1101 1111 1110 1010 1011 1001 1000
致謝:感謝您的耐心閱讀!