中國象棋將帥問題:
在一把象棋的殘局中,象棋雙方的將帥不能夠相見,即不能夠在中間沒有其餘棋子的狀況下在同一列出現。而將、帥各被限制在己方的3*3的格子中運動。相信你們都很是熟悉象棋的玩法吧,這裏就不詳細說明遊戲規則了。 用A、B表明將和帥,請寫出一個程序,輸出A、B全部合法的位置。要求在代碼中只能用一個變量。
分析與解法:
這個問題的解法並不複雜。
遍歷A的全部位置
遍歷B的全部位置
若是A的位置和B的位置在同一列
輸出結果
不然 繼續尋找
地圖能夠用0-8表示A或B可能的9個位置
0------1------2
3------4------5
6------7------8
關鍵問題在於只使用一個變量來表示A和B的位置。因此可使用位運算來解決。一個無符號字符類型的長度是1字節,也就是8位,8位能夠表示2^8=256個值,對於A、B的9個位置來講足夠。能夠用前4位來表示A的位置狀況,後4位表示B的位置狀況。而4位能夠表示16個數也足夠表示A、B的位置狀況了。
經過位運算能夠對A、B的位置進行讀取和修改。
幾種基本的位運算:
(1)& 按位與運算
(2)| 按位或運算 "與"和"或"就不用說了吧
(3)^ 按位異或運算 相同爲假,不一樣爲真
(4)~ 按位取反 一元運算符
(5)<< 按位左移 如 0000 0111 << 2 = 0001 1100,將此數左移兩位至關於將此數擴大兩倍。
(6)>> 按位右移 如 0001 1000 >> 2 = 0000 0110,將此數右移兩位至關於將此數縮小兩倍。
令LMASK爲1111 0000,另任意一個1字節的字符型變量與其作與運算,結果右移四位,即可獲得此變量的高四位的值。
Example,
0110 1011
&1111 0000
= 0110 0000 >> 4 = 0000 0110
同理,令RMASK爲0000 1111,便可獲得它低四位的值。
Ex.
0110 1011
& 0000 1111
= 0000 1011
設置1字節字符型變量,好比對高四位進行設置,先將變量與RMASK相與,將要修改的變量左移四位後於前一結果進行「異或」或「或運算」。
Ex.將0110 1011高四位設置爲1001.
0110 1011
& 0000 1111
= 0000 1011 0000 1001 << 4 = 1001 0000
^ 1001 0000
= 1001 1011
一樣的方法設置低四位的值。
代碼:
- #include<stdio.h>
- #define HALF_BITS_LENGTH 4 //一半字節的長度
- #define FULLMASK 255 //即1111 1111
- #define LMASK (FULLMASK << HALF_BITS_LENGTH) //即1111 0000
- #define RMASK (FULLMASK >> HALF_BITS_LENGTH) //即0000 1111
- #define RSET(b, n) (b = (LMASK & b) ^ n) //設置低四位
- #define LSET(b, n) (b = (RMASK & b) ^ (n << HALF_BITS_LENGTH)) //設置高四位
- #define RGET(b) (RMASK & b) //獲得低四位
- #define LGET(b) ((LMASK & b) >> HALF_BITS_LENGTH) //獲得高四位
- #define GRIDW 3 //將帥活動範圍尺寸
- #define BYTE unsigned char
- int main(void)
- {
- BYTE b; //只有一個變量
- for(LSET(b, 1); LGET(b) <= GRIDW * GRIDW; LSET(b, (LGET(b) + 1)))//從A的1位置一直找到9位置,注意自增的寫法
- for(RSET(b, 1); RGET(b) <= GRIDW * GRIDW; RSET(b, (RGET(b) + 1)))//從B的1位置找到9位置
- if((LGET(b) % GRIDW) != (RGET(b) % GRIDW)) //若是不在同一列,則打印結果
- printf("A = %d, B = %d\n", LGET(b), RGET(b));
- return 0;
- }
這是個關於如何利用位運算解決問題的一個簡單的運用,能夠看到位運算合理地利用一個變量解決象棋將帥問題。算法自己很簡單,重點是位運算的應用。
<BOP>上還有兩個更簡潔的算法:
第一個:
- #include<stdio.h>
- #define BYTE unsigned char
- int main(void)
- {
- BYTE i = 81;
- while(i--)
- {
- if((i / 9) % 3 == (i % 9) % 3)
- continue;
- printf("A = %d, B = %d\n", i /9 + 1, i%9 + 1);
- }
- return 0;
- }
能夠把變量i想象成一個兩位九進制的變量,而i在計算機中存儲的值是i的十進制表示。則i/9的計算機處理結果,即結果直接去掉小數點後部分的結果便是此九進制數的第二位,而i%9便是此九進制數的個位。本程序用此九進制數的第二位保存A的位置,個位表示B的位置。最大值爲88,即爲十進制的80.程序從十進制的80,即九進制的88遍歷到十進制的0,即九進制的0.將符合條件的位置所有輸出。
第二個:
- #include<stdio.h>
- int main(void)
- {
- struct
- {
- unsigned char a:4;
- unsigned char b:4;
- }i;
- for(i.a = 1; i.a <= 9; i.a++)
- for(i.b = 1; i.b <= 9; i.b++)
- if(i.a % 3 != i.b % 3)
- printf("A = %d, B = %d\n", i.a, i.b);
- return 0;
- }
算法與上面的一模一樣。
其中unsigned char a:4表示結構體中a的位域只有4位,高位用做它用。只能在結構體裏使用,建議儘可能少用,會破壞程序的移植性。
當結構體中的元素的取值範圍很小時,能夠將幾個字段按位合成一個字段來表示,起到節省內存空間的做用。
Ex:
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- unsigned char a:4;
- unsigned char b:4;
- }i;
- i.a = 15;
- i.b = 10;
- printf("%d\n", sizeof(i));
- }
將上面例子中的變量i的大小輸出,結果爲1字節。說明i.a和i.b各佔4位。
結構體是C語言中的一種經常使用的自定義數據結構。
看下面的例子:
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- int a;
- char b;
- }i;
- printf("%d\n",sizeof(i));
- }
按理說結構體變量i的大小應該是sizeof(int)+sizeof(char),即5,而輸出顯示的結果爲8。再看一個例子:
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- int a;
- char b,c;
- }i;
- printf("%d\n",sizeof(i));
- }
應該是6對吧?結果仍是8.這是爲何呢?
這是由於在32位的操做系統上,操做系統組織數據是以32位(4個字節)做爲一個標準,所以各類變量的size都通常都是4的倍數。並且結構體數據都是按照定義時所使用的順序存放的,所以在第一個例子中儘管b變量只會佔有一個字節,可是a + b = 5 > 4,所以第一個4個字節存放a,第二個4個字節用於存放b,這樣實際上就浪費了3個字節。在第二個例子中第二個4個字節用來存放b和c。
因此,在結構體中要注意結構體中的變量定義的順序,不一樣的順序可能會形成佔用空間的不一樣。這在嵌入式程序設計等系統資源比較少的狀況下尤其重要。好比以下兩種結構體:
- #include<stdio.h>
- struct m
- {
- char a;
- int b;
- char c;
- }x;
- struct n
- {
- char a;
- char c;
- int b;
- }y;
- int main(void)
- {
- printf("m:%d\nn:%d\n", sizeof(x), sizeof(y));
- return 0;
- }
對於結構體m來講,x變量的大小爲12,而y變量的大小爲8.編譯器是按程序員在結構體中聲明變量的順序處理的。不當的順序會形成空間的浪費。讀者能夠想到發生這樣狀況的緣由的。因此建議聲明結構體時,按照不一樣變量的類型,按佔用空間的大小升序或降序聲明會取得較好的空間佔用。