我们的代码中哪里用到了链表?

205 阅读3分钟

链表定义

使用指针将一连串分散在不同内存地址的数据串联起来的数据结构

image.png

链表和数组的区别

  • 数组定义

    数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

    提问:为何要类型相同?

    不同类型的数据占用的字节不同,无法利用寻址公式查询。

  • js中的数组

    提问:js的数组为何可以存不同类型的数据结构?

    js中的Array类是对数组的封装,支持数组的操作和动态扩容等特性。 在v8中,js的数组有两种不同的实现。fast&slow。

    • fast

    若数组的类型相同,且数据直接地址相差不大,利于开辟连续存储空间,则使用快数组(FixedArray)

    • slow

    否则使用index为键值的HashTable实现的慢数组。

    const arr = new Array(10); // fast
    
    const arr = new Array([1,2,3]) // fast
    arr[2000] = 4; // fast -> slow
    
    const arr = [1, "2", {"key": 3}]
    

    课后:数组如何动态扩容和收缩?以及为何要扩容收缩,利弊在哪?

  • 数组和链表的区别

    • 数组需要开辟一串连续的存储空间,链表的数据地址可以是不连续的。

      • 数组优势:利用cpu缓存
      • 链表优势:内存利用率高
    • 数组随机访问的时间复杂度是O(1),链表是O(n)。

      • 随机访问 -> arr[3],使用寻址公式
      • 查找数组中某个值是否存在的时间复杂度?
    • 数组的插入删除操作是O(n),链表是O(1)。

      • 数组插入需要移动插入位置往后的所有节点

链表的应用

  • ReactFiber为什么使用链表

    DOM节点是多叉树结构,虽然适合递归,但是无法暂停。

    dfs(root) {
      ...
      for (let i = 0; i < root.children.length;i++) {
          dfs(root.children[i])
      }    
    }
    

    所以使用链表结构来表述dom节点。

    // 获取当前work in progress
    if (wip.child) {
      	wip = wip.child
      	return
      }
    
      let next = wip
      while (next) {
      	if (next.sibling) {
      		wip = next.sibling
      		return
      	}
      	next = next.return
      }
      wip = null
    
    
  • hashTable中的散列冲突的解决方案

    • 开放寻址法

      不在当前范围内

    • 链表法 image.png

  • 跳表

    redis的数据存储类型

image.png

  • 有序集合的操作

    • 插入一个数据
    • 删除一个数据
    • 查找一个数据
    • 按照区间查找数据 -> [3-5]
    • 迭代输出有序序列

    时间复杂度O(logN),查找数据使用hashTab的话是O(1),空间复杂度O(N)

image.png

  • 职责链模式

我们处理某些校验,过滤等问题时,可以通过A,B,C等处理器,当Q经过A处理完后可以交给B再交给C,直到全部处理完成,每个处理器(A,B,C)只处理自己职责范围内的内容,这就是职责链模式。

职责链可以将主问题与子过滤解耦,满足代码的开闭原则和可扩展性。下面是使用链表实现职责链模式的demo:

class Handler {
    private successor: Handler;
    private doHandler: Function;
    constructor(callback) {
        this.doHandler = callback;
    }

    public setSuccessor(successor: Handler) {
       this.successor = successor;
    }

    public handle() {
        this.doHandler();
        if (this.successor) {
            this.successor.doHandler()
        }
    }
}

class HandlerChain {
    private head: Handler = null;
    private tail: Handler = null;

    public addHandler(handler: Handler) {
        // 设置下一个执行
        handler.setSuccessor(null);

        if (!this.head) {
            this.head = handler;
            this.tail = handler;
            return;
        }

        this.tail.setSuccessor(handler)
        this.tail = handler;
    }

    public handle() {
        if (this.head) {
            this.head.handle()
        }
    }

}

const handler1 = new Handler(() => console.log('handler-1'))
const handler2 = new Handler(() => console.log('handler-2'))

const chain = new HandlerChain();
chain.addHandler(handler1);
chain.addHandler(handler2);

chain.handle();
  • LRU(最近最少使用策略) LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰
        /**
       * @param {number} capacity
       */
      var LRUCache = function (capacity) {
          this.map = new Map();
          this.max = capacity;
          this.count = 0;
          this.head = new ListNode(-1, -1)
          this.tail = new ListNode(-1, -1)
          this.head.next = this.tail;
          this.tail.prev = this.head;
      };
    
      /** 
       * @param {number} key
       * @return {number}
       */
      LRUCache.prototype.get = function (key) {
          if (!this.map.has(key)) return -1;
          this.moveToHead(this.map.get(key));
          return this.map.get(key).val
      };
    
      /** 
       * @param {number} key 
       * @param {number} value
       * @return {void}
       */
      LRUCache.prototype.put = function (key, value) {
          if (this.map.has(key)) {
              const node = this.map.get(key)
              node.val = value;
              this.moveToHead(node)
              return
          }
    
          const node = new ListNode(key, value)
          this.map.set(key, node);
          if (this.count === this.max) {
              this.removeTail();
              this.count--;
          }
          this.addToHead(node);
          this.count++;
      };
    
      LRUCache.prototype.moveToHead = function (node) {
          this.removeNode(node);
          this.addToHead(node);
      };
    
      LRUCache.prototype.removeTail = function () {
          const node = this.tail.prev;
          if (!node || node === this.head) {
              return
          }
          node.prev.next = this.tail;
          this.tail.prev = node.prev;
          this.map.delete(node.key);
      };
    
      LRUCache.prototype.removeNode = function (node) {
          node.prev.next = node.next;
          node.next.prev = node.prev;
      };
    
      LRUCache.prototype.addToHead = function (node) {
          node.prev = this.head;
          node.next = this.head.next;
    
          this.head.next = node;
          node.next.prev = node;
      };
    
    
      /**
       * Your LRUCache object will be instantiated and called as such:
       * var obj = new LRUCache(capacity)
       * var param_1 = obj.get(key)
       * obj.put(key,value)
       */
    
      function ListNode(key = null, val = null, prev = null, next = null) {
          this.key = key
          this.val = val
          this.prev = prev
          this.next = next
      }