1. 程式人生 > 其它 >演算法刷題筆記 - 連線網路的操作次數(dfs, 並查集)

演算法刷題筆記 - 連線網路的操作次數(dfs, 並查集)

技術標籤:演算法刷題筆記資料結構演算法c++圖論

連線所有點的最小費用

題目描述

  • 用乙太網線纜將 n 臺計算機連線成一個網路,計算機的編號從 0 到 n-1。線纜用 connections 表示,其中 connections[i] = [a, b] 連線了計算機 a 和 b。

  • 網路中的任何一臺計算機都可以通過網路直接或者間接訪問同一個網路中其他任意一臺計算機。

  • 給你這個計算機網路的初始佈線 connections,你可以拔開任意兩臺直連計算機之間的線纜,並用它連線一對未直連的計算機。請你計算並返回使所有計算機都連通所需的最少操作次數。如果不可能,則返回 -1

    題述

解法一:DFS

解法構思:

  • 這個問題可以轉化成此計算機網路構成的圖中有幾個連通分量,利用冗餘的邊將所有的連通分量連起來就可以構成連通圖。
  • 連通圖的生成樹構成的子圖就是要求邊最少的能連通的子圖,所以只要保證此題中的線纜的個數不少於計算機數目 - 1即可。
  • 這樣若符合構成最少邊連通圖的條件此題的結果即連通分量數目減一(將M個連通子圖相連需要M-1條邊)。
  • 若本身邊的數目就不夠將圖重構成生成樹,即是此題的返回-1的情況。

上手程式設計:

class Solution {
public:
   vector<vector<int>> edges;  // 鄰接表
   vector<int> visited;  // 記錄是否被遍歷

   void dfs(int start) {  // DFS
       visited[start] = true;
       for (auto & e : edges[start]) {
           if (!visited[e])
               dfs(e);
       }
   }
   int makeConnected(int n, vector<vector<int>>& connections) {
       if (connections.size() < n -1) return -1;  // 邊數太少直接返回-1
       
       int res = 0;
       
       visited.resize(n);
       edges.resize(n);
       
       for (auto & e : connections) {  // 構建鄰接表
           edges[e[0]].push_back(e[1]);
           edges[e[1]].push_back(e[0]);
       }
       
       for (int i = 0; i < n; ++i) {  // 尋找連通分量,每找到一個計數加一
           if (!visited[i]) {
               dfs(i);
               ++res;
           }
       }
       return res - 1;  // 連通分量個數 - 1
   }
};

解法二:並查集

解法構思:

  • 並查集能夠快速判斷兩個元素是否在同一集合,能夠快速合併兩個集合。
  • 並查集本身就是一個樹(邊最少連通圖),我們可以利用並查集模擬網路最終建構的過程,我們保證將每個在同一連通分量的點都執行合併,最終得到的並查集對應的連通分量的組成肯定和網路對應的相同。
  • 過程:一開始使每一個頂點成為一個連通分量,之後利用並查集合併網絡中有邊相連的點。每成功合併一次,連通分量個數減一,最後就得到了網路的連通分量個數。

上手程式設計:

// 並查集
class UnionFindSet {
private:
   vector<int> m_parents;
   vector<int > m_ranks;
public:

   UnionFindSet(int n) : m_parents(n), m_ranks(n, 1) {
       for (int i = 0; i < n; ++i) {
           m_parents[i] = i;
       }
   }
   
   bool unite(int a, int b) {
       int x = find(a);
       int y = find(b);
       if (x == y) return false;
       if (m_ranks[x] < m_ranks[y]) {
           swap(x, y);
       }
       m_parents[y] = x;
       m_ranks[x] += m_ranks[y];
       return true;
   }
   
   int find(int a) {
       return m_parents[a] == a ? a : (m_parents[a] = find(m_parents[a]));
   }
   
   bool same(int a, int b) {
       return find(a) == find(b);
   }
};


class Solution {
public:

   int makeConnected(int n, vector<vector<int>>& connections) {
       if (connections.size() < n - 1) return -1;
       
       UnionFindSet s(n);
       
       int res = n;  // 一開始連通分量個數為n
       
       for (auto & e : connections) {
           if (s.unite(e[0], e[1])) --res; // 每成功合併就更新連通分量個數
       }
       
       return res - 1;
   }
};