前言
单链表、栈、队列作为基础数据结构,常用的存储方式有链式存储和顺序存储。
链式存储的方式由于要不断的
n
e
w
new
new 或
m
a
l
l
o
c
malloc
malloc 一个新的内存空间,而这两个函数的运行速度是极其缓慢的。对于算法竞赛或算法题来说,运行时间和效率才是放在第一位的,运行空间是可以牺牲的。所以算法竞赛和算法题中常采用顺序模拟的方式来构建这几个数据结构。
我们此处采用数组模拟的方式来模拟这几个数据结构,缺点是需要提前开辟可能会用到的极限量的内存空间,造成一定的空间浪费。优点是快,非常快!!
1.数组模拟单链表
1.1 单链表初始化
假定单链表的最大长度为
N
N
N,我们就需要提前开辟大小为
N
N
N的数组。
const int N = 1e6;
int head = -1, e[N], ne[N], idx=0;
我们开辟了两个数组,分别为
e
[
N
]
和
n
e
[
N
]
e[N]和ne[N]
e[N]和ne[N],再定义了两个变量
h
e
a
d
,
i
d
x
head,idx
head,idx,将其作为我们单链表的存储模拟数组。
h
e
a
d
head
head作为头指针,存储的是单链表的头结点的下标。
i
d
x
idx
idx作为序号指针,记录了下一条链要用的序号。
e
[
N
]
e[N]
e[N]存储了第
i
i
i个节点的值,
n
e
[
N
]
ne[N]
ne[N]存储了第
i
i
i个节点
n
e
x
t
next
next指针指向了哪个节点。
1.2 头插法插入节点
画图示意一下,此处我们要在空链表的头结点后面插入一个值为3的节点。
这是头插法的实现步骤,为了方便统一代码,我们在算法题中统一采用头插法建立链表。
下面给出具体的实现代码。
void add(int x){
ne[idx]=head; //ne[0]=-1
head=idx; //head=0
e[idx++]=x; //e[0]=3
}
// x = 3
我们可以清楚的看到,这样就能以头插法的方式不断添加新的元素。再来看其他操作。
1.3 移除元素(remove)
删除操作:在算法题和其他常用情景中(例如数据库的维护),删除数据并不是真的删除数据,而是直接将要删除的数据视为无效化,只要在合理的逻辑顺序中,跳过这个要删去的值,就可以做到“删除元素”。
这样做是有原因的,可以降低删除操作的操作次数,甚至可能之后会用到这些“伪删除”的数据。
我们来看删除数据怎么做。
显然,想要将第
k
k
k个节点的后续节点删除,只要将第
k
k
k个节点指向他的下下个节点即可。具体到操作代码里实现:
void remove(int k){
ne[k]=ne[ne[k]];
}
由函数名为
r
e
m
o
v
e
remove
remove,我们也可以看出这里的删除操作其实是“移除”,而不是删除(delete)。
2. 图的邻接表存储
2.1 邻接表的定义及初始化
我们在存储图的时候,常用邻接矩阵和邻接表来存储图。针对稠密图来说,常用邻接矩阵存储,针对稀疏图来说,常用邻接表来存储。
我们将采用上述单链表的存储方式,来建立邻接表的存储。
同样的,我们也需要初始化一下邻接表。
const int N = 500 , M=N*N;
int h[N],w[M],e[M],ne[M],idx = 0;
对于图的存储来说,每一个节点都要记录他可能会到达的节点,以及边的权重,所以要开一个
h
e
a
d
[
N
]
head[N]
head[N]的数组,存储每个节点的头指针指向,如果为-1,则说明此节点没有能到达的节点。
因为此图可能是完全图,假设一共有500个节点,则最多可能会有
n
2
n^2
n2条边,我们定义
M
M
M为边数。
w
[
M
]
w[M]
w[M]记录的是第i条边的权值,
e
[
M
]
e[M]
e[M]记录的是第i条边的指出节点的序号,
n
e
[
M
]
ne[M]
ne[M]记录的是第i条边的
n
e
x
t
next
next指针指向的序号。
2.2邻接表添加元素
下面给出采用头插法在邻接表中插入元素的代码。
void add(int a,int b,int c){
e[idx] = b , ne[idx] = h[a] , w[idx] = c ,h[a] = idx++;
}
这里表示建立一条从序号为 a a a的节点指向序号为 b b b的节点的一条权重为 c c c的边。
举个例子,我们要建立一张这样的图
我们仅需要在主函数里调用 a d d add add函数即可。
int main(){
memset(h,-1,sizeof h);
add(1,3,2);
add(3,2,4);
add(2,1,5);
}
在添加元素之前,一定要记得将所有
h
[
N
]
h[N]
h[N]的值置为-1,表示所有节点没有指向的节点。
在这样存储邻接表后,可以极大地加快运行速度。
亲测!!数组模拟邻接表比真正的链式存储在大规模数据下要快了接近1倍。
总结
在算法题和竞赛中,经常采用牺牲空间来换取时间的思想,但是在真正面试中,一定要注意合理选择数组模拟链表或链式存储。如果面试官脸色不好,就果断换用链式存储hh。