「程式碼隨想錄演算法訓練營」第四十六天 | 圖論 part4

云雀AC了一整天發表於2024-08-26

目錄
  • 110. 字串接龍
  • 105. 有向圖的完全可達性
    • DFS
    • BFS
  • 106. 島嶼的周長
    • 解法一
    • 解法二

110. 字串接龍

題目連結:https://kamacoder.com/problempage.php?pid=1183
文章講解:https://programmercarl.com/kamacoder/0110.字串接龍.html
題目狀態:看題解

思路:

  1. 輸入部分:

    • 讀取單詞數量 ( n )。
    • 讀取起始單詞 beginStr 和目標單詞 endStr
    • 讀取並儲存單詞集合 strSet
  2. 輔助資料結構:

    • visitMap: 記錄每個單詞是否被訪問過,以及路徑長度。
    • queue<string> que: 用於廣度優先搜尋(BFS)的佇列。
  3. 演算法流程:

    • 將起始單詞 beginStr 放入佇列,並在 visitMap 中標記路徑長度為 1。
    • 使用 BFS 遍歷單詞:
      • 取出佇列的前端單詞 word,獲取其路徑長度 path
      • word 的每個字元進行替換,生成新單詞 newWord
      • 檢查 newWord 是否為目標單詞 endStr,如果是,輸出路徑長度並結束程式。
      • 如果 newWord 在單詞集合中且未被訪問過,將其加入佇列並記錄路徑長度。
  4. 結束條件:

    • 如果佇列為空且未找到目標單詞,輸出 0。

程式碼:

#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <queue>
using namespace std;

int main() {
    string beginStr, endStr, str;
    int n;
    cin >> n;
    unordered_set<string> strSet;
    cin >> beginStr >> endStr;
    for(int i = 0; i < n; ++i) {
        cin >> str;
        strSet.insert(str);
    }

    // 記錄strSet裡的字串是否被訪問過,同時記錄路徑長度
    unordered_map<string, int> visitMap; // <記錄的字串, 路徑長度>

    // 初始化佇列
    queue<string> que;
    que.push(beginStr);

    // 初始化visitMap
    visitMap.insert(pair<string, int>(beginStr, 1));

    while(!que.empty()) {
        string word = que.front();
        que.pop();
        int path = visitMap[word]; // 這個字串在路徑中的長度

        // 開始在這個str中,挨個字元去替換
        for(int i = 0; i < word.size(); ++i) {
            string newWord = word; // 用一個新字串替換str,因為每次要置換一個字元

            // 遍歷26個字母
            for(int j = 0; j < 26; ++j) {
                newWord[i] = j + 'a';
                if(newWord == endStr) { // 發現替換字母后,字串與重點字串相同
                    cout << path + 1 << endl; // 找到了路徑
                    return 0;
                }
                // 字串集合裡出現了newWord,並且newWord沒有被訪問過
                if(strSet.find(newWord) != strSet.end() && visitMap.find(newWord) == visitMap.end()) {
                    // 新增訪問資訊,並將新字串放到佇列中
                    visitMap.insert(pair<string, int>(newWord, path + 1));
                    que.push(newWord);
                }
            }
        }
    }
    // 沒找到輸出0
    cout << 0 << endl;
}

105. 有向圖的完全可達性

題目連結:https://kamacoder.com/problempage.php?pid=1177
文章講解:https://programmercarl.com/kamacoder/0105.有向圖的完全可達性.html
題目狀態:看題解

DFS

思路:

  1. 輸入部分:

    • 讀取節點數 ( n ) 和邊數 ( m )。
    • 讀取每條邊的起點 ( s ) 和終點 ( t ),構建鄰接表 graph
  2. DFS 函式:

    • 引數:
      • graph: 圖的鄰接表表示。
      • key: 當前訪問的節點。
      • visited: 標記節點是否被訪問過的布林陣列。
    • 邏輯:
      • 如果當前節點 key 已訪問,直接返回。
      • 標記 key 為已訪問。
      • 對於 key 的所有鄰接節點,遞迴呼叫 dfs
  3. 主函式:

    • 初始化圖的鄰接表 graph
    • 初始化 visited 陣列,大小為 ( n+1 )(因為節點編號從 1 開始)。
    • 從節點 1 開始呼叫 dfs
    • 檢查所有節點是否被訪問:
      • 如果有未訪問的節點,輸出 -1
      • 如果所有節點都訪問過,輸出 1

程式碼:

// dfs 處理當前訪問的節點
#include <iostream>
#include <vector>
#include <list>
using namespace std;

