1579. 保证图可完全遍历(并查集)

package com.heu.wsq.leetcode.bingchaji;

import java.util.ArrayList;
import java.util.List;

/**
 * 1579. 保证图可完全遍历
 * @author wsq
 * @date 2021/1/27
 * Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3  种类型的边:
 * 类型 1:只能由 Alice 遍历。
 * 类型 2:只能由 Bob 遍历。
 * 类型 3:Alice 和 Bob 都可以遍历。
 * 给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
 * 返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
 *
 *  
 * 示例1:
 * 输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,3],[1,2,4],[1,1,2],[2,3,4]]
 * 输出:2
 * 解释:如果删除 [1,1,2] 和 [1,1,3] 这两条边,Alice 和 Bob 仍然可以完全遍历这个图。再删除任何其他的边都无法保证图可以完全遍历。所以可以删除的最大边数是 2 。
 *
 * 链接:https://leetcode-cn.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable
 */
public class MaxNumEdgesToRemove {
    public int maxNumEdgesToRemove(int n, int[][] edges){
        // 新增两个虚拟结点alice和bob
        int ret = 0;
        int alice = 0;
        int bob = n + 1;
        // 构建并查集对象
        UnionFind unionFind = new UnionFind(2 * n + 2);
        // 保存是否alice和bob是否与当前边相连
        boolean aliceFlag = true;
        boolean bobFlag = true;
        // 单独处理公共边
        List<int[]> otherList = new ArrayList<>();
        for (int[] edge : edges) {
            if (edge[0] == 3){
                if (unionFind.isConnected(edge[1], edge[2])){
                    ret += 1;
                    continue;
                }
                unionFind.union(edge[1], edge[2]);
                unionFind.union(bob + edge[1], bob + edge[2]);
                if (aliceFlag && bobFlag){
                    unionFind.union(edge[1], alice);
                    unionFind.union(bob + edge[1], bob);
                    aliceFlag = false;
                    bobFlag = false;
                }
            }else{
                otherList.add(edge);
            }
        }
        for (int[] edge : otherList) {
            if (edge[0] == 1){
                boolean connected = unionFind.isConnected(edge[1], edge[2]);
                if (connected){
                    ret += 1;
                }else{
                    unionFind.union(edge[1], edge[2]);
                    if (aliceFlag){
                        unionFind.union(edge[1], alice);
                    }
                }
            }else{
                boolean connected = unionFind.isConnected(bob + edge[1], bob + edge[2]);
                if (connected){
                    ret += 1;
                }else{
                    unionFind.union(bob + edge[1], bob + edge[2]);
                    if (bobFlag){
                        unionFind.union(bob, bob + edge[1]);
                    }
                }
            }
        }
        return unionFind.getSize(alice) == n + 1 && unionFind.getSize(bob) == n + 1 ? ret : -1;
    }

    private class UnionFind{

        private int[] parent;
        private int[] size;

        public UnionFind(int n){
            this.parent = new int[n];
            this.size = new int[n];
            for (int i = 0; i < n; i++){
                this.parent[i] = i;
                this.size[i] = 1;
            }
        }

        public void union(int x, int y){
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY){
                return;
            }
            this.parent[rootX] = rootY;
            this.size[rootY] += this.size[rootX];
        }

        public int find(int x){
            if (x != this.parent[x]){
                this.parent[x] = find(this.parent[x]);
            }
            return this.parent[x];
        }

        public int getSize(int x){
            return this.size[find(x)];
        }

        public boolean isConnected(int x, int y){
            return find(x) == find(y);
        }
    }
}

