【知识点】
在算法竞赛中,对算法的时间复杂度要求都比较高。
而利用结构体struct实现的单链表比用数组模拟实现的单链表运行速度慢,因此算法竞赛选手多用数组模拟实现单链表。
现对单链表的数组模拟实现简述如下:
一、变量含义
head:头指针。初始化时指向空结点(值为-1),插入元素后指向头结点后的第一个元素。
idx:存储结点编号
e[idx]:存储结点idx的值
ne[idx]:存储结点idx的next指针
二、主要操作
1.单链表的初始化
void init() { //单链表初始化
head=-1;
idx=0;
}
2.利用头插法在链表头插入元素 a
void insert(int a) { //利用头插法在链表头插入元素a
e[idx]=a,ne[idx]=head,head=idx++;
}
3.在第k个插入的元素后插入一个元素a
void add(int k, int a) { //在第k个插入的元素后插入一个元素a
e[idx]=a;
ne[idx]=ne[k];
ne[k]=idx++;
}
4.删除第k个插入的元素后面的元素
void remove(int k) { //删除第k个插入的元素后面的元素
ne[k]=ne[ne[k]];
}
三、单链表 → 邻接表
邻接表本质上是以图/树中各个结点为头结点构成的多个单链表,因此必然可以上文所述进行理解。只是稍作拓展而已。
图/树的数组模拟实现的邻接表代码为:
void add(int a,int b) { //Adjacent List, Head insertion method.
e[idx]=b,ne[idx]=h[a],h[a]=idx++; //Insert node b.
}
在此实现方式下,对树进行遍历的代码模板为:
void dfs(int u) { //Recursion, u is a node
......
for(int i=h[u]; i!=-1; i=ne[i]) { //Traverse the tree
int j=e[i];
dfs(j);
......
}
}
由于树是特殊的图,所以数组模拟实现的邻接表表示方法完全适用于图及树。
由于树的遍历需从树根开始,所以在对树进行遍历时需要编码判断哪个结点是树根。而图可以从任意一个结点开始进行遍历。
若要以文献 http://wiki.jikexueyuan.com/project/easy-learn-algorithm/clever-adjacency-list.html 来理解上文所述的图/树的数组模拟实现的邻接表代码,需约定如下:
按照读入的顺序为每一条边进行编号(从1开始计数);
idx表示树中结点的编号;
h[] 相当于文献 http://wiki.jikexueyuan.com/project/easy-learn-algorithm/clever-adjacency-list.html 中的 first[] 数组,初始化为-1;
ne[] 相当于文献 http://wiki.jikexueyuan.com/project/easy-learn-algorithm/clever-adjacency-list.html 中的 next[] 数组。
若按序插入边(1,4)、(4,3)、(1,2)、(2,4)、(1,3),按照上文所述的图/树的数组模拟实现的邻接表代码,其模拟执行过程如下图所示:
显然,由上图可以查看所有以1 号结点为起点的每一条边的遍历过程。
首先由 h[1] 提供的值开始,然后再依据 ne[] 数组提供的值便可查看整个过程。
当然,下图可以更直观的方式展示。
【参考文献】
http://wiki.jikexueyuan.com/project/easy-learn-algorithm/clever-adjacency-list.html https://blog.csdn.net/Ahub_Ha/article/details/108401760
https://blog.csdn.net/riba2534/article/details/54571026
https://www.acwing.com/solution/content/3472/
https://www.acwing.com/solution/content/16251/