zoukankan      html  css  js  c++  java
  • 数据结构-链表

    数据结构-链表

    链表类型

    1. 单链表
    • 链表通过指针将一组零散的内存块串联在一起。每一个节点除了存储数据外还需记录上下文节点的地址指针(next)。
    • 如图第一个节点,我们称之为头节点,用来记录链表的基地址。最后一个节点,我们称之为尾节点,尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址null,表示这是链表上的最后一个节点。
    • 代码实现
    <!--节点-->
    export default class LinkedListNode {
      constructor(value, next = null) {
        this.value = value;
        this.next = next;
      }
    
      toString(callback) {
        return callback ? callback(this.value) : `${this.value}`;
      }
    }
    
    <!--链表-->
    export default class LinkedList {
      /**
       * @param {Function} [comparatorFunction]
       */
      constructor(comparatorFunction) {
        /** @var LinkedListNode */
        this.head = null;
    
        /** @var LinkedListNode */
        this.tail = null;
    
        this.compare = new Comparator(comparatorFunction);
      }
    
      /**
       * @param {*} value
       * @return {LinkedList}
       */
      prepend(value) {
        // Make new node to be a head.
        const newNode = new LinkedListNode(value, this.head);
        this.head = newNode;
    
        // If there is no tail yet let's make new node a tail.
        if (!this.tail) {
          this.tail = newNode;
        }
    
        return this;
      }
    
      /**
       * @param {*} value
       * @return {LinkedList}
       */
      append(value) {
        const newNode = new LinkedListNode(value);
    
        // If there is no head yet let's make new node a head.
        if (!this.head) {
          this.head = newNode;
          this.tail = newNode;
    
          return this;
        }
    
        // Attach new node to the end of linked list.
        this.tail.next = newNode;
        this.tail = newNode;
    
        return this;
      }
    
      /**
       * @param {*} value
       * @return {LinkedListNode}
       */
      delete(value) {
        if (!this.head) {
          return null;
        }
    
        let deletedNode = null;
    
        // If the head must be deleted then make next node that is differ
        // from the head to be a new head.
        while (this.head && this.compare.equal(this.head.value, value)) {
          deletedNode = this.head;
          this.head = this.head.next;
        }
    
        let currentNode = this.head;
    
        if (currentNode !== null) {
          // If next node must be deleted then make next node to be a next next one.
          while (currentNode.next) {
            if (this.compare.equal(currentNode.next.value, value)) {
              deletedNode = currentNode.next;
              currentNode.next = currentNode.next.next;
            } else {
              currentNode = currentNode.next;
            }
          }
        }
    
        // Check if tail must be deleted.
        if (this.compare.equal(this.tail.value, value)) {
          this.tail = currentNode;
        }
    
        return deletedNode;
      }
    
      /**
       * @param {Object} findParams
       * @param {*} findParams.value
       * @param {function} [findParams.callback]
       * @return {LinkedListNode}
       */
      find({ value = undefined, callback = undefined }) {
        if (!this.head) {
          return null;
        }
    
        let currentNode = this.head;
    
        while (currentNode) {
          // If callback is specified then try to find node by callback.
          if (callback && callback(currentNode.value)) {
            return currentNode;
          }
    
          // If value is specified then try to compare by value..
          if (value !== undefined && this.compare.equal(currentNode.value, value)) {
            return currentNode;
          }
    
          currentNode = currentNode.next;
        }
    
        return null;
      }
    
      /**
       * @return {LinkedListNode}
       */
      deleteTail() {
        const deletedTail = this.tail;
    
        if (this.head === this.tail) {
          // There is only one node in linked list.
          this.head = null;
          this.tail = null;
    
          return deletedTail;
        }
    
        // If there are many nodes in linked list...
    
        // Rewind to the last node and delete "next" link for the node before the last one.
        let currentNode = this.head;
        while (currentNode.next) {
          if (!currentNode.next.next) {
            currentNode.next = null;
          } else {
            currentNode = currentNode.next;
          }
        }
    
        this.tail = currentNode;
    
        return deletedTail;
      }
    
      /**
       * @return {LinkedListNode}
       */
      deleteHead() {
        if (!this.head) {
          return null;
        }
    
        const deletedHead = this.head;
    
        if (this.head.next) {
          this.head = this.head.next;
        } else {
          this.head = null;
          this.tail = null;
        }
    
        return deletedHead;
      }
    
      /**
       * @return {LinkedListNode[]}
       */
      toArray() {
        const nodes = [];
    
        let currentNode = this.head;
        while (currentNode) {
          nodes.push(currentNode);
          currentNode = currentNode.next;
        }
    
        return nodes;
      }
    
      /**
       * @param {function} [callback]
       * @return {string}
       */
      toString(callback) {
        return this.toArray().map(node => node.toString(callback)).toString();
      }
    }
    
    <!--公共方法-->
    export default class Comparator {
      /**
       * @param {function(a: *, b: *)} [compareFunction]
       */
      constructor(compareFunction) {
        this.compare = compareFunction || Comparator.defaultCompareFunction;
      }
    
      /**
       * @param {(string|number)} a
       * @param {(string|number)} b
       * @returns {number}
       */
      static defaultCompareFunction(a, b) {
        if (a === b) {
          return 0;
        }
    
        return a < b ? -1 : 1;
      }
    
      equal(a, b) {
        return this.compare(a, b) === 0;
      }
    
      lessThan(a, b) {
        return this.compare(a, b) < 0;
      }
    
      greaterThan(a, b) {
        return this.compare(a, b) > 0;
      }
    
      lessThanOrEqual(a, b) {
        return this.lessThan(a, b) || this.equal(a, b);
      }
    
      greaterThanOrEqual(a, b) {
        return this.greaterThan(a, b) || this.equal(a, b);
      }
    
      reverse() {
        const compareOriginal = this.compare;
        this.compare = (a, b) => compareOriginal(b, a);
      }
    }
    复制代码
    1. 双向链表
    • 一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)
    • 图片示例
    <!--双链表节点-->
    export default class DoublyLinkedListNode {
      constructor(value, next = null, previous = null) {
        this.value = value;
        this.previous = previous;
        this.next = next;
      }
    
      toString(callback) {
        return callback ? callback(this.value) : `${this.value}`;
      }
    }
    
    <!--双链表-->
    import DoublyLinkedListNode from './DoublyLinkedListNode';
    
    <!--公共方法参考单链表里已给出-->
    import Comparator from '../../utils/comparator/Comparator';
    
    export default class DoublyLinkedList {
      constructor(comparatorFunction) {
        // 头指针
        this.head = null;
        // 尾指针
        this.tail = null;
        this.compare = new Comparator(comparatorFunction);
      }
    
      /**
       * @param {*} value
       * @return { DoublyLinkedList }
       */
      prepend(value) {
        // 生成新的newNode.next-->上一个newNode
        const newNode = new DoublyLinkedListNode(value, this.head);
    
        // 如果头结点存在,将head.previous指向新的newNode
        if (this.head) {
          this.head.previous = newNode;
        }
        // 移动头指针永远指向newNode
        this.head = newNode;
    
        if (!this.tail) {
          this.tail = newNode;
        }
        return this;
      }
    
      /**
       * @param {*} value
       * @return { DoublyLinkedList }
       */
      append(value) {
        const newNode = new DoublyLinkedListNode(value);
    
        if (!this.head) {
          this.head = newNode;
          this.tail = newNode;
          return this;
        }
    
        // 将新节点附加到链接列表的末尾
        this.tail.next = newNode;
        // 将当前尾部附加到新节点的先前引用
        newNode.previous = this.tail;
    
        this.tail = newNode;
    
        return this;
      }
    
      /**
       * @param {*} value
       * @return {DoublyLinkedListNode}
       */
      delete(value) {
        if (!this.head) {
          return null;
        }
        let deleteNode = null;
        let currentNode = this.head;
    
        while (currentNode) {
          if (this.compare.equal(currentNode.value, value)) {
            deleteNode = currentNode;
            if (deleteNode === this.head) {
              this.head = deleteNode.next;
              if (this.head) {
                this.head.previous = null;
              }
    
              if (deleteNode === this.tail) {
                this.tail = null;
              }
            } else if (deleteNode === this.tail) {
              const previousNode = deleteNode.previous;
              this.tail = previousNode;
              this.tail.next = null;
            } else {
              const previousNode = deleteNode.previous;
              const nextNode = deleteNode.next;
              previousNode.next = nextNode;
              nextNode.previous = previousNode;
            }
          }
          currentNode = currentNode.next;
        }
    
        return deleteNode;
      }
    
      /**
       * @param {Object} findParams
       * @param {*} findParams.value
       * @param {function} [findParams.callback]
       * @return {DoublyLinkedListNode}
       */
      find({
        value = undefined,
        callback = undefined
      }) {
        if (!this.head) {
          return null;
        }
        let currentNode = this.head;
        while (currentNode) {
          // If callback is specified then try to find node by callback.
          if (callback && callback(currentNode.value)) {
            return currentNode;
          }
    
          if (value !== undefined && this.compare.equal(currentNode.value, value)) {
            return currentNode;
          }
          currentNode = currentNode.next;
        }
        return null;
      }
    
      /**
       * 删除头节点
       */
      deleteHead() {
        if (!this.head) {
          return null;
        }
    
        const deleteNode = this.head;
        if (this.head.next) {
          this.head = this.head.next;
          this.head.previous = null;
        } else {
          this.head = null;
          this.tail = null;
        }
    
        return deleteNode;
      }
    
      /**
       * 删除尾节点
       */
      deleteTail() {
        if (!this.tail) {
          return null;
        }
    
        if (this.head === this.tail) {
          this.tail = null;
          this.head = null;
          return this.tail;
        }
        const deleteNode = this.tail;
        this.tail = this.tail.previous;
        this.tail.next = null;
        return deleteNode;
      }
    
      /**
       * @return {DoublyLinkedListNode[]}
       */
      toArray() {
        const nodes = [];
    
        let currentNode = this.head;
        while (currentNode) {
          nodes.push(currentNode);
          currentNode = currentNode.next;
        }
    
        return nodes;
      }
    
      /**
       * @param {function} [callback]
       * @return {string}
       */
      toString(callback) {
        return this.toArray().map(node => node.toString(callback)).toString();
      }
    }
    
    复制代码
    1. 循环链表
    • 首节点和末节点被连接在一起
    • 双链表对于给定指针指向的结点删除、插入操作比单链表有优势,双链表pre和next分别记录了当前节点的前驱、后继节点的指针,而单链表需要通过查询才能找到相应的节点
    • java中LinkedHashMap容器底层实现其中就用到了双向链表这种数据结构
    • 这里也有一种思想,用空间换时间,当内存空间比较充裕的情况下,如果我们更加追加代码的执行速度,我们可以选择空间复杂度较高,时间复杂度相对较低的算法。反之我们就需要用空间换时间(如单片机内存有限,对时间得要求没那么高)
    • 图片示例

    写好链表代码的注意事项

    1. 理解指针或引用的含义
    • 某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
    • 如:p->next=q。这行代码是说,p 结点中的 next 指针存储了q结点的内存地址。
    1. 警惕指针丢失和内存泄漏
    p->next = x;  // 将 p 的 next 指针指向 x 结点;
    x->next = p->next;  // 将 x 的结点的 next 指针指向 b 结点;
    p->next 指针在完成第一步操作之后,已经不再指向结点b 了,而是指向结点 x。
    第 2 行代码相当于将x赋值给x->next,自己指向
    自己。因此,整个链表也就断成了两半,从结点 b 往后的所有结点都无法访问到了。
    修正只需要把第 1 行和第 2 行代码的顺序颠倒一下就可以了
    复制代码
    1. 哨兵:是用来解决边界问题不参与业务逻辑
    • 在任何时候链表是否为空,head指针都会指向哨兵节点。我们也把这种有哨兵节点的链表叫带头链表。
    1. 注意边界条件
    • 如果链表为空时,代码是否能正常工作
    • 如果链表只有一个节点时,代码能否正常工作
    • 如果链表只包含两个节点时,代码能否正常工作
    • 代码在处理头节点和尾节点时,代码能否正常工作
    1. 举例画图、辅助思考

    链表与数组的对比

    • 数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据(对于javascript不适用)。由于内存是连续的,支持随机访问,删除、和插入为了保存内存的连续性需要移动数据。
    • 链表是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。删除、插入节点只需要相邻的节点改变指针,查找节点,需要遍历找到找到相应的节点
    • 性能对比

    应用

    • 如何用单链表模拟实现 LRU 缓存淘汰策略算法呢
    • 思路:
    • 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
    • 如果此数据没有在缓存链表中,又可以分为两种情况
    • 1.如果此时缓存未满,则将此结点直接插入到链表的头部
    • 2.如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部
    • 优化:引入散列表(Hash table)来记录每个数据的位置,将缓存访问的时间复杂度降到 O(1)。
    import LinkedList from '../../data-structures/linked-list/LinkedList';
    
    export default class LRUCache {
      constructor(capacity = Number.MAX_VALUE) {
        this.capacity = capacity > 0 ? capacity : Number.MAX_VALUE;
        this.data = {};
        this.hash = {};
        this.linkedList = new LinkedList();
      }
    
      put(val) {
        const currentNode = this.linkedList.find({
          value: val
        });
        if (currentNode && currentNode.value === val) {
          this.linkedList.delete(val);
          this.linkedList.prepend(val);
        } else {
          if (this.linkedList.toArray().length >= this.capacity) {
            this.linkedList.prepend(val);
            this.linkedList.deleteTail();
          } else {
            this.linkedList.prepend(val);
          }
        }
      }
    
      displayInfo() {
        this.linkedList.toString();
      }
    }
    复制代码

    参考链接

  • 相关阅读:
    react-redux不完全指北
    ztext简单的介绍
    svg配合css多变形
    微服务异常感知与快速定位排错
    K8S(rancher) 服务(POD)定时重启服务方案
    记一次简单的微服务项目拆分
    K8S(rancher)Nginx Ingress Controller 负载均衡多路径问题解决方案
    15个必须知道的JavaScript数组方法
    python实现视频分帧
    python实现随机复制若干个文件到新目录
  • 原文地址:https://www.cnblogs.com/twodog/p/12135683.html
Copyright © 2011-2022 走看看