1. 链表的定义、构成、术语和性质
链表(Linked list)是一种线性的数据结构。
如图所示,这就是一个链表:
可以发现,链表的元素之间是指向的关系(这使得我们很容易想到指针)。
每一个元素有两个内容,一个是元素值,一个是其所指向的下一个元素。
元素值为
α
\alpha
α 的节点是链表的首位,元素值为
δ
\delta
δ 的节点是链表的末端。
容易看出,最后一个节点指向的是空并且每一个元素都指向其后一个元素。这种链表被称为单链表。
有时,我们不让其末端的元素指向空,而是指向第一个元素。这种链表被称为循环单链表:
我们现在只让一个元素指向其后一个元素,有一种链表既指向其后一个元素,又指向其前一个元素。这种链表就是双向链表:
类似地,还有双向循环链表:
有趣的是,数组支持
O
(
1
)
O(1)
O(1) 访问元素,但不支持
O
(
1
)
O(1)
O(1) 插入删除;但链表恰好相反,支持
O
(
1
)
O(1)
O(1) 插入删除,但不支持
O
(
1
)
O(1)
O(1) 访问元素。
但是为什么呢?
这是因为,数组预先定义时已经固定好每一个数的前一个和后一个数的位置;而链表不同,对于插入删除,只需要进行改变某数所指向的前一个数和后一个数即可。
以双向链表为例(下同)。
比如要在
p
p
p 后面插入元素
x
x
x,那么我们只要将
p
p
p 所指向的下一个数的上一个数指向
x
x
x,将
x
x
x 指向的下一个数指向
p
p
p 的下一个数,然后将
p
p
p 指向的下一个数指向
x
x
x,
x
x
x 指向的上一个数指向
p
p
p 即可。
再比如要删除
p
p
p,就将
p
p
p 指向的下一个数的上一个数指向
p
p
p 的上一个数,再将
p
p
p 指向的上一个数的下一个数指向
p
p
p 的下一个数就可以做到删除了。
有些拗口,具体实现在下面代码中。
2. 链表的实现
链表的实现主要有两种:数组模拟和指针模拟(当然,C++ STL 中的 list 也为我们提供了这些功能,不过不常用,在此不再赘述,需要的可以自行搜集)。
2-1. 数组模拟
首先定义一个结构体:
struct node{
int val,prv,nxt;//val 表示元素值,prv 表示该节点指向的上一个数,nxt 表示节点指向的下一个数
}l[N];
int head,tail,tot//分别表示头指针、尾指针和元素个数
新建链表:
void init()
{
head=1;
tail=tot=2;
l[head].nxt=tail;
l[tail].prv=head;
}
插入:
void insert(int p,int x)//在 p 后插入元素 x
{
l[++tot].val=x;
l[l[p].nxt].prv=tot;
l[tot].nxt=l[p].nxt;
l[p].nxt=tot;
l[tot].prv=p;
}
删除:
void remove(int p)//删除 p
{
l[l[p].prv].nxt=l[p].nxt;
l[l[p].nxt].prv=l[p].prv;
}
清空链表:
void clear()
{
memset(l,0,sizeof(l));
head=tail=tot=0;
}
2-2. 指针模拟
和数组模拟有些许不同。
定义:
struct node{
int val;
node *prv,*nxt;//定义指针
}*head,*tail;//头指针和尾指针
新建链表:
void init()
{
head=new node();
tail=new node();//申请使用新的空间
head->nxt=tail;
tail->prv=head;
}
插入:
void insert(node *p,int x)//在 p 后插入数据为 x 的新节点
{
q=new node();
q->val=x;
p->nxt->prv=q;
q->nxt=p->nxt;
p->nxt=q;
q->prv=p;
}
删除:
void remove(node *p)
{
p->prv->nxt=p->nxt;
p->nxt->prv=p->prv;
delete p;//回收内存,下同
}
清空(删除)链表:
void clear()
{
while(head!=tail)
{
head=head->nxt;
delete head->prv;
}
delete tail;
}
3. 例题详解
3-1. 洛谷 P1160 队列安排
可以发现,这题还要求我们在一个节点之前插入,然鹅上面的模板并没有。
事实上也很简单,我们只需要转化一下。
对于
p
p
p 之前插入值为
x
x
x 的节点,就是在
p
p
p 的前一个节点的后面插入值为
x
x
x 的节点。
其他没什么可说的,大家自己实现一下吧。
4. 巩固练习
- P1996 约瑟夫问题
我们之前尝试了队列做法,这次来试一下链表做法。 - P1168 中位数