首页 > 代码库 > [考研系列之数据结构]线性表之链表

[考研系列之数据结构]线性表之链表

1.链表分类

通过线性表概述,我们知道了链表这样一种数据结构,它又分成三类,分别是
  • 单向链表
  • 循环链表
  • 双向链表

单向链表



单向链表的指针域只有一个指向下一个节点的指针,需要注意几点:
1.头指针——指向第一个节点
2.最后一个结点的指针指向NULL
3.头结点——在链表的第一个结点之前附设一个结点,它的数据域为空
所以,我们看到:
    单向链表为空的<=>链表有且只有一个头结点<=>头结点的指针指向NULL

循环链表



循环链表和单向链表最大的不同就是:最后一个结点的指针不再指向NULL而是指向第一个结点。那么:
    循环链表为空的<=>链表有且只有一个头结点<=>头结点的指针指向自己

双向链表


双向链表的指针域有两个,分别为:prior和next:
prior指向前一个结点,next指向后一个结点。
对于双向链表:
双向链表为空的<=>链表有且只有一个头结点<=>头结点的prior和next指针都指向自己

2.关于链表操作

对所有数据结构的操作最基本的都是增删改查,让我们对单向链表的增删改查进行一些分析:

在单向链表中insert一个结点,我们分为两种情况

insert的结点在表尾
insert的结点不在表尾

如果insert的结点在表尾的话,我们要进行的操作就是:

遍历链表到表尾,找到最后一个结点O(n)
将最后一个结点的指针从NULL改成指向需要insert的结点O(1)

所以很明显,整个操作的算法复杂度是O(n)
如果insert的结点是在第i个结点之后,而且i<n,整体操作如下:

遍历链表,找到第i个结点O(n)
让需要插入结点的指针指向第i+1个结点O(1)
将第i个结点的指针指向需要插入的结点O(1)

整个算法复杂度也是O(n)

删除操作也分两种情况

delete的结点是最后一个
delete的结点不是最后一个

对于delete的结点是最后一个的情况,操作:

遍历找到最后一个结点和它的前一个结点O(n)
将它的前一个结点的指针指向NULLO(1)
[如果结点是存储在堆上,可以使用free释放此结点空间]O(1)

而如果delete的结点是第i个,而且i<n,相应的操作

遍历找到第i个结点和它的前一个结点i-1O(n)
将i-1结点的指针指向第i+1个结点[即将i的指针的值赋给i-1的指针]O(1)
将第i个结点的指针指向NULLO(1)
[如果结点是存储在堆上,可以使用free释放此结点空间]O(1)

对于结点数据域的改操作是建立在查操作之上的,只要找到了这个结点,那么对于数据域的修改就只是赋值那么简单了。

单向链表由于只能从头结点开始向后遍历,所以找到第i个结点也肯定会遍历前面的i-1个结点,关于遍历的算法复杂度:
(数学公式不好机打只好手写了)



扩展

1.两个链表的归并

有两个链表La和Lb,它们同递增或同递减,求一个新的链表Lc,它是La和Lb的元素合并,且Lc也具有相同的递增递减性质
首先,我们明确Lc的长度是
Lc.length=La.length+Lb.length
我们先定义三个指针Pa、Pb和Pc,
初始状态:
Pa指向La的头结点,Pb指向Lb的头结点,Pc指向Lc的头结点
结束状态:
Pa=Pb=Pc=NULL(他们都分别遍历了三个链表)
中间过程:
从初始状态开始,三个指针分别指向三个链表的头,下面用一段伪代码去描述算法(递减):

while(Pa!=NULL&Pb!=NULL)
{
    if(Pa->next->data>=Pb->next->data)
    {
        Pc->next->data=Pa->next->data;
        Pa=Pa->next;
    }
    else
    {
        Pc->next->data=Pb->next->data;
        Pb=Pb->next;
    }
    Pc=Pc-next;
}

算法的核心就是:每次将Pa和Pc指向的数据比较,如果Da(Data Of La)>=Db(Data Of Lb),则Dc=Da,Da指向下一个结点的数据,这样下次循环就是下一个Da和上一次的Db比较了,每次循环Lc长度都会增加1,增加的数据是Pa和Pb指向元素中较大的。

2.多项式的表示和相加

见严书P39

最后,链表更多的是提供一种数据的表示方法,以后的树和图也会使用链式表示的方法,但是单独作为链表还是会有很多小的知识点和技巧的,更多的可以看我以前的一篇博客:
http://blog.csdn.net/acidsweet/article/details/8642689