`

Leetcode - Graph Valid Tree

 
阅读更多
Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.

For example:

Given n = 5 and edges = [[0, 1], [0, 2], [0, 3], [1, 4]], return true.

Given n = 5 and edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]], return false.

Show Hint
Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1] is the same as [1, 0] and thus will not appear together in edges.

[分析]
应用Union Find算法的好例子,对Union Find算法不熟悉,可参看
http://blog.csdn.net/dm_vincent/article/details/7655764
http://blog.csdn.net/dm_vincent/article/details/7769159
个人觉得该博文阐述非常通俗易懂,第二个链接是博主举的几个应用例子。
对这题依次实现了不同优化程度的union find算法,从运行时间可看出优化是很有效果的。

第二部分代码是利用教材中的UnionFind算法实现,实现中包含路径压缩和按大小以及按秩(rank)求并思想,且仅需要一个数组s[],初始时s[i] = -1, 在合并过程中根的s[]值是该根所对应树的size的相反数,而非根节点的s[]指向其父节点。每次find一个节点p会将p到根上的所有节点的父节点均置为根,从而高效压缩路径。利用unionFind求解该题时,需要稍修改union,union函数返回值改为布尔类型,union时,若发现两个节点已在同一颗树中,即root相同,说明该条边会使得树中产生一个cycle,返回false,说明输入并非valid tree。全部边顺利union结束还不能说明输入是valid tree,验证union后有且仅有一个根方能确认输入是valid tree。
优化的union可以按大小也可以按高度合并,因为路径压缩过程会改变树的高度,所以其与按高度求并并不完全兼容,书中提到目前并不清楚这种情况下如何有效重新计算高度,答案是不去计算,就让每棵树存储的高度是估计的高度(rank),理论上证明按秩求并和按大小求并效率是一样的,且高度的更新不如大小更新频繁。



public class Solution {
    // http://blog.csdn.net/dm_vincent/article/details/7655764
    int count = 0;
    int[] id = null;
    int[] size = null;
    public boolean validTree(int n, int[][] edges) {
        initUnionFind(n);
        for (int i = 0; i < edges.length; i++) {
            int p = edges[i][0], q = edges[i][1];
            if (find(p) == find(q))
                return false;
            union(p, q);
        }
        return count == 1 ? true : false;
    }
    public void initUnionFind(int n) {
        id = new int[n];
        size = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
            size[i] = 1;
        }
        count = n;
    }
    //version 4: weighted quick union with path compression, find & union, very close to O(1)
    public int find(int p) {
        while (p != id[p]) {
            id[p] = id[id[p]];
            p = id[p];
        }
        return p;
    }
    public void union(int p, int q) { //same with version 3
        int pRoot = find(p), qRoot = find(q);
        if (size[pRoot] < size[qRoot]) {
            id[pRoot] = qRoot;
            size[qRoot] += size[pRoot];
        } else {
            id[qRoot] = pRoot;
            size[pRoot] += size[qRoot];
        }
        count--;
    }
    //version 3: weighted quick union, find & union, O(logn)
    public int find3(int p) { // same with version 2
        while (p != id[p]) p = id[p];
        return p;
    }
    public void union3(int p, int q) {
        int pRoot = find(p), qRoot = find(q);
        if (size[pRoot] < size[qRoot]) {
            id[pRoot] = qRoot;
            size[qRoot] += size[pRoot];
        } else {
            id[qRoot] = pRoot;
            size[pRoot] += size[qRoot];
        }
        count--;
    }
    // version 2: quick union, find & union, O(tree height)
    public int find2(int p) {
        while (p != id[p]) p = id[p];
        return p;
    }
    public void union2(int p, int q) {
        int pRoot = find(p), qRoot = find(q);
        id[pRoot] = qRoot;
        count--;
    }
    // version 1: quick find, find O(1), union O(n)
    public int find1(int p) {
        return id[p];
    }
    public void union1(int p, int q) {
        int pId = find(p), qId = find(q);// 特别注意进入循环先保存原始值,循环过程id[p]会被更改
        for (int i = 0; i < id.length; i++) {
            if (id[i] == pId)
                id[i] = qId;
        }
        count--;
    }
}


public boolean validTree(int n, int[][] edges) {
        initUnionFind(n);
        for (int i = 0; i < edges.length; i++) {
            if (!union(edges[i][0], edges[i][1]))
                return false;
        }
        int count = 0;
        for (int i = 0; i < n; i++) {
            if (s[i] < 0) {
                count++;
            }
        }
        return count == 1;
    }
    private int[] s;
    private int count;
    public void initUnionFind(int n) {
        s = new int[n];
        for (int i = 0; i < n; i++)
            s[i] = -1;
    }
    public int find(int p) {
        if (s[p] < 0)
            return p;
        else {
            s[p] = find(s[p]);
            return s[p];
        }
    }
    // union by rank
    public boolean union(int p, int q) {
        int pRoot = find(p), qRoot = find(q);
        if (pRoot == qRoot)
            return false;
        if (s[pRoot] < s[qRoot]) {
            s[qRoot] = pRoot;
        } else {
            if (s[pRoot] == s[qRoot])
                s[qRoot]--;
            s[pRoot] = qRoot;
        }
        return true;
    }
    // union by size
    public boolean union1(int p, int q) {
        int pRoot = find(p), qRoot = find(q);
        if (pRoot == qRoot)
            return false;
        if (s[pRoot] < s[qRoot]) {
            s[pRoot] += s[qRoot];
            s[qRoot] = pRoot;
        } else {
            s[qRoot] += s[pRoot];
            s[pRoot] = qRoot;
        }
        return true;
    }
分享到:
评论
3 楼 likesky3 2015-09-21  
看了数据结构书得知并不是迭代和递归的区别,yb君的写法的效果是从p到根的路径上每个节点都将根设为父节点,而我的写法效果是从p到根路径上每隔一个节点重置爷爷节点为父节点,压缩力度没有书上标准写法大,当然标准写法会多些递归开销。
2 楼 likesky3 2015-09-03  
迭代和递归的区别吧~
1 楼 qb_2008 2015-09-02  
还有一种find写法:
int find(int p) {
  if (id[p] == p) {
    return p;
  }
  return id[p] = find(id[p]);
}

相关推荐

    算法-leetcode-剑指offer上的题很多

    - **二叉树(Binary Tree)**: 一种树形数据结构,每个节点最多有两个子节点,通常用于实现快速查找和排序操作。 - **霍夫曼压缩(Huffman Compression)**: 一种使用变长编码表对源符号进行编码的方法。 - **队列(Queue...

    最大公共字符串leetcode-lc:液晶显示器

    最大公共字符串leetcode leetcode 关键词: interview 最长回文子串硬币问题 ...Valid Tree (Leetcode Premium) - 无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetco

    lrucacheleetcode-leetcode:力码解决方案

    Graph Valid Tree (Leetcode Premium) - 无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表

    leetcodepremium-coding_practice:我自己对Leetcode和其他编码挑战问题的解决方案

    Valid Tree (Leetcode Premium) - 无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表 -

    leetcodepremium是什么-myProjects:个人iOS项目

    Graph Valid Tree (Leetcode Premium) - 无向图中的连通分量数 (Leetcode Premium) - 间隔 插入间隔 - 合并间隔 - 非重叠区间 - 会议室 (Leetcode Premium) - 会议室 II (Leetcode Premium) - 链表 反转链表 - 检测...

    LeetCode最全代码

    * [Tree](https://github.com/kamyu104/LeetCode#tree) * [Hash Table](https://github.com/kamyu104/LeetCode#hash-table) * [Data Structure](https://github.com/kamyu104/LeetCode#data-structure) * [Math]...

    leetcode分类-leetcode:leetcode

    主要分为七大类别:数组(Array)、二叉树(Binary Tree)、动态规划(Dynamic Programming)、回溯法(Backtracking)、字符串(String)、哈希表(Hash Table)以及图(Graph)。每个类别下都包含了众多的子问题,...

    算法刷题笔记leetcode/lintcode

    - BinaryTree(二叉树) - HuffmanCompression(霍夫曼编码) - Queue(队列) - Heap(堆) - Stack(栈) - Set(集合) - Map(映射/哈希表) - Graph(图) - **Basics Sorting**(基本排序算法) - ...

    Leetcode book刷题必备

    根据提供的文件信息,我们能提炼出一系列IT相关知识点,主要是围绕LeetCode这本电子书的主题——即编程面试题目解答。此电子书内容丰富,涵盖了算法和数据结构中常见的问题及其解决方案,非常适合准备技术面试的读者...

    gasstationleetcode-leetcode_java:为求职做准备。Leetcode的java版本

    Tree/Graph traverse: DFS,BFS,LFS 07/01/2015 Set up IntelliJ for Github Finish easy problems in Leetcode using java. 1.Roman to Integer 2.Binary Tree Level Order Travasal II 3.Binary Tree Level Order ...

    leetcode常见的100热点算法题

    5. **二叉树与图**:二叉树题目如"Binary Tree Inorder Traversal"(二叉树的中序遍历)和"Lowest Common Ancestor of a Binary Tree"(二叉树的最近公共祖先),图题目如"Shortest Path in Bidirectional Graph"...

    LeetCode:AC源代码

    "二叉树的最近公共祖先"(Lowest Common Ancestor of a Binary Tree)可以用递归或迭代的方法解决。 5. **图**:图问题可能涉及到深度优先搜索(DFS)、广度优先搜索(BFS)等。例如,"最短路径"(Shortest Path in...

    leetcode:leetcode练习

    - 树形结构:二叉树、平衡树等在LeetCode中也频繁出现,如“二叉树的前序遍历”(Binary Tree Preorder Traversal)和“有效的二叉搜索树”(Valid Binary Search Tree)。C++的`std::set`和`std::map`可以用于实现...

    LeetCode

    例如,"Binary Tree Preorder Traversal"可以通过递归来实现前序遍历,"N-Queens"问题则需要结合回溯算法找到所有可行的皇后放置方案。 三、排序与查找 排序算法是LeetCode中常见的考点,JavaScript提供了sort()...

    算法学习笔记

    - BinaryTree(二叉树):是由节点和指针组成的层级数据结构,每个节点最多有两个子节点,通常用于实现二叉搜索树等高效的数据结构。 - Huffman Compression(霍夫曼编码):一种用于无损数据压缩的算法,通过构建一...

    常见算法题答案及解析

    根据提供的文件内容,可以整理出以下IT知识点,针对LeetCode算法题目的解题方法、时间复杂度、空间复杂度的分析,以及算法相关基础知识点的总结。 一、数组/字符串 ***o Sum:需要查找数组中两个数相加等于特定值的...

Global site tag (gtag.js) - Google Analytics