链表是数据结构的一种。以节点的方式来存储,是链式存储,每个节点包含 data 域, next 域,并且链表的各个节点不一定是连续存储,区别于数组。
1. 链表的定义
n个节点离散分配,彼此通过指针相连,每个节点只有1个后续节点,1个1前驱节点,第一个节点没有前驱节点,尾节点没有后续节点。
2. 专业术语:
首节点:第一个有效节点
尾节点:最后一个有效节点
头结点:头节点是第一个有效节点之前的那个节点,头节点并不存放有效数据,也没有存放链表中有效节点的个数。加头节点的目的主要是为了方便对链表的操作,简便算法。(对第一个元素结点前插入和删除第一个结点,其操作与其他结点的操作就统一啦)头结点的数据域可以不存储任何信息,也可以存储线性表的长度等3附加信息。
头指针:是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
无论链表是否为空,头指针均不为空。
头结点不一定是链表的必要元素,头指针是链表的必要元素。
头指针:指向头结点的指针变量
尾指针:指向尾节点的指针变量
3.如果希望通过一个函数来对链表进行处理,我们至少需要接受链表的哪些参数?
只需要一个参数:头指针
因为我们通过头指针可以推算出链表的其他所有参数
4. 每一个链表节点的数据类型该如何表示
- C语言:
1 | typedef struct Node |
- Java语言:
1 | class Node{ |
5. 链表的分类
单链表:每个链表的指针域只能指向后面的节点
双链表:每一个节点有两个指针域 双链表程序
循环链表:能通过任何一个节点找到其他所有的结点
非循环链表:
6. 链表与数组区别
数组优点:
- 存取速度很快
数组缺点:
事先需要知道数组的长度
插入删除元素很慢
空间通常有限制
需要大块连续的内存块
链表优点:
- 空间没有限制
- 插入删除元素很快
链表缺点:
- 存取速度很慢
7.非循环单链
1) 非循环单链表插入节点图解
C语言角度:
插入算法1 推荐 q->Next=p->pNext; p->Next=q;
注意: p,q不是节点,是指针变量!这两行的顺序不能倒过来!
插入算法2 r=p->pNext; p->Next=q; q->pNext=r;
2)非循环双链表删除节点图解
C语言角度:
算法1(不采用):
1 | p->pNext=p->pNext->pNext; //代码是A步骤,把第2个节点的指向给了第1个节点的指向 |
**为什么不行?**
如图,这样做了之后指向第2个节点的指针就没了,就是说我就找不到第2个节点了。这样
我就没有释放第2个节点的内存了!
算法2:算法1加一步即可,先临时定义一个指向p后面节点的指针r
1 | r=p->pNext; |
3) 非循环单链表C语言实现:
1 |
|
4) 非循环单链表Java实现:
1 | package datastructure; |
5) 面试题目
(1)查找单链表中倒数第k个节点(新浪面试题)
1 | /** |
(2)将单链表进行反转(腾讯面试题)
1 | /** |
(3)从尾到头打印单链表(百度面试题)
方法一:循环遍历,逆序输出
1 | /** |
方法二:利用栈这个数据结构,将每个节点压入进去,利用栈先进后出的特点,逆序打印
1 | /** |
(4)合并两个有序单链表,合并之后的链表仍然是有序的(都是从小到大)
思路:
1.传入两个链表的头结点
2.新建一个链表的头结点newHead
3.循环比较传入的两个链表的排序变量,谁小(这里按从小到大安排序)谁加到newHead的后边
4.如果有一个链表已经为空,那么就只是针对另一个链表进行添加即可,直到两个链表都为空。
1 | /** |
8.循环单向链表
(1)实际应用—-解决约瑟夫问题
约瑟夫问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
以下面情况为例:
n = 5 , 即有5个人
k = 1, 从第一个人开始报数
m = 2, 数2下,数到2的出列
(2)总体思路
用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
(3)构建一个单向的环形链表思路
先创建第一个节点, 让 first 指向该节点,并形成环形
后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可.
(4)遍历环形链表思路
先让一个辅助指针(变量) curNode,指向first节点
然后通过一个while循环遍历 该环形链表即可, curBoy.next == first 结束
(5)出圈思路
- 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点.
补充: 小孩报数前,先让 first 和 helper 移动 k - 1次(也就是让first移动到开始数数的地方)
当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次
这时就可以将first 指向的小孩节点 出圈
first = first .next
helper.next = first
原来first 指向的节点就没有任何引用,就会被回收
出圈的顺序
2->4->1->5->3
(6)Java程序实现
1 | package datastructure; |