链表
使用数组模拟链表
为什么要这么做而不是使用结构体加指针或者直接用stl中的list
其一 new运算符很慢很慢 如果使用常规的链表 大概率是会超时的
其二 使用数组去模拟链表 更能深入理解两者的性质
总结一下就是提速和加深理解
要注意的是 这个知识点是非常重要的 不是说学着模拟一下就够了
这种静态链表要比动态链表更考验思路 并且在后面的图论中 基本是使用数组模拟的链表进行操作的 可以说 这是后面算法的一个基础
用数组模拟链表呢 只要就是要清楚 只是模拟逻辑 物理位置就是连续的前驱后继 把这里搞清楚了就没什么问题了
先分析一下链表 把它的核心拎出来才能谈模拟
链表核心不过就是一个值域和一个指针域 它们是一个整体 组成一个“结点”
那么 我完全就可以去使用两个数组 应该去记录值 一个去记录下一个位置的下标
重点在于 如何让这两个数组始终能够对应 对 用下标 相同下标始终表示一个结点
n[i],ne[i]就是i.data,i.next
这个地方我本来疑惑了蛮久 后面发现其实物理位置没变
变的只是记录的下标(即ne[]中的东西) 它是静态的而非动态
打个比方吧 一开始以为的是对于
e 66 23 11
下标 0 1 2
ne 1 2 -1
在0~1间插入33
要变成
e 66 33 23 11
下标 0 1 2 3
ne 1 2 3 -1
这样的话 e中和ne中的下标就变了 不再对应了 那么ne中记录的东西就没意义了
实际上呢 只是模拟链表 新插入一个数的物理位置不需要插在真正的0~1下标之间
它只需要插在下标3处就好了 要变的只有ne 它存储的是下一个位置的下标 改这个就好了
e 66 23 11 33
下标 0 1 2 3
ne 3 2 -1 1
可以尝试把它抽象成链表 就是66-33-23-11
关键就是: 所有操作都是在数组的可用下标处进行的 改变ne中的值也就是抽象的一种指针(这里用下标抽象指针) 改变数组中各元素的逻辑位置关系 从而实现数组模拟链表
物理or逻辑!!!
只要这个物理位置没变 e和ne就永远会一一以下标对应 而只要对应了 这那个东西就可以抽象成一个“结点”
具体操作分析
刚只是大概知道怎么回事 很多细节还没有扣出来
显而易见就存在一个问题
这里我们并不知道是从头哪个地方开始的 只知道33后面是11 因为ne中33对应的是1
就是说 缺少一个head 这个head不一定就是下标0位置 意思就是说 对于这样的一个模拟链表 第一个元素不一定就是66
要使用一个head去表示头结点的下标 它可以指向(为)任何一个下标 并且支持链表的头插操作
然后呢 可以看出我们需要
一个数组e 记录节点的值
一个数组ne 记录某节点的下一结点的下标
还有 每次操作都是在数组的可用位置进行 那就需要一个指针(下标)去记录当前可用位置
即 需要一个idx 表示数组中可用于操作的位置
准备工作完成了
首先就是考虑初始化问题
void init()
{
head=-1;
ind=0;
}
跟普通链表一样 head一开始指向空 表示没有元素 这里用的数组 用下标表示那就让head=-1 表示当前链表无元素
然后idx=0 就是说 数组现在是可用的位置为0号单元 这很显而易见的事 不用过多解释
考虑一下头插操作
对于一般链表来说 就是一个i.next=h.next h=i的过程
其实在这里也一样
在ne的idx(可用位置)处存放h所在的下标
然后h移动到该位置即可
然后别忘了 数组中这个位置就存放东西了 可用位置要后移 idx++
void add_to_head(int x)
{
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
就是一样的 e数组为值域 ne数组为指针域 不用担心下标会变的问题了 刚已经证明过了
放心去用就行了 抛开数组 直接只看逻辑里的链表示意就行了
然后k位置插入x也是一样
找到k位置(注意这里是以下标0开始 调用时要传入k-1 函数实现里就别想这些了 k就k)
嗯 idx位置的ne要变成k的ne 然后k的ne要变成idx 记得++
void add(int k,int x){
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
idx++;
}
然后是删除操作
不过就是k的ne直接变成k的ne的ne
void remove(int k)
{
ne[k]=ne[ne[k]];
}
注意 这里其实也只是逻辑上的删除
这个数还存在于数组当中 只不过没有那个数的ne为该数的下标了
它无法被链接到 所以就认为它被删除了
另外 删除头结点应该是head=ne[haed]; 并没有放进去 所以另行考虑
也可以写成
void remove(int k)
{
if(k==-1)//因为传入的是k-1
head=ne[head];
else
ne[k]=ne[ne[k]];
}
最后就是怎么遍历得到“链表”结果
回忆一下链表怎么遍历 就是根据链顺下去呗
同理 这里就跟着ne[]顺下去
for(int i=head;i!=-1;i=ne[i])
cout<<e[i];
喏 先输出下标0的 66
然后下一个是下标3的 33
下标1的23
下标2的11
然后等于-1了说明到结尾了 结束
得到的就是66 33 23 11
so easy 搞清楚逻辑和物理位置的关系 就能很容易的模拟出链表
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int head;
int e[N],ne[N];
int idx;
void init()
{
head=-1;
idx=0;
}
void add_to_head(int x)
{
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
void add(int k,int x)
{
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
idx++;
}
void remove(int k)
{
if(k==-1)//因为传入的是k-1
head=ne[head];
else
ne[k]=ne[ne[k]];
}
void print()
{
for(int i=head;i!=-1;i=ne[i])
cout<<e[i]<<" ";
}
int main()
{
int M;
cin>>M;
init();
while(M--)
{
char op;
cin>>op;
switch(op)
{
case 'H':
{
int x;
cin>>x;
add_to_head(x);
}break;
case 'D':
{
int k;
cin>>k;
//if(k==0)
// head = ne[head];//删除头结点
remove(k-1);
}break;
case 'I':
{
int k,x;
cin>>k>>x;
add(k-1,x);
}break;
}
}
print();
return 0;
}