This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!

preface

This article introduces the “linked list” structure and uses JS code to implement the corresponding structure.

introduce

A linked list is a data structure composed of nodes, and nodes are generally composed of data and a “pointer” to record the address of the next node (two-way linked lists also have a “pointer” to record the address of the previous node).

There are many kinds of lists.

  1. Singly linked lists
  2. Circular list (or unidirectional circular list)
  3. Two-way linked list
  4. Two-way circular list
  5. Lead list (acyclic list with sentinel nodes)

Note: several terms will be used in this article. We generally call the next node of a node a “successor node”, and the “pointer” of a node that records the address of the next node a “successor pointer”; The previous node of the node is called the “precursor node”, and the “pointer” of the node that records the address of the previous node is called the “precursor pointer”.

Singly linked lists

A singly linked list has two special nodes: the head node and the tail node.

The header node records the initial address and is not pointed to by any “successor pointer” to the node.

The tail node records the last address, and the “successor pointer” points to null.

Note: The list code below records the end node. In fact, you can implement the list structure without recording the end node at all.

Js code implementation

    / / node
    class Node {
      constructor(data) {
        this.data = data
        this.next = null}}/ / singly linked lists
    class LinkedList {
      #size = 0 // List length
      #head = null / / head node
      #tail = null / / end nodes

      /* 增 */
      // Add a node to the end
      append(data) {
        let node = new Node(data)
        if (this.#tail) {
          this.#tail.next = node
          this.#tail = node
        } else {
          this.#head = node
          this.#tail = node
        }
        this.#size++
      }
      // Add nodes based on location (location is 0)
      insert(index, data) {
        if (index > this.#size || index < 0) {
          return false
        }
        let node = new Node(data)
        if (this.#size === 0) {
          // Insert directly without length
          this.#head = node
          this.#tail = node
        } else if (index == this.#size) {
          // End node, special operation
          this.#tail.next = node
          this.#tail = node
        } else if (index == 0) {
          // Head node, special operation
          node.next = this.#head
          this.#head = node
        } else {
          // There must be intermediate nodes
          let target = this.#head
          let prev = null
          for (let i = 1; i <= index; i++) {
            prev = target
            target = target.next
          }
          node.next = target
          prev.next = node
        }
        this.#size++
        return true
      }

      / * * / delete
      // Delete the node closest to the relevant value
      delete(data) {
        let node = this.#head
        let prev = null
        while (node) {
          if (node.data === data) {
            if (node === this.#head) { // The node to be deleted is the head node, which needs to be changed
              if (this.#head === this.#tail) {
                this.#head = null
                this.#tail = null
              } else {
                this.#head = this.#head.next
              }
            } else if (node === this.#tail) { // The deleted node is the tail node, and the tail node needs to be changed
              prev.next = null
              this.#tail = prev
            } else {
              prev.next = node.next
            }
            this.#size--
            return true
          } else {
            prev = node
            node = node.next
          }
        }
        return false
      }
      // Delete nodes at relevant locations
      deleteIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return false
        }
        if (index === 0) { // Delete the header node
          if (this.#head === this.#tail) {
            this.#head = null
            this.#tail = null
          } else {
            this.#head = this.#head.next
          }
        } else {
          let node = this.#head
          let prev = null
          for (let i = 1; i <= index; i++) {
            prev = node
            node = node.next
          }
          if (node === this.#tail) {
            prev.next = null
            this.#tail = prev
          } else {
            prev.next = node.next
          }
        }
        this.#size--
        return true
      }

      /* 查 */
      // List length
      size() {
        return this.#size
      }
      // Get the header node
      head() {
        return this.#head
      }
      // Get the tail node
      tail() {
        return this.#tail
      }
      // Find the node that first appeared
      find(data) {
        let node = this.#head
        while (node) {
          if (node.data === data) {
            return node
          } else {
            node = node.next
          }
        }
      }
      // Find the node by location (location is 0)
      findIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return
        }
        if (index == 0) {
          return this.#head
        }
        if (index == this.#size - 1) {
          return this.#tail
        }
        let node = this.#head
        for (let i = 1; i <= index; i++) {
          node = node.next
        }
        return node
      }
      // Find the first occurrence of the value (position is 0)
      indexOf(data) {
        let node = this.#head
        let i = 0
        while (node) {
          if (node.data === data) {
            return i
          } else {
            node = node.next
            i++
          }
        }
        return -1
      }
      // Whether to be empty
      isEmpty() {
        return this.#size === 0 ? true : false
      }
      // Print (from beginning to end)
      print() {
        if (this.#size === 0) {
          console.log('Empty list, head node:'.this.#head, 'Tail node:'.this.#tail)
          return
        }
        let node = this.#head
        while (node) {
          console.log(node.data)
          node = node.next
        }
        console.log('Head node:'.this.#head)
        console.log('Tail node:'.this.#tail)
        console.log(Total length: + this.#size)
      }
    }

Copy the code

Unidirectional circular linked list

The unidirectional circular list is derived from the single linked list. The difference is that the end node of the unidirectional circular list no longer points to null, but to the head node, forming a ring structure.

This structure is particularly useful for solving the Joseph problem.

Js code implementation

    / / node
    class Node {
      constructor(data) {
        this.data = data
        this.next = null}}// Circular list
    class CircleLinkedList {
      #size = 0 // List length
      #head = null / / head node
      #tail = null / / end nodes

      /* 增 */
      // Add a node to the end
      append(data) {
        let node = new Node(data)
        if (this.#tail) {
          this.#tail.next = node
          this.#tail = node
          // Reconnect the end node to the end node
          this.#tail.next = this.#head
        } else {
          this.#head = node
          this.#tail = node
          this.#tail.next = this.#head
        }
        this.#size++
      }
      // Add nodes based on location (location is 0)
      insert(index, data) {
        if (index > this.#size || index < 0) {
          return false
        }
        let node = new Node(data)
        if (this.#size === 0) {
          // Insert directly without length
          this.#head = node
          this.#tail = node
        } else if (index == this.#size) {
          // End node, special operation
          this.#tail.next = node
          this.#tail = node
          this.#tail.next = this.#head
        } else if (index == 0) {
          // Head node, special operation
          node.next = this.#head
          this.#head = node
          this.#tail.next = this.#head
        } else {
          // There must be intermediate nodes
          let target = this.#head
          let prev = null
          for (let i = 1; i <= index; i++) {
            prev = target
            target = target.next
          }
          node.next = target
          prev.next = node
        }
        this.#size++
      }

      / * * / delete
      // Delete the node closest to the relevant value
      delete(data) {
        let node = this.#head
        let prev = null
        while (node) {
          if (node.data === data) {
            if (node === this.#head) {
              // The node to be deleted is the head node, which needs to be changed
              if (this.#head === this.#tail) {
                this.#head = null
                this.#tail = null
                node.next = null
              } else {
                this.#head = node.next
                this.#tail.next = this.#head
              }
            } else if (node === this.#tail) {
              // The deleted node is the tail node, and the tail node needs to be changed
              prev.next = this.#head
              this.#tail = prev
            } else {
              prev.next = node.next
            }
            node.next = null // Set next to null for the node to be deleted
            this.#size--
            return true
          } else {
            if (node === this.#tail) {
              break
            }
            prev = node
            node = node.next
          }
        }
        return false
      }
      // Delete nodes at relevant locations
      deleteIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return false
        }
        if (index === 0) {
          if (this.#head === this.#tail) {
            this.#head = null
            this.#tail = null
          } else {
            this.#head = this.#head.next
            this.#tail.next = this.#head
          }
        } else {
          let node = this.#head
          let prev = null
          for (let i = 1; i <= index; i++) {
            prev = node
            node = node.next
          }
          if (node === this.#tail) {
            prev.next = this.#head
            this.#tail = prev
          } else {
            prev.next = node.next
          }
          node.next = null
        }
        this.#size--
        return true
      }

      /* 查 */
      // List length
      size() {
        return this.#size
      }
      // Get the header node
      head() {
        return this.#head
      }
      // Get the tail node
      tail() {
        return this.#tail
      }
      // Find the node that first appeared
      find(data) {
        let node = this.#head
        while (node) {
          if (node.data === data) {
            return node
          } else if (node === this.#tail) {
            break
          } else {
            node = node.next
          }
        }
      }
      // Find the node by location (location is 0)
      findIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return
        }
        if (index == 0) {
          return this.#head
        }
        if (index == this.#size - 1) {
          return this.#tail
        }
        let node = this.#head
        for (let i = 1; i <= index; i++) {
          node = node.next
        }
        return node
      }
      // Find the first occurrence of the value (position is 0)
      indexOf(data) {
        let node = this.#head
        let i = 0
        while (node) {
          if (node.data === data) {
            return i
          } else if (node === this.#tail) {
            break
          } else {
            node = node.next
            i++
          }
        }
        return -1
      }
      // Whether to be empty
      isEmpty() {
        return this.#size === 0 ? true : false
      }
      // Print (from beginning to end)
      print() {
        if (this.#size === 0) {
          console.log('Empty list, head node:'.this.#head, 'Tail node:'.this.#tail)
          return
        }
        let node = this.#head
        while (node) {
          console.log(node.data)
          if (node === this.#tail) {
            break
          } else {
            node = node.next
          }
        }
        console.log('Head node:'.this.#head)
        console.log('Tail node:'.this.#tail)
        console.log('Is the node next to the tail node the head node?'.this.#head === this.#tail.next)
        console.log(Total length: + this.#size)
      }
    }

Copy the code

Two-way linked list

Compared with the unidirectional linked list, the bidirectional linked list has a “precursor pointer”, which is used to record the address of the previous node. Because the bidirectional linked list has both a “precursor pointer” and a “successor pointer”, the bidirectional linked list takes up much more memory than the unidirectional linked list. However, even if bidirectional linked lists take up more memory, we prefer to use this structure in development, because this structure can find nodes forward, finding data is more efficient than unidirectional linked lists, and in some insert or delete operations, it is much easier to manipulate the previous node to connect to another node. A bidirectional linked list is a typical “space for time” structure.

Note here that the following method, findIndex, no longer blindly traverses from beginning to end like a single linked list. Since the bidirectional list can be traversed from the end to the end, here first determine whether the index is less than size/2, if less than, then traversal from the beginning to the end, if greater than, then traversal from the end to the end.

Js code implementation

// Add a pointer to the node
    class Node {
      constructor(data) {
        this.data = data
        this.next = null
        this.prev = null}}// Two-way linked list
    class DoubleLinkedList {
      #size = 0 // List length
      #head = null / / head node
      #tail = null / / end nodes

      /* 增 */
      // Add a node to the end
      append(data) {
        let node = new Node(data)
        if (this.#tail) {
          this.#tail.next = node
          node.prev = this.#tail
          this.#tail = node
        } else {
          this.#head = node
          this.#tail = node
        }
        this.#size++
      }
      // Add nodes based on location (location is 0)
      insert(index, data) {
        if (index > this.#size || index < 0) {
          return false
        }
        let node = new Node(data)
        if (this.#size === 0) {
          // Insert directly without length
          this.#head = node
          this.#tail = node
        } else if (index == this.#size) {
          console.log(123)
          // End node, special operation
          node.prev = this.#tail
          this.#tail.next = node
          this.#tail = node
        } else if (index == 0) {
          // Head node, special operation
          this.#head.prev = node
          node.next = this.#head
          this.#head = node
        } else {
          // There must be intermediate nodes
          let target = this.#head
          for (let i = 1; i <= index; i++) {
            target = target.next
          }
          node.next = target
          node.prev = target.prev
          target.prev.next = node // Note this step
          target.prev = node
        }
        this.#size++
        return true
      }

      / * * / delete
      // Delete the node closest to the relevant value
      delete(data) {
        let node = this.#head
        while (node) {
          if (node.data === data) {
            if (node === this.#head) { // The node to be deleted is the head node, which needs to be changed
              if (this.#head === this.#tail) {
                this.#head = null
                this.#tail = null
              } else {
                this.#head = this.#head.next
                this.#head.prev = null}}else if (node === this.#tail) { // The deleted node is the tail node, and the tail node needs to be changed
              node.prev.next = null
              this.#tail = node.prev
            } else {
              node.next.prev = node.prev
              node.prev.next = node.next
            }
            this.#size--
            return true
          } else {
            node = node.next
          }
        }
        return false
      }
      // Delete nodes at relevant locations
      deleteIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return false
        }
        if (index === 0) { // Delete the header node
          if (this.#head === this.#tail) {
            this.#head = null
            this.#tail = null
          } else {
            this.#head = this.#head.next
            this.#head.prev = null}}else if (index === this.#size-1) { // Delete the tail node
          this.#tail.prev.next = null
          this.#tail = this.#tail.prev
        } else {
          // let node = this.#head
          // for (let i = 1; i <= index; i++) {
          // node = node.next
          // }
          let node = this.findIndex(index)
          if (node) {
            node.next.prev = node.prev
            node.prev.next = node.next
          } else {
            return false}}this.#size--
        return true
      }

      /* 查 */
      // List length
      size() {
        return this.#size
      }
      // Get the header node
      head() {
        return this.#head
      }
      // Get the tail node
      tail() {
        return this.#tail
      }
      // Find the node that first appeared
      find(data) {
        let node = this.#head
        while (node) {
          if (node.data === data) {
            return node
          } else {
            node = node.next
          }
        }
      }
      // Find the node by location (location is 0)
      findIndex(index) {
        if (index > this.#size - 1 || index < 0) {
          return
        }
        if (index == 0) {
          return this.#head
        }
        if (index == this.#size - 1) {
          return this.#tail
        }
        // If index is to the left or right of size/2, you can find the data faster
        let node
        if (index < this.#size/2) {
          node = this.#head
          for (let i = 1; i <= index; i++) {
            node = node.next
          }
        } else {
          node = this.#tail
          for (let i = this.#size-1; i > index; i--) {
            node = node.prev
          }
        }
        return node
      }
      // Find the first occurrence of the value (position is 0)
      indexOf(data) {
        let node = this.#head
        let i = 0
        while (node) {
          if (node.data === data) {
            return i
          } else {
            node = node.next
            i++
          }
        }
        return -1
      }
      // Whether to be empty
      isEmpty() {
        return this.#size === 0 ? true : false
      }
      // Print (isPrev: false, start to end; True, from end to end)
      print(isPrev) {
        if (this.#size === 0) {
          console.log('Empty list, head node:'.this.#head, 'Tail node:'.this.#tail)
          return
        }
        if(! isPrev) {console.log('Print from beginning to end')
          let node = this.#head
          while (node) {
            console.log(node.data)
            node = node.next
          }
        } else {
          console.log('Print from end to end')
          let node = this.#tail
          while(node) {
            console.log(node.data)
            node = node.prev
          }
        }
        console.log('Head node:'.this.#head)
        console.log('Tail node:'.this.#tail)
        console.log(Total length:.this.#size)
      }
    }
Copy the code

Two-way circular list

Similar to unidirectional linked list, bidirectional circular linked list is based on bidirectional linked list, and the “successor pointer” of the tail node points to the head node.

Look at the above unidirectional circular linked list and two-way linked list, I believe you have a two-way circular linked list has a certain idea, here is not implemented.

Leading the list

In both unidirectional and bidirectional lists, insert a new head node and delete a new head node. (Circular lists are not considered here, because circular lists have no boundaries.)

Singly linked list, for example, our normal insert operation, is the previous node “subsequent Pointers point to the new node, the new node of the” subsequent Pointers point to the next node, but if the insert is the new head node, because before a node does not exist, we only need the new head node “subsequent pointer pointing to the original head node.

The normal deletion operation is to point the “successor pointer” of the previous node to the next node to be deleted. However, if the node to be deleted is the head node, since the previous node does not exist, we only need to refer the head node to the next node of the original head node.

Now, some people might wonder, is there a special operation for inserting or deleting a tail node?

Don’t.

Insert the end node node operation, pseudocode

node.next = tail.next // First, the new tail node's "successor pointer" points to the node next to the original tail node, which is null
tail.next = node // Then point the original tail node "successor pointer" to the new tail node
Copy the code

This operation is exactly the same as inserting a new node in the middle of the list, except that the “successor pointer” to the last node of the new insertion will point to null instead of a node. But if you’re writing code that needs to keep track of the tail node at all times, as I did above, and your LinkedList has both #head and #tail, you do need a little extra operation to point #tail to the new tail node. In fact, you can write your list without having to keep track of the tail node at all times.

Get back to business, due to the boundary problems, it is easy to miss mistakes when writing code, so it can be joined a special nodes “sentinel node” to solve the problem of the boundary, the sentinel node “” subsequent pointer pointing to the head node, therefore” sentinel node “instead of the original head node, as the new head node, The Sentinel node does not store any data; its data is always null.

Let’s look at the insert operation. If I want to insert the first new node with data, we need to point the sentinel’s successor pointer to the new node, which points to the original first node with data. Is this exactly the same as inserting a new node in the middle of the list?

Delete the same, here do not do detailed description.

Code implementation is also very simple, as long as the above unidirectional linked list based on a slightly modified, the original head node to sentinel node, and then a little change, here is not implemented.

practice

List although is a very basic structure, and I’ve used the Java language to realize again, but I still problems in the process of writing, it took me some time to debug, these errors included “border didn’t handle”, and “pointer refers to the right place”, “forget to put on a node pointer refers to the new nodes” and so on.

I have made a small summary, I hope to be useful to you, when writing a linked list, pay attention to the following points:

  • Singly linked lists
  1. Insert or delete header nodes
  2. When you have only one node and you want to delete that node, you assign null to both the head node and the tail node
  • Circular linked list
  1. Insert or delete a head node, noting the connection between the head node and the tail node
  2. Inserting or deleting a tail node, note the connection between the head node and the tail node
  3. When iterating through the data, be careful to avoid an infinite loop and break out of the loop when you hit the tail node
  • Two-way linked list
  1. Delete or insert the middle node, notice the next and prev pointing, can first draw in the notebook, with arrows to show the relationship between nodes, which relationship to cut off, which relationship to connect
  2. Because bidirectional lists can find nodes forward, some query operations can be optimized (such as findIndex for “bidirectional lists” above)

Common progress

Welcome to like, follow, collect and share. If there are any mistakes, please go to the comments section to correct them. Without authorization, reproduction is strictly prohibited. If you need to reproduce, please contact me for authorization. O ~) ~)o