有段时间没有接触并查集了,但是看了一下代码还是能够快速的看懂,整理一下。
    并查集中最主要的两个方法就是连接两个集合(unionElements),前提是两个集合不是同一个集合(isConnected)也就是集合的根节点(find)不相同。

    1. public interface UF {
    2. int getSize();
    3. boolean isConnected(int p, int q);
    4. void unionElements(int p, int q);
    5. int find(int p);
    6. }
    • implementation1

    所有的节点都指向根节点,维护一个数组,数组元素值代表节点的根节点编号。合并两个集合就是把一个集合所有的节点都指向另外一个集合的根节点。
    image.png

    1. public class UnionFind_v1 implements UF{
    2. private int[] id;
    3. public UnionFind_v1(int size) {
    4. id = new int[size];
    5. for(int i = 0; i < id.length; i++) {
    6. id[i] = i;
    7. }
    8. }
    9. @Override
    10. public int getSize() {
    11. return id.length;
    12. }
    13. /**
    14. * 查看两个元素是否在同一个集合
    15. * @param p
    16. * @param q
    17. * @return
    18. */
    19. @Override
    20. public boolean isConnected(int p, int q) {
    21. return find(p) == find(q);
    22. }
    23. /**
    24. * 合并两个元素
    25. * 时间复杂度为 O(n)
    26. * @param p
    27. * @param q
    28. */
    29. @Override
    30. public void unionElements(int p, int q) {
    31. int pID = find(p);
    32. int qID = find(q);
    33. if(pID == qID)
    34. return;
    35. for(int i = 0;i < id.length; i++) {
    36. if(id[i] == pID)
    37. id[i] = qID;
    38. }
    39. }
    40. /**
    41. * 查找元素p所对应的集合编号
    42. * 时间复杂度为 O(1)
    43. * @param p
    44. * @return
    45. */
    46. private int find(int p) {
    47. return id[p];
    48. }
    49. }
    • implementation2

    与上述实现不同,连接两个集合不再把某个集合的所有节点都连接到另一个集合的根节点,而是直接把两个集合的根节点直接相连。数组维护的是父节点不再是根节点,父节点不一定是根节点。
    image.png

    1. public class UnionFind_v2 implements UF {
    2. private int[] parent;
    3. public UnionFind_v2(int size) {
    4. parent = new int[size];
    5. for(int i = 0; i < size; i++) {
    6. parent[i] = i;
    7. }
    8. }
    9. @Override
    10. public int getSize() {
    11. return parent.length;
    12. }
    13. @Override
    14. public boolean isConnected(int p, int q) {
    15. return find(p) == find(q);
    16. }
    17. /**
    18. * 合并元素
    19. * 时间复杂度为O(H)
    20. * @param p
    21. * @param q
    22. */
    23. @Override
    24. public void unionElements(int p, int q) {
    25. int pRoot = find(p);
    26. int qRoot = find(q);
    27. if(pRoot == qRoot)
    28. return;
    29. parent[pRoot] = qRoot;
    30. }
    31. /**
    32. * 查找p元素的根节点
    33. * 时间复杂度是O(H)
    34. * @param p
    35. * @return
    36. */
    37. private int find(int p) {
    38. while(p != parent[p])
    39. p = parent[p];
    40. return p;
    41. }
    42. }

    当然还有些优化,比如找到根节点时候进行路径压缩,或者直接用递归。

    1. private int find(int p) {
    2. while(p != parent[p]) {
    3. parent[p] = parent[parent[p]];//路径压缩
    4. p = parent[p];
    5. }
    6. return p;
    7. }

    如果需要所有节点都指向根节点,也可以在find方法里递归调用。

    1. private int find(int p) {
    2. if(p != parent[p]) {//递归保证所有节点都指向根节点
    3. parent[p] = find(parent[p]);
    4. }
    5. return parent[p];
    6. }

    除此之外,我们还可以维护集合树的高度和节点树等。。。例题等下碰到比较经典的再write down。