引言:
链表是一种线性数据结构,由一系列节点组成,每个节点包含数据和一个或多个指向其他节点的引用(指针)。链表的主要类型包括单向链表、双向链表和循环链表。
简单看一个图表来理解链表:
链表
对于链表内部是如何的?看看代码的实现:
let lb = {
val: 1,
next: {
val: 2,
next: {
val: 3,
next: {
val: 4
}
}
}
}
存值和节点的引入,依次下去组成链表。
链表的属性和功能:
- 定义链表的节点:
function ListNode(val) {
this.val = val;
this.next = null;
}
每个ListNode
实例都有一个val
属性,用于存储节点的值,以及一个next
属性,用于指向链表中的下一个节点。
- 创建链表:
function createLinkedList(arr) {
if (arr.length === 0) return null;
let head = new ListNode(arr[0]);
let current = head;
for (let i = 1; i < arr.length; i++) {
current.next = new ListNode(arr[i]);
current = current.next;
}
return head;
}
- 插入节点:
function insertAtHead(head, value) {
let newNode = new ListNode(value);
newNode.next = head;
return newNode;
}
- 删除节点:
function deleteNode(head, value) {
if (head.val === value) {
return head.next;
}
let current = head;
while (current.next !== null) {
if (current.next.val === value) {
current.next = current.next.next;
return head;
}
current = current.next;
}
return head;
}
其实对于插入节点来说,打破1和2的节点,将3插入:
function ListNode(val) {
this.val = val
this.node = null
}
const node = new ListNode(1)
const node2 = new ListNode(2)
const node3 = new ListNode(3)
node.next = node2
node2.next = node3
node.next = node3
代码将node2
设置为node
的下一个节点,将node3
设置为node2
的下一个节点。这样,node
、node2
和node3
就形成了一个链表,链表的顺序是node
-> node2
-> node3
。
最后,代码执行了node.next = node3
,这导致node
的直接下一个节点变成了node3
,从而跳过了node2
。此时,链表的结构变为node
-> node3
,node2
不再与node
或node3
直接相连,因此在当前的链表中,node2
成为了孤立的节点,不再属于链表的一部分。
这段代码创建了一个链表,然后修改了链表的链接,使得原始链表中的第二个节点被移除,链表的头节点直接指向了第三个节点。
所以链表的属性功能总结一下几点:
-
有序的列表
-
线性的结构
-
所有的节点都是离散分布的
-
访问链表中任何一个节点,都要从头结点逐个查找
-
相比数组,数组中增加或者删除一个元素,会导致需要移动n个元素,时间复杂度为O(n), 链表的增加和删除元素不需要移动其他元素,时间复杂度为O(1)
-
链表查找元素的时间复杂度为O(n),而数组查找元素的时间复杂度为O(1)
实践链表
思路:判断节点中值的大小,寻找下一个节点,根据剩下的list链表直接连接,这里要注意返回的不是头部节点,而是头部的next,防止头部是空节点。
代码实现:
var mergeTwoLists = function (list1, list2) {
let head = new ListNode()
let cur = head
while (list1 && list2) {
if (list1.val <= list2.val) {
cur.next = list1
list1 = list1.next
} else {
cur.next = list2
list2 = list2.next
}
cur = cur.next
}
cur.next = list1 !== null ? list1 : list2
return head.next
};
2.删除链表的节点
分析:
val = 5
head = {
val: 4,
next: {
val: 1,
next: {
val: 5,
next: {
val: 9,
next: null
}
}
}
}
也就是在这个链表给你val=5 的值,删除此节点。
判断之前若在头部就要记得移除头结点,移除的值需要头结点的next来代替,如果发现cur.next.val等于val,那么cur.next就是要被删除的节点。
为了删除这个节点,将cur.next更新为cur.next.next,这样cur就跳过了要删除的节点,直接指向了要删除节点的下一个节点,从而实现了删除操作。
最后,函数返回头节点head
,这可能是一个新的头节点(如果原来的头节点被删除了),或者是原来的头节点(如果头节点没有被删除)。
附上完整代码:
var deleteNode = function (head, val) {
let cur = head
if(cur.val == val) {// 移除头结点
head = head.next
}
while (cur && cur.next){
if(cur.next.val == val) {
// cur.next 就是要被移除的值
cur.next = cur.next.next
}
cur = cur.next
}
return head
};
总结:
链表作为数据结构的一部分不可或缺,跟着文章了解基础内容,大家差不多就可以来上实战了!