貪心算法之覆蓋問題

一、監控相機佈局問題

1、問題背景

  爲保障師生的生命財產安全,學校計劃在校園內安裝視頻監控,確保每一個 角落都有相機覆蓋。爲簡化問題,可以考慮將三維空間離散化成一系列採樣點 (Sample),而監控相機只能安裝在有限的候選機位(Candidate)上,受限於鏡 頭分辨率和遮擋物,安裝在每一個候選機位上的相機只能覆蓋數量有限的採樣點。 監控相機佈局問題要求在每個採樣點都有相機覆蓋的情況下,最小化使用的相機 數量。

2、形式化描述

  進行離散化採樣之後,相機佈局問題可以抽象爲經典的集合覆蓋問題。每個 採樣點是一個元素(Element),每個候選機位是一個集合(Set)。
已知所有元素構成的集合爲𝐸 = {𝑒1,𝑒2,…,𝑒𝑛},所有集合的集合爲𝑆 = {𝑠1,𝑠2,…,𝑠𝑚},其中每個集合𝑠𝑖 ⊂𝐸。集合覆蓋問題要求選出一個子集𝑋⊂𝑆,使 得選出的集合的數量儘可能少(最小化|𝑋|),同時使得子集中所有集合的並集包 含所有元素(⋃𝑠𝑖∈𝑋 𝑠𝑖 = 𝐸)。
集合覆蓋問題的實例可以用二分圖表示。以如下二分圖爲例,字母 A、B、 C 爲集合(候選機位),數字 1、2、3、4、5 爲元素(採樣點)。無向邊表示覆蓋 關係,例如邊 A-1 表示集合 A 可以覆蓋元素 1。

在這裏插入圖片描述
  顯然,上圖所示的實例的最優解爲𝑋 = {𝐴, 𝐶},此時總共選出了兩個集合, 且所有元素均至少被一個集合覆蓋。𝑋 = {𝐴, 𝐵}爲不可行解,因爲元素 5 既不能 被 A 覆蓋也不能被 B 覆蓋。𝑋 = {𝐴, 𝐵, 𝐶}是一個可行解,但因爲總共選出了三個 集合,選中集合數量較多,故不是最優解。

3、輸入輸出格式

3.1、輸入數據格式

  每個算例文件格式如下。
  第一行有兩個數字,依次爲元素數 n 和集合數 m。
  接下來有 3*n 行,其中每 3 行一組,第一行爲元素編號 e,第二行爲能覆蓋
元素 e 的集合數,第三行爲能覆蓋元素 e 的集合編號列表。 下面給出了一個具體的算例(注意實際算例中沒有註釋)。
在這裏插入圖片描述

3.2、輸出結果格式

  第一行輸出選中的集合數,第二行輸出選中的集合編號列表。下面給出了一個具體的解文件(注意輸出的解文件不要包含註釋)。
在這裏插入圖片描述

4、重要提示

  集合覆蓋問題爲 NP 問題,與 ACM/ICPC、IOI 等競賽中的 P 問題不同,該 類問題往往難以在有限的時間內求得最優解。因此,建議給你的程序設置足夠長 的運行時間限制(建議不少於 5 分鐘),超時之後保存目前找到的最好結果。
推薦使用的算法(僅供參考):貪心構造、局部搜索、遺傳算法、蟻羣算法、 分支限界、強化學習等。

二、解決方法

貪心算法
核心策略:優先選擇監測點最多的攝像頭,監測點的數量是動態的
具體解釋
1、先按照攝像頭監控監測點數量由大到小排序(每個攝像頭之間都有交集,即某個監測點有可能被多個攝像頭監控)
2、把監控監控點數量最大的攝像頭取出
3、標記該攝像頭監控的監控點
4、把剩餘的攝像頭監控的監控點中被標記的監控點移除
5、回到第一步

三、具體實現(Java)

Camera.java(攝像頭實體)

public class Camera {
    // 攝像頭編號
    int number;
    // 該攝像頭邏輯監控地區個數
    int sum;
    public Camera(int number, int sum) {
        this.number = number;
        this.sum = sum;
    }
}

Coverage.java(核心方法)

import java.util.*;

/**
 * 貪心算法
 * 核心策略:優先選擇監測點最多的攝像頭,監測點的數量是動態的
 *
 * 設有n個監測點,編號由0~n-1
 * 設有m個攝像頭,編號又0~m-1
 * 1、移除沒有監控監測點的攝像頭
 * 2、對m個攝像頭按照該攝像頭監控監測點的數量由大到小排序(貪心策略)
 * 3、獲取監控監測點數量最多的攝像頭p
 * 4、獲取p攝像頭監控的監測點集合f
 * 5、對集合進行遍歷,同時在n數組中,標記這些監測點(需要而外創建一個n長度的bool型的數組)
 * 6、查找攝像頭集合,依次刪除某攝像頭中存在於f集合的監測點(這裏利用邏輯刪除,效率不會太低)
 * 接着到第2步
 */
