数组模拟链表和邻接表的建立及相关题目
一、数组模拟链表
1.定义
#define N 100010
int head, e[N], ne[N], idx;
- 根据上图可以更好理解。
- head表示头节点的下标 e[i]表示节点i的值是多少 ne[i]表示节点下一个节点的下标。
- idx表示当前可以用的点的下标。
2.初始化
void init(){
head = -1;
idx = 0;
}
- 初始化head为-1即对应的是head一开始指向NULL
- 当前可用点的下标初始为0
3.插入(头插法和在中间插入)
a.头插法
void add_to_head(int x){
ne[idx] = head;
e[idx] = x;
head = idx++;
}
- 首先当前要插入的点的索引为idx。
- ne[idx]表示当前节点的下一个节点的索引。将其更新为head(head表示头节点的索引)。
- e[idx]存储的是当前节点的值,将其赋值为x(函数参数中的x表示要插入的节点的值)。
- 此时头节点变成了新插入的节点,所以将head赋值为idx(idx表示当前节点的索引)。
- 将idx++,表示下一个可用的点的索引为idx++。
根据下图可以更好理解此过程
b.在中间插入
void add_to_k(int k, int x){
ne[idx] = ne[k];
e[idx] = x;
ne[k] = idx++;
}
-
这个函数表示的意思是在下标为k的点的后面插入一个新的节点。
-
第一步将当前节点指向的下一个节点的下标赋值为下标为k的节点的下一个节点的下标;
-
第二步将这个节点赋值为x;
-
第三步将下标为k的节点指向的下一个节点的下标赋值为idx;
-
第四步idx++,表示接下来可用的点的索引为idx++。
3.删除
void Remove(int k){
ne[k] = ne[ne[k]];
}
-
在这里,删除操作并不是真正删除这个数,而是实现下面图上的操作,使得这个数在模拟的链表中被删除了。
-
ne[k]表示下标为k的节点指向的下个节点的下标,将其赋值为下标为k的节点的下下个节点的索引,即ne[ne[k]]。
4.遍历链表
for(int i = head; i != -1; i = ne[i]){
printf("%d ", e[i]);
}
- 赋值i为头节点的下标。
- 当i不等于-1时,将i赋值为下一个节点的下标。
5.826. 单链表 - AcWing题库
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100010
int m;
int e[N], ne[N], head, idx;
//head表示头节点的下标 e[i]表示节点i的值是多少 ne[i]表示节点下一个节点的下标
//表示当前可以用的点的下标
void init(){
head = -1;
idx = 0;
}
void add_to_head(int x){
ne[idx] = head;
e[idx] = x;
head = idx++;
}
void add_to_k(int k, int x){
ne[idx] = ne[k];
e[idx] = x;
ne[k] = idx++;
}
void Remove(int k){
ne[k] = ne[ne[k]];
}
int main()
{
init();
scanf("%d", &m);
while(m--){
int k, x;
char op;
scanf(" %c", &op);
if(op == 'H'){
scanf("%d", &x);
add_to_head(x);
}
else if(op == 'D'){
scanf("%d", &k);
if(k == 0){
head = ne[head];
}
Remove(k - 1);
}
else{
scanf("%d %d", &k, &x);
add_to_k(k - 1, x);
}
}
for(int i = head; i != -1; i = ne[i]){
printf("%d ", e[i]);
}
return 0;
}
二、邻接表(数组模拟链表存储)
1.定义
#define N 100010
int e[2 * N], ne[2 * N], h[N], idx;
- 邻接表有n个链表(n为图的节点数量)。
- 其余初始化与链表的初始化类似。
- h[]存储每个顶点链表的头部索引。
- e[]存储边的目标顶点。
- ne[]存储同一链表中下一个元素的索引。
- idx是当前可用的数组索引。
2.插入
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
-
add函数的意思是顶点a向顶点b加一条又向边。
-
插入和头插法类似。
-
插入后的结果和哈希表的拉链法有点相似。
3.初始化
memset(h, -1, sizeof(h));
4.邻接表的应用846. 树的重心 - AcWing题库
题库847. 图中点的层次 - AcWing题库
1.题目 树的重心
给定一颗树,树中包含 n𝑛 个结点(编号 1∼n)和 n−1条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n,表示树的结点数。
接下来 n−1行,每行包含两个整数 a和 b,表示点 a 和点 b 之间存在一条边。
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围
1≤n≤10^5
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define N 100010
int e[2 * N], ne[2 * N], h[N], idx;
int n;
bool st[N];
int ans = N;
int max(int a, int b){
return a > b ? a : b;
}
int min(int a, int b){
return a < b ? a : b;
}
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int dfs(int u){
st[u] = true;
int sum = 1, size = 0;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(st[j] == false){
int s = dfs(j);
sum = s + sum;
size = max(size, s);
}
}
size = max(size, n - sum);
ans = min(ans, size);
return sum;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof(h));
idx = 0;
for(int i = 0; i < n - 1; i++){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
add(b, a);
}
dfs(1);
printf("%d\n", ans);
return 0;
}
- 写一个邻接表的add函数,方法已给出。并定义一个st[N]数组,用来记录节点是否被遍历。
- 初始化h[N],和链表类似。
- 在a和b之间添加双向边,因为它是无向边。
- 写出dfs,dfs()的含义是以节点u为根的子树的节点的数量。
- 首先遍历u节点,并将其标记为已遍历(true)。此时sum应该为1。
- 接着通过邻接表遍历树,写法与遍历链表类似。如果节点j没有被遍历dfs(j)。
- 随后进行一系列操作得到重心。
2.题目 图中点的层次
给定一个 n个点 m 条边的有向图,图中可能存在重边和自环。
所有边的长度都是 1,点的编号为 1∼n。
请你求出 1 号点到 n 号点的最短距离,如果从 1号点无法走到 n 号点,输出 −1。
输入格式
第一行包含两个整数 n 和 m。
接下来 m行,每行包含两个整数 a和 b,表示存在一条从 a走到 b的长度为 1的边。
输出格式
输出一个整数,表示 1号点到 n号点的最短距离。
数据范围
1≤n,m≤10^5
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define N 100010
int n, m;
int e[2 * N], ne[2 * N], h[N], idx;
int d[N];
void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int queue[N], head = 0, tail = 0;
int bfs(int x){
memset(d, -1, sizeof(d));
d[x] = 0;
queue[tail++] = 1;
while(head < tail){
int temp = queue[head++];
for(int i = h[temp]; i != -1; i = ne[i]){
int j = e[i];
if(d[j] == -1){
d[j] = d[temp] + 1;
queue[tail++] = j;
}
}
}
return d[n];
}
int main()
{
scanf("%d %d", &n, &m);
memset(h, -1, sizeof(h));
for(int i = 0; i < m; i++){
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
}
printf("%d", bfs(1));
return 0;
}
- 写出邻接表的add函数并初始化h[N]数组。
- 定义一个栈和距离数组d[N]。d[x]的含义表示x位置到起始位置的距离,将其所有数初始化为-1,显然这里要将d[x]初始化为0。
- 将第一个元素入队。
- 当队列不为空时,拿出队列的第一个元素,通过邻接表遍历,如果d[j] = -1(表示这个位置还没被遍历),就将其距起点的距离更新为的d[j] + 1,随后入队。
- 最后返回d[n]即可。