一点碎碎念
之前学基本算法的时候感觉数据结构没啥用,把数组和vector学透彻不就完了?直到开始接触到图的相关知识和算法,好家伙连图咋存咋遍历都不会谈什么进一步的算法
而存图(稀疏图)的主流方法是邻接表,一看人家又是e[N],ne[N]又是idx的,h[N]更是抽象的不行,然后接着找,发现邻接表的前置技能树是数组模拟链表,好家伙,又去学,虽然还是很抽象,但起码算是硬啃理解了。接下来对数组如何模拟单链表做出讲解,学好它是进入图的基础
数组和链表的区别
为什么非要用链表存图呢,为什么不能用数组呢。那是因为图的点和点之间是有联系的,想要表示这种联系,仅仅用只能存值的数组显然是做不到的(只能存节点是谁,存不下和谁又联系),而链表由于具有数值域和指针域可以很好的存下该节点是什么,并且它的下一个节点是谁。简而言之,链表比数组多了记录下一个节点在哪的信息,所以能做到仅靠数组这一数据结构做不到的东西。
链表的另一个优点是添删元素比数组快很多,仅改变局部节点而不用像数组要把所有数前移或后移浪费时间,这里我们也要想办法让数组模拟出来仅改变局部就能在有序数字串中添删元素
数组怎么模拟链表
既然数组比起链表缺少指向下一节点的信息,为什么还能模拟出链表呢?俗话说人多力量大,我自己只能存节点是什么,我找我兄弟来帮我存我这个节点下一个节点在哪不就行了吗。链表的一个节点能存两个信息,我两个数组也可以存两个信息,并且只要我两个数组存的信息下标一致,二者就能联系起来,完全可行。
关于添删数据元素方式的区别我们该怎么解决呢,聪明的前辈们想到了用head指针模拟链表的头节点在哪,用idx指针操作未被利用的数组空间,并为新加入的元素提供暂时的存身之处。
最重要的一点是,数组不能(像链表一样一步到位)在中间存元素是不变的,我们只能加在数组最后,但我们可以通过改变ne数组来改变访问的顺序,从而通过跳跃的查找数组实现插入等操作
我自己讲都感觉太抽象了,看别人也没几个人给我讲懂的,这里通过几个问题并且画图模拟代码一步步进行了什么操作进行讲解
1.在头节点插入元素(!!!!!值为5对应的ne应该是-1的,画的时候顺手了!!!不是4啊啊)
ps:还有add_to_head函数中有形参(要插入的值)的!!!!更改为add_to_head(int x)
2.在第k个元素后插入x
3.删除第k个元素后面一个元素
直接让ne[k]=ne[ne[k]],不会访问k后的一个元素了,idx不用加加,留给读者自己画图理解
具体代码实现
从上面的思考中我们得知我们需要两个兄弟数组分别存节点的值和下一个节点在哪,所以我们用e[N]数组和ne[N]数组分别表示值和next节点的下标(位置)在哪,这样对于信息量的大小我们就模拟的和链表一样了。而index和head的用法通过画图会好理解许多
这里给一组操作,读者自己参考着我画的四个元素画一画,时刻追踪着e,ne,idx和head的变化
1. 头插6;
2. 6后插8;
3. 8后插9;
4. 9后插10;
5. 删9;
6. 8后插13
7. 头插100;
8. 头插101;
9. 用for(int i=head;i!=-1;i=ne[i])遍历该链表并写出每一个节点
三个功能的实现如下
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int e[N],ne[N],idx,head;//head,idx初始全为0
void add_to_head(int x){//x插入头节点
e[idx]=x;ne[idx]=head;head=idx;idx++;
}
void add_x_after_kth(int k,int x){//x插入到链表第k个元素后面
e[idx]=x;ne[idx]=ne[k];ne[k]=idx;idx++;
}
void del(int k){//删掉第k个元素后面一个
ne[k]=ne[ne[k]];
}
int main(){
//数据定义,读入,该插入插入,该删删
}