拼圖可解的充要條件


拼圖問題又叫N數碼問題。這個問題比較簡單,基本上有一個人研究透徹之后就再也沒有研究價值了。

2010年《計算機應用軟件》上發表的一篇論文《N數碼問題直接解與優化問題研究》對N數碼問題的可解性和直接解法進行了透徹的研究。

此[repo](https://github.com/weiyinfu/pintu)提供了一個拼圖自動求解算法(非最優解)。

 

一.拼圖問題定義

給定一個m行n列的平面方格圖(m!=1&&n!=1),只有一個空位,其余每個方格內為1~(m*n-1)的數字.可以將空格與其上下左右相鄰方格內的卡片交換位置.目標就是從左到右,從上到下依次排成從1到(m*n-1)的陣列,空位在最后一格內.

二.定義:拼圖某狀態的逆序數

從左到右,從上到下,各個格點內的數字形成一個序列,這個序列的逆序數就是當前狀態的逆序數.對於任意一個拼圖,目標狀態的逆序數一定是0,因為肯定是1,2,3....這樣排列的.

三.操作對拼圖逆序數的影響

對於一個狀態,可以將空格與其上下左右4個位置的卡片交換位置.左右交換不影響狀態的逆序數,這是顯然易見的.

上下交換,相當於多次交換.當列數為奇數,上下交換相當於交換偶數次,奇偶性不變;當列數為偶數,上下交換相當於交換奇數次,奇偶性變化.

例如,狀態[1,2,3;4,_,6;5,7,8]的逆序列為12346578.將空格與空格下方的7交換位置,變成12347658,相當於先是7與5換,然后再跟6換,交換了偶數次,逆序數不變.

所以,操作是否影響奇偶性取決於列數的奇偶性.

四.空格狀態的奇偶性

如果空格所在行與目標行的行距為偶數,則稱空格狀態為偶數性;若為奇數,則稱空格狀態為奇數性.

五.拼圖問題可解的充要條件

知道目標狀態,知道操作過程,就足以攻克一切問題.

操作與奇偶性的關系有兩種:左右交換始終不影響奇偶性.(1)列數為奇數,上下交換不影響奇偶性;(2)列數為偶數,上下交換影響奇偶性.

關鍵在於找到操作中的守恆量,雖然每一個操作都會產生下一個狀態,但是這個過程中有守恆量:

如果列數為奇數,狀態逆序數的奇偶性守恆.

如果列數為偶數,狀態逆序數的奇偶性^空位狀態的奇偶性守恆.其中^表示異或運算.

於是結論是,當列數為奇數時,一切操作不影響奇偶性,當前狀態逆序數為偶數 等價於 拼圖有解.

當列數為偶數時,上下交換影響奇偶性,只要當前狀態逆序數奇偶性^當前空格狀態的奇偶性=偶數 等價於 拼圖有解.其中^符號表示異或運算.

一言以蔽之,拼圖有解定理就是:當前狀態守恆量的值為偶數.

六.證明:拼圖有解=>當前狀態守恆量的值為偶數

對於列數為奇數的拼圖,操作中滿足狀態逆序數奇偶性不變,所以只有當前狀態與目標狀態奇偶性一致才有可能有解.

對於列數為偶數的拼圖,操作中滿足狀態逆序數奇偶性^當前空格狀態奇偶性不變,所以只有當前狀態的逆序數奇偶性^當前空格狀態奇偶性與目標狀態一致才有可能有解.

這個問題蘊含的道理十分豐富:

(1)分析變化的事物要找到變化中的守恆量.

(2)要注重開頭和結尾,不要在意中間的過程.

七.證明:拼圖守恆量的值與目標狀態相同=>拼圖有解

把拼圖分成四個部分:左上角的m-2行n-2列、下面的2行n-2列、右面的m-2行2列、右下角的2行2列,這四部分分別記作A、B、C、D。完成順序為A、B、C、D,逐塊拼成。

A部分很容易拼成,不必贅言。

B、C兩部分同構,只需要討論其中一個。

D部分不用說了,2行2列太簡單了。

下面重點討論B部分。

第一步,先處理好1位置;第二步,把1上面的鄰居挪到4位置;第三步,把空格挪到5。這三步都是輕而易舉可以完成的。

至此就可以應用一個固定的“公式”。讓1迎接4位置回家。

上述證明的思想就是,構造幾個操作,某些區塊它們能夠不影響別人,而把自己調整成正確的狀態.

上面是以行少列多為例,對於行多列少的情況顯然也成立.

八.關於拼圖問題的其他結論

(1)將空格移動到右下角后拼圖狀態逆序數奇偶性為偶數<=>拼圖有解.

(2)交換任意兩個非空格塊(可以不相鄰),有解的會變成無解,無解的會變成有解.

(3)將空格移動到右下角后,若有偶數對方塊正好顛倒,問題有解;若有奇數對方塊顛倒,問題無解.

(4)拼圖的狀態構成一張圖,邊就是操作.拼圖的結點有兩種(有解和無解),有解的結點必然能夠到達目標結點,目標結點也能到達它們,所以有解結點集是連通的,無解結點集其實也是連通的,此圖有兩個連通分量.但不知道如何證明.

九.應用

生成拼圖問題時,關鍵是要保證拼圖有解.一種方法是先生成目標狀態,一番隨機操作打亂之.這種方法在拼圖行數列數較小時比較適用,一旦拼圖規模變大,隨機操作的次數不夠就容易生成很簡單的拼圖.

另一種方法就是利用拼圖有解的充要條件.隨機生成拼圖序列,如3*3的拼圖隨機生成為312450678,其中0表示空位.然后判斷它是否有解,如果無解交換兩個非空方格內的數字,如果有解,就更好了.這種方法對拼圖的打亂強度比較大,很容易生成雜亂無章的拼圖.

十.以2行4列拼圖為例檢驗一下結論

//一個2行4列的拼圖,檢驗是否規律成立
public class Main {
    public static void main(String[] args) {
        new Main();
    }
    int a[];
    int fac[] = new int[9];
    void init() {
        fac[0] = 1;
        for (int i = 1; i < 9; i++)
            fac[i] = fac[i - 1] * i;
        a = new int[fac[8]];
        for (int i = 0; i < a.length; i++)
            a[i] = -1;
    }
    //將一個狀態數值解析成數組,使用全排列散列
    int[] toArray(int x) {
        int ans[] = new int[8];
        boolean used[] = new boolean[8];
        for (int i = 0; i < 8; i++) {
            int ind = x / fac[7 - i];
            int k;
            for (k = 0; k < 8; k++) {
                if (used[k] == false) {
                    ind--;
                    if (ind < 0)
                        break;
                }
            }
            ans[i] = k;
            used[k] = true;
            x %= fac[7 - i];
        }
        return ans;
    }
    //將狀態數組用全排列散列映射為一個數字
    int fromArray(int[] a) {
        int ans = 0;
        boolean used[] = new boolean[8];
        for (int i = 0; i < 8; i++) {
            int cnt = 0;
            for (int k = 0; k < a[i]; k++) {
                if (used[k] == false)
                    cnt++;
            }
            used[a[i]] = true;
            ans += cnt * fac[7 - i];
        }
        return ans;
    }
    // 獲取一個狀態的逆序數,統計后面比我小的個數,這等價於統計后面比我大的個數
    int getReverse(int[] a) {
        int ans = 0;
        for (int i = 0; i < a.length; i++) {
            if (a[i] == 0)
                continue;
            for (int j = i + 1; j < a.length; j++) {
                if (a[j] != 0 && a[j] < a[i])
                    ans ^= 1;
            }
        }
        return ans;
    }
    // 獲取一個狀態的逆序數
    int getReverse(int x) {
        int[] a = toArray(x);
        return getReverse(a);
    }
    // 交換,x處為空位,y處為數字
    void swap(int[] a, int x, int y) {
        a[x] = a[y];
        a[y] = 0;
    }
    public Main() {
        init();
        int start = fromArray(new int[]{1, 2, 3, 4, 5, 6, 7, 0});
        Queue<Integer> q = new LinkedList<>();
        q.add(start);
        a[start] = start;
        while (!q.isEmpty()) {
            int now = q.poll();
            //在狀態轉換中,如果能不把它拆成數組直接產生子狀態效率更高,但實現要麻煩
            int[] ar = toArray(now);
            int i;
            for (i = 0; i < ar.length; i++) {
                if (ar[i] == 0)
                    break;
            }
            //與其上面的交換位置
            if (i - 4 >= 0) {
                swap(ar, i, i - 4);
                int s = fromArray(ar);
                if (a[s] == -1) {
                    a[s] = now;
                    q.add(s);
                }
                swap(ar, i - 4, i);
            }
            //與下面的交換位置
            if (i + 4 < 8) {
                swap(ar, i, i + 4);
                int s = fromArray(ar);
                if (a[s] == -1) {
                    a[s] = now;
                    q.add(s);
                }
                swap(ar, i + 4, i);
            }
            //與左面交換位置
            if (i % 4 != 3) {
                swap(ar, i, i + 1);
                int s = fromArray(ar);
                if (a[s] == -1) {
                    a[s] = now;
                    q.add(s);
                }
                swap(ar, i + 1, i);
            }
            //與右面交換位置
            if (i % 4 != 0) {
                swap(ar, i, i - 1);
                int s = fromArray(ar);
                if (a[s] == -1) {
                    a[s] = now;
                    q.add(s);
                }
                swap(ar, i - 1, i);
            }
        }
        for (int i = 0; i < a.length; i++) {
            int[] ar = toArray(i);
            if (getReverse(ar) + pos(ar) == 1 && a[i] == -1) {
                System.out.println(i);
            }
        }
    }
    // 空位所在的行號奇偶性
    int pos(int[] a) {
        for (int i = 0; i < 4; i++)
            if (a[i] == 0)
                return 0;
        return 1;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM