圖論+回溯解QQ一筆畫紅包

[春節整活]

QQ的一筆畫紅包有幾個特性:

1.最大為5×5的點陣,所以可以把每個點從左到右,從上到下標為1-25號點

2.每兩個點只能存在一條線

3.線可以被蓋住(例如連接2-1-3,2-1的線會被後來的1-3的連接線蓋住),對肉眼觀察很不利,但是對代碼來說沒有影響

 

解題思路:

1.對於線較多的點陣,可以使用鄰接矩陣來畫無向無權圖(線較少可以使用鄰接表),由於最大只有25個點,不必要考慮內存開銷,所以直接使用鄰接矩陣了

2.若某個節點所連接的線數為奇數,即為起點/終點,為偶數即為經過的點。若所有節點所連接的線數都為偶數,即首尾相連,任意點都可為起點/終點

3.使用回溯算法,找出將全部線只走一遍的方案,即為點陣的解

 

鄰接矩陣代碼如下:

/**
 * 鄰接矩陣
 */
public class DenseGraph {
    // 節點數
    private int n;
    // 邊數
    private int m;
    // 是否為有向圖
    private boolean directed;
    // 圖的具體數據
    private boolean[][] g;
    //記錄節點的線數量
    private int[] line;
    //已連接數量
    private int connected;
    //換行專用
    private int lineFeed;
    
    // 構造函數
    public DenseGraph(){
        n = 26;
        m = 0;
        directed = false;
        // g初始化為n*n的布爾矩陣, 每一個g[i][j]均為false, 表示沒有任和線
        g = new boolean[n][n];
        line=new int[n];
    }
    
    //初始化點陣
    private void initialization(){
        //臨時變量,保存每個節點連接節點的數量,以判斷起點
        int count=0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (g[i][j]) {
                    count++;
                }
            }
            line[i]=count;
            count=0;
        }
    }

    // 返回節點個數
    public int V(){ return n;}
    // 返回邊的個數
    public int E(){ return m;}

    // 向圖中添加一個邊
    public void addEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        if( hasEdge( v , w ) )
            return;
        g[v][w] = true;
        if( !directed )
            g[w][v] = true;
        m ++;
    }

    // 驗證圖中是否有從v到w的邊
    boolean hasEdge( int v , int w ){
        assert v >= 0 && v < n ;
        assert w >= 0 && w < n ;
        return g[v][w];
    }
    
    //開始運行
    public void start(){
        //初始化
        this.initialization();
        //輸出鄰接矩陣
        this.print();
        //尋找起點
        int qsd=-1;
        for (int i = 0; i < line.length; i++) {
            if (line[i]%2!=0){
                qsd=i;
                break;
            }else if (line[i]!=0){
                qsd=i;
            }
        }
        //從起點開始回溯尋找路線
        flashBack(qsd);
    }

    public boolean flashBack(int a){
        //如果已經走過的線數量等於總線數量,說明尋路完成
        if (connected==m){
            System.out.print("路線:["+a+"] -> ");
            return true;
        }else {
            //遍歷此點與全部節點的關係並按照以下執行
            //1.如果兩點之間有連接,假設此路線為正確路線,將此線改變為無連接,並開始從此點遍歷
            //2.如果最終無法走過全部線,則確定此路線不正確,回溯並將此線還原為連接,繼續遍歷
            for (int i = 0; i < n; i++) {
                if (g[a][i]){
                    g[a][i]=false;
                    g[i][a]=false;
                    connected++;
                    if (flashBack(i)){
                        lineFeed++;
                        if (lineFeed%20==0)
                            System.out.println();
                        System.out.print("["+a+"] -> ");
                        return true;
                    }else {
                        connected--;
                        g[a][i]=true;
                        g[i][a]=true;
                    }
                }
            }
            return false;
        }
    }

    //輸出鄰接矩陣
    public void print(){
        System.out.println("邊共:"+m+"條");
        System.out.print("鄰接矩陣 ");
        for (int i = 1; i < n; i++) {
            System.out.print(i+"   \t");
        }
        System.out.println();
        for (int i = 1; i < n; i++) {
            System.out.print(i+" \t");
            for (int j = 1; j < n; j++) {
                System.out.print(g[i][j]+" \t");
            }
            System.out.println();
        }
    }
}

使用方法:

創建對象(new)

添加邊(addEdge)

開始運行(start)

 

對於線非常多的圖,一條一條添加線依然很麻煩,有沒有更好的辦法呢?