Headless bidirectional acyclic linked list (Java language)

preface

After being familiar with sequential list and single linked list, we have a simple understanding of data structure. This time, we understand the relevant knowledge of headless two-way linked list.

introduce

First, let's take a look at the structure of the two-way linked list:

This is a very simple two-way linked list. A node can find its successors and precursors. The first is the head node (not the head node of the header linked list), and the last is the last node. Why do you need a two-way linked list? When doing questions, we often encounter the need to remember the precursor of a node to prevent the next of the node from being overwritten and unable to find the next of the node. Therefore, we construct a two-way linked list to solve this problem, which also improves the efficiency.

Headless bidirectional acyclic linked list

In the headless bidirectional acyclic linked list, we call each element a node. Therefore, we encapsulate the node into a class:

Node class - ListNode

class ListNode {
    public int val;
    public ListNode prev;
    public ListNode next;

    public ListNode(int val) {
        this.val = val;
    }
}

There are three member variables in the class, val (value), next (address of the next node), prev (address of the previous node). The construction method is to assign the val value to the val of the object, which is a node. We connect several nodes to form a linked list.

Linked list class - MyLinkedList

public class MyLinkedList {
    public ListNode head;//Point to the head node of the bidirectional linked list
    public ListNode last;//It points to the tail node
    }

Instantiate two nodes in the linked list class, head and last, which point to the first node and the last node respectively.

Print linked list

//Print linked list
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

Printing a two-way linked list is the same as printing a one-way linked list. Use a cur node to traverse the entire array, and then print.

Get the length of the linked list

//Get the length of the linked list
    public int size() {
        ListNode cur = head;
        int size = 0;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        return size;
    }

Similarly, as for the length of a single linked list, instantiate a node and count how many nodes have been traversed with a counter while traversing.

Find whether the keyword key is included

    //Find whether the keyword key is included
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

The same is true for whether the query contains keywords. When traversing, it compares whether it is equal to the keyword key. If it is equal, it returns true. If it is not equal, it traverses down until it walks through the complete linked list and returns false.

Insert an element - header insertion

    //Head insertion
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }

The head insertion method of double linked list is a little more complex than that of single linked list because it needs to change the value of prev. Let's draw a picture to understand:

Now there is a node that is inserted into the linked list with the header insertion method. How can it be completed? We found that we need to assign the head to the next of node, assign the prev of head to node, and move the head to the position of node. like this:

This completes the header insertion method, but we should note that this is the case when the linked list is not null. Therefore, we should take into account the case when the linked list is null. When the linked list is null, we only need to point to both head and last to node.

Insert an element -- tail interpolation

//Tail interpolation
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = last.next;
        }
    }

The tail interpolation method is very similar to the head interpolation method. There will not be too much introduction here. If you are interested, you can draw a diagram according to the head interpolation method and write your own code.

Insert an element -- anywhere

For convenience, we write the function of finding nodes as a separate method:

Find node at index location

    //Find node at index location
    public ListNode searchIndex(int index) {
        if (index < 0 || index > size()) {
            System.out.println("index Illegal location");
            return null;
        }
        ListNode cur = head;
        while (index > 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    //Insert at any position, and the first data node is subscript 0
    public void addIndex(int index, int data) {
        ListNode node = new ListNode(data);
        ListNode cur = this.searchIndex(index);
        if (index == 0) {
            this.addFirst(data);
        } else if (index == size()) {
            this.addLast(data);
        } else {
            node.next = cur;
            cur.prev.next = node;
            node.prev = cur.prev;
            cur.prev = node;
        }

    }

Insertion at any position can be divided into three cases. First, if the index is 0, the header insertion method can be used; Second, if the index is size () (the length of the linked list), you can use the tail interpolation method; Third, insert the middle position, which we need to modify ourselves. Let's focus on the third case. for instance:

First, insert node into the position with index 2, that is, insert 0x888 in front of 0x345. Then we need to change four places: prev of 0x888, next of 0x888, prev of 0x345 and next of 0x234, that is, assign prev of node to 0x234, next of node to 0x345, 0x234 to 0x888 and prev of 0x345 to 0x888, like this:

This completes the intermediate insertion. The searchIndex () method we wrote is to find the specified location and assign the return value to cur. If the index does not meet the requirements, an error will be reported.

Delete the node where the key appears for the first time

    //Delete the node where the key appears for the first time
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else {
                        last = null;
                    }
                } else if (cur == last) {
                    last = last.prev;
                    last.next = cur.next;
                } else {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
                return;
            } else {
                cur = cur.next;
            }
        }
        System.out.println("There is no node you want to delete!");
    }

Similarly, deleting the first key node can be divided into three cases. First, the key node we want to delete when the header node is used; Second, the tail node is the node we want to delete; Third, the intermediate node is deleted. Let's look at the first case: delete the header node

