2013年6月3日 星期一

圍棋算氣程式

Java code:
import java.util.Scanner;

public class Chi {
    static int[][] board = new int[19][19];
    static boolean[][] gone = new boolean[19][19];
    static boolean[][] isCount = new boolean[19][19];
    static int chessColor;
    static int chiNum = 0;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x, y;
        
        try {
            System.out.println("請輸入棋盤:");
            for (int i=0; i<board.length; i++) {
                for (int j=0; j<board[i].length; j++)
                    board[i][j] = sc.nextInt();
            }
            System.out.print("請輸入座標: ");
            x = sc.nextInt();
            y = sc.nextInt();
        } finally {
            sc.close();
        }
        if (board[x][y] == 0)
            System.out.println("這個座標沒有任何棋子");
        else {
            chessColor = board[x][y];
            countChi(x, y);
            if (chessColor == 1)
                System.out.println("您算的是黑子的氣");
            else
                System.out.println("您算的是白子的氣");
            System.out.println("共有" + chiNum + "個氣");
        }
    }

    private static void countChi(int x, int y) {
        if (board[x][y] == 0 && !isCount[x][y]) {
            chiNum++;
            isCount[x][y] = true;
        }
        else if (!gone[x][y] && board[x][y] == chessColor) {
            gone[x][y] = true;
            countChi(x - 1, y);
            countChi(x, y - 1);
            countChi(x, y + 1);
            countChi(x + 1, y);
            gone[x][y] = false;
        }
    }
}

範例輸入輸出:



這是老師出的額外題目,題目的要求是輸入一個棋盤,並且指定一個座標,並算出指定座標棋子所在區域的氣。此程式會自動辨識您指定的座標是黑子區域還是白子區域,若您指定黑子就會為您算出黑子的氣;若您指定白子就會為您算出白子的氣,若您指定的地方沒有任何棋子,程式會告訴您這地方沒有任何棋子。此程式的作法就是不斷地搜尋這區域周圍的氣(也就是沒有任何棋子的地方),若已經搜尋過得地方用gone這個布林陣列把它記起來,以避免無限的搜尋,若找到氣了,就把氣數+1然後用布林陣列記住這個地方已經算過氣了,避免未來再重複算一次。
此程式的輸入為一個19 * 19的棋盤,0代表沒有棋子, 1代表黑棋, 2代表白棋,完成後程式會要求您輸入一個座標,格式為(x y),x和y的範圍皆為0~18。輸出為你算的是什麼棋子的氣,以及總共是多少氣。

0-1 Knapsack problem

Java code:
import java.util.LinkedList;
import java.util.List;

class Item {
    int price;
    int weight;
    int pricePerWeight;
    
    public Item(int price, int weight, int pricePerWeight) {
        this.price = price;
        this.weight = weight;
        this.pricePerWeight = pricePerWeight;
    }
}

public class KnapsackProblem {
    static Item[] allItem =
        {new Item(20, 2, 10), new Item(30, 5, 6), new Item(35, 7, 5),
         new Item(12, 3, 4), new Item(3, 1, 3)};
    static int limitWeight = 9;
    static int nowPrice = 0;
    static int nowWeight = 0;
    static int maxPrice = Integer.MIN_VALUE;
    static List<Integer> takeItems = new LinkedList<Integer>();
    static List<Integer> maxPriceTakeItems = new LinkedList<Integer>();
    
    public static void main(String[] args) {
        stealItem(0);
        System.out.print("要拿的物品: ");
        for (int index: maxPriceTakeItems)
            System.out.print(index + 1 + " ");
        System.out.println("\n最大價值: " + maxPrice);
    }

    private static void stealItem(int itemIndex) {
        if (nowWeight <= limitWeight) {
            int boundPrice;
            
            if (nowPrice > maxPrice) {
                maxPrice = nowPrice;
                maxPriceTakeItems.clear();
                for (int index: takeItems)
                    maxPriceTakeItems.add(index);
            }
            boundPrice = calBound(itemIndex);
            if (boundPrice > nowPrice && maxPrice < boundPrice) {
                for (int i=itemIndex; i<allItem.length; i++) {
                    nowPrice += allItem[itemIndex].price;
                    nowWeight += allItem[itemIndex].weight;
                    takeItems.add(itemIndex);
                    stealItem(i + 1);
                    nowPrice -= allItem[itemIndex].price;
                    nowWeight -= allItem[itemIndex].weight;
                    takeItems.remove(takeItems.size() - 1);
                }
            }    
        }
    }

