算法基础课学习笔记:(四)单链表及邻接表

本文介绍了如何使用数组模拟单链表和邻接表,以提高算法竞赛和题目的运行效率。通过预先分配内存空间,虽然存在空间浪费,但能显著提升插入和删除操作的速度。详细阐述了单链表的头插法插入和移除元素,以及邻接表的初始化和添加边的方法,并展示了实际操作示例。强调在实际面试中,应根据情况选择合适的数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

       单链表、栈、队列作为基础数据结构,常用的存储方式有链式存储和顺序存储。
       链式存储的方式由于要不断的 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。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值