連續郵資問題-回溯法

回溯法的設計思想:

回溯法從根節點出發,按照深度優先策略遍歷解空間樹,搜索滿足約束條件的解。在搜索至樹中任一節點時,先判斷該節點對應的部分解是否滿足約束條件I,是否超出目標函數的界,也就是判斷該節點是否包含問題的最優解,如果肯定不包含,則跳過對以該節點爲根的子樹的搜索(即所謂剪枝),返回上一層的節點,從其他子節點尋找通向最優解的路徑;否則,進入以該節點爲根的子樹,繼續按照深度優先策略搜索。
回溯法的搜索過程涉及的節點稱爲搜索空間,只是整個解空間樹的一部分,在搜索過程中,通常採用兩種策略避免無效搜索:

  • 用約束條件剪去得不到可行解的子樹
  • 用目標函數剪去得不到最優解的子樹
    這兩類函數統稱剪枝函數。

需要注意的是:
問題的解空間樹是虛擬的,並不需要在算法運行時構造一棵真正的樹結構,只需要存儲從根節點到當前節點的路徑。

連續郵資問題

問題描述:

假設某國家發行了n種不同面值的郵票,並且規定每張信封上最多隻允許貼m張郵票。連續郵資問題要求對於給定的n和m,給出郵票面值的最佳設計,在1張信封上貼出從郵資1開始,增量爲1的最大連續郵資區間。 例如當n=5,m=4時,面值爲1,3,11,15,32的5種郵票可以貼出郵資的最大連續區間是1到70。

輸入樣例:

5 4
1 3 11 15 32

輸出樣例:

70

算法思路:

參考:https://www.jianshu.com/p/313d1e98c0d7

每張信封最多貼m張郵票,也就是說可能貼了m張,也可能貼了0張、1張等等。爲了便於計算,我們可以把未貼滿m張郵票看作貼了x張郵票和m-x張面值爲0的郵票,從而構造一棵完全多叉樹。若求所有可能的組合情況,解空間是(n+1)^m。
以n=5,m=4爲例,解空間爲完全多叉樹,圖中只以一條路徑爲例:
在這裏插入圖片描述

實際求解需要對其進行剪枝:解的約束條件是必須滿足當前解的郵資可以構成增量爲1的連續空間,所以在搜索至樹中任一節點時,先判斷該節點對應的部分解是否滿足約束條件,也就是判斷該節點是否包含問題的最優解,如果肯定不包含,則跳過對以該節點爲根的子樹的搜索,返回上一層的節點,從其他子節點尋找通向最優解的路徑;否則,進入以該節點爲根的子樹,繼續按照深度優先策略搜索。
求解過程:

  1. 讀入郵票面值數n,每張信封最多貼的郵票數m
  2. 讀入郵票面值數組nums[],除了正常面值,再加入一個值爲0的面值
  3. 循環求取區間最大值maxValue,maxValue初始設爲0
    ① 從0張郵票開始搜索解空間,如果當前未達到葉節點,且過程值temp=temp+nums[i]未達到當前記錄的區間最大值maxValue,則繼續向下搜索
    ② 若超過了區間最大值maxValue,則當前面值不是可行解,計算下一個面值nums[i+1]。若循環結束,當前節點的所有面值都無法滿足,則說明再往下搜索也不可能有可行解,這個時候回溯到上一節點
    ③ 若當前已搜索到葉節點,判斷當前路徑下的解temp是否滿足比當前記錄的區間最大值maxValue大1。若滿足,則更新區間最大值maxValue;若不滿足,回溯到上一節點
  4. 重複步驟3直到沒有滿足當前區間最大值maxValue+1的可行解,則當前記錄的maxValue就是區間最大值

算法實現:

public class Main {

    // n表示郵票面值數,m表示每張信封最多貼的郵票數
    private static int n;
    private static int m;

    // 郵票面值數組
    private static int[] nums;
    // 當前解是否可行
    private static boolean accFlag;
    // 記錄區間最大值
    private static int maxValue = 0;
    // 搜索過程中的郵資
    private static int temp = 0;

    public static void main(String[] args) {
        // 讀取鍵盤輸入
        Scanner sc = new Scanner(System.in);
        while (true) {
            n = sc.nextInt();
            m = sc.nextInt();
            // 多一個是假設有面值爲0的郵票
            nums = new int[n + 1];
            nums[0] = 0;
            for (int i = 1; i < n + 1; i++) {
                nums[i] = sc.nextInt();
            }
            solution(n, m, nums);
            System.out.println("maxValue=" + maxValue);
        }
    }

    public static void solution(int n, int m, int[] nums) {
        // 求解區間最大值,一直搜索,直到確定最大值
        while (true) {
            accFlag = false;
            // 每次都從0張郵票開始搜索解空間
            // 當前求解的目標是判斷是否存在比當前區間最大值大1的解
            search(0);
            // 若存在
            if (accFlag) {
                // 連續區間增量加1
                maxValue++;
            } else {
                break;
            }
        }
    }

    /**
     * 搜索解空間,找到比當前區間最大值大1的解
     *
     * @param t 當前郵票數量
     */
    public static void search(int t) {
        // 葉節點,結束搜索
        if (t == m) {
            // 當前解可以將連續區間最大值加1
            if (temp == maxValue + 1) {
                accFlag = true;
            }
            // 此時已經是葉節點,若不是可行解,則需要回溯到t-1
            return;
        }
        // 未結束,繼續向下搜索
        // 遍歷所有面值
        for (int i = 0; i < nums.length; i++) {
            temp += nums[i];
            // 如果當前未達到葉節點,且過程值未達到當前記錄的區間最大值,則繼續向下搜索
            if (temp <= maxValue + 1) {
                search(t + 1);
            }
            // 若超過了區間最大值,則當前面值不是可行解,需要回溯,計算下一個面值
            temp -= nums[i];
        }
    }
}

運行結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章