图解数据结构(02) -- 链表

(3条消息) 图解数据结构(02) -- 链表_姜皓的博客-CSDN博客
https://blog.csdn.net/jianghao233/article/details/103743842?utm_source=app&app_version=4.20.0

姜皓 2019-12-28 16:02:57 134 收藏
分类专栏: 玩转数据结构和算法 # 数据结构 文章标签: 数据结构 链表
版权

玩转数据结构和算法
同时被 2 个专栏收录
52 篇文章4 订阅
订阅专栏

数据结构
37 篇文章1 订阅
订阅专栏
链表
1、什么是链表
单向链表
双向链表
链表的存储方式
2、链表的基本操作
【1】查找节点
【2】更新节点
【3】插入节点
【4】删除元素
3、数组VS链表
1、什么是链表
单向链表
链表(linkedlist)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成;单向链表的每一个节点又包含两部分,一部分是存放数据的变量 data,另一部分是指向下一个节点的指针 next
结构图:

代码实现:

private static class Node {
int data;
Node next;
}
1
2
3
4
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的 next 指针指向空;
与数组按照下标来随机寻找元素不同,对于链表的其中一个节点A,只能根据节点A的 next 指针来找到该节点的下一个节点B,再根据节点B的next指针找到下一个节点C……一级一级,单线传递!想让每个节点都能回溯到它的前置节点,可以使用双向链表

双向链表
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指 针,还拥有指向前置节点的prev指针;


链表的存储方式
说数组在内存中的存储方式是顺序存储,那么链表在内存中的存储方式则是随机存储
数组在内存中占用了连续完整的存储空间,而链表则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠 next 指针关联起来,这样可以灵活有效地利用零散的碎片空间。

数组的内存分配方式图:

链表的内存分配方式图:

图中的箭头代表链表节点的 next 指针
2、链表的基本操作
【1】查找节点
在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
例如给出一个链表,需要查找从头节点开始的第3个节点:

查找步骤:

第1步,将查找的指针定位到头节点

第2步,根据头节点的next指针,定位到第2个节点

第3步,根据第2个节点的next指针,定位到第3个节点,查找完毕

链表中的数据只能按顺序进行访问,最坏的时间复杂度是O(n)

【2】更新节点
如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可


【3】插入节点
链表插入节点时,分为3种情况:

尾部插入
尾部插入把最后一个节点的next指针指向新插入的节点即可

头部插入
头部插入可以分成两个步骤:
第1步,把新节点的next指针指向原先的头节点
第2步,把新节点变为链表的头节点

中间插入
中间插入同样分为两个步骤:
第1步,新节点的 next 指针,指向插入位置的节点
第2步,插入位置前置节点的 next 指针,指向新节点

只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要像数组那样考虑扩容的问题
【4】删除元素
链表的删除操作同样分为3种情况:

尾部删除
尾部删除把倒数第2个节点的 next 指针指向空即可:

头部删除
头部删除把链表的头节点设为原先头节点的next指针即可:

中间删除
中间删除把要删除节点的前置节点的 next 指针,指向要删除元素的下一个节点即可:

这里需要注意的是,许多高级语言,如Java,拥有自动化的垃圾回收机制,所以不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点 会被自动回收
链表的插入和删除操作中如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入和删除操作,时间复杂度都是O(1)

实现链表的完整代码:

public class MyLinkedList {
// 头节点指针
private Node head;
// 尾节点指针
private Node last;
// 链表实际长度
private int size;
//链表插入元素; data插入元素 ;index插入位置
public void insert(int data, int index) throws Exception{
if (index<0 || index>size) {
throw new IndexOutOfBoundsException(" 超出链表节点范围!");
}
Node insertedNode = new Node(data);
if(size == 0){
//空链表
head = insertedNode;
last = insertedNode;
} else if(index == 0){
//插入头部
insertedNode.next = head;
head = insertedNode;

}else if(size == index){
//插入尾部
last.next = insertedNode;
last = insertedNode;
}else {
//插入中间
Node prevNode = get(index-1);
insertedNode.next = prevNode.next;
prevNode.next = insertedNode;
}
size++;
}

//链表删除元素 ; index删除的位置
public Node remove(int index) throws Exception{
if (index<0 || index>=size) {
throw new IndexOutOfBoundsException(" 超出链表节点范围!");
}
Node removedNode = null;
if(index == 0){
//删除头节点
removedNode = head;
head = head.next;
}else if(index == size-1){
//删除尾节点
Node prevNode = get(index-1);
removedNode = prevNode.next;
prevNode.next = null;
last = prevNode;
}else {
//删除中间节点
Node prevNode = get(index-1);
Node nextNode = prevNode.next.next;
removedNode = prevNode.next;
prevNode.next = nextNode;
}
size--;
return removedNode;
}

//链表查找元素 ; index查找的位置
public Node get(int index) throws Exception {
if (index<0 || index>=size) {
throw new IndexOutOfBoundsException(" 超出链表节点范围!");
}
Node temp = head;
for(int i=0; i<index; i++){
temp = temp.next;
}
return temp;
}

// 输出链表
public void output(){
Node temp = head;
while (temp!=null) {
System.out.println(temp.data);
temp = temp.next;
}
}

// 链表节点
private static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}

public static void main(String[] args) throws Exception {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.insert(3,0);
myLinkedList.insert(7,1);
myLinkedList.insert(9,2);
myLinkedList.insert(5,3);
myLinkedList.insert(6,1);
myLinkedList.remove(0);
myLinkedList.output();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
输出:

以上是对单链表相关操作的代码实现。为了尾部插入的方便,代码中额外增加 了指向链表尾节点的指针 last

3、数组VS链表
数组和链表相关操作的性能对比如下图:

数组的优势在于能够快速定位元素,对于读操作多、写操作少的场景来说,用数组更合适一些;
链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适一些。

—————————————————————————————————————————
内容来源:《漫画算法》
————————————————
版权声明:本文为CSDN博主「姜皓」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jianghao233/article/details/103743842

posted @ 2021-12-25 13:09  ArielMeng  阅读(44)  评论(0编辑  收藏  举报