链表———任意位置插入、删除元素/反转链表
background:文科转码,在工作过程中有许多用到数据结构的地方,但并没有系统的了解各种结构的定义,原理与实现,希望通过业余时间对数据结构有系统的认知方便后续的一系列学习。暂时先用熟悉的语言练习实现,看之后有没有机会学C或者C++(上班几年后又重新像个大一新生一样学习的感觉还挺好的,有种收钱上学的感觉)
⚠️ 反转链表注意:cur+prev+next法实现反转链表的重点在于反转完一遍之后,要把head指向最终的prev,不然头节点链接不上最后得到的只有反转后的末位节点;以及声明next要在while循环内部,在外部的话相当于给了固定值next就不变化了
class myNode {
data: string;
next: Node | null;
constructor(data: string) {
this.data = data;
this.next = null;
}
}
class linkedList{
...
// 反转链表
reverse(){
let prev = null
let cur = this.head
while(cur!==null){
let next = cur.next
cur.next = prev
prev = cur
cur = next
}
this.head = prev
}
print() {
let temp = this.head;
let result = '';
while (temp) {
result += temp.data + ' -> ';
temp = temp.next;
}
console.log(result + 'null');
}
}
const list = new linkedList();
list.append('A');
list.append('B');
list.append('C');
list.append('D');
console.log('初始链表:');
list.print();
// console.log('删除第 1 个节点:');
// list.delete(1);
// list.print();
// console.log('删除第 2 个节点(原来的 C):');
// list.delete(2);
// list.print();
list.reverse()
list.print()
class myNode {
data: string;
next: Node | null;
constructor(data: string) {
this.data = data;
this.next = null;
}
}
class linkedList{
head: any;
// 尾插法添加节点
append(data: string) {
const newNode = new myNode(data);
if (!this.head) {
this.head = newNode;
return;
}
let temp = this.head;
while (temp.next) {
temp = temp.next;
}
temp.next = newNode;
}
delete(n){
// 如果没有head说明是空的,无法执行delete操作
if(!this.head){
return
}
// 如果要删除的是第一个位置也就是头节点,那么头指针要指向头节点的next
if(n===1){
this.head=this.head.next
return
}
let temp = this.head
// 定位到n-1需要移动n-2次
for(let i=1;i<n-1;i++){
if(!temp || !temp.next) return
temp = temp.next
}
if(!temp.next) return
temp.next = temp.next.next
}
print() {
let temp = this.head;
let result = '';
while (temp) {
result += temp.data + ' -> ';
temp = temp.next;
}
console.log(result + 'null');
}
}
const list = new linkedList();
list.append('A');
list.append('B');
list.append('C');
list.append('D');
console.log('初始链表:');
list.print();
console.log('删除第 1 个节点:');
list.delete(1);
list.print();
console.log('删除第 2 个节点(原来的 C):');
list.delete(2);
list.print();
一、链表基础
链表是一种非连续存储结构,不像数组那样在内存中开辟一整块空间。链表中的每个节点(Node)存储的数据是值 + 一个指向下一个节点的引用(指针/地址)。
interface Node {
data: string;
next: Node | null;
}
⚠️ 注意:在 JS/TS 中并没有真正的“指针”,但对象引用的本质也可以看作是“地址”或“指向”。
二、链表插入逻辑(任意位置)
要在链表的第 n 个位置插入一个新的节点,需要分两种情况处理:
- n === 1(在头部插入)
新节点的next指向原本的头结点,然后把这个新节点设为新的头结点。 - n > 1(插入中间或尾部)
需要先找到第 n-1 个节点,然后进行插入操作。
插入操作过程
- 创建一个新节点
temp1 - 找到插入位置前的节点
temp2,因为要修改这个节点的next为新插入节点 - 将
temp1.next = temp2.next(接上原本的链) - 再执行
temp2.next = temp1(让前一个节点连接到新节点)
简图:
原链表:
A -> B -> C
在第2个位置插入X:
A -> X -> B -> C
三、代码实现
interface Node {
data: string;
next: Node | null;
}
class LinkedList {
head: Node | null = null;
insert(data: string, n: number) {
const newNode: Node = { data, next: null };
if (n === 1) {
newNode.next = this.head;
this.head = newNode;
return;
}
// 找到第 n - 1 个节点
let temp: Node | null = this.head;
for (let i = 0; i < n - 2; i++) {
if (temp === null) {
console.error('位置超出范围');
return;
}
temp = temp.next;
}
// 执行插入
newNode.next = temp?.next || null;
if (temp) temp.next = newNode;
}
print() {
let temp = this.head;
const output = [];
while (temp !== null) {
output.push(temp.data);
temp = temp.next;
}
console.log(output.join(' -> '));
}
}
四、测试用例
const list = new LinkedList();
list.insert('A', 1); // 插入到第1位
list.insert('B', 2); // 插入到第2位
list.insert('C', 3); // 插入到第3位
list.insert('X', 2); // 插入到第2位
list.print(); // 输出:A -> X -> B -> C
五、总结
- 链表插入和删除的核心是:操作指针(next)而不是数组索引。
- 插入第 n 个位置时,需要先找到第 n-1 个节点。
- 和数组相比,链表在插入时性能更稳定,不需要移动其他元素。由于其存储方式的不同,链表不像数组那样存储在连续固定的区域,而是分散的依靠指针相互链接的聚合体,所以在插入元素时比数组更灵活,数组插入元素时会可能会出现内存不够而需要重新复制数组再插入的情况