JavaScript 数据结构和算法——链表

908 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 7 天,点击查看活动详情

介绍

链表是数据结构当中的一种线性表,它和数组的区别在于链表是链式存储的,它在内存当中的内存分配不是连续的,要通过指针来链接,而数组是顺序存储,它在内存当中的空间分配是连续的,连续的意味着我们只需要知道数组的首地址就可以访问数组当中的每一个元素。当线性表需要频繁查找,较少插入和删除时,宜采用顺序存储结构。若需要频繁插入和删除,宜采用单链表。当线性表的元素个数变化较大或不确定时,最好用单链表,这样不需要考虑存储空间大小问题。当事先知道线性表的大小长度,用顺序存储结构效率会高一些。

实现思路

前面我们介绍链表的一些基本概念,了解到链表在内存当中的地址不是连续的,那我们该如何将链表的每一个节点链接起来呢?是的,通过指针来链接链表的每一个节点。下面我们先思考一下,链表的每个节点的结构体是怎么样的?既然链表是用来存储数据的,那么每个节点肯定有一个数据域,而我们还说过,链表是通过指针来链接的,所以每个节点都还有需要一个指针域,这样一个最基本的链表节点就构成了;

class LinkNode<T> {
  public value: T;
  public next: LinkNode<T> | null;
  constructor(value: T, next: LinkNode<T> | null) {
    //数据域
    this.value = value;
    // 指针域
    this.next = next;
  }
}

创建链表

class LinkList implements ILinkList<number> {
  private head: LinkNode<number> | null;
  private count: number;
  constructor() {
    //初始化头结点,头指针指向头结点
    this.head = new LinkNode(null, null);
    //链表长度
    this.count = 0;
  }
}

首先我们需要创建一个类LinkList,在类的构造函数当中,我们初始化一个头指针,头指针指向一个头结点,头结点一般不会存放数据,而是作为链表的起始节点,这样我们可以更加方便的对链表进行一些操作。然后我们定义一个变量count来记录链表的长度。

向链表尾部添加元素

//添加元素
  push(value: number) {
    // 通过遍历找到尾元素
    let current: LinkNode<number> | null;
    const node = new LinkNode(value, null);
    current = this.head;

    if (current.next) {
      //添加位置不是首元节点
      while (current.next) {
        current = current.next;
      }

      current.next = node;
      this.count++;
      return true;
    } else {
      // 添加位置是首元节点
      current.next = node;
      this.count++;
      return true;
    }
  }

首先我们定义一个函数push,这个函数的功能是向链表的尾部添加元素,我们通过变量current来指向首节点,然后创建一个新节点node。先判断当前的链表是否为空,如果为空,则直接添加在首点,如果不为空,这通过遍历找到尾节点,然后将新节点添加在尾节点之后。

向链表指定位置添加元素

insert(index: number, value: any): boolean {
  // 判断掺入位置是否合法
    if (index < 0 && index >= this.count) {
      return false;
    }
    // 声明一个新节点
    const node = new LinkNode(value, null);
    // current指向当前节点
    let current = this.head;

    let i: number = 0;
    //找到需要插入的节点位置
    while (i < index && current.next) {
      i++;
      current = current.next;
    }
    //先将当前节点的next保存到previous当中
    let previous = current.next;
    //然后将指定节点的next指向新节点
    current.next = node;
    //新节点的next指向previous
    node.next = previous;
    this.count++;

    return true;
  }

我们定义insert,方法主要向指定位置添加节点,接收一个index参数,参数表示要添加节点的位置,我们首先判断要添加的节点是否合法,如果不合法直接退出函数,如果合法,我们通过遍历找到插入的位置,然后创建一个新节点node,然后将当前节点的next当前节点的 next 保存到previous当中,然后将指定节点的 next 指向新节点,新节点的 next 指向 previous。

删除指定位置元素

remove(index: number): any {
    let current = this.head;
    let i: number = 0;

    while (i < index && current.next) {
      i++;
      current = current.next;
    }

    let result: any = current.next.value;
    current.next = current.next.next;

    return result;
  }

remove方法是删除链表当中指定位置的节点,接收一个index参数,参数表示要删除节点的位置,我们通过current变量指向当前节点的首节点,然后通过遍历将current指向指定位置的节点,然后我们将当前节点的 next 指向 next 节点的 next。

完整实现

//创建链表

class LinkNode<T> {
  public value: T;
  public next: LinkNode<T> | null;
  constructor(value: T, next: LinkNode<T> | null) {
    this.value = value;
    this.next = next;
  }
}

interface ILinkList<T> {
  //向链表尾部添加元素
  push(value: T): boolean;

  //向链表指定位置添加元素
  insert(index: number, value: T): boolean;

  //删除指定位置元素
  remove(index: number): T;

  //返回指定位置的元素
  getNode(index: number): T;

  //根据节点内容返回节点下标,没有找到就返回-1
  indexOf(value: T): number;

  //pop移除链表的尾元素,并返回
  pop(): T;

  //在链表首部添加元素
  unshift(value: T): boolean;

  //在链表首部删除元素
  shift(): T;

  //返回链表长度
  length(): number;

  //返回链表字符串
  toString(): string;

  //判断链表是否为空
  isEmpty(): boolean;

  //遍历链表
  mapLinkList();
}

class LinkList implements ILinkList<number> {
  private head: LinkNode<number> | null;
  private count: number;
  constructor() {
    //初始化头结点,头指针指向头结点
    this.head = new LinkNode(null, null);
    //链表长度
    this.count = 0;
  }

  //添加元素
  push(value: number) {
    // 通过遍历找到尾元素
    let current: LinkNode<number> | null;
    const node = new LinkNode(value, null);
    current = this.head;

    if (current.next) {
      //添加位置不是首元节点
      while (current.next) {
        current = current.next;
      }

      current.next = node;
      this.count++;
      return true;
    } else {
      // 添加位置是首元节点
      current.next = node;
      this.count++;
      return true;
    }
  }

  insert(index: number, value: any): boolean {
    if (index < 0 && index >= this.count) {
      return false;
    }
    const node = new LinkNode(value, null);
    let current = this.head;

    let i: number = 0;
    while (i < index && current.next) {
      i++;
      current = current.next;
    }

    let previous = current.next;
    current.next = node;
    node.next = previous;
    this.count++;

    return true;
  }

  remove(index: number): any {
    let current = this.head;
    let i: number = 0;

    while (i < index && current.next) {
      i++;
      current = current.next;
    }

    let result: any = current.next.value;
    current.next = current.next.next;

    return result;
  }
}