public class Coverage {

	// 讀取文件的路徑
    static final String TXT_PATH = "";

    public void coverageProblem() {
        List<String> list = ControllerFile.readTxt(TXT_PATH);
        if(list == null) {
            return;
        }
        String[] ss = list.get(0).split(" ");
        int n = Integer.parseInt(ss[0]);
        int m = Integer.parseInt(ss[1]);
        // 地區集
        int[] regions = new int[n];
        // 每個攝像頭對應覆蓋地區的集合
        Map<Integer, List<Integer>> cameraRegionsMap= new HashMap<>();
        // 每個攝像頭對應覆蓋地區的個數
        Camera[] cameraRegionsSum = new Camera[m];
        // 該攝像頭是否已經被訪問過
        boolean[] cameraFlag = new boolean[m];
        // 已被監控的地區
        boolean[] regionFlag = new boolean[n];
        int lineNumber = 1;
        for(int i = 0; i < n; i++) {
            regions[i] = Integer.parseInt(list.get(lineNumber++));
            int k = Integer.parseInt(list.get(lineNumber++));
            // k地區覆蓋的攝像頭
            int[] cameras = new int[k];
            String[] cameraSs = list.get(lineNumber++).split(" ");
            for(int j = 0; j < k; j++) {
                cameras[j] = Integer.parseInt(cameraSs[j]);
                // 判斷該攝像頭是否被訪問過
                if(cameraFlag[cameras[j]]) {
                    // 如果被訪問過, 把覆蓋的地區放入集合中
                    List<Integer> tempList = cameraRegionsMap.get(cameras[j]);
                    tempList.add(regions[i]);
                } else {
                    // 未被訪問過
                    List<Integer> tempList = new ArrayList<>();
                    tempList.add(regions[i]);
                    cameraRegionsMap.put(cameras[j], tempList);
                    // 設置爲已訪問
                    cameraFlag[cameras[j]] = true;
                    cameraRegionsSum[cameras[j]] = new Camera(cameras[j], 0);
                }
                // 該攝像頭覆蓋地區數量+1
                cameraRegionsSum[cameras[j]].sum++;
            }
        }

        // 移除沒監控地區的攝像頭
        List<Camera> cameraList = new ArrayList<>();
        for(int i = 0; i < m; i++) {
            if(cameraRegionsSum[i] != null) {
                cameraList.add(cameraRegionsSum[i]);
            }
        }

        // 最後選中的攝像頭集合
        List<Camera> resultList = new ArrayList<>();
        int sum = 0;
        do {
            // 按照監控地區個數排序, 排序算法的不同會導致結果有偏差,最好使用穩定算法,例如歸併排序
            Comparator<Camera> mod = (o1, o2) -> {
                // 降序
                return o2.sum - o1.sum;
            };
            cameraList.sort(mod);
            resultList.add(cameraList.get(0));
            List<Integer> tempList = cameraRegionsMap.get(cameraList.get(0).number);
            for(int i : tempList) {
                if (!regionFlag[i]) {
                    regionFlag[i] = true;
                    sum++;
                }
            }
            for(Camera camera : cameraList) {
                if (camera.sum == 0) {
                    break;
                }
                List<Integer> tempList2 = cameraRegionsMap.get(camera.number);
                Iterator<Integer> it = tempList2.iterator();
                while(it.hasNext()) {
                    Integer t = it.next();
                    if (regionFlag[t]) {
                        // 如果該地區已經被其他攝像頭監控
                        it.remove();
                        camera.sum--;
                    }
                }
            }
        } while(sum != n);

        System.out.println(resultList.size());
        for(int i = 0; i < resultList.size(); i++) {
            if(i != resultList.size() - 1) {
                System.out.print(resultList.get(i).number + " ");
            } else {
                System.out.println(resultList.get(i).number);
            }
        }
    }
}

ControllerFile(文件操作類)

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ControllerFile {

    public static List<String> readTxt(String txtPath) {
        File file = new File(txtPath);
        if (file.isFile() && file.exists()) {
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
                InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                List<String> list = new ArrayList<>();
                String text = null;
                while ((text = bufferedReader.readLine()) != null) {
                    list.add(text);
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

Main(主類)

public class Main {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Coverage coverage = new Coverage();
        coverage.coverageProblem();
        long end = System.currentTimeMillis();
        // 打印所用時間
        System.out.println(end - start);
    }
}

三、具體實現(C++)

待實現

四、測試樣例

測試樣例很大,如果有需要可以郵箱私我[email protected]

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