JS数据结构与算法==>链表、双向链表、循环链表

本文介绍了链表作为一种非连续存储结构,其逻辑顺序通过节点间的指针链接实现。讨论了链表的插入和删除操作,以及它们相对于数组的优缺点。文章还详细讲解了单链表、双向链表和循环链表的实现,并提供了相关操作如添加、删除节点的示例代码。
摘要由CSDN通过智能技术生成
五. 链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

链表的特点

1.插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)。
2.和数组相比,内存空间消耗更大,因为每个存储数据的节点都需要额外的空间存储后继指针。

1. 单链表

每个节点只包含一个指针,即后继指针。

image-20221124153431191

class Node {
    constructor(element){
        this.element = element  // 链表节点中存放的内容
        this.next = null	// 用于指向下一个节点的指针
    }
}

class LinkedList {
    constructor(){
        this.head = null	// 链表的表头
        this.count = 0		// 链表的节点数量			
    }

    push(element){		// 用于向链表(末尾)添加元素
        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++	// 完成添加操作后,令count++ 
    }


    removeAt(index){	// 用于删除第index个元素
        if(index>=0&&index<this.count){
            let current = this.head
            if(index===0){
                this.head = current.next	// 如果只有一个元素,则直接将表头指向下一个(空)节点
            }else{
                let previous 
                for(let i =0;i<index;i++){	// 找出要删除的节点current 和 该节点的上一个节点previous
                    previous = current
                    current = current.next
                }

                previous.next = current.next  // 令上一个节点的指针指向该节点的下一个节点
            }

            this.count--	// 完成删除操作后,令count--
            return current.element   // 返回被删除节点的内容
        }

        return   // 如果index越界,则直接返回
    }

    getNodeAt(index){  // 获得第index个节点
        if(index>=0&&index<this.count){
            let node = this.head

            for(let i=0;i<index;i++){
                node= node.next
            }
            return node
        }
        return   // 如果index越界,则直接返回
    }

    removeAt2(index){	// 利用getNodeAt()优化后的删除节点方法
        if(index>=0&&index<this.count){
            let current = this.head
            if(index===0){
                this.head = current.next
            }else{
                const previous = this.getNodeAt(index-1)

                current = previous.next

                previous.next = current.next
            }

            this.count--
            return current.element
        }

        return 
    }

    equalFn(a,b){	// 比较两个数据是否相同
        return JSON.stringify(a)===JSON.stringify(b)	// 仍然存在隐患,无法解决复杂数据类型,对于对象类型而言,如果对象初始化时赋值的顺序不同(例如{a:1,b:2} 和 {b:2,a:1} 这两个对象,本质上他们相同,可是根据JSON的处理生成的字符串不同),结果依然是false
    }

    indexOf(element){	// 根据值(内容)来获取索引(index表示第几个)
        let current = this.head

        for(let i=0;i<this.count;i++){
            if(this.equalFn(element,current.element)){
                return i
            }
            current = current.next
        }

        return -1
    }


    remove(element){	// 根据值删除节点
        const index = this.indexOf(element)	// 根据值来获取'索引'
        return this.removeAt(index)	// 调用根据'索引'删除节点的方法
    }


    insert(element,index){	// 在指定位置处插入节点的方法
        if(index>=0&& index<=this.count){
            const node = new Node(element)	// 创建将要插入的节点

            if(index===0){	//在首位插入节点

                const current = this.head
                node.next = current	// 让该节点指向head节点
                this.head = node	// 改变head指向(指向第一个节点也就是该节点)
            }else{  // 不在首位插入节点
                const previous = this.getNodeAt(index-1)
                const current = previous.next
                node.next =  current
                previous.next = node
            }

            this.count++	// 完成添加操作后令count++
            return true
        }
        return false
    }

    isEmpty(){
        return this.size()===0
    }
    size(){
        return this.count
    }

    getHead(){
        return this.head
    }
}

2.利用链表解决十进制转二进制 击鼓传花 回文判断
<1>十进制转二进制:
function docToBinl(num) {
  let transList = new linkedList()
  while (num > 0) {
    let remainder = num % 2
    num = Math.floor(num / 2)
    transList.push(remainder)
  }
  return transList.toString().split('').reverse().join('')
}
<2> 击鼓传花
function gamel(list, num) {
  let q = new linkedList()
  for (let i = 0; i < list.length;i++) {
    q.push(list[i])
  }
  console.log(list);
  while (q.size() > 1) {
    for (let k = 1; k <= num; k++){
      q.push(q.removeAt(0))
    }
    console.log(`${q.getHead().element}出局`)
    q.removeAt(0)
  }
  console.log(`winner: ${q.getHead().element}!!!`);
}

