Skip to content

Latest commit

 

History

History
223 lines (178 loc) · 7.81 KB

File metadata and controls

223 lines (178 loc) · 7.81 KB

Graph

理论基础

  • 深度优先搜索 (Depth-First Search)
  • 广度优先搜索 (Breadth-First Search)

💡 二叉树的递归遍历其实就是 DFS,而迭代遍历就是 BFS。

DFS 代码框架

void dfs(参数) {
  if (终止条件) {
    存放结果;
    return;
  }

  for (选择:本节点所连接的其他节点) {
    处理节点;
    dfs(图,选择的节点); // 递归
    回溯,撤销处理结果
  }
}

并查集 (Disjoint-Set)

并查集常用来解决连通性问题。需要判断两个元素是否在同一个集合里的时候,就可以用并查集。 两个主要功能:

  1. 将两个元素添加到一个集合中;
  2. 判断两个元素在不在同一个集合。

并查集理论基础

将三个元素 A, B, C(分别是数字)放在同一个集合,其实就是将三个元素连通在一起。

只需要用一个一维数组表示,即:father[A] = B, father[B] = C。 这样就表示 A 与 B 与 C 连通了(有向连通图)。

// 将v,u 这条边加入并查集
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

// 并查集里寻根的过程
int find(int u) {
    if (u == father[u])
        return u; // 如果根就是自己,直接返回
    else
        return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
}

要表示 C 也在同一个集合里,需要 father[C] = C,即 C 的根也为 C,这样就表示 A, B, C 都在同一个集合里了。

// 并查集初始化,默认指向自己
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

路径压缩

find 搜索过程像是在一个“多叉树”中从叶子反向搜索到根结点。 如果这棵多叉树高度很深的话,find 的寻跟过程会递归很多次。

由于查找的最终目的是只要知道这些结点在同一个根下就可以,所以这棵“多叉树”只需要构造成高度为 2。 即所有叶子结点指向根结点。

father 数组构造成“多叉树”的过程就叫路径压缩。

// 并查集里寻根的过程
int find(int u) {
    if (u == father[u])
        return u;
    else
        return father[u] = find(father[u]); // 路径压缩
}

并查集代码模板

int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0);

// 并查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
    }
}

// 并查集里寻根的过程
int find(int u) {
    if (u == father[u])
        return u;
    else
        return father[u] = find(father[u]); // 路径压缩
}

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 将v->u 这条边加入并查集
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根

    if (u == v)
        return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回

    father[v] = u;
}

拓展:按秩 (rank) 合并路径压缩

rank 表示树的高度,即树中结点层次的最大值。

当有两个集合,即两个多叉树需要合并时,将高度小的树合并入高度大的树,使合成后的树高度最小。

int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
vector<int> rank = vector<int> (n, 1); // 初始每棵树的高度都为1

// 并查集初始化
void init() {
    for (int i = 0; i < n; ++i) {
        father[i] = i;
        rank[i] = 1; // 也可以不写
    }
}
// 并查集里寻根的过程
int find(int u) {
    return u == father[u] ? u : find(father[u]);// 注意这里不做路径压缩
}

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
}

// 将v->u 这条边加入并查集
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根

    if (rank[u] <= rank[v]) father[u] = v; // rank小的树合入到rank大的树
    else father[v] = u;

    // 如果两棵树高度相同,则 v 的高度 +1
    // if (rank[u] <= rank[v]) father[u] = v;
    if (rank[u] == rank[v] && u != v) rank[v]++;
}

路径压缩复杂度

  • 空间复杂度: O(n),申请一个 father 数组。
  • 时间复杂度: 路径压缩后的并查集时间复杂度在 O(logn)O(1) 之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于 O(1)

在第一次查询的时候,相当于 n 叉树上从叶子节点到根节点的查询过程,时间复杂度是 logn。 但路径压缩后,后面的查询操作都是 O(1),而 join 函数和 isSame 函数里涉及的查询操作也是一样的过程。

References