    private static int calBound(int itemIndex) {
        int thePrice = nowPrice;
        int theWeight = nowWeight;
        int i;
        
        for (i=itemIndex; i<allItem.length && theWeight<=limitWeight; i++) {
            thePrice += allItem[i].price;
            theWeight += allItem[i].weight;
        }
        i--;
        thePrice -= allItem[i].price;
        theWeight -= allItem[i].weight;
        return thePrice + allItem[i].pricePerWeight * (limitWeight - theWeight);
    }
}

範例輸入輸出:



0-1背包問題就是解決在物品沒有辦法被分割且背包負重有限的情況下能帶走物品的最大價值。程式裡的類別Item就是代表物品,裡面的field有price(價值), weight(重量), 還有pricePerWeight(單位重量的價值),allItem陣列裡面放的是所有的物品(我偷偷地把它們依照單位重量的價值排序好了XD)。程式的作法就是不斷地拿物品和放物品來找到最大價值,重點是有幾種情況就可以知道物品不要再往下拿了。第一種情況就是總重量已經超過背包的負重,第二種情況就是未來可以拿到的最大價值(利用單位重量的價值計算)等於現在的價值,第三種情況就是未來可以拿到的最大價值比現在已經拿到的最大價值還要少。
因為此程式是解決課本第5章的第33題,所以輸入參數已經是寫死的,輸出為最後拿的物品和最大總價值。

Hamilton Circuit

Java code:
import java.util.LinkedList;
import java.util.List;

public class HamiltonCircuit {
    static boolean[][] graph =
        {{false, true, false, false, true, false, false, false, false, false, false, false},
         {true, false, true, false, false, false, true, true, false, false, false, false},
         {false, true, false, true, false, false, false, true, false, false, false, false},
         {false, false, true, false, false, false, false, false, true, false, false, false},
         {true, false, false, false, false, true, false, false, false, true, false, false},
         {false, false, false, false, true, false, true, false, false, false, true, false},
         {false, true, false, false, false, true, false, true, false, false, false, false},
         {false, true, true, false, false , false, true, false, true, false, false, false},
         {false, false, false, true, false, false, false, true, false, false, false, true},
         {false, false, false, false, true, false, false, false, false, false, true, false},
         {false, false, false, false, false, true, false, false, false, true, false, true},
         {false, false, false, false, false, false, false, false, true, false, true, false}};
    static boolean[] isGone = new boolean[graph.length];
    static List<Integer> path = new LinkedList<Integer>();
    
    public static void main(String[] args) {
        for (int i=0; i<graph.length; i++)
            findCircuit(i);
    }

    private static void findCircuit(int vertex) {
        if (path.size() == graph.length) {
            if (graph[vertex][path.get(0)]) {
                for (int v: path)
                    System.out.print(v + 1 + " ");
                System.out.print((path.get(0) + 1) + "\n\n");
            }
        }
        else {
            isGone[vertex] = true;
            path.add(vertex);
            for (int i=0; i<graph.length; i++) {
                if (graph[vertex][i] && !isGone[i])
                    findCircuit(i);
            }
            isGone[vertex] = false;
            path.remove(path.size() - 1);
        }
    }
}

範例輸入輸出:



Hamilton Circuit 是要在圖上找一個點當作起點,接著把圖上所有的點走訪一遍後回到原點。程式的作法是把所有點都當作起點看看,然後想辦法把每個點都先走過一遍,最後再看看終點能不能回到起點。
因為此程式是解決課本第5章的第26題,所以輸入參數已經寫死在程式裡,之所以沒有範例輸入輸出,是因為這題找不到Hamilton Circuit(如果我的程式沒有錯的話)。

Coloring Problem

Java code:
public class ColoringProblem {
    private enum Color {
        RED, GREEN, WHITE
    }
    static boolean[][] graph =
        {{false, true, false, true, false, false},
         {true, false, true, false, true, false},
         {false, true, false, false, false, true},
         {true, false, false, false, true, false},
         {false, true, false, true, false, true},
         {false, false, true, false, true ,false}};
    static Color[] graphColor = new Color[graph.length];
    static int countComb = 0;
    
    public static void main(String[] args) {
        paintColor(0);
        System.out.println("總共有" + countComb + "種塗色方法");
    }

