1 链表前插法
头插法,顾名思义,就是在链表的头部插入元素。
head
:链表的头,指向链表的第一个元素的地址。
以数列1 3 2 4
举例模拟头插法的实现过程:
模拟链表 | 说明 |
---|---|
head
→
\to
→ NULL | 初始状态,head 为空(NULL),表示无元素 |
head
→
\to
→ 1
→
\to
→ NULL | 插入1,1指向NULL,head 指向1 |
head
→
\to
→ 3
→
\to
→ 1
→
\to
→ NULL | 插入3,3指向1,head 指向3 |
head
→
\to
→ 2
→
\to
→ 3
→
\to
→ 1
→
\to
→ NULL | 插入2,2指向3,head 指向2 |
head
→
\to
→ 4
→
\to
→ 2
→
\to
→ 3
→
\to
→ 1
→
\to
→ NULL | 插入4,4指向2,head 指向4 |
现在用数组来模拟链表:
ver[]
:保存元素的数值。nxt[]
:保存元素的后继地址(下标)。
*在表示时为了方便,把NULL标记为-1。
地址(下标) | 1 | 2 | 3 | 4 |
---|---|---|---|---|
ver[] | 1 | 3 | 2 | 4 |
nxt[] | -1 | 1 | 2 | 3 |
head
:4
链表头插法的定义、插入函数add()
、遍历代码:
int head, ver[N], nxt[N], tot; //定义
void add(int x) { //插入
ver[tot] = x;
nxt[tot] = head;
head = tot;
tot++;
}
for (int i = head; ~i; i = nxt[i]) { //从头遍历链表
cout << ver[i] << " ";
}
2 链式前向星
观察图示的有向图和链表:
可以发现:
- 一共有4个链表,每一个链表的头部
head
对应一个节点。 - 每个
head
后面的元素为它的邻接点及其边权。
这种保存图的方法,就是链式前向星。
那么此时需要对原有的链表头插法进行改变:
head[]
:保存每个点的邻接点链表的头部地址(下标)。w[]
:保存边权。
用数组模拟链式前向星存边方法的最后结果:
地址(下标) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
ver[] | 2 | 3 | 3 | 2 | 3 |
nxt[] | -1 | 1 | -1 | -1 | 4 |
w[] | 3 | 6 | 2 | 7 | 8 |
head[] | 2 | 3 | -1 | 4 |
链式前向星的定义、存边函数add()
、邻接点遍历代码:
- 数组式
int head[N], ver[N], nxt[N], w[N], tot; //定义
void add(int x, int y, int w) { //存边
ver[tot] = y;
w[tot] = w;
nxt[tot] = head[x];
head[x] = tot;
tot++;
}
for (int i = head[x]; ~i; i = nxt[i]) { //遍历x的邻接点
cout << ver[i] << "-" << w[i] << " ";
}
- struct 结构体式
int head[N], size = 0; //定义
struct edge{
int ver, nxt, w;
}e[N];
void add(int x, int y, int w) { //存边
e[++size].ver = y;
e[size].nxt = head[x];
e[size].w = w;
head[x] = size;
}
for (int i = head[x]; ~i; i = nxt[i]) { //遍历x的邻接点
cout << e[i].ver << "-" << e[i].w << " ";
}
- vector 动态数组式
vector<vector<pair<int, int>>> e(N); //定义
e[x].push_back(make_pair(y, w)); //存边
for (int i = 0; i < e[x].size(); i++) { //遍历x的邻接点
cout << e[x][i].first << "-" << e[x][i].second << " ";
}
3 例题
3.1 题目描述 Tree Cutting
在树形网络中找到若干个节点,删除它和它的边,使删除后的网络各部分子网的节点数的最大值不超过总节点数的一半。如果没有一个可删除的节点,输出 NONE
。
3.2 题解
补充:树的重心的性质
- 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
- 一棵树最多有两个重心,且相邻。
由此可知,这道题的答案是寻找树的重心。
定义链式前向星:
vector<vector<int>> e(N);
注意,这道题中由于未知父亲节点和儿子节点,所以需要双向存边,遍历时需要处理非法的反向搜索。
通过dfs()
计算出每个点的子节点数,保存到cnt
数组中:
void dfs(int x, int fat) {
f[x] = fat;
cnt[x] = 1;
for (int i = 0; i < e[x].size(); i++) {
if (e[x][i] == fat) continue;
dfs(e[x][i], x);
cnt[x] += cnt[e[x][i]];
}
}
遍历点,找到符合要求的点:
for (int i = 1; i <= n; i++) {
int maxn = 0;
for (int j = 0; j < e[i].size(); j++) {
if (e[i][j] == f[i]) maxn = max(maxn, n - cnt[i]);
else maxn = max(maxn, cnt[e[i][j]]);
}
if (maxn <= n / 2) {
flag = 1;
cout << i << endl;
}
}