• 数据结构学习链表、双向链表、循环链表


    链表存储有序的元素集合,链表中的元素在内存中不是连续放置的,每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展
    示了一个链表的结构:

    相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素,只需要找到指针的位置。而数组添加元素和移除元素,需要移动元素的位置,成本比较高

    链表

    链表要实现的方法

    append(element) :向列表尾部添加一个新的项。
    insert(position, element) :向列表的特定位置插入一个新的项。
    remove(element) :从列表中移除一项。
    indexOf(element) :返回元素在列表中的索引。如果列表中没有该元素则返回 -1 。
    removeAt(position) :从列表的特定位置移除一项。
    isEmpty() :如果链表中不包含任何元素,返回 true ,如果链表长度大于0则返回 false 。
    size() :返回链表包含的元素个数。与数组的 length 属性类似。
    toString() :由于列表项使用了 Node 类,就需要重写继承自JavaScript对象默认的toString 方法,让其只输出元素的值。
    
    function LinkedList() {
      let Node = function(element){
        this.element = element;
        this.next = null;
      };
      let length = 0;
      let head = null;
    
      this.append = function(element){
        let node = new Node(element),
          current;
        if (head === null){ //列表中第一个节点
          head = node;
        } else {
          current = head; // current = {element: 'a', next: null}
          // 从第一项开始循环列表,直到找到最后一项
          while(current.next){
            current = current.next;
          }
          //找到最后一项,将其next赋为node,建立链接
          current.next = node;
        }
          length++; //更新列表的长度
      };
    
      this.removeAt = function(position) {
        //检查越界值
        if (position > -1 && position < length) { 
          let current = head, 
            previous, 
            index = 0; 
          //移除第一项
          if (position === 0) { 
            head = current.next;
          } else {
            // 找到移除项的前一项和待移除项
            while (index++ < position) { 
              previous = current; 
              current = current.next; 
            }
            // 将previous与current的下一项链接起来:跳过current,从而移除它
            previous.next = current.next; 
          }
          length--; 
          return current.element; // 返回移除项
        } else {
          return null; 
        }
      };
    
    
      this.insert = function(position, element) {
        //检查越界值
        if (position >= 0 && position <= length) {
          let node = new Node(element),
            current = head,
            previous,
            index = 0;
          if (position === 0) { //在第一个位置添加
            node.next = current;
            head = node;
          } else {
            while (index++ < position) {
              previous = current;
              current = current.next;
            }
            node.next = current;
            previous.next = node;
          }
          length++; //更新列表的长度
          return true;
        } else {
          return false;
        }
      };
    
      this.toString = function() {
        let current = head,
          string = '';
        // 从第一项遍历到最后一项,拼接element的值
        while (current) {
          string += current.element + (current.next ? ',' : '');
          current = current.next;
        }
        return string;
      };
    
      this.indexOf = function(element) {
        let current = head,
          index = -1;
        while (current) {
          // 找到链表中匹配项的位置
          if (element === current.element) {
            return index;
          }
          index++;
          current = current.next;
        }
        return -1;
      };
    
      this.remove = function(element) {
        let index = this.indexOf(element);
        return this.removeAt(index);
      };
    
      this.isEmpty = function() {
        return length === 0;
      };
    
      this.size = function() {
        return length;
      };
    
      this.getHead = function() {
        return head;
      };
    };
    
    let l = new LinkedList();
    l.append('a');
    l.append('b');
    console.log(l.size()); // 2
    
    l.insert(1, 'aa');
    console.log(l.size()); // 3
    

    以上就是链表的实现方法,这里的属性和方法都定义在了函数内部而非原型对象上,这么做是为了保证私有属性不会暴露到外面

    双向链表

    链表有多种不同的类型,上面实现的是链表是单向的,即每个节点只有链向下一个节点的链接。而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素,如下图所示:

    基于前面的链表稍加改动,便可以实现双向链表

    function DoublyLinkedList() {
      let Node = function(element) {
        this.element = element;
        this.next = null;
        this.prev = null; // 新增
      };
    
      let length = 0;
      let head = null; // 第一个节点
      let tail = null; // 最后一个节点 新增
    
      this.append = function(element) {
        let node = new Node(element),
          current;
        if (head === null) { // 链表中的第一个节点
          head = node;
          tail = node; // 新增
        } else {
          // 新增
          tail.next = node; // 尾节点的下一个节点指向新增节点
          node.prev = tail; // 新节点的前一个节点指向尾节点
          tail = node; // 更新尾节点
        }
        length++; // 更新链表长度
      };
    
      this.insert = function(position, element) {
        // 边界值检测
        if (position >= 0 && position <= length) {
          let node = new Node(element),
            current = head,
            previous,
            index = 0;
          if (position === 0) { // 特殊情况:新节点要添加到头部
            if (!head) { // 新增
              head = node;
              tail = node;
            } else {
              node.next = current;
              current.prev = node; // 新增
              head = node;
            }
          } else if (position === length) { // 特殊情况:新节点要添加到头部尾部 // 新增
            current = tail;
            current.next = node;
            node.prev = current;
            tail = node;
          } else {
            while (index++ < position) {
              previous = current;
              current = current.next;
            }
            node.next = current;
            previous.next = node;
    
            current.prev = node; // 新增
            node.prev = previous; // 新增
          }
          length++; // 更新链表长度
          return true;
        } else {
          return false;
        }
      };
    
      this.removeAt = function(position) {
        // 边界值检测
        if (position > -1 && position < length) {
          let current = head,
            previous,
            index = 0;
    
          // 移除第一个节点
          if (position === 0) {
            head = current.next;
            // 如果链表只有一个节点,此时需要更新尾节点 // 新增
            if (length === 1) {
              tail = null;
            } else {
              head.prev = null;
            }
          } else if (position === length - 1) { // 移除最后一个节点 // 新增
            current = tail;
            tail = current.prev;
            tail.next = null;
          } else {
            while (index++ < position) { // 移除中间节点
              previous = current;
              current = current.next;
            }
            // 将previous与current的下一项链接起来——跳过current
            previous.next = current.next;
            current.next.prev = previous; // 新增
          }
          length--;
          return current.element;
        } else {
          return null;
        }
      };
    
      this.remove = function(element) {
        let index = this.indexOf(element);
        return this.removeAt(index);
      };
    
      this.indexOf = function(element) {
        let current = head,
          index = -1;
        // 第一个节点,无需遍历,节省时间
        if (element == current.element) {
          return 0;
        }
        index++;
        // 中间节点,需要遍历才能找到位置
        while (current.next) {
          if (element == current.element) {
            return index;
          }
          current = current.next;
          index++;
        }
        // 最后一个节点
        if (element == current.element) {
          return index;
        }
        return -1;
      };
    
      this.isEmpty = function() {
        return length === 0;
      };
    
      this.size = function() {
        return length;
      };
    
      this.toString = function() {
        let current = head,
          s = current ? current.element : '';
        while (current && current.next) {
          current = current.next;
          s += ', ' + current.element;
        }
        return s;
      };
    
      this.inverseToString = function() {
        let current = tail,
          s = current ? current.element : '';
        while (current && current.prev) {
          current = current.prev;
          s += ', ' + current.element;
        }
        return s;
      };
    
      this.getHead = function() {
        return head;
      };
    
      this.getTail = function() {
        return tail;
      }
    }
    

    节点移除示意图

    移除链表头部节点

    移除链表尾部节点

    移除链表中间节点

    节点新增示意图

    链表头部新增节点

    链表尾部新增节点

    链表中间新增节点

    循环链表

    还有一种链表叫做循环链表,循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针再是null,而是指向第一个元素( head ),整体形成了一个环装,如下图所示

    循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用,双向引用的叫双向循环链表。这种链表有指向 head 元素的 tail.next ,和指向 tail 元素的 head.prev,如图

    循环链表的实现和链表类似,只是需要调整最后一个节点的指向为第一个节点

    function CircularLinkedList() {
      let Node = function(element) {
        this.element = element;
        this.next = null;
      };
    
      let length = 0;
      let head = null;
    
      this.append = function(element) {
        let node = new Node(element),
          current;
    
        if (head === null) { // 链表中第一个节点
          head = node;
        } else {
          current = head;
          //找到链表中最后一个节点
          while (current.next !== head) { //最后一个元素指向head而不是NULL
            current = current.next;
          }
          //获取最后一项,并将其分配到添加项中以创建链接
          current.next = node;
        }
        //设置 node.next 指向 head
        node.next = head;
        length++;
      };
    
      this.insert = function(position, element) {
        if (position >= 0 && position <= length) {
          let node = new Node(element),
            current = head,
            previous,
            index = 0;
          if (position === 0) {
            if (!head) {
              head = node;
              node.next = head;
            } else {
              node.next = current;
              // 更新最后一个节点
              while (current.next !== head) { //最后一个元素指向head而不是NULL
                current = current.next;
              }
              head = node;
              current.next = head;
            }
          } else {
            while (index++ < position) {
              previous = current;
              current = current.next;
            }
            node.next = current;
            previous.next = node;
          }
          length++;
          return true;
        } else {
          return false;
        }
      };
    
      this.removeAt = function(position) {
        if (position > -1 && position < length) {
          let current = head,
            previous,
            index = 0;
          if (position === 0) {
            while (current.next !== head) { // 需要更新最后一个节点的指向
              current = current.next;
            }
            head = head.next;
            current.next = head;
          } else {
            while (index++ < position) {
              previous = current;
              current = current.next;
            }
            //把前一个节点和当前节点的next链接,删除当前节点
            previous.next = current.next;
          }
          length--;
          return current.element;
        } else {
          return null;
        }
      };
    
      this.remove = function(element) {
        let index = this.indexOf(element);
        return this.removeAt(index);
      };
    
      this.indexOf = function(element) {
        let current = head,
          index = -1;
        if (element == current.element) {
          return 0;
        }
        index++;
        while (current.next !== head) {
          if (element == current.element) {
            return index;
          }
          current = current.next;
          index++;
        }
        if (element == current.element) {
          return index;
        }
        return -1;
      };
    
      this.isEmpty = function() {
        return length === 0;
      };
    
      this.size = function() {
        return length;
      };
    
      this.getHead = function() {
        return head;
      };
    
      this.toString = function() {
        let current = head,
          s = current.element;
        while (current.next !== head) {
          current = current.next;
          s += ', ' + current.element;
        }
        return s.toString();
      };
    }
    

    总结

    链表相比数组最重要的优点,就是无需移动链表中的元素,就能轻松地添加和移除元素。因此,当需要添加和移除很多元素时,最好的选择就是链表,而非数组。

    优秀文章首发于聚享小站,欢迎关注!
  • 相关阅读:
    Sicily 1153. 马的周游问题 解题报告
    回溯法与八皇后问题
    Sicily 1151. 魔板 解题报告
    Sicily 1176. Two Ends 解题报告
    Sicily 1046. Plane Spotting 解题报告
    Java多线程11:ReentrantLock的使用和Condition
    Java多线程10:ThreadLocal的作用及使用
    Java多线程9:ThreadLocal源码剖析
    Java多线程8:wait()和notify()/notifyAll()
    Java多线程7:死锁
  • 原文地址:https://www.cnblogs.com/yesyes/p/15349370.html
Copyright © 2020-2023  润新知