1. 什么是链表
1.1 链表的定义
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
如下图所示,假设要存储线性表a={1, 2, 3},如果通过链表的方式存储则可以不考虑:1)物理空间是否连续;2)元素之间是否顺序。而是通过【指针】的方式来显式指定各个元素之间的相对位置关系即可。
1.2 链表特性
链表中的数据元素在内存中的存储位置是非连续、非顺序的。
1)优点:相比顺序存储结构,链表的增删改更加灵活,只需要更改当前数据元素附近的数据及指针即可,时间复杂度位O(1);对空间的利用更加灵活;
2)缺点:查询慢,每个数据元素的位置,都需要从链表起始位置开始遍历,时间复杂度O(n);空间利用率低,容易造成存储碎片化;需要额外的指针来指明相对位置,空间成本较高。
1.3 链表基本结构(以单向链表为例)
1)节点、数据域&指针域
首先,链表实现数据元素在物理存储中随机,同时元素间又保持逻辑位置关系的秘诀,在于链表中每个数据元素附带了指针。具体而言,链表中的每个数据元素被设计成一个节点,每个节点由数据域和指针域构成,如下图(单向链表的节点)所示:
数据域(data):用于存储数据元素本身的数据信息
指针域(next):用于存储该数据元素与其他数据元素之间的逻辑位置关系(例如下图中,单向链表的节点的数据域用于指向其下一个数据元素的位置)
2)链式结构
我们以线性表的单向链式存储结构为例,说明链表中元素的位置结构。如下图所示:
头节点:一般情况下链表会设置一个头节点作为起始节点,其数据域是null。头节点不是必须的,它的作用只是为了方便解决某些实际问题;
首元节点:第一个存储了真实数据的节点;
其他节点:除头节点和首元节点以外的节点。
尾节点的指针指向null。
1.4 链表分类
我们这里只考虑线性表的链式存储结构,可以分为以下几种类型:
单向链表、双向链表、单向循环链表、双向循环链表。
2. 单向链表实现
这里以单向链表为例,进行java代码实现,双向及循环链表将在后面的文章中学习和介绍。
2.1 初始化
要点:
1)设计节点类
2)初始化的时候需要先new一个数据域为空的头节点
1 public classMLinkList {2 /**
3 * 节点4 */
5 public class Node{6 /*数据域*/
7 publicE data;8 /*单向链表的指针域指向下一个节点*/
9 publicNode next;10
11 publicNode(E data) {12 this.data =data;13 }14 }15
16 /*头节点*/
17 privateNode head;18
19 /**
20 * 初始化单向链表21 */
22 publicMLinkList() {23 head = new Node(null); //头节点数据域为空
24 }25 }
2.2 基本操作
2.2.1 增加数据元素
操作如下,时间复杂度为O(1)
2.2.2 删除数据元素
操作如下,时间复杂度为O(1)
2.2.3 修改数据元素
只需要修改数据域的值即可,时间复杂度为O(1)
2.2.4 查询数据元素
需要从起始节点开始遍历链表,时间复杂度为O(n)
2.3 完整代码
2.3.1 完整java代码实现
1 packagecom.lrspace.learn.ds.list;2
3 /**
4 * Author: llx5 * Description: 单向链表(首元节点的下标是0)6 * Date: 2021/2/167 */
8 public class MLinkList{9 /**
10 * 节点11 */
12 public class Node{13 /*数据域*/
14 publicE data;15 /*单向链表的指针域指向下一个节点*/
16 publicNode next;17
18 publicNode(E data) {19 this.data =data;20 }21 }22
23 /*头节点*/
24 privateNode head;25 /*当前数据元素个数*/
26 private intsize;27
28 /**
29 * 初始化单向链表(首元节点的下标是0)30 */
31 publicMLinkList() {32 head = new Node(null); //头节点数据域为空
33 size = 0;34 }35
36 /**
37 * 在末尾添加数据元素38 *39 *@paramdata 待添加的数据元素40 */
41 public voidadd(E data) {42 Node tmp = newNode(data);43 Node cur =head;44 while (cur.next != null) {45 cur =cur.next;46 }47 cur.next =tmp;48 size++;49 }50
51 /**
52 * 插入数据元素53 *54 *@parampos 待插入的位置55 *@paramdata 待插入的数据56 */
57 public void insert(intpos, E data) {58 if (pos >= size || pos < 0) {59 throw new IllegalArgumentException("error: the input pos " + pos + " is exceed the bound.");60 }61 Node tmp = newNode(data);62 Node cur =head;63 if (pos == 0) {64 cur =cur.next;65 head.next =tmp;66 tmp.next =cur;67 return;68 }69 for (int i = 0; i < pos; i++) {70 cur =cur.next;71 }72 Node curNext =cur.next;73 cur.next =tmp;74 tmp.next =curNext;75 size++;76 }77
78 /**
79 * 删除数据元素80 *81 *@parampos 待删除数据元素的位置82 */
83 public void delete(intpos) {84 if (pos >= size || pos < 0) {85 throw new IllegalArgumentException("error: the input pos " + pos + " is exceed the bound.");86 }87 if (pos == 0) {88 head.next =head.next.next;89 return;90 }91 Node cur =head;92 for (int i = 0; i < pos; i++) {93 cur =cur.next;94 }95 Node curNext =cur.next;96 cur.next =curNext.next;97 size--;98 }99
100 /**
101 * 更新数据元素102 *103 *@parampos 待更新数据元素的位置104 *@paramdata 待更新数据元素的目标值105 */
106 public void update(intpos, E data) {107 if (pos >= size || pos < 0) {108 throw new IllegalArgumentException("error: the input pos " + pos + " is exceed the bound.");109 }110 Node cur =head;111 for (int i = 0; i < pos + 1; i++) {112 cur =cur.next;113 }114 cur.data =data;115 }116
117 /**
118 * 查询数据元素119 *120 *@parampos 带查询数据元素的位置121 *@return查询出的数据元素122 */
123 public E select(intpos) {124 if (pos >= size || pos < 0) {125 throw new IllegalArgumentException("error: the input pos " + pos + " is exceed the bound.");126 }127 Node cur =head;128 for (int i = 0; i < pos + 1; i++) {129 cur =cur.next;130 }131 return(E) cur.data;132 }133
134 @Override135 publicString toString() {136 StringBuilder out = newStringBuilder();137 out.append("[");138 Node cur =head;139 for (int i = 0; i < size; i++) {140 cur =cur.next;141 out.append(cur.data.toString());142 out.append(", ");143 }144 if (size > 0) {145 out.delete(out.length() - 2, out.length());146 }147 out.append("]");148 returnout.toString();149 }150 }
2.3.2 功能验证
1 packagecom.lrspace.learn.ds.list;2
3 importorg.junit.Test;4
5 import static org.junit.Assert.*;6
7 public classMLinkListTest {8
9 @Test10 public voidtest() {11 /*初始化单向链表*/
12 MLinkList mLinkList = new MLinkList<>();13 System.out.println("单向链表初始化: " +mLinkList.toString());14 /*在末尾添加数据元素*/
15 mLinkList.add("first");16 System.out.println("在末尾添加数据元素1: " +mLinkList.toString());17 mLinkList.add("third");18 System.out.println("在末尾添加数据元素3: " +mLinkList.toString());19 /*插入数据元素*/
20 mLinkList.insert(1, "second");21 System.out.println("插入数据元素: " +mLinkList.toString());22 /*删除数据元素*/
23 mLinkList.delete(1);24 System.out.println("删除数据元素: " +mLinkList.toString());25 /*更新数据元素*/
26 mLinkList.update(1, "second");27 System.out.println("更新数据元素: " +mLinkList.toString());28 /*查询数据元素*/
29 System.out.println("查询数据元素,位置0: " + mLinkList.select(0));30 System.out.println("查询数据元素,位置1: " + mLinkList.select(1));31 }32 }
输出结果:
单向链表初始化: []
在末尾添加数据元素1: [first]
在末尾添加数据元素3: [first, third]
插入数据元素: [first, second, third]
删除数据元素: [first, third]
更新数据元素: [first, second]
查询数据元素,位置0: first
查询数据元素,位置1: second
3. 参考材料
1)百度百科:https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8/9794473?fr=aladdin
2)南孚先生的博客:https://www.cnblogs.com/782687539-nanfu/p/10333031.html
3)解学武的教程:http://data.biancheng.net/view/160.html
原文:https://www.cnblogs.com/llxrl/p/14407804.html