Data structure - linked list

Linked list

Linked list is a non continuous and non sequential storage structure on the physical storage unit. The logical order of data elements is realized through the pointer link order in the linked list. The linked list consists of a series of nodes (each element in the linked list is called a node), which can be generated dynamically at run time. Each node consists of two parts: one is the data field that stores the data element, and the other is the pointer field that stores the address of the next node. Compared with the linear table sequential structure, the operation is complex. Because it does not have to be stored in order, the complexity of the linked list can reach O(1) when inserting, which is much faster than that of another linear list sequential table, but it takes O(n) time to find a node or access a node with a specific number, and the corresponding time complexity of the linear table and the sequential table are O(logn) and O(1) respectively.

Using the linked list structure can overcome the disadvantage that the array linked list needs to know the data size in advance. The linked list structure can make full use of the computer memory space and realize flexible memory dynamic management. However, the linked list loses the advantage of random reading of arrays. At the same time, the space overhead of the linked list is relatively large due to the increase of the pointer field of nodes. The most obvious advantage of the linked list is that the arrangement of associated items in a conventional array may be different from the order of these data items in memory or disk, and the access of data often needs to be converted in different arrangement order. The linked list allows the insertion and removal of nodes anywhere on the list, but random access is not allowed. There are many different types of linked lists: one-way linked list, two-way linked list and circular linked list. Linked lists can be implemented in a variety of programming languages. The built-in data types of languages such as Lisp and Scheme include the access and operation of linked lists. Programming languages or object-oriented languages such as C,C + + and Java rely on variable tools to generate linked lists.

Implementation of one-way linked list of leading nodes

  • Create a Node class to store some required variables and references to the next Node node~
  • To create an operation class, you first need to initialize a header node. The header node does not move or store any data. It is only used as an identification bit

Linked list insertion node (regardless of number)

  • Insert an element at the end of the linked list. Because the head node is not movable, we need to set an auxiliary node pNode as a pointer to the head node
  • When the linked list is not empty, the pointer moves back (pNode=pNode.next) until pNode.next=null, which means that pNode points to the last node in the linked list
  • When outputting the linked list, it is necessary to avoid returning the head node without data and the node with data together. Therefore, the auxiliary node will point to pNode=head.next at the beginning. When pNode=null, exit the loop.
public class SingleLinkedList {
    public static void main(String[] args) {
        SingleLinkedList list = new SingleLinkedList();
        list.add(new Node(1, "Apple", 2.00));
        list.add(new Node(2, "Pear", 2.00));
        list.add(new Node(3, "Strawberry", 2.00));
        list.display();
    }
    // Set the number of storage nodes of a variable
    private int size;
    Node head = new Node(0, "", 0.00);
    public void add(Node node) {
        Node pNode = head;
        while (true) {
            if (pNode.next == null) {
                break;
            }
            pNode = pNode.next;
        }
        pNode.next = node;
        size++;
    }
    public void display() {
        if (size == 0) {
            System.out.println("The linked list is empty!");
            return;
        }
        Node pNode = head.next;
        while(true) {
            if (pNode == null) {
                break;
            }
            System.out.println(pNode);
            pNode = pNode.next;
        }
    }
}

Linked list insertion node (inserted at the specified position)

The inserted elements need to be inserted into the linked list in the order of numbers:

  • Firstly, find the location of the newly added node through the auxiliary node
  • Point the new node newNode.next to the auxiliary node pNode.next
  • Let's talk about pNode.next pointing to newNode
  • In combination with the above three points, we need to query the added location of the new Node through traversal, and the auxiliary Node needs to point to the previous Node of the newly added location. At this time, we think that our Node node has an ID parameter. When pnode.next.id > newnode.id, the auxiliary Node points to the Node of the previous location.

Linked list delete node

