单链表
每个结点包括数据和next指针,数据用数组e[]存储,指针指向下一个结点的数据对应在数组的下标,用ne[]存除;一开始头指针head设为-1,表示为空或者链表的尾部;维持一个变量idx表示下一个添加的结点的数据存储在数组中的位置,初始化为0。
const int N=1e5+10;
int e[N],ne[N],idx,head;
void init()
{//初始化
head=-1;
idx=0;
}
void insert_head(int x)
{//向链表头插入x
e[idx]=x;
ne[idx]=head;
head=idx++;
}
void insert(int k,int x)
{//在第 k 个插入的数后面插入一个数 x k>0
//第 k 个插入的数对应的就是数组下标为 k-1 的结点
e[idx]=x;
ne[idx]=ne[k-1];
ne[k-1]=idx++;
}
void delete_k(int k)
{//删除第 k 个插入的数后面的数
//当 k为 0 时,表示删除头结点
if(k==0)
{
head=ne[head];
return;
}
ne[k-1]=ne[ne[k-1]];
}
双链表
双链表最主要的是在初始化上,需要把左端点和右端点初始化好,左端点为下标为0的结点,右端点为下标为1的结点,初始化时需要让左端点的右指针指向右端点,右端点的左指针指向左端点。因为左端点和右端点占用了0和1,所以idx从2开始。
const int N=1e5+10;
int e[N],idx,l[N],r[N];
//函数中的所有k统一成下标
void init()
{
r[0]=1;
l[1]=0;
idx=2;
}
void insert_l(int x)
{//在链表的最右端插入数 x
e[idx]=x;
r[idx]=r[0];
l[idx]=0;
r[0]=idx++;
}
void insert_r(int x)
{//在链表的最左端插入数 x
e[idx]=x;
l[idx]=l[1];
r[idx]=1;
l[1]=idx++;
}
void delete_k(int k)
{//将第 k 个插入的数删除
l[r[k]]=l[k];
r[l[k]]=r[k];
}
void insert_kr(int k,int x)
{//在第 k 个插入的数右侧插入一个数
e[idx]=x;
r[idx]=r[k];
l[idx]=k;
l[r[idx]]=idx;
r[k]=idx++;
}
void insert_kl(int k,int x)
{//在第 k 个插入的数左侧插入一个数
e[idx]=x;
r[idx]=k;
l[idx]=l[k];
l[k]=idx;
r[l[idx]]=idx;
idx++;
//最后两句不能合并成:r[l[idx]]=idx++;!!!
}
其实对于在左/右端点插入不需要特别实现函数:
insert_kr(l[1],x);//在右端点的左边结点后插入就是在左端点插入
insert_kr(0,x);//在左端点插入
栈和队列
对于栈和队列我们只需要明确一点,我们需要维持边界:
栈需要维持栈顶指针tt;队列需要维持队头和队尾指针hh和tt;
栈的栈顶指针表示新的元素需要插入的位置,取栈的顶部元素需要使用tt-1的下标,tt最开始初始化为0
队列hh表示队头,tt表示队尾,分别表示第一个元素和最后一个元素的下标。
队列为空则有tt<hh。tt初始化为-1,hh初始化为0。++tt才表示新元素需要插入的位置
int tt=0;
int st[N];//栈
int q[N];//队列
int hh=0,tt=-1;