void dfs(const vector<list<int>>& graph, int key, vector<bool>& visited) {
    if (visited[key]) {
        return;
    }
    visited[key] = true;
    list<int> keys = graph[key];
    for (int key : keys) {
        // 深度優先搜尋遍歷
        dfs(graph, key, visited);
    }
}

int main() {
    int n, m, s, t;
    cin >> n >> m;

    // 節點編號從1到n,所以申請 n+1 這麼大的陣列
    vector<list<int>> graph(n + 1); // 鄰接表
    while (m--) {
        cin >> s >> t;
        // 使用鄰接表 ,表示 s -> t 是相連的
        graph[s].push_back(t);
    }
    vector<bool> visited(n + 1, false);
    dfs(graph, 1, visited);
    //檢查是否都訪問到了
    for (int i = 1; i <= n; i++) {
        if (visited[i] == false) {
            cout << -1 << endl;
            return 0;
        }
    }
    cout << 1 << endl;
}

BFS

思路:

  1. 輸入部分:

    • 讀取節點數 ( n ) 和邊數 ( m )。
    • 讀取每條邊的起點 ( s ) 和終點 ( t ),構建鄰接表 graph
  2. BFS 初始化:

    • 建立一個布林陣列 visited 用於標記節點是否被訪問。
    • 從節點 1 開始,標記為已訪問,並將其加入佇列 que
  3. BFS 過程:

    • 當佇列不為空時,執行以下步驟:
      • 取出佇列的前端節點 key
      • 遍歷 key 的所有鄰接節點:
        • 如果鄰接節點未被訪問,將其加入佇列並標記為已訪問。
  4. 訪問檢查:

    • 遍歷 visited 陣列,檢查所有節點是否被訪問。
    • 如果有未訪問的節點,輸出 -1
    • 如果所有節點都訪問過,輸出 1

程式碼:

#include <iostream>
#include <vector>
#include <list>
#include <queue>
using namespace std;

int main() {
    int n, m, s, t;
    cin >> n >> m;

    vector<list<int>> graph(n + 1);
    while (m--) {
        cin >> s >> t;
        graph[s].push_back(t);

    }
    vector<bool> visited(n + 1, false);
    visited[1] = true; //  1 號房間開始
    queue<int> que;
    que.push(1); //  1 號房間開始

    // 廣度優先搜尋的過程
    while (!que.empty()) {
        int key = que.front(); que.pop();
         list<int> keys = graph[key];
         for (int key : keys) {
             if (!visited[key]) {
                 que.push(key);
                 visited[key] = true;
             }
         }
    }

    for (int i = 1; i <= n; i++) {
        if (visited[i] == false) {
            cout << -1 << endl;
            return 0;
        }
    }
    cout << 1 << endl;
}

106. 島嶼的周長

題目連結:https://kamacoder.com/problempage.php?pid=1178
文章講解:https://programmercarl.com/kamacoder/0106.島嶼的周長.html
題目狀態:看題解

解法一

思路:

遍歷每一個格,遇到島嶼則計算其上下左右的空格情況。

遍歷整個網格:
如果當前格子是陸地(grid[i][j] == 1),則檢查其四周。
使用方向陣列計算相鄰格子的座標(x,y)。
如果相鄰格子超出邊界或者是水域(grid[x][y] == 0),則周長增加 1。

程式碼:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    int direction[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) {
                for (int k = 0; k < 4; k++) {       // 上下左右四個方向
                    int x = i + direction[k][0];
                    int y = j + direction[k][1];    // 計算周邊座標x,y
                    if (x < 0                       // x在邊界上
                            || x >= grid.size()     // x在邊界上
                            || y < 0                // y在邊界上
                            || y >= grid[0].size()  // y在邊界上
                            || grid[x][y] == 0) {   // x,y位置是水域
                        result++;
                    }
                }
            }
        }
    }
    cout << result << endl;
}

解法二

思路:

計算周長公式。

遍歷網格計算總陸地個數。之後計算其周長:

  • 每個陸地格子初始有 4 條邊。
  • 每對相鄰陸地共享 2 條邊。
  • 因此,周長計算為:sum * 4 - cover * 2。

程式碼:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    int sum = 0;    // 陸地數量
    int cover = 0;  // 相鄰數量
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) {
                sum++; // 統計總的陸地數量
                // 統計上邊相鄰陸地
                if(i - 1 >= 0 && grid[i - 1][j] == 1) cover++;
                // 統計左邊相鄰陸地
                if(j - 1 >= 0 && grid[i][j - 1] == 1) cover++;
                // 為什麼沒統計下邊和右邊? 因為避免重複計算
            }
        }
    }
    cout << sum * 4 - cover * 2 << endl;
}

相關文章