用集合来表示一种数据结构,用以实现如确定某个集合中含有哪些元素、判断某两个元素是否在同一个集合中、求集合的数量等。使用双亲节点表示法来表示一棵树,即每个节点都保存其双亲节点,如用数组来表示以下的图,得到的结果为:
图片
保存结果:
1 | 2 | 3 | 4 |
---|---|---|---|
-1 | 1 | 1 | 3 |
两棵树的合并就是让其中一棵树称为另一颗树根节点的子树,在合并的过程中为避免极端的情况,还需要一遍合并一遍压缩路径。详情如下所示:
首先定义一个数组,用双亲表示法来表示各棵树,所有元素集合总数为N:
int Tree[N]
查找某个节点的根节点,边查找,边压缩路径:
int FingRoot(int x) {
if (Tree[x] == -1) return x;
else {
int tmp = FindRoot(x);
Tree[x] = tmp;
return tmp;
}
}
分析:这道题目就是求将图中连通分量的个数。
方法一:
#include
#define N 1000
#define NU -2
int Tree[N] = {0};
void Find(int n, int &road) {
// 将图中非连通的图合并起来
int num = 0;
int root[2] = {0};
for (int i = 1; i <= n; i++) {
if (num == 2) {
// 找到了两个非连通的图,合并起来;
// 其中一个图根节点成为另一个图根节点的子树
Tree[root[1]] = root[0];
num -= 1;
road++;
// 每合并两个图,路径就加1
}
if (Tree[i] == -1) {
// 寻找根节点,并记录根节点下标
root[num++] = i;
} else if (Tree[i] == -2) {
road++;
}
}
if (road == n)
road -= 1;
}
int main() {
int n, m;
while(scanf("%d%d", &n, &m) != EOF) {
if (n != 0) {
for(int i = 0; i < N; i++)
Tree[i] = NU; // 最开始的每个节点都没有保存任何节点
int road = 0;
int tmp = m;
while (tmp--) {
int n1, n2;
scanf("%d %d", &n1, &n2);
//第一个节点已经在图中,那么把第二个节点添加上去就好了
if (Tree[n1] != -2)
Tree[n2] = Tree[n1]; // n2的根节点与n1相同
else if (Tree[n2] != -2)
Tree[n1] = Tree[n2]; // n1的根节点与n2相同
else if (Tree[n1] == -2 && Tree[n2] == -2) {
Tree[n1] = -1;
Tree[n2] = n1;
// 两个节点都还没有在图中,设置其中一个为根节点
}
}
Find(n, road);
printf("%d\n", road);
}
else break;
}
return 0;
}
方法二:
#include
#define N 1000
#define NU -2
int Tree[N] = {0};
int FindRoot(int x) {
if (Tree[x] == -1) return x;
else {
// 寻找父节点的根
int tmp = FindRoot(Tree[x]);
// 根节点设置为父节点的根
Tree[x] = tmp;
return tmp;
}
}
int main() {
int n, m;
while(scanf("%d", &n) != EOF && n != 0) {
scanf("%d", &m);
for (int i = 0; i < N; i++)
Tree[i] = -1; // 最开始的时候每个节点都是孤立的
while(m--) {
int n1, n2;
int root1, root2;
scanf("%d %d", &n1, &n2);
root1 = FindRoot(n1);
root2 = FindRoot(n2);
// 如果两个节点不在一个集合,就放到一起
if (root1 != root2)
Tree[root1] = root2;
}
int road = 0; // 计算连通分量的个数
for (int i = 1; i <= n; i++)
if (Tree[i] == -1)
road++;
// 需要新加的路径数等于连通分量的个数减1
printf("%d\n", road - 1);
}
return 0;
}
#include
#define N 10000001
int Tree[N]; // 记录的是父亲节点
int num[N]; // 表示每个连通分量的节点个数
int FindRoot(int x) {
if (Tree[x] == -1) return x;
else {
// 寻找父节点的根
int tmp = FindRoot(Tree[x]);
// 根节点设置为父节点的根
Tree[x] = tmp;
return tmp;
}
}
int main() {
int n;
while(scanf("%d", &n) != EOF && n != 0) {
for (int i = 0; i < N; i++) {
Tree[i] = -1; // 最开始的时候每个节点都是孤立的
num[i] = 1; // 每个集合的节点个数为1
}
int tmp = n;
while(tmp--) {
int n1, n2;
int root1, root2;
scanf("%d %d", &n1, &n2);
root1 = FindRoot(n1);
root2 = FindRoot(n2);
if (root1 != root2) {
// 如果两个节点不在一个集合,就放到一起
// root1的父亲节点现在变成了root2.
Tree[root1] = root2;
// root2的节点数量增加
num[root2] += num[root1];
}
}
int ans = 1; // 节点的数量为1
for (int i = 1; i <= N; i++)
if (Tree[i] == -1 && ans < num[i])
ans = num[i];
// 需要新加的路径数等于连通分量的个数减1
printf("%d\n", ans);
}
return 0;
}
#include
#include
#define N 100
int Tree[N]; // 记录的是父亲节点
int arr[N][N]; //存储两个村庄之间的距离
bool mark[N][N]; //标记是否选过路径
int FindRoot(int x) {
if (Tree[x] == -1) return x;
else {
// 寻找父节点的根
int tmp = FindRoot(Tree[x]);
// 根节点设置为父节点的根
Tree[x] = tmp;
return tmp;
}
}
int Kruskal(int n) {
int road = 0; // 记录已经选定的路径的条数
int minlen = 0; // 记录最小的公路长度
int min = INT_MAX;
int n1 = 0, n2 = 0;
while (road < n - 1) {
// 寻找没有被选择过的最小的边
min = INT_MAX;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!mark[i][j] && min > arr[i][j]) {
n1 = i;
n2 = j;
min = arr[i][j];
}
mark[n1][n2]= mark[n2][n1] = true;
int root1 = FindRoot(n1);
int root2 = FindRoot(n2);
if (root1 != root2) {
// 如果两个节点不在一个集合,就放到一起
// root1的父亲节点现在变成了root2.
Tree[root1] = root2;
// root2的节点数量增加
road++;
minlen += arr[n1][n2];
}
}
return minlen;
}
int main() {
int n;
while(scanf("%d", &n) != EOF && n != 0) {
for (int i = 0; i < N; i++) {
Tree[i] = -1; // 最开始的时候每个节点都是孤立的
for (int j = 0; j < N; j++) {
arr[i][j] = INT_MAX; // 最开始任意两个村庄之间是不连通的
mark[i][j] = false; // 最开始的时候任意一条路径都还未被选择
}
}
int tmp = n * (n - 1) / 2;
while(tmp--) {
int n1, n2, len;
scanf("%d %d %d", &n1, &n2, &len);
arr[n1][n2] = len;
arr[n2][n1] = len;
}
printf("%d\n", Kruskal(n));
}
return 0;
}
题目: 平面上有若干个点,需要使用线段将这些点连接起来,使得任意两个点之间能够通过一系列的点相连,求一种连接方式使得所有线段的长度和最小。也即,这是一个求最小生成树的问题,首先需要把输入转化为图。
#include
#include
#include
#include
#define N 101
using namespace std;
int Tree[N]; // 记录的是父亲节点
struct DOT {
// 表示点的结构体
double x;
double y;
} Dot[N];
struct EDGE {
// 表示边的结构体
int a; //节点a编号
int b; //节点b编号
double dis; //节点a,b之间的距离
bool operator < (const EDGE &E) const {
return dis < E.dis;
}
} Edge[N * N];
int FindRoot(int x) {
if (Tree[x] == -1) return x;
else {
// 寻找父节点的根
int tmp = FindRoot(Tree[x]);
// 根节点设置为父节点的根
Tree[x] = tmp;
return tmp;
}
}
double Dis(double x1, double y1, double x2, double y2) {
double dist = sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
return dist;
}
double Kruskal(int n) {
int road = 0; // 记录已经选定的路径的条数
double minlen = 0; // 记录最小的公路长度
int n1 = 0, n2 = 0;
int len = n * n;
for (int i = 1; i <= len; i++) {
n1 = Edge[i].a;
n2 = Edge[i].b;
n1 = FindRoot(n1);
n2 = FindRoot(n2);
if (n1 != n2) {
Tree[n1] = n2;
minlen += Edge[i].dis;
}
}
return minlen;
}
int main() {
int n;
while(scanf("%d", &n) != EOF && n != 0) {
for (int i = 0; i < N; i++)
Tree[i] = -1; //最开始所有节点是孤立的
for (int i = 1; i <= n; i++)
scanf("%lf %lf", &Dot[i].x, &Dot[i].y);
int index = 1;
for (int i = 1; i < n; i++) {
// 计算任意两个节点之间的距离
for (int j = i + 1; j <= n; j++) {
Edge[index].a = i;
Edge[index].b = j;
Edge[index].dis = Dis(Dot[i].x, Dot[i].y, Dot[j].x, Dot[j].y);
index++;
}
}
sort(Edge + 1, Edge + index);
printf("%.2lf\n", Kruskal(index - 1));
}
return 0;
}