假设我们有一些数据集合,我们对这些集合有俩种操作:
(1)给出俩个数据x,y,把x所在的集合和y所在的集合进行合并。
(2)给出俩个数据x,y,询问x与y是否在同一个集合中。
如何进行存储和表示呢?
存储:采用父亲表示法,由于涉及到集合直接用数组下标就表示某个数,故不用结构体。
合并:寻找俩个数据的根节点,让一个根结点做另一个根节点的孩子。
查询:寻找俩个数据的根节点进行比较。如果相同则在同一棵树中,不同则不在同一棵树中。
一、时间复杂度为O(n)
#include <iostream>
int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int n, m;//n个点,m次操作
int find(int x) //查找根节点
{
if (tree[x] == x) return x; //因为下面操作是开始令自己的根节点都是自己本身
else return find(tree[x]); //查找自己父亲的根节点
}
int main()
{
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
tree[i] = i;//开始令父亲为自己本身
}
int q, x, y;
while (m--)
{
std::cin >> q >> x >> y;
int xi, yi;
if (q == 1) // q=1代表合并
{
xi = find(x); //查找它的根节点
yi = find(y);
tree[xi] = yi;//tree[yi]=xi;
}
else //q=2代表查询
{
xi = find(x);
yi = find(y);
if (xi == yi)std::cout << "YES\n";
else std::cout << "NO\n";
}
}
return 0;
}
在find()函数中可知,如果当一颗树为斜树时,它最坏就是查找完所有的数据从而找到根节点,因此时间复杂度为O(n)。
二、时间复杂度为O(logn)
#include <iostream>
int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int h[105];//结点的高度
int n, m;//n个点,m次操作
int find(int x) //查找根节点
{
if (tree[x] == x) return x;
else return find(tree[x]);
}
int main()
{
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
tree[i] = i;//开始令父亲为自己本身
h[i] = 1; //开始高度都为1
}
int q, x, y;
while (m--)
{
std::cin >> q >> x >> y;
int xi, yi;
if (q == 1) // q=1代表合并
{
xi = find(x); //查找它的根节点
yi = find(y);
if (h[xi] >= h[yi])
{
tree[yi] = xi; //让根节点高度低的树做根节点高度高的树的孩子
h[yi] = std::max(h[yi], h[xi] + 1);//更新根结点的高度
}
else
{
tree[xi] = yi;
h[xi] = std::max(h[xi], h[yi] + 1);
}
}
else //q=2代表查询
{
xi = find(x);
yi = find(y);
if (xi == yi)std::cout << "YES\n";
else std::cout << "NO\n";
}
}
return 0;
}
引入高度数组,数组中存在每个结点的高度。在每次合并时都比较俩个数据的根节点的高度,始终把高度低的根节点去做高度高的根节点的孩子,使得树保持完全二叉树形状,这样树的高度就降下来为logn。查找最慢就是O(logn)。
三、时间复杂度为常数型:O(α(n))
#include <iostream>
int tree[105];//父亲表示法,t[x]=y----->x的父亲是y
int n, m;//n个点,m次操作
int find(int x) //查找根节点
{
if (tree[x] == x) return x;
else
{
int f = find(tree[x]); //因为find函数是查找根节点,所以返回值是一定是根节点
tree[x] = f; //路劲压缩:每次使结点都直接连根节点
return f;
}
}
int main()
{
std::cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
tree[i] = i;//开始令父亲为自己本身
}
int q, x, y;
while (m--)
{
std::cin >> q >> x >> y;
int xi, yi;
if (q == 1) // q=1代表合并
{
xi = find(x); //查找它的根节点
yi = find(y);
tree[xi] = yi;
}
else //q=2代表查询
{
xi = find(x);
yi = find(y);
if (xi == yi)std::cout << "YES\n";
else std::cout << "NO\n";
}
}
return 0;
}
使得查询速度更快的就是引入路劲压缩思想。n个结点的树高度最小的情况就是2,即一个结点为根节点,其他结点都为根节点的孩子。这样每次查询都是常数级的。在查找次数少时,查询还是会从父亲开始查,但当查询次数多时候,最终所有的结点都直接连上根节点。