暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JavaScript数据结构与算法-链表(一)

Geeker工作坊 2020-11-11
224

开始之前

数组是一种非常简单的存储数据序列的数据结构,本次分享如何实现和使用链表这种动态的数据结构,它可以随意的从中添加或移除项,它还会按需进行扩容。

本次分享内容主要包括:

  1. 链表数据结构

  2. 实现LinkedList类

链表数据结构

要存储多个元素,数组可能是最常用的数据结构。这种数据结构非常方便,提供了一个便利的[]
语法来访问其元素。然而,这种数据结构有一缺点:数组大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素(尽管JavaScript中有Array类方法帮我们做这些事情,但底层的情况同样如此)。

链表存储有序的元素集合,但是不同于数组,链表中的元素在内存中并不是连续放置的。每个元素有一个存储元素本身的节点和指向下一个元素的引用组成。

相对于传统数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需额外注意。在数组中,我们可以直接访问任何位置的任何元素,而想要访问链表中的一个元素,则需要从起点(表头)开始迭代链表直到找到所需的元素。

现实生活中火车就是理解链表很好的例子。一列火车是一系列车厢组成的。每节车厢都相互连接。你很容易分离一节车厢,改变它的位置、添加或移除它。下图演示了一列火车。每节车厢就是链表的元素,车厢间的连接就是指针。

链表的实现

理解链表是什么之后,现在就开始实现我们的数据结构。以下是LinkedList
类的“骨架”。

    import { defaultEquals, IEqualsFunction } from "../utils";
    import {Node} from './linked-list-models';


    export default class LinkedList<T>{
    protected count = 0;
    protected head:Node<T> | undefined;
    constructor(protected equalsFn:IEqualsFunction<T>=defaultEquals){
    this.equalsFn = defaultEquals;
    }
    }

    对于LinkedList
    数据结构,我们从声明count
    属性开始,它用来存储链表元素数量值。

    我们要实现一个名为indexOf的方法,它使我们能够在链表中,找到一个特定的元素。要比较链表中的元素是否相等,我们需要使用一个内部调用的函数,名为euqalsFn
    。使用linkedList
    类的开发者可以自行传入用于比较两个javascript
    对象或值是否相等的自定义函数。如果没有传入这个自定义函数,我们提供一个默认的defaultEquals
    函数,作为默认的相等性比较函数。定义如下。

      export function defaultEquals<T>(a:T,b:T):boolean{
      return a === b;
      }

      由于该数据结构是动态的,我们还需要将第一个元素的引用保存下来。我们可以用一个叫做head
      元素保存引用。

      要表示链表中的第一个元素以及其他元素,我们需要一个助手类,叫做Node
      Node
      类表示我们想要添加到链表中的项。它包含一个element
      属性,该属性表示要加入链表元素的值;以及一个next
      属性,该属性是指向链表中下一个元素的指针。代码如下。

        export class Node<T> {
        constructor(public element: T, public next?: Node<T>) {}
        }

        然后就是LinkedList
        类的方法。在实现这些方法之前,我们先来看看它们的职责。

        push(element)
        :向链表尾部添加一个新元素。

        insert(element,position)
        :向链表的特定位置插入一个新元素。

        getElementAt(index)
        : 返回链表中特定位置的元素。如果链表中不存在这样的元素则返回undefined。

        remove(element)
        : 从链表中移除一个元素。

        indexOf(element)
        :返回元素在链表中的索引。如果链表中没有该元素则返回-1。

        removeAt(position)
        : 从链表的特定位置移除一个元素。

        isEmpty()
        :如果链表中不包含任何元素就返回true,否则返回false。

        size()
        :返回链表包含的元素个数,与数组length属性类似。

        完整实现如下。

          import { defaultEquals, IEqualsFunction } from "../utils";
          import {Node} from './linked-list-models';


          export default class LinkedList<T>{
          protected count = 0;
          protected head:Node<T> | undefined;
          constructor(protected equalsFn:IEqualsFunction<T>=defaultEquals){
          this.equalsFn = defaultEquals;
          }
          /**
          * 添加一个元素
          * @param element <T>
          */
          push(element:T){
          const node = new Node(element);
          let current;
          if(this.head === null){
          this.head = node;
          }else{
          current = this.head;
          while(current.next !== null){
          current = current.next;
          }
          current.next = node
          }
          this.count++;
          }
          /**
          * 返回链表特定位置的元素
          * @param index number 索引值
          */
          getElementAt(index:number){
          if(index >= 0 && index <= this.count){
          let node = this.head;
          for(let i=0; i<index && node != null; i++){
          node = node.next;
          }
          return node;
          }
          return undefined;
          }
          /**
          * 向链表的特定位置插入一个新元素
          * @param element 元素值
          * @param index 索引值
          */
          insert(element:T,index:number){
          if(index >= 0 && index <= this.count){
          const node = new Node(element);
          if(index === 0){
          const current = this.head;
          node.next = current;
          this.head = node;
          }else{
          const previous = this.getElementAt(index - 1);
          node.next = previous.next;
          previous.next = node;
          }
          this.count++;
          return true;
          }
          return false;
          }
          /**
          * 从链表的特定位置移除一个元素
          * @param index 索引值
          */
          removeAt(index:number){
          if(index>=0 && index < this.count){
          let current = this.head;
          if(index===0){
          this.head = current?.next;
          }else{
          const previous = this.getElementAt(index - 1);
          current = previous?.next;
          previous.next = current.next;
          }
          }
          }
          /**
          * 从链表中移除一个元素
          * @param element 元素
          */
          remove(element:T){
          const index = this.indexOf(element);
          return this.removeAt(index);
          }
          /**
          * 返回元素在链表中的索引
          * @param element 元素
          */
          indexOf(element:T){
          let current = this.head;
          for (let i=0;i<this.size()&&current!=null;i++) {
          if(this.equalsFn(element,current.element)){
          return i;
          }
          current = current.next
          }
          return -1;
          }
          isEmpty(){
          return this.size() === 0;
          }
          size(){
          return this.count;
          }
          getHead(){
          return this.count;
          }
          clear(){
          this.head = undefined;
          this.count = 0;
          }
          toString() {
          if (this.head == null) {
          return '';
          }
          let objString = `${this.head.element}`;
          let current = this.head.next;
          for (let i = 1; i < this.size() && current != null; i++) {
          objString = `${objString},${current.element}`;
          current = current.next;
          }
          return objString;
          }
          }

          小结

          本次分享介绍了链表这种数据结构。链表相对数组最重要的优点就是无需移动链表元素,就能轻松的添加和移除元素。因此当你需要添加和移除很多元素的时候,最好的选择就是使用链表,而非数组。然后介绍了linkedList
          类以及如何实现它。

          链表还有很多变体,如:双向链表、循环链表和有序链表,在下一次分享它们的实现方式。

          文章转载自Geeker工作坊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论