1、概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
- 特点: 多个元素组成的列表.
- 元素存储不连续,用next指针连在一起.
- 插入、删除数据效率高,只需要考虑相邻结点的指针改变,不需要搬移数据,时间复杂度是 O(1).
- 随机查找效率低,需要根据指针一个结点一个结点的遍历查找,时间复杂度为O(n).
- 与内存相比,链表的空间消耗大,因为每个结点除了要存储数据本身,还要储存上(下)结点的地址.
2、数组 vs 链表
时间复杂度 | 数组 | 链表 |
---|---|---|
插入删除 | O(n) | O(1) |
随机访问 | O(1) | O(n) |
- 数组: 增删非首尾元素时往往需要移动元素.
- 链表: 增删非首尾元素,不需要移动元素,只需要更改next的指向即可.
数组缺点
- 数组必须占用整块、连续的内存空间,如果声明数组过大,可能回导致“内存不足”。
- 数组不够灵活,一旦需要扩容,会重新申请连续整块空间,并需要把原数组的数据全部拷贝到新申请的空间。 链表缺点
- 内存空间消耗更大,用于储存结点指针信息。
- 对链表进行频繁的插入、删除操作会导致频繁的内存申请、释放,容易造成内存碎片,如果是JAVA语言,还有可能会导致频繁的GC(Garbage Collection,垃圾回收)。
3、js中的链表
js中没有链表,但可以用
Object
模拟链表。
const a = { val: 'a' }
const b = { val: 'b' }
const c = { val: 'c' }
const d = { val: 'd' }
a.next = b // a指向b a->b
b.next = c // b指向c a->b->c
c.next = d // c指向d a->b->c->d
// 遍历链表
// 声明一个指针指向a
let p = a
while (p) {
console.log(p.val)
p = p.next // 指针移动到下一位
}
// 插入值
// 例如: 插入一个e到 c和d之间
const e = { val: 'e' }
c.next = e
e.next = d // a->b->c->e->d
// 删除
// 例如:删除e
c.next = d // a->b->c->d
4、leetCode
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且>每个节点只能存储一位数字。
- 请你将两个数相加,并以相同形式返回一个表示和的链表。
- 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
let l3 = new ListNode(0);
// p1 p2 p3为了遍历链表(参考上面遍历链表操作)
let p1 = l1;
let p2 = l2;
let p3 = l3;
// 进位
let carry = 0;
while (p1 || p2) {
const v1 = p1 ? p1.val : 0;
const v2 = p2 ? p2.val : 0;
const val = v1 + v2 + carry;
p3.next = new ListNode(val % 10);
// 获取进位
carry = Math.floor(val / 10);
// 链表移动到下一位
if (p1) p1 = p1.next;
if (p2) p2 = p2.next;
p3 = p3.next;
}
// 如果最后一位相加 存在进位 则将进位添加到链表末尾
if (carry) {
p3.next = new ListNode(carry);
}
return l3.next; // 因为起始值为0 所以返回的是下一位
};
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let r = null // 反转的链表
let p = head
// 遍历链表
while(p) {
// 临时变量 存储下个节点
const temp = p.next
// 将当前节点的下个节点指向上个节点(起始节点的上个节点为null)
p.next = r
r = p
// 遍历下个节点
p = temp
}
return r
};
存在一个按升序排列的链表,给你这个链表的头节点
head
,请你删除所有重复的元素,使每个元素 只出现一次 。返回同样按升序排列的结果链表。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
let p = head;
while (p && p.next) {
// 判断当前节点与下个节点值是否相同
// 相同则删除下个节点 直接将指针指向下下个节点
if (p.val === p.next.val) {
p.next = p.next.next;
} else { // 不相同 则进行下个节点遍历
p = p.next;
}
}
return head
};