Delete the specified node in the linked list (delete the node with the specified id):

  • First, make a judgment. When the linked list is empty or there is no node in the linked list, an error message is returned
  • Move backward through the auxiliary node pNode=head to find the node with the same id parameter, that is, when pNode.next.id==id, point pNode.next to the next node, that is, pNode.next=pNode.next.next, and then reduce the number of nodes by one (size –)
// Delete Vertex 
public void delete(int id) {
    if (size == 0) {
        System.out.println("The linked list is empty");
        return;
    }
    Node pNode = head;
    while(true) {
        if (pNode.next == null) {
            System.out.println("The node was not found~");
            break;
        }
        if (pNode.next.id == id) {
            pNode.next = pNode.next.next;
            size--;
            break;
        }
        pNode = pNode.next;
    }
}

Linked list modification node

Modify the properties of the specified node in the linked list:

  • Through the auxiliary node pNode, find the node equal to the id parameter of pNode, and replace the current attribute of pNode with the attribute of newNode node~
// Modify node
public void updata(Node newNode) {
    if (size == 0) {
        System.out.println("The linked list is empty");
        return;
    }
    Node pNode = head.next;
    while(true) {
        if (pNode == null) {
            System.out.println("The node was not found~");
            break;
        }
        if (newNode.id == pNode.id) {
            pNode.name = newNode.name;
            pNode.price = newNode.price;
            break;
        }
        pNode = pNode.next;
    }
}

Extension: get the penultimate node of the single linked list

  • Set a variable index to store the penultimate node. When the index is less than or equal to 0 or greater than the length of the linked list, the subscript out of bounds error will be returned!
  • Exclude the head node, set the auxiliary node pNode to point to head.next, and find the penultimate node through the for loop. At this time, we need to contact the size of the number of nodes in the single chain table for calculation. Assuming that the current node is 4, we need to find the penultimate node. At this time, pNode points to the first node through for (int i = 0; I < size index; I + +) It is found that the penultimate node, that is, the third node, can be found only by moving the pNode down twice~
// Get the penultimate node of the single linked list
public Node findLastIndexNode(int index) {
    if (index > size || index <= 0) {
        System.out.println("Subscript does not exist!");
        return null;
    }
    if (head.next == null) {
        System.out.println("The linked list is empty");
        return null;
    }
    Node pNode = head.next;
    for (int i = 0; i < size-index; i++) {
        pNode = pNode.next;
    }
    return pNode;
}

Extension: inversion of linked list

  • First define a new head node reverseHead, and then traverse the original linked list from beginning to end. Every time you traverse a node, take it out and put it at the front of the new linked list reverseHead!
  • Set an auxiliary node pNode to point to the first node head.next in the linked list except the head node
  • Set a temporary node next to save the next node of the current node pointed to by the auxiliary node pNode, that is, next=pNode.next
  • Place the node pointed to by pNode at the front of the new linked list, that is, pNode.next=reverseHead.next, and point the new head node to pNode, that is, reverseHead.next=pNode, to connect the linked list~
  • Move pNode down, that is, pNode=next
  • Finally, you only need to overwrite the head node with the new head node reverseHead (or point head.next to reverseHead.next)
// Reversal of linked list
public void reverse() {
    Node reverseHead = new Node(0, null, 0.00);
    Node pNode = head.next;
    // Set a node to store the next node of the node pointed to by the auxiliary node pNode
    Node next;
    if (pNode == null || pNode.next == null) {
        return;
    }
    while(pNode != null) {
        next = pNode.next;
        pNode.next = reverseHead.next;
        reverseHead.next = pNode;
        pNode = next;
    }
    head.next = reverseHead.next;
}

Extension: reverse output of linked list

There are two methods to print a single linked list from end to end. The first is to reverse the single linked list and then traverse the output, but this method will destroy the structure of the original single linked list. If a very long single linked list is encountered, the reverse operation will take up a lot of space; The second is to press the nodes of the single linked list into the stack in turn, and use the characteristics of last in first out of the stack to achieve the effect of reverse printing!