    private static void paintColor(int vertex) {
        if (vertex < graph.length) {
            for (Color c: Color.values()) {
                boolean isUsed = false;
                
                for (int i=0; i<graph.length; i++) {
                    if (graph[vertex][i] && graphColor[i] == c) {
                        isUsed = true;
                        break;
                    }
                }
                if (!isUsed) {
                    graphColor[vertex] = c;
                    paintColor(vertex + 1);
                    graphColor[vertex] = null;
                }
            }
        }
        else {
            for (int i=0; i<graphColor.length; i++)
                System.out.printf("%-6s", "v" + (i + 1));
            System.out.println();
            for (Color c: graphColor) {
                switch (c) {
                    case RED:
                        System.out.printf("%-6s", "red");
                        break;
                    case GREEN:
                        System.out.printf("%-6s", "green");
                        break;
                    case WHITE:
                        System.out.printf("%-6s", "white");
                }
            }
            System.out.print("\n\n");
            countComb++;
        }
    }
}

範例輸入輸出:



此程式解決的是塗色問題,也就是在一個graph上面,用m個顏色去著色,並且相鄰的點不可以塗上相同的顏色。程式的作法是先選一個點並且從第一個顏色開始嘗試,塗上顏色後在選擇下一個點塗色。塗色時需要檢查相鄰的點有沒有使用一樣的顏色,若有使用一樣的顏色,就必須換色,若沒有顏色可以塗,就必須更改上一個點的顏色。若所有點都成功上色了,就表示找到一個塗色方法了,依照這個方法不斷做下去,就可以找出所有塗色的方法了。
因為此程式是解決課本第5章第18題的問題,所以輸入參數已經寫死在程式裡,輸出為所有的塗色方法和塗色方法的總數目。因為輸出結果過長,所以以上只貼出部份輸出結果。

Sum-of-Subsets

Java code:
import java.util.LinkedList;
import java.util.List;

public class SumOfSubsets {
    static int[] nums = {2, 10, 13, 17, 22, 42};
    static List<Integer> ansList = new LinkedList<Integer>();
    
    public static void main(String[] args) {
        int totalWeight = 52;
        
        System.out.println("以下是所有的解:");
        findAns(totalWeight, 0);
    }

    private static void findAns(int restWeight, int startIndex) {
        if (restWeight > 0) {
            for (int i=startIndex; i<nums.length; i++) {
                ansList.add(nums[i]);
                findAns(restWeight - nums[i], i + 1);
                ansList.remove(ansList.size() - 1);
            }    
        }
        else if (restWeight == 0) {
            for (int i: ansList)
                System.out.print(i + " ");
            System.out.println();
        }
    }
}

範例輸入輸出:



此程式解決的問題是Sum-of-Subsets問題,問題是給一個數字集合S還有一個數字N,找出S裡面所有加起來總和會等於N的組合。解法就是窮舉所有的加法組合看看那一組有符合上述條件。
此程式是解決課本第5章第13題的問題,所以參數已經設死在程式裡。輸出結果就是所有總和為52的組合。

N-Queen

Java code:
import java.util.Scanner;

public class N_Queen {
    static int n;
    static int countQueen = 0;
    static int ansCount = 0;
    static boolean[] colHasQueen, leftSlashHasQueen, rightSlashHasQueen;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        try {
            System.out.print("請輸入n: ");
            n = sc.nextInt();
        } finally {
            sc.close();
        }
        colHasQueen = new boolean[n];
        leftSlashHasQueen = new boolean[2 * n - 1];
        rightSlashHasQueen = new boolean[2 * n - 1];
        findQueen(0, 0);
        System.out.println("總共有" + ansCount + "組解");
    }

    private static void findQueen(int row, int col) {
        if (countQueen == n)
            ansCount++;
        else if (col < n) {
            if (!colHasQueen[col] && !leftSlashHasQueen[row - col + n - 1] &&
                    !rightSlashHasQueen[row + col]) {
                colHasQueen[col] = true;
                leftSlashHasQueen[row - col + n - 1] = true;
                rightSlashHasQueen[row + col] = true;
                countQueen++;
                findQueen(row + 1, 0);
                colHasQueen[col] = false;
                leftSlashHasQueen[row - col + n - 1] = false;
                rightSlashHasQueen[row + col] = false;
                countQueen--;
            }
            findQueen(row, col + 1);
        }
    }
}

範例輸入輸出:



此程式解決的問題是經典的n-queen問題,三個布林陣列存的分別是行,  左斜線, 右斜線有沒有被擺過皇后,之所以沒有存列有無擺過皇后是因為這列擺過皇后之後,直接換下一列,所以沒有會碰到同一列的可能。若放置此位置後無法找到皇后共存的組合,則此皇后會往右移一格,若皇后移到此列的盡頭都沒有位置可以擺放則直接回到上一列更改皇后的位置,因為是在n*n的棋盤上擺放n個皇后,所以不可能有任何一列沒有被擺到皇后。
輸入為一個整數n,輸出則為總共有多少種擺放的方式,並沒有去除掉對稱的情況。