数组模拟链表,链式前向星及其应用

数组模拟链表和邻接表的建立及相关题目

826. 单链表 - AcWing题库

846. 树的重心 - AcWing题库

847. 图中点的层次 - AcWing题库

一、数组模拟链表

1.定义

5432_1bf67fa4b2-链表

#define N 100010
int head, e[N], ne[N], idx;
  1. 根据上图可以更好理解。
  2. head表示头节点的下标 e[i]表示节点i的值是多少 ne[i]表示节点下一个节点的下标。
  3. idx表示当前可以用的点的下标。

2.初始化

void init(){
    head = -1;
    idx = 0;
}
  1. 初始化head为-1即对应的是head一开始指向NULL
  2. 当前可用点的下标初始为0

image-20240426212137879

3.插入(头插法和在中间插入)

a.头插法
void add_to_head(int x){
    ne[idx] = head;
    e[idx] = x;
    head = idx++;
}

image-20240426210502471

  1. 首先当前要插入的点的索引为idx。
  2. ne[idx]表示当前节点的下一个节点的索引。将其更新为head(head表示头节点的索引)。
  3. e[idx]存储的是当前节点的值,将其赋值为x(函数参数中的x表示要插入的节点的值)。
  4. 此时头节点变成了新插入的节点,所以将head赋值为idx(idx表示当前节点的索引)。
  5. 将idx++,表示下一个可用的点的索引为idx++。
根据下图可以更好理解此过程

image-20240426211007217

image-20240426211024493

b.在中间插入
void add_to_k(int k, int x){
    ne[idx] = ne[k];
    e[idx] = x;
    ne[k] = idx++;
}
  1. 这个函数表示的意思是在下标为k的点的后面插入一个新的节点。

  2. image-20240426211520074

  3. 第一步将当前节点指向的下一个节点的下标赋值为下标为k的节点的下一个节点的下标;

  4. 第二步将这个节点赋值为x;

  5. 第三步将下标为k的节点指向的下一个节点的下标赋值为idx;

  6. 第四步idx++,表示接下来可用的点的索引为idx++。

3.删除

void Remove(int k){
    ne[k] = ne[ne[k]];
}
  1. 在这里,删除操作并不是真正删除这个数,而是实现下面图上的操作,使得这个数在模拟的链表中被删除了。

  2. image-20240426213452645

  3. ne[k]表示下标为k的节点指向的下个节点的下标,将其赋值为下标为k的节点的下下个节点的索引,即ne[ne[k]]。

4.遍历链表

for(int i = head; i != -1; i = ne[i]){
        printf("%d ", e[i]);
}
  1. 赋值i为头节点的下标。
  2. 当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;
  1. 邻接表有n个链表(n为图的节点数量)。
  2. 其余初始化与链表的初始化类似。
    1. h[]存储每个顶点链表的头部索引。
    2. e[]存储边的目标顶点。
    3. ne[]存储同一链表中下一个元素的索引。
    4. idx是当前可用的数组索引。

2.插入

void add(int a, int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
  1. add函数的意思是顶点a向顶点b加一条又向边。

  2. 插入和头插法类似。

  3. 插入后的结果和哈希表的拉链法有点相似。

  4. image-20240426215510747

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;
}
  1. 写一个邻接表的add函数,方法已给出。并定义一个st[N]数组,用来记录节点是否被遍历。
  2. 初始化h[N],和链表类似。
  3. 在a和b之间添加双向边,因为它是无向边。
  4. 写出dfs,dfs()的含义是以节点u为根的子树的节点的数量。
  5. 首先遍历u节点,并将其标记为已遍历(true)。此时sum应该为1。
  6. 接着通过邻接表遍历树,写法与遍历链表类似。如果节点j没有被遍历dfs(j)。
  7. 随后进行一系列操作得到重心。
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;
}
  1. 写出邻接表的add函数并初始化h[N]数组。
  2. 定义一个栈和距离数组d[N]。d[x]的含义表示x位置到起始位置的距离,将其所有数初始化为-1,显然这里要将d[x]初始化为0。
  3. 将第一个元素入队。
  4. 当队列不为空时,拿出队列的第一个元素,通过邻接表遍历,如果d[j] = -1(表示这个位置还没被遍历),就将其距起点的距离更新为的d[j] + 1,随后入队。
  5. 最后返回d[n]即可。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值