// gamel(['apple','rice','green','pencil','dollar','mirror'],5)
<3>判断回文:
function isPalindromel(str) {
  // 处理字符串
  let list = str.split(' ').join('').toLowerCase()
  // console.log(list);
  let deq1 = new linkedList()
  // 初始化队列
  for (let i = 0; i < list.length; i++) {
    // console.log(`list[i]:${list[i]}`)
    deq1.push(list[i])
  }

  let deq2 = new linkedList()
  // 初始化队列
  for (let i = 0; i < list.length; i++) {
    // console.log(`list[${i}]:${list[i]}`);
    deq2.insert(list[i],0)
  }
  // console.log(deq1.toString());
  // console.log(deq2.toString());

  return deq1.toString() === deq2.toString()
}

// let strl = '我D bab d 我  '
// console.log(isPalindromel(strl)); 
3. 双向链表

节点除了存储数据外,还有两个指针分别指向前一个节点地址(前驱指针prev)和下一个节点地址(后继指针next)。

image-20221124153552974

image-20221130115202342

class DoublyNode extends Node { 
    constructor(element) {
        super(element); 
        this.prev = null;   // 新增前驱指针
    }
}

class DoublyLinkedList extends LinkedList {
    constructor() {
        super();
        this.tail = null;	// 新增后继指针
    }

    push(element) {
        const node = new DoublyNode(element);
        if (this.head == null) {	// 若链表为空,则让链表的头和尾都指向新增节点
            this.head = node;
            this.tail = node; 
        } else {
            this.tail.next = node;
            node.prev = this.tail;
            this.tail = node;
        }
        this.count++;
    }

    insert(element, index) {
        if (index >= 0 && index <= this.count) {
            const node = new DoublyNode(element);
            let current = this.head;
            if (index === 0) {
                if (this.head == null) { 
                    this.head = node;
                    this.tail = node; 
                } else {
                    node.next = this.head;
                    this.head.prev = node; 
                    this.head = node;
                }
            } else if (index === this.count) { 
                current = this.tail;
                current.next = node;
                node.prev = current;
                this.tail = node;
            } else {
                const previous = this.getNodeAt(index - 1);
                current = previous.next;
                node.next = current;
                previous.next = node;
                current.prev = node; 
                node.prev = previous; 
            }
            this.count++;
            return true;
        }
        return false;
    }

    removeAt(index) {
        if (index >= 0 && index < this.count) {
            let current = this.head;
            if (index === 0) {
                this.head = this.head.next;

                if (this.count === 1) {
                    // {2}
                    this.tail = undefined;
                } else {
                    this.head.prev = undefined;
                }
            } else if (index === this.count - 1) {
                current = this.tail;
                this.tail = current.prev;
                this.tail.next = undefined;
            } else {
                current = this.getNodeAt(index);
                const previous = current.prev;
                previous.next = current.next;
                current.next.prev = previous; // NEW
            }
            this.count--;
            return current.element;
        }
        return undefined;
    }


    getHead() {
        return this.head;
    }

    getTail() {
        return this.tail;
    }
}

4. 循环链表

循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 undefined,而是指向第一个元素(head)

image-20221130143728398

 class CircularLinkedList extends LinkedList {
     constructor() {
         super();
     }

     push(element) {
         const node = new Node(element);
         let current;
         if (this.head == null) {
             this.head = node;
         } else {
             current = this.getNodeAt(this.size() - 1);
             current.next = node;
         }
         node.next = this.head;
         this.count++;
     }

     insert(element, index) {
         if (index >= 0 && index <= this.count) {
             const node = new Node(element);
             let current = this.head;
             if (index === 0) {
                 if (this.head == null) {
                     // if no node  in list
                     this.head = node;
                     node.next = this.head;
                 } else {
                     node.next = current;
                     current = this.getNodeAt(this.size() - 1);
                     // update last element
                     this.head = node;
                     current.next = this.head;
                 }
             } else {
                 const previous = this.getNodeAt(index - 1);
                 node.next = previous.next;
                 previous.next = node;
             }
             this.count++;
             return true;
         }
         return false;
     }

     removeAt(index) {
         if (index >= 0 && index < this.count) {
             let current = this.head;
             if (index === 0) {
                 if (this.size() === 1) {
                     this.head = undefined;
                 } else {
                     let last  = this.getNodeAt(this.size() - 1);
                     this.head = this.head.next;
                     last.next = this.head;
                 }
             } else {
                 const previous = this.getNodeAt(index - 1);
                 current = previous.next;
                 previous.next = current.next;// 如果删的是最后一个也不怕,current.next 是head
             }
             this.count--;
             return current.element;
         }
         return undefined;
     }
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值