链表

一.简介

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,链表比较方便插入和删除操作。


由于不必按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表:顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。

二.两种链表形式

1.循环链表

循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

  1. 在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。
  2. 在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。
2.双向链表

双向链表其实是单链表的改进。当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。

三.优缺点

1、链表更适合于需要动态分配内存、经常改变结构的场景
2、插入和删除元素较快,查找速度慢
3、相比于链表,数组更为节省空间。毕竟链表节点还需要保存一个或者两个引用
4、链表可以充分利用计算机内存空间,实现灵活的内存动态管理

四.链表常见问题

一、单链表中有环的问题
1)如何判断一个单链表中有环?
采用快慢步法。 令两个指针p和q分别指向头结点,p每次前进一步,q每次前进两步,如果p和q能重合,则有环。总的时间复杂度为O(n),n为链表的总长度。
2)如何判断一个环的入口点在哪里?
先找到碰撞点,然后分别从链表投和碰撞点开始,同步地一步一步前进扫描,知道碰撞,此碰撞点即时环的入口。
3)如何知道环的长度?
从碰撞点开始,两个指针p和q,q以一步步长前进,q以两步步长前进,到下次碰撞所经过的操作次数即是环的长度。

二、两个单链表相交问题
1)如何知道两个单链表(无环)是否相交,它们相交的第一个节点是什么?
解法一:将链表A的尾节点的next指针指向链表B的头结点,从而构造了一个新链表。问题转化了为就这个新链表是否有环的问题。
解法二:两个链表相交,则从相交的节点起,其后所有的节点都是两个链表共有的。因此,判断两链表相交的方法是:遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时,和第一个链表的最后一个结点做比较,如果相同,则相交。
2)在此种情形下,如何知道第一个相交的节点在哪里?
将链表A的尾节点的next指针指向链表B的头结点,从而构造了一个环。问题转化为求这个环的入口问题。
3)如何知道两个单链表(有环)是否相交,并且相交的第一个节点是什么?
判断是否相交:(1)分别判断两个链表A、B是否有环(注,两个有环链表相交是指这个环属于两个链表共有)
(2)如果仅有一个有环,则A、B不可能相交
(3)如果两个都有环,则求出A的环入口,判断其是否在B链表上,如果在,则说明A、B相交。
相交的第一个节点
(1)分别计算出两个链表A、B的长度LA和LB(环的长度和环到入口点长度之和就是链表长度)。
(2)如果LA>LB,则链表A指针先走LA-LB,链表B指针再开始走,则两个指针相遇的位置就是相交的第一个节点。
(3)如果LB>LA,则链表B指针先走LB-LA,链表A指针再开始走,则两个指针相遇的位置就是相交的第一个节点。
*该方法中当相交点在环前面时起作用,所以应当首先判断环的入口是否一样,若一样,则用以上三个步骤找出第一个相交节点;否则,A、B两条链的入口即为相交点。

三、约瑟夫环问题
问题描述编号为1,2,3,…,n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
解析思想建立一个有N个元素的循环链表,然后从链表头开始遍历并记数,如果计数i==m(i初始为1)踢出元素,继续循环,当当前元素与下一元素相同时退出循环。

四、单向链表的倒置
头插法:假设原来的顺序是pHead->1->2->3;倒置的链表头结点是pReverseHead;
    首先,从原链表中取第一个节点1,插入到倒置链表中,pReverseHead->1;
    再从原链表中去第二个节点2,插入到导致链表头节点之后,pReverseHead->2->1;
    一次类推,就可以利用原先的节点改变连接顺序,构成一个倒置链表。

五、只读单向链表的逆序输出
方法一:单向顺序遍历链表,用栈记录每个节点的数据。遍历完后,输出栈中数据,即可得到单向链表的逆序输出。
方法二:递归遍历遍历单向链表,每次在递归函数返回之后,输出当前节点的数据,也可得到单向链表的逆序输出。

六、寻找链表中倒数第k个节点
要求一次遍历。
则方法是,设定两个指针p1和p2,p1先开始遍历,当遍历到第k个节点时,p2也开始同步遍历。当p1到达结尾时,p2所处的位置恰好为链表倒数第k个节点。

七、合并两个排序链表
同时遍历,一一对比,小的插入到新的链表中;依次直到所有元素都插入到链表中,完成合并。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值