前端高薪内功心法:数据结构之深度掌握链表01

142 阅读4分钟

链表是一种常见的数据结构,它的应用场景非常广泛,比如在前端框架源码中就有许多使用链表的实现。本篇技术博客将结合LeetCode题目和前端框架源码等实际例子,介绍如何使用TypeScript来实现链表,并帮助读者更好地掌握链表的基础知识与应用。

TypeScript实现链表

在TypeScript中实现链表,可以定义一个节点类和一个链表类。节点类包含一个值和一个指向下一个节点的指针,链表类则包含一个头节点和一些基本的操作方法,比如增加节点、删除节点等。

class ListNode<T> {
  public val: T;
  public next: ListNode<T> | null;

  constructor(val: T) {
    this.val = val;
    this.next = null;
  }
}

class LinkedList<T> {
  public head: ListNode<T> | null;

  constructor() {
    this.head = null;
  }

  public add(val: T): void {
    if (!this.head) {
      this.head = new ListNode(val);
    } else {
      let curr = this.head;
      while (curr.next) {
        curr = curr.next;
      }
      curr.next = new ListNode(val);
    }
  }

  public remove(val: T): boolean {
    if (!this.head) {
      return false;
    } else if (this.head.val === val) {
      this.head = this.head.next;
      return true;
    } else {
      let curr = this.head;
      while (curr.next && curr.next.val !== val) {
        curr = curr.next;
      }
      if (curr.next && curr.next.val === val) {
        curr.next = curr.next.next;
        return true;
      } else {
        return false;
      }
    }
  }

  public print(): void {
    let curr = this.head;
    let str = '';
    while (curr) {
      str += `${curr.val} -> `;
      curr = curr.next;
    }
    str += 'null';
    console.log(str);
  }
}

上述代码中,我们使用了泛型来定义节点和链表的类型,使得它们可以适用于不同类型的值。在节点类中,我们定义了一个值和一个指向下一个节点的指针;在链表类中,我们定义了一个头节点和一些基本的操作方法,比如增加节点、删除节点、打印链表等。

LeetCode题目实战

接下来,我们将通过LeetCode上的一些链表题目来实践链表的使用和应用。

题目1:反转链表

题目描述:反转一个单链表。

例如,给定一个链表 1->2->3->4->5,反转后的链表为 5->4->3->2->1。

解题思路:使用三个指针prev、curr和next,分别表示前一个节点、当前节点和下一个节点。初始时,prev为null,curr为头节点,next为curr的下一个节点。依次遍历链表,将curr的指针指向prev,然后将prev、curr、next向后移动,直到遍历完整个链表。

代码实现:

function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let curr: ListNode | null = head;
  while (curr) {
    const next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  return prev;
}

题目2:删除链表的倒数第N个节点

题目描述:给定一个链表,删除链表的倒数第n个节点,并返回链表的头节点。

例如,给定一个链表 1->2->3->4->5,n = 2,则删除链表的倒数第二个节点后,链表变为 1->2->3->5。

解题思路:使用快慢指针的方法,先让快指针移动n步,然后让快指针和慢指针同时移动,当快指针到达链表 末尾时,慢指针指向的节点就是要删除的节点。

代码实现:

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
  const dummy = new ListNode(0);
  dummy.next = head;
  let fast: ListNode | null = dummy;
  let slow: ListNode | null = dummy;
  for (let i = 0; i < n; i++) {
    fast = fast.next;
  }
  while (fast && fast.next) {
    fast = fast.next;
    slow = slow.next;
  }
  if (slow && slow.next) {
    slow.next = slow.next.next;
  }
  return dummy.next;
}

题目3:合并两个有序链表

题目描述:将两个升序链表合并为一个新的升序链表并返回。新链表应该通过拼接给定的两个链表的所有节点组成。

例如,链表1为 1->2->4,链表2为 1->3->4,合并后的链表为 1->1->2->3->4->4。

解题思路:使用递归的方法,比较两个链表的头节点的值大小,将小的节点作为合并后的链表的头节点,然后递归合并剩下的节点。

代码实现:

function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
  if (!l1) {
    return l2;
  } else if (!l2) {
    return l1;
  } else if (l1.val <= l2.val) {
    l1.next = mergeTwoLists(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoLists(l1, l2.next);
    return l2;
  }
}

前端框架源码实战

除了LeetCode题目,链表在前端框架源码中也有许多使用实例。下面以Vue.js为例,介绍如何在前端框架中使用链表。

Vue.js中的响应式原理

Vue.js中的响应式原理是通过使用链表来实现的。Vue.js通过将每个组件实例的数据对象包装成一个响应式对象,当数据对象发生变化时,会自动触发视图的重新渲染。Vue.js使用了一个Watcher类来实现这个功能,Watcher类中维护了一个链表,用于存储当前组件实例对应的所有响应式对象的Dep对象。

class Watcher {
  public deps: Dep[] = [];
  // ...

  public addDep(dep: Dep) {
    if (!this.deps.includes(dep)) {
      this.deps.push(dep);
      dep.addSub(this);
    }
  }
}

class Dep {
  public subs: Watcher[] = [];
  // ...

  public addSub(sub: Watcher) {
    this.subs.push(sub);
  }

  public notify() {
    this.subs.forEach(sub => sub.update());
  }
}

在Watcher类中,我们维护了一个deps数组,deps数组中存储了当前组件实例对应的所有响应式对象的Dep对象。在addDep方法中,我们向deps数组中添加新的Dep对象,并调用Dep对象的addSub方法将当前Watcher对象添加到Dep对象的subs数组中。

在Dep类中,我们维护了一个subs数组,subs数组中存储了所有订阅该Dep对象的Watcher对象。在notify方法中,我们遍历subs数组,依次调用每个Watcher对象的update方法,触发对应的视图更新。

总结

本文介绍了链表的基本概念和常见操作,以及链表在LeetCode题目和前端框架源码中的应用实例。链表作为一种常用的数据结构,在算法和框架开发中都有着重要的作用,它的学习和掌握对于提高编程能力和开发效率都具有重要的意义。