并查集

我们之前遇到的树结构都是由父亲指向孩子,但是并查集不一样,它是由孩子指向父亲的一种结构,并查集结构可以非常高效的回答连接问题(Connectivity Problem),它可以很快的判断网络中节点的连接状态。并查集主要支持两个动作

  • union(p, q)
    • 将元素p, q连接起来
  • isConnected(p, q)
    • 判断元素p, q是否是连接的,即是否所属一个集合

这里先给出并查集的接口,后面我们将实现多个版本的并查集

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

Quick Find

并查集 - 图1

  1. //第一版的并查集
  2. public class UnionFind1 implements UF{
  3. private int[] id;
  4. public UnionFind1(int size) {
  5. id = new int[size];
  6. //这时id全部为0,相当于在一个集合中 一开始应该全部不在一个集合中
  7. for (int i = 0; i < id.length; i++) {
  8. id[i] = i;
  9. }
  10. }
  11. @Override
  12. public int getSize() {
  13. return id.length;
  14. }
  15. //找到元素p所属的集合
  16. private int find(int p) {
  17. if (p < 0 || p >= id.length) {
  18. throw new IllegalArgumentException("参数错误");
  19. }
  20. return id[p];
  21. }
  22. @Override
  23. public boolean isConnected(int p, int q) {
  24. return find(p) == find(q);
  25. }
  26. @Override
  27. public void unionElements(int p, int q) {
  28. if (find(p) == find(q)) {
  29. return;
  30. }
  31. for (int i = 0; i < id.length; i++) {
  32. if (id[i] == find(p)) {
  33. id[i] = find(q);
  34. }
  35. }
  36. }
  37. }

Quick Union

并查集 - 图2

  1. //第二版的并查集
  2. public class UnionFind2 implements UF{
  3. private int[] parent;
  4. public UnionFind2(int size) {
  5. parent = new int[size];
  6. for (int i = 0; i < parent.length; i++) {
  7. parent[i] = i;
  8. }
  9. }
  10. @Override
  11. public int getSize() {
  12. return parent.length;
  13. }
  14. private int find(int index) {
  15. if (index < 0 || index >= parent.length) {
  16. throw new IllegalArgumentException("参数错误");
  17. }
  18. while (index != parent[index]) {
  19. index = parent[index];
  20. }
  21. return index;
  22. }
  23. @Override
  24. public boolean isConnected(int p, int q) {
  25. return find(p) == find(q);
  26. }
  27. @Override
  28. public void unionElements(int p, int q) {
  29. int pRoot = find(p);
  30. int qRoot = find(q);
  31. if (pRoot == qRoot) {
  32. return;
  33. } else {
  34. parent[pRoot] = parent[qRoot];
  35. }
  36. }
  37. }

基于rank的优化

并查集 - 图3

  1. //第三版的并查集
  2. public class UnionFind3 implements UF{
  3. private int[] parent;
  4. //记录根节点的高度
  5. private int[] rank;
  6. public UnionFind3(int size) {
  7. parent = new int[size];
  8. rank = new int[size];
  9. for (int i = 0; i < parent.length; i++) {
  10. parent[i] = i;
  11. rank[i] = 1;
  12. }
  13. }
  14. @Override
  15. public int getSize() {
  16. return parent.length;
  17. }
  18. private int find(int index) {
  19. if (index < 0 || index >= parent.length) {
  20. throw new IllegalArgumentException("参数错误");
  21. }
  22. while (index != parent[index]) {
  23. index = parent[index];
  24. }
  25. return index;
  26. }
  27. @Override
  28. public boolean isConnected(int p, int q) {
  29. return find(p) == find(q);
  30. }
  31. @Override
  32. public void unionElements(int p, int q) {
  33. int pRoot = find(p);
  34. int qRoot = find(q);
  35. if (pRoot == qRoot) {
  36. return;
  37. }
  38. if (rank[pRoot] <= rank[qRoot]){
  39. parent[pRoot] = parent[qRoot];
  40. //只要在两个数的高度相等的时候 树的高度才会增加
  41. if (rank[pRoot] == rank[qRoot]) {
  42. rank[qRoot]++;
  43. }
  44. } else {
  45. parent[qRoot] = parent[pRoot];
  46. }
  47. }
  48. }

路径压缩

并查集 - 图4

  1. //第四版的并查集
  2. public class UnionFind4 implements UF{
  3. private int[] parent;
  4. private int[] rank;
  5. public UnionFind4(int size) {
  6. parent = new int[size];
  7. rank = new int[size];
  8. for (int i = 0; i < parent.length; i++) {
  9. parent[i] = i;
  10. rank[i] = 1;
  11. }
  12. }
  13. @Override
  14. public int getSize() {
  15. return parent.length;
  16. }
  17. private int find(int index) {
  18. if (index < 0 || index >= parent.length) {
  19. throw new IllegalArgumentException("参数错误");
  20. }
  21. while (index != parent[index]) {
  22. //只添加了这一行代码
  23. parent[index] = parent[parent[index]];
  24. index = parent[index];
  25. }
  26. return index;
  27. }
  28. @Override
  29. public boolean isConnected(int p, int q) {
  30. return find(p) == find(q);
  31. }
  32. @Override
  33. public void unionElements(int p, int q) {
  34. int pRoot = find(p);
  35. int qRoot = find(q);
  36. if (pRoot == qRoot) {
  37. return;
  38. }
  39. //这时rank不代表高度 因为在路径压缩时没有维护rank
  40. //但是整体上rank还是能够表示大小关系的
  41. if (rank[pRoot] <= rank[qRoot]){
  42. parent[pRoot] = parent[qRoot];
  43. if (rank[pRoot] == rank[qRoot]) {
  44. rank[qRoot]++;
  45. }
  46. } else {
  47. parent[qRoot] = parent[pRoot];
  48. }
  49. }
  50. }

并查集 - 图5

  1. //第五版的并查集
  2. public class UnionFind5 implements UF{
  3. private int[] parent;
  4. private int[] rank;
  5. public UnionFind5(int size) {
  6. parent = new int[size];
  7. rank = new int[size];
  8. for (int i = 0; i < parent.length; i++) {
  9. parent[i] = i;
  10. rank[i] = 1;
  11. }
  12. }
  13. @Override
  14. public int getSize() {
  15. return parent.length;
  16. }
  17. private int find(int index) {
  18. if (index < 0 || index >= parent.length) {
  19. throw new IllegalArgumentException("参数错误");
  20. }
  21. //修改了这里
  22. if (index != parent[index]) {
  23. parent[index] = find(parent[index]);
  24. }
  25. return parent[index];
  26. }
  27. @Override
  28. public boolean isConnected(int p, int q) {
  29. return find(p) == find(q);
  30. }
  31. @Override
  32. public void unionElements(int p, int q) {
  33. int pRoot = find(p);
  34. int qRoot = find(q);
  35. if (pRoot == qRoot) {
  36. return;
  37. }
  38. if (rank[pRoot] <= rank[qRoot]){
  39. parent[pRoot] = parent[qRoot];
  40. if (rank[pRoot] == rank[qRoot]) {
  41. rank[qRoot]++;
  42. }
  43. } else {
  44. parent[qRoot] = parent[pRoot];
  45. }
  46. }
  47. }

第五版的效率不一定比第四版的好,因为第四版最后也可能做到”扁平化”,并且第五版的递归操作比较耗时。