[Data Structure and Algorithm] JavaScript implements one-way linked list

1. Introduction to one-way linked lists

Like arrays, linked lists can be usedto store a series of elements, but linked lists and arraysThe implementation mechanism is completely different. Each element of the linked list consists of a node that storesthe element itselfand areference to the next element (called pointer or connection in some languages). Similar to a locomotive, one carriage carries passengers (data) and is connected to another carriage through nodes.

Insert image description here

Insert image description here

Insert image description here

  • 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 points directly to null;

Disadvantages of arrays:

  • The creation of an array usually requires the application of a section ofcontinuous memory space (a whole block of memory), and the size is fixed. Therefore, when the original array cannot meet the capacity requirement, it needs to be expanded (generally In this case, it is to apply for a larger array, such as 2 times, and then copy the elements in the original array).
  • Inserting data at the beginning or middle of an array is expensive and requires a large number of element shifts.

Advantages of linked lists:

  • The elements in the linked list are in memoryDo not have to be a continuous space, you can make full use of the computer's memory to achieve flexibility Dynamic memory management.
  • The linked list does not have to be determined at the time of creation, and the size can be extended infinitelyGo down.
  • When the linked list inserts and deletesdata in,time complexity It can reach O(1), which is much more efficient than arrays.

Disadvantages of linked lists:

  • When accessing an element at any position in a linked list, you need toaccess it from the beginning (you cannot skip the first element to access any element) .
  • Elements cannot be accessed directly through subscript values. You need to access them one by one from the beginning until you find the corresponding element.
  • While thenext node can be reached easily, returning to theprevious node is difficult.

Common operations in linked lists:

  • 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, -1 is returned;
  • 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, you need to override the default toString method inherited from the JavaScript object so that it only outputs the value of the element;

First of all, you need to clarify: the position below refers to the relationship between two nodes, and the relationship with index is as shown in the figure below:

Insert image description here

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

2. Encapsulation of one-way linked list class

2.0. Create a one-way linked list class

First create the one-way linked list class Linklist, add basic attributes, and then implement the common methods of one-way linked lists:

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

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

Code:

      // 一.实现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
      }

Detailed explanation of the process:

  • First let current point to the first node:

Insert image description here

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

Insert image description here

Test code:

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

Test Results:

Insert image description here

2.2.toString()

Code:

      // 实现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());

Test Results:

Insert image description here

2.3.insert(position,element)

Code:

      // 实现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
      }

Detailed explanation of the process:

The process of implementing the inset method: It can be divided into many situations according to the location of the inserted node:

  • Situation 1:position = 0:

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

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

Insert image description here

  • Situation 2: position > 0:

First define two variables, previous and curent, respectively pointing to the previous node and the next node that need to be inserted at position pos = X;

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

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

Insert image description here

  • Special case of case 2: position = length:

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

Insert image description here

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);

Test Results:
Insert image description here

Insert image description here

2.4.get(position)

Code:

      //实现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
      }

Detailed explanation of the process:

The implementation process of the get method: Take getting position = 2 as an example, as shown in the following figure:

  • First, make current point to the first node, at this time index=0;

Insert image description here

  • Make the current loop point to the next node through the while loop. Pay attention to the condition for loop termination index++ < position, that is, stop the loop when index = position. At this time, it loops once, and current points to the second node (Node2). Finally, through current .data returns the data of Node2 node;

Insert image description here

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));

Test Results:

Insert image description here

2.5.indexOf(element)

Code:

      //实现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
      }

Detailed explanation of the process:

The implementation process of indexOf method:

  • Use the variable current to record the node currently pointed to, and use the variable index to record the index value of the current node (note that index = node number - 1):

Insert image description here

Test code:

	//测试代码
    //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'));

Test Results:

Insert image description here

2.6.update(position,element)

Code:

      //实现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
      }

Test code:

	//测试代码
    //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, '能修改么'));

Test Results:

Insert image description here

2.7.removeAt(position)

Code:

      //实现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
      }

Detailed explanation of the process:

Implementation process of removeAt method: There are many situations when deleting a node:

  • Case 1: position = 0, that is, remove the first node (Node1).

Pass: this.head = this.head.next, just change the pointer to 1;

Although Node1's next still points to Node2, but there is no reference pointing to Node1, Node1 will be automatically recycled by the garbage collector, so there is no need to deal with Node1's reference next pointing to Node2.

Insert image description here

  • Case 2: positon > 0, for example, pos = 2 means removing the third node (Node3).

**Note: **When position = length, the node after position is null and cannot be deleted, so position != length;

First, define two variables, previous and curent, respectively pointing to the previous node and the next node where the position pos = x needs to be deleted;

Then, pass: previous.next = current.next, and change the pointer to 1;

Subsequently, if there is no reference pointing to Node3, Node3 will be automatically recycled, and Node3 will be successfully deleted.

Insert image description here

Test code:

    //测试代码
    //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);

Test Results:

Insert image description here

2.8. Other methods

Other methods include:remove(element), isEmpty(), size()

Code:

/*-------------其他方法的实现--------------*/
      //一.实现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
      }

Test code:

    //测试代码
    //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());

Test Results:

Insert image description here

2.9. Complete implementation
    // 封装链表类
    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_46862327/article/details/134309713