<think>嗯,用户现在想要查找关于二叉树和并查集的知识点总结。让我先看看用户之前的对话历史。用户之前已经问过关于二叉树和并查集的问题,我给出了详细的解释和相关问题。现在用户再次提到需要知识点总结,并且引用了几个站内的资料,但看起来这些引用可能并不完全符合用户的实际需求,或者用户希望得到更系统的整理。 首先,我需要确认用户的需求到底是什么。用户提到“知识点总结”,可能希望有一个结构化的概述,涵盖基本概念、实现方法、时间复杂度、应用场景等。在之前的回答中,我已经分点讲解了二叉树和并查集,但现在用户可能需要更详细或更全面的内容,或者希望将两者分开总结以便复习或学习。 接下来,我要检查用户提供的引用内容。引用[1]主要关于二叉排序树的结构和操作,引用[2]是一个广泛的数据结构与算法学习指南,引用[3]涉及哈夫曼树和存储结构,引用[4]是数据结构的总体知识点汇总。用户可能希望我综合这些信息,但需要结合之前的回答,避免重复。 用户当前的问题明确提到了“二叉树 并查集 知识点总结”,所以需要将两者分开总结,每个部分的结构要清晰,可能包括定义、类型、操作、复杂度、应用等。同时,用户之前的问题中已经涉及了一些代码示例,可能需要再次呈现,但要注意格式正确,使用$...$和$$...$$来包裹数学表达式。 另外,用户可能是在备考或复习,需要高效地掌握重点内容,因此知识点总结应简明扼要,突出关键点。比如二叉树部分需要涵盖基本结构、类型(BST、AVL、堆等)、遍历方式、复杂度分析;并查集则需要包括基本操作、优化方法、时间复杂度以及典型应用。 还要注意用户提供的引用中提到的内容,比如引用[1]提到二叉排序树的时间复杂度是O(logn),而引用[3]提到了双亲表示法和孩子表示法,这可能与并查集的实现相关,但需要确认是否相关。并查集通常使用父指针数组或树结构,可能和双亲表示法类似,但用户可能需要更直接的并查集实现方法,如路径压缩和按秩合并。 最后,用户可能需要相关的问题来巩固知识点,所以在回答的最后生成相关问题,帮助用户自测或进一步学习。这些问题应覆盖二叉树和并查集的重要概念,如平衡二叉树的证明、并查集的优化策略、二叉搜索树的退化问题等。 总结一下,回答的结构应该是分两部分,二叉树和并查集,每个部分有清晰的子标题,使用适当的代码示例和数学公式,引用相关的引用资料,最后提供相关问题。同时,确保使用正确的中文表达,避免技术术语错误,保持逻辑连贯。</think>--- ### 二叉树与并查集知识点总结(结构化整理) --- #### 一、二叉树核心知识点 ##### 1. **基本定义与性质** - **结构特性**:每个节点最多有**两个子节点**(左子树、右子树)。 - **数学形式**: $$ \text{高度为} h \text{的二叉树最多有} 2^{h+1}-1 \text{个节点} $$ $$ \text{叶子节点数} = \text{度为2的节点数} + 1 $$ [^1] - **特殊类型**: - **二叉搜索树 (BST)**:左子树节点值均小于根节点,右子树节点值均大于根节点。 - **平衡二叉树 (AVL)**:任意节点左右子树高度差不超过1,保证操作时间复杂度为$O(\log n)$[^1]。 - **完全二叉树**:除最后一层外,其他层节点全满,最后一层从左向右填充。 - **堆**:完全二叉树,满足父节点值 ≥(或 ≤)子节点值。 ##### 2. **关键操作与复杂度** - **插入/删除/查找**: - **BST平均情况**:$O(\log n)$,最坏情况(退化为链表)$O(n)$。 - **AVL树**:通过旋转平衡,强制保持$O(\log n)$时间复杂度[^1]。 - **遍历算法**: - **前序/中序/后序**:递归或栈实现,时间复杂度$O(n)$。 - **层次遍历**:队列实现,时间复杂度$O(n)$。 ##### 3. **应用场景** - **数据库索引**:B/B+树(多叉平衡树)优化磁盘I/O[^2]。 - **优先队列**:堆结构用于快速获取最大/最小值(如Dijkstra算法)。 - **表达式解析**:语法树表示数学表达式。 --- #### 二、并查集核心知识点 ##### 1. **核心功能** - **动态连通性**:高效处理集合的**合并(Union)**与**查询(Find)**操作。 - **数学表示**: $$ \text{初始状态:} n \text{个独立集合,每个元素自成一树} $$ $$ \text{Union操作:合并两棵树,Find操作:追溯根节点} $$ ##### 2. **优化方法** - **路径压缩**(Find优化): ```python def find(x): if parent[x] != x: parent[x] = find(parent[x]) # 直接指向根节点 return parent[x] ``` - **按秩合并**(Union优化):总将小树合并到大树根节点,降低树高[^3]。 ##### 3. **时间复杂度** - **未优化**:最坏情况$O(n)$。 - **优化后**:接近$O(\alpha(n))$($\alpha$为反阿克曼函数,实际应用中≈常数)[^3]。 ##### 4. **应用场景** - **连通性问题**:社交网络好友关系、电路连接检测。 - **算法**:Kruskal最小生成树算法中动态管理连通分量[^3]。 --- ### 代码示例对比 #### 二叉树(BST插入) ```python class TreeNode: def __init__(self, val): self.val = val self.left = self.right = None def insert(root, val): if not root: return TreeNode(val) if val < root.val: root.left = insert(root.left, val) else: root.right = insert(root.right, val) return root ``` #### 并查集(路径压缩 + 按秩合并) ```c int find(int x, int parent[]) { if (parent[x] != x) parent[x] = find(parent[x], parent); // 路径压缩 return parent[x]; } void union(int x, int y, int parent[], int rank[]) { int rootX = find(x, parent); int rootY = find(y, parent); if (rootX == rootY) return; if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else { parent[rootX] = rootY; if (rank[rootX] == rank[rootY]) rank[rootY]++; } } ``` --- ### 常见问题总结 1. **二叉树退化问题**:BST在插入有序数据时退化为链表,需通过平衡树(如AVL、红黑树)避免。 2. **并查集时间复杂度证明**:基于摊还分析,路径压缩与按秩合并共同保证极低复杂度[^3]。 3. **堆与优先队列**:堆的插入/删除操作时间复杂度为$O(\log n)$,优于普通线性结构[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值