Suppose we want to delete 12, we instantiate a node cur, which is used to traverse the entire linked list. When cur.val=12, we judge whether cur is equal to head. When cur==head node, we move the head back a node to 0x234, and set head.prev to null. like this:

Then the deletion of the head node jumps out of the loop, but there is a bug here. If the head is null, the null pointer exception will be reported if the head.prev is assigned null, that is, when there is only one node in the linked list. Therefore, we need to judge again whether the head is null when the cur is head. If it is null, the last is also assigned null (when there is only one node, head and last point to this node at the same time, so head is already null, we only need to assign last null). Otherwise, head.prev is assigned null. Like this:

Therefore, this completes the deletion when there is only one node, which further reflects the preciseness of the data structure. Let's look at the second case: deleting the tail node.

This time, delete the last node 56. At this time, cur.val is 56 and cur is last. We will move last to the previous node (last=last.prev), and then assign last.next to cur.next (null). This completes the deletion of the tail node. Like this:

Let's look at the third case: delete the intermediate node, if we want to delete 34 this node.

At this time, cur.val is 34, so we need to change the next value of cur predecessor node (0x234) and the prev value of cur successor node (0x456). Assign cur.prev (predecessor node). Next to cur.next and cur.next (successor node). Prev to cur.prev. This completes the deletion of intermediate nodes. Like this:

Delete all key nodes

    //Delete all key nodes
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else {
                        last = null;
                    }
                } else if (cur == last) {
                    last = last.prev;
                    last.next = cur.next;
                } else {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
                //return;
            } else {
                cur = cur.next;
            }
        }
    }

With the above assistance, it is much easier to delete all key nodes. We know that when deleting the first key node, the cycle will jump out after deletion. However, to delete all key nodes, you only need to traverse the linked list until cur is null.

Empty linked list

    //Empty linked list
    public void clear() {
        while (head != null) {
            ListNode cur = head.next;
            head.prev = null;
            head.next = null;
            head = cur;
        }
        last = null;
    }

There are two methods to empty the linked list - "violent" emptying and "gentle" emptying. Until we empty the linked list, there is no variable to reference it, so "violent" emptying the linked list is very simple. We only need to set both head and last to null, and "gentle" emptying is our code above, which uses the traversal method to set all nodes to null, Of course, in the end, you can't forget to set last to null, which completes the "gentle" emptying of the linked list.

Total code

class ListNode {
    public int val;
    public ListNode prev;
    public ListNode next;

    public ListNode(int val) {
        this.val = val;
    }
}

public class MyLinkedList {
    public ListNode head;//Point to the head node of the bidirectional linked list
    public ListNode last;//It points to the tail node

    //Print linked list
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    //Get the length of the linked list
    public int size() {
        ListNode cur = head;
        int size = 0;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        return size;
    }

    //Find whether the keyword key is included
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //Head insertion
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }

    //Tail interpolation
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = last.next;
        }
    }

    //Delete the node where the key appears for the first time
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else {
                        last = null;
                    }
                } else if (cur == last) {
                    last = last.prev;
                    last.next = cur.next;
                } else {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
                return;
            } else {
                cur = cur.next;
            }
        }
        System.out.println("There is no node you want to delete!");
    }

    //Delete all key nodes
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                if (cur == head) {
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else {
                        last = null;
                    }
                } else if (cur == last) {
                    last = last.prev;
                    last.next = cur.next;
                } else {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
                //return;
            } else {
                cur = cur.next;
            }
        }
    }

    //Find node at index location
    public ListNode searchIndex(int index) {
        if (index < 0 || index > size()) {
            System.out.println("index Illegal location");
            return null;
        }
        ListNode cur = head;
        while (index > 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

    //Insert at any position, and the first data node is subscript 0
    public void addIndex(int index, int data) {
        ListNode node = new ListNode(data);
        ListNode cur = this.searchIndex(index);
        if (index == 0) {
            this.addFirst(data);
        } else if (index == size()) {
            this.addLast(data);
        } else {
            node.next = cur;
            cur.prev.next = node;
            node.prev = cur.prev;
            cur.prev = node;
        }

    }
    //Empty linked list
    public void clear() {
        while (head != null) {
            ListNode cur = head.next;
            head.prev = null;
            head.next = null;
            head = cur;
        }
        last = null;
    }
}

Well, there is so much about the whole process of creating headless two-way linked list. The knowledge of data structure will come to an end here. Other data structures will be introduced later. Thank you for your praise. If you have any questions or suggestions, welcome private letters and comments. Thank you!

Tags: Java data structure linked list

Posted on Mon, 06 Dec 2021 18:06:31 -0500 by Levan