// Reverse printing of single linked list
public void reversePrint() {
    if (head.next == null) {
        System.out.println("The linked list is empty!");
    }
    Stack<Node> stack = new Stack<>();
    Node pNode = head.next;
    while(pNode != null) {
        stack.push(pNode); // Stack nodes
        pNode = pNode.next;
    }
    while(stack.size() > 0) {
        System.out.println(stack.pop());
    }
}

Extension: merge two single linked tables in numbering order

Merge the two linked lists into one linked list according to the numbering sequence, and print out:

  • Before that, we need a method getHead() to get the header node. Because in the above code example, we set a header node without data, so the getHead() method should return head.next. And create a method. The parameters are the head nodes head1 and head2 of the two linked lists.
  • Judge before merging. If one single linked list is empty, another single linked list will be returned.
  • Set two auxiliary nodes headNode and tailNode, point to head at first, and then compare the IDs of head1 and head2. If head1.id < head.id, point the auxiliary nodes headNode.next and tailNode to head1 node, and then move head1 node backward, that is, head1 = head1.next.
  • Establish a cycle. The cycle ends with two linked lists, one of which is empty. In the cycle body, compare the IDs of head1 and head2. If head1.id < head.id, point tailNode.next to head1, point tailNode to head1, and then move head1 backward, and vice versa.
  • When one of the linked lists is empty, for example, when head1.next=null, it indicates that there is still data not added to the other linked list, so point tailNode.next to the linked list, that is, tailNode.next=head2.
// Combine two linked lists in numbered order
public void combine(Node head1, Node head2) {
    // If one of the linked lists is empty, another linked list is returned
    if (head1 == null) {
        head.next = head2;
        return;
    }
    if (head2 == null) {
        head.next = head1;
        return;
    }
    Node headNode = head;
    Node tailNode = head;
    // Point the auxiliary head node and auxiliary tail node to the head node with the smallest id
    if (head1.id < head2.id) {
        headNode.next = head1;
        tailNode = head1;
        head1 = head1.next;
    } else {
        headNode.next = head2;
        tailNode = head2;
        head2 = head2.next;
    }
    // Add the node with smaller id to the tail node
    while(head1.next != null || head2.next != null) {
        if (head1.id < head2.id) {
            tailNode.next = head1;
            tailNode = head1;
            head1 = head1.next;
        } else {
            tailNode.next = head2;
            tailNode = head2;
            head2 = head2.next;
        }
        if (head1 == null) {
            tailNode.next = head2;
        }
        if(head2 == null) {
            tailNode.next = head1;
        }
    }
}

Implementation of bidirectional linked list of leading nodes

Bidirectional linked list, also known as double linked list, is a kind of linked list. There are two pointers in each data node, pointing to the direct successor and direct precursor respectively. Therefore, starting from any node in the two-way linked list, you can easily access its predecessor node and successor node. Generally, we construct a two-way circular linked list.

Difference between one-way linked list and two-way linked list:

  • One way linked list can only be found in one direction, while two-way linked list can be found forward or backward.

  • A single linked list cannot delete itself (use the auxiliary node to find the previous node of the node to be deleted through traversal), but a two-way linked list can.

    Node modification and traversal of bidirectional linked list

Because modifying nodes as like as two peas, there is no need to modify the pointer fields. So the modified nodes of the linked list are exactly the same as the traversal operation and the one-way linked list operation.

// Modify node
public void updata(Node newNode) {
    if (size == 0) {
        System.out.println("The linked list is empty");
        return;
    }
    Node pNode = head.next;
    while(true) {
        if (pNode == null) {
            System.out.println("The node was not found~");
            break;
        }
        if (newNode.id == pNode.id) {
            pNode.name = newNode.name;
            pNode.price = newNode.price;
            break;
        }
        pNode = pNode.next;
    }
}
    // Traversing bidirectional linked list
    public void display() {
        if (size == 0) {
            System.out.println("The linked list is empty!");
            return;
        }
        Node pNode = head.next;
        while(true) {
            if (pNode == null) {
                break;
            }
            System.out.println(pNode);
            pNode = pNode.next;
        }
    }
}

Insertion node of bidirectional linked list (at the end and at the specified position)

Since the two-way linked list has an additional pointer field pre for the one-way linked list, which points to the previous node, it is necessary to modify the pointer field pre during the insertion operation:

  • Inserting a node at the tail also requires an auxiliary node pNode. Through traversal, find the last node in the bidirectional linked list, and point the next of the last node to the new node, that is, pNode.next=newNode. Don't forget that there is a pre pointer field. We need to point the pre of the new node to the last node, that is, newNode.pre=pNode.

  • Inserting a node at the specified location is similar to inserting a node in a one-way linked list. We need to find the previous node at the inserted node location through the auxiliary node, that is, pnode.next.id > newnode.id, and then point the next field and pre field of the new node to the next node and node respectively, that is, newNode.next=pNode.next;
    newNode.pre=pNode; Then point the next field of the previous node and the pre field of the latter node to the new node, that is, pNode.next.pre=newNode;pNode.next=newNode; However, it should be noted that when the inserted node happens to be the last node, you can directly execute the method of inserting the tail node, and then cycle~

        // Add node
        public void add(Node node) {
            Node pNode = head;
            while (true) {
                if (pNode.next == null) {
                    break;
                }
                pNode = pNode.next;
            }
            pNode.next = node;
            node.pre = pNode;
            size++;
        }
        // Add node at specified location
        public void addOrderBy(Node newNode) {
            Node pNode = head.next;
            while(true) {
                if (pNode == null) {
                    break;
                }
                // When the inserted new node is just the last node of the linked list
                if (pNode.next.id < newNode.id) {
                    add(newNode);
                    break;
                }
                if (pNode.next.id > newNode.id) {
                    newNode.next = pNode.next;
                    newNode.pre = pNode;
                    pNode.next.pre = newNode;
                    pNode.next = newNode;
                    size++;
                    break;
                }
                if (pNode.next.id == newNode.id) {
                    System.out.println("Number occupied!");
                    break;
                }
                pNode = pNode.next;
            }
        }
    

Deletion of bidirectional linked list

Because the two-way linked list can be searched forward and backward, its deletion operation does not need the auxiliary node to find the previous node for deletion, but calls the pointer field of the node to be deleted for deletion, that is, self deletion!

  • Using the auxiliary node, find the node to be deleted through traversal, and point the next field of the previous node to be deleted to the next node, that is, pNode.pre.next=pNode.next; Then point the pre domain of the latter node to the previous node, that is, pNode.next.pre = pNode.pre.
  • It should be noted that when the deleted node happens to be the last node, its next node is null. When performing the above deletion operation, a null pointer exception will be reported. Therefore, before pointing the pre field of the latter node to the previous node, a judgment needs to be added, that is, pnode.next= Null, and when the deleted node happens to be the first node, we also need to add a judgment. However, we use the two-way linked list of the leading node here. The head node must not be empty, so this judgment is omitted!
// Delete Vertex 
public void delete(int id) {
    if (size == 0) {
        System.out.println("The linked list is empty");
        return;
    }
    // The deletion operation here is different from that of the single linked list
    // The auxiliary node used for deleting a single linked list refers to the head node, because we hope that the auxiliary node can find the previous node of the node to be deleted
    // The deletion of the two-way linked list does not need to find the previous node, so we can directly start searching at the first node
    Node pNode = head.next;
    while(true) {
        if (pNode == null) {
            System.out.println("The node was not found~");
            break;
        }
        if (pNode.id == id) {
            pNode.pre.next = pNode.next;
            if (pNode.next != null) {
                pNode.next.pre = pNode.pre;
            }
            size--;
            break;
        }
        pNode = pNode.next;
    }
}

Tags: data structure linked list

Posted on Sun, 19 Sep 2021 00:22:31 -0400 by Dr Evil