Linked list structure of JS data structure and algorithm

Know the linked list structure

Advantages of linked list over array

  • The memory space does not have to be continuous, and the memory of the computer can be fully utilized to achieve flexible dynamic memory management

  • The linked list does not have to determine the size when it is created and the size can be extended indefinitely

  • The time complexity of the linked list can reach O(1) when inserting and deleting data, which is much more efficient than the array

Disadvantages of linked list compared to array

  • To access any element in a linked list, you need to start from the beginning and cannot skip the first element to access any element

  • Elements cannot be directly accessed through subscripts and need to be accessed one by one from the beginning until the corresponding element is found

  • While it is easy to get to the next node , it is difficult to go back to the previous node

The implementation mechanisms of linked lists and arrays are completely different . Each element of the linked list consists of a node that stores the element itself and a reference (some languages ​​call it a pointer or connection) pointing to the next element. Similar to a locomotive, one car carries passengers (data) and connects to another car through nodes.

  • The head attribute points to the first node of the linked list;

  • The last node in the linked list points to null;

  • When there is no node in the linked list, head directly points to null;

Common operations in linked list

  • append(element): Add a new item to the end of the linked list;

  • insert(position, element): Insert a new item into a specific position of the linked list;

  • get(position): Get the element at the corresponding position;

  • indexOf(element): Returns the index of the element in the linked list. If there is no element in the linked list, return -1;

  • update(position, element): modify an element at a certain position;

  • removeAt(position): Remove an item from a specific position in the linked list;

  • remove(element): remove an item from the linked list;

  • isEmpty(): If the linked list does not contain any elements, return trun, if the length of the linked list is greater than 0, return false;

  • size(): Returns the number of elements contained in the linked list, similar to the length attribute of the array;

  • toString(): Since the linked list item uses the Node class, it is necessary to rewrite the default toString method inherited from the JavaScript object, so that it only outputs the value of the element;

The value of position generally indicates the next node at the position pointed to by position. When the value of position is equal to the value of index, such as position = index = 1, then they both represent Node2.

js encapsulates one-way linked list class

Create a singly linked list class

First create a one-way linked list class Linklist, and add basic attributes, and then implement the common method of one-way linked list

    // 封装单向链表类
    function LinkList(){
      // 封装一个内部类:节点类
      function Node(data){
        this.data = data;
        this.next = null;
      }

      // 属性
      // 属性head指向链表的第一个节点
      this.head = null;
      this.length = 0;
      }

append(element) How to add

      // 一.实现append方法
      LinkList.prototype.append = data => {
        //1.创建新节点
        let newNode = new Node(data)

        //2.添加新节点
        //情况1:判断是否添加的是第一个节点
        if(this.length == 0){
          this.head = newNode
        //情况2:节点数大于1,在链表的最后添加新节点  
        }else {              
          //让变量current指向第一个节点
          let current = this.head
          //当current.next(下一个节点不为空)不为空时,一直循环,直到current指向最后一个节点
          while (current.next){
            current = current.next
          }
          // 最后节点的next指向新的节点
          current.next = newNode
        }
        //3.添加完新结点之后length+1
        this.length += 1
      }

test code

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.测试append方法
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    console.log(list);  

Detailed process

First let current point to the first node:

Use the while loop to make current point to the last node, and finally make the last node point to the new node newNode through current.next = newNode

toString()

      // 实现toString方法
      LinkList.prototype.toString = () => {
        // 1.定义变量
        let current = this.head
        let listString = ""

        // 2.循环获取一个个的节点
        while(current){ 
          listString += current.data + " "
          current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点
        }
        return  listString
      }

test code

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    
    //3.测试toString方法
    console.log(list.toString());

insert(position,element)

      // 实现insert方法
      LinkList.prototype.insert = (position, data) => {
      //理解positon的含义:position=0表示新界点插入后要成为第1个节点,
      //position=2表示新界点插入后要成为第3个节点
      //1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的length
        if(position < 0 || position > this.length){
          return false
        }
        //2.根据data创建newNode
        let newNode = new Node(data)

        //3.插入新节点
        //情况1:插入位置position=0
        if(position == 0){
          // 让新节点指向第一个节点
          newNode.next = this.head
          // 让head指向新节点
          this.head = newNode
        //情况2:插入位置position>0(该情况包含position=length)
        } else{
          let index = 0
          let previous = null
          let current = this.head
          //步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)
          while(index++ < position){
          //步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点
            previous = current
            current = current.next
          }
          // 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点
          newNode.next = current
          //步骤4:通过变量previous,使position位置的前一个节点指向newNode
          previous.next = newNode
          /*
            启示:
            1.我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点(替身使者);
            比如current指向节点3,想要节点3指向节点4只需要:current.next = 4即可。
            2.两个节点间是双向的,想要节点2的前一个节点为节点1,可以通过:1.next=2,来实现;
          */
        }
        //4.新节点插入后要length+1
        this.length += 1;

        return true
      }

test code

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    
    //3.测试insert方法
    list.insert(0, '在链表最前面插入节点');
    list.insert(2, '在链表中第二个节点后插入节点');
    list.insert(5, '在链表最后插入节点');
    alert(list);
    console.log(list);

Detailed process:

The process of implementing the inset method: According to the position of the inserted node, it can be divided into several situations:

Case 1: position = 0 :

Pass: newNode.next = this.head, establish connection 1;

Pass: this.head = newNode, establish connection 2; (cannot establish connection 2 first, otherwise this.head will no longer point to Node1)

Case 2: position > 0 :

First define two variables previous and curent to point to the previous node and the next node at the position pos = X, respectively;

Then, change the point to 3 by: newNode.next = current;

Finally, change the point to 4 by: previous.next = newNode;

Special case for case 2: position = length :

Case 2 also includes the case of pos = length, in which current and newNode.next both point to null; the way to establish connection 3 and connection 4 is the same as case 2.

get(position)

      //实现get方法
      LinkList.prototype.get = (position) => {
        //1.越界判断
        // 当position = length时,取到的是null所以0 =< position < length
        if(position < 0 || position >= this.length){
          return null
        }
        //2.获取指定的positon位置的后一个节点的data
        //同样使用一个变量间接操作节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        return current.data
      }

test code

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    

    //3.测试get方法
    console.log(list.get(0));
    console.log(list.get(1));

Detailed process:

get方法的实现过程:以获取position = 2为例,如下图所示:

首先使current指向第一个节点,此时index=0;

通过while循环使current循环指向下一个节点,注意循环终止的条件index++ < position,即当index=position时停止循环,此时循环了1次,current指向第二个节点(Node2),最后通过current.data返回Node2节点的数据;

indexOf(element)

      //实现indexOf方法
      LinkList.prototype.indexOf = data => {
        //1.定义变量
        let current = this.head
        let index = 0

        //2.开始查找:只要current不指向null就一直循环
        while(current){
          if(current.data == data){
            return index
          }
          current = current.next
          index += 1
        } 

        //3.遍历完链表没有找到,返回-1
        return -1
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    
    
    //3.测试indexOf方法
    console.log(list.indexOf('aaa'));
    console.log(list.indexOf('ccc'));

过程详解:

indexOf方法的实现过程:

使用变量current记录当前指向的节点,使用变量index记录当前节点的索引值(注意index = node数-1):

update(position,element)

      //实现update方法
      LinkList.prototype.update = (position, newData) => {
        //1.越界判断
        //因为被修改的节点不能为null,所以position不能等于length
        if(position < 0 || position >= this.length){
          return false
        }
        //2.查找正确的节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //3.将position位置的后一个节点的data修改成newData
        current.data = newData
        //返回true表示修改成功
        return true
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    
    
    //3.测试update方法
    list.update(0, '修改第一个节点')
    list.update(1, '修改第二个节点')
    console.log(list);
    console.log(list.update(3, '能修改么'));

removeAt(position)

      //实现removeAt方法
      LinkList.prototype.removeAt = position => {
        //1.越界判断
        if (position < 0 || position >= this.length) {//position不能为length
          return null
        }
        //2.删除元素
        //情况1:position = 0时(删除第一个节点)
        let current = this.head
        if (position ==0 ) {
        //情况2:position > 0时
          this.head = this.head.next
        }else{
          let index = 0
          let previous = null
          while (index++ < position) {
            previous = current
            current = current.next
          }
          //循环结束后,current指向position后一个节点,previous指向current前一个节点
          //再使前一个节点的next指向current的next即可
          previous.next = current.next
        }
        //3,length-1
        this.length -= 1

        //返回被删除节点的data,为此current定义在最上面
        return current.data
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
  
  //3.测试removeAt方法
    console.log(list.removeAt(0));
    console.log(list.removeAt(0));
    console.log(list);

过程详解:

removeAt方法的实现过程:删除节点时存在多种情况:

情况1:position = 0,即移除第一个节点(Node1)。

通过:this.head = this.head.next,改变指向1即可;

虽然Node1的next仍指向Node2,但是没有引用指向Node1,则Node1会被垃圾回收器自动回收,所以不用处理Node1指向Node2的引用next。

情况2:positon > 0,比如pos = 2即移除第三个节点(Node3)。

注意:position = length时position后一个节点为null不能删除,因此position != length;

首先,定义两个变量previous和curent分别指向需要删除位置pos = x的前一个节点和后一个节点;

然后,通过:previous.next = current.next,改变指向1即可;

随后,没有引用指向Node3,Node3就会被自动回收,至此成功删除Node3 。

remove(element)、isEmpty()、size()

/*-------------其他方法的实现--------------*/
      //一.实现remove方法
      LinkList.prototype.remove = (data) => {
        //1.获取data在列表中的位置
        let position = this.indexOf(data)
        //2.根据位置信息,删除结点
        return this.removeAt(position)
      }

      //二.实现isEmpty方法
      LinkList.prototype.isEmpty = () => {
        return this.length == 0
      }

      //三.实现size方法
      LinkList.prototype.size = () => {
        return this.length
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')

/*---------------其他方法测试----------------*/
      //remove方法
      console.log(list.remove('aaa'));
      console.log(list);
      //isEmpty方法
      console.log(list.isEmpty());
      //size方法
      console.log(list.size());

完整实现

    // 封装链表类
    function LinkList(){
      // 封装一个内部类:节点类
      function Node(data){
        this.data = data;
        this.next = null;
      }

      // 属性
      // 属性head指向链表的第一个节点
      this.head = null;
      this.length = 0;

      // 一.实现append方法
      LinkList.prototype.append = data => {
        //1.创建新节点
        let newNode = new Node(data)

        //2.添加新节点
        //情况1:只有一个节点时候
        if(this.length == 0){
          this.head = newNode
        //情况2:节点数大于1,在链表的最后添加新节点  
        }else {              
          //让变量current指向第一个节点
          let current = this.head
          //当current.next(下一个节点不为空)不为空时,一直循环,直到current指向最后一个节点
          while (current.next){
            current = current.next
          }
          // 最后节点的next指向新的节点
          current.next = newNode
        }
        //3.添加完新结点之后length+1
        this.length += 1
      }

      // 二.实现toString方法
      LinkList.prototype.toString = () => {
        // 1.定义变量
        let current = this.head
        let listString = ""

        // 2.循环获取一个个的节点
        while(current){ 
          listString += current.data + " "
          current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点
        }
        return  listString
      }

      // 三.实现insert方法
      LinkList.prototype.insert = (position, data) => {
      //理解positon的含义:position=0表示新界点插入后要成为第1个节点,position=2表示新界点插入后要成为第3个节点
        //1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的length
        if(position < 0 || position > this.length){
          return false
        }
        //2.根据data创建newNode
        let newNode = new Node(data)

        //3.插入新节点
        //情况1:插入位置position=0
        if(position == 0){
          // 让新节点指向第一个节点
          newNode.next = this.head
          // 让head指向新节点
          this.head = newNode
        //情况2:插入位置position>0(该情况包含position=length)
        } else{
          let index = 0
          let previous = null
          let current = this.head
          //步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)
          while(index++ < position){
          //步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点
            previous = current
            current = current.next
          }
          // 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点
          newNode.next = current
          //步骤4:通过变量previous,使position位置的前一个节点指向newNode
          previous.next = newNode
          
        //我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点;
        }
        //4.新节点插入后要length+1
        this.length += 1;

        return true
      }

      //四.实现get方法
      LinkList.prototype.get = (position) => {
        //1.越界判断
        // 当position = length时,取到的是null所以0 =< position < length
        if(position < 0 || position >= this.length){
          return null
        }
        //2.获取指定的positon位置的后一个节点的data
        //同样使用一个变量间接操作节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        return current.data
      }

      //五.实现indexOf方法
      LinkList.prototype.indexOf = data => {
        //1.定义变量
        let current = this.head
        let index = 0

        //2.开始查找:只要current不指向null就一直循环
        while(current){
          if(current.data == data){
            return index
          }
          current = current.next
          index += 1
        } 

        //3.遍历完链表没有找到,返回-1
        return -1
      }

      //六.实现update方法
      LinkList.prototype.update = (position, newData) => {
        //1.越界判断
        //因为被修改的节点不能为null,所以position不能等于length
        if(position < 0 || position >= this.length){
          return false
        }
        //2.查找正确的节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //3.将position位置的后一个节点的data修改成newData
        current.data = newData
        //返回true表示修改成功
        return true
      }

      //七.实现removeAt方法
      LinkList.prototype.removeAt = position => {
        //1.越界判断
        if (position < 0 || position >= this.length) {
          return null
        }
        //2.删除元素
        //情况1:position = 0时(删除第一个节点)
        let current = this.head
        if (position ==0 ) {
        //情况2:position > 0时
          this.head = this.head.next
        }else{
          let index = 0
          let previous = null
          while (index++ < position) {
            previous = current
            current = current.next
          }
          //循环结束后,current指向position后一个节点,previous指向current前一个节点
          //再使前一个节点的next指向current的next即可
          previous.next = current.next
        }
        //3,length-1
        this.length -= 1

        //返回被删除节点的data,为此current定义在最上面
        return current.data
      }

/*-------------其他方法的实现--------------*/
      //八.实现remove方法
      LinkList.prototype.remove = (data) => {
        //1.获取data在列表中的位置
        let position = this.indexOf(data)
        //2.根据位置信息,删除结点
        return this.removeAt(position)
      }

      //九.实现isEmpty方法
      LinkList.prototype.isEmpty = () => {
        return this.length == 0
      }

      //十.实现size方法
      LinkList.prototype.size = () => {
        return this.length
      }
    }

Guess you like

Origin blog.csdn.net/weixin_52479225/article/details/129501120