Data structure - linked list

Posted by booga1138 on Fri, 26 Nov 2021 10:42:56 +0100

Linked list structure

There are many kinds of linked list structures. Today, I will focus on three most common linked list structures: single linked list, two-way linked list and circular linked list. Let's first look at the simplest and most commonly used single linked list.

Single linked list

We habitually call the first node the head node and the last node the tail node. The header node is used to record the base address of the linked list. With it, we can traverse the whole linked list. The tail node is special: the pointer does not point to the next node, but to an empty address

NULL indicates that this is the last node on the linked list.

For the insertion and deletion of linked list, we only need to consider the pointer change of adjacent nodes, so the corresponding time complexity is O(1). The performance of random access to linked lists is not as good as that of arrays, which requires O(n) time complexity.

Circular linked list

Circular linked list is a special single linked list. In fact, the circular linked list is also very simple. The only difference between it and a single linked list is the tail node. We know that the tail node pointer of the single linked list points to the null address, indicating that this is the last node. The tail node pointer of the circular linked list points to the head node of the linked list. From the circular list diagram I drew, you should see that it is connected end to end like a ring, so it is called "Circular" list.

It can be seen from the diagram I drew that the two-way linked list needs two additional spaces to store the addresses of subsequent nodes and precursor nodes. Therefore, if the same amount of data is stored, the two-way linked list occupies more memory space than the single linked list. Although two pointers waste storage space, they can support two-way traversal, which also brings the flexibility of two-way linked list operation. Compared with single linked list, what kind of problem is bidirectional linked list suitable for? Structurally, two-way linked lists can support

Finding the precursor node in the case of O(1) time complexity is such a feature, which also makes the insertion and deletion of the two-way linked list simpler and more efficient than the single linked list in some cases.

The head node is the first node, undefined, and the tail node points to an empty address

The node with Sentry is conducive to simplifying the code, which is recommended

Bidirectional linked list

Circular linked list is a special single linked list. In fact, the circular linked list is also very simple. The only difference between it and a single linked list is the tail node. We know that the tail node pointer of the single linked list points to the null address, indicating that this is the last node. The tail node pointer of the circular linked list points to the head node of the linked list. From the circular list diagram I drew, you should see that it is connected end to end like a ring, so it is called "Circular" list.

Bidirectional circular linked list

Understand the circular linked list and two-way linked list. If the two linked lists are integrated together, it is a new version: two-way circular linked list.

Example of bidirectional linked list [to be added]

Must practice operation

Interface definition

package com.s1.array;
public interface IList {
    void print();
    // Add to tail
    void add(Integer data);
    void addFirst(Integer data);
    void addLast(Integer data);
    // Add an element at the specified location (within the valid range)
    void add(int position, Integer data);
    // Delete to tail
    Object remove();
    // Delete first element
    Object removeFirst();
    // Delete tail element
    Object removeLast();
    // Deletes the element with the specified subscript
    Object remove(int index);
    // Deletes the element of the specified data
    Object remove(Object obj);  
    // Change 1: find the subscript and change it
    boolean updateByPosition(int position, Integer value);
    // Change 2: find the original data and change it
    boolean updateByData(Integer oldData, Integer newData);
    void clear();   
}
  • Realize single linked list [optional circular linked list and two-way linked list], and support addition and deletion operations
  • Single linked list reverse chain
  • Detection of ring in table
  • Merge two ordered linked lists
  • Delete the penultimate node of the linked list
  • Find the middle node of the linked list

Thinking problem: LRU algorithm based on linked list

LRU idea I

  1. If this data has been cached in the linked list before, we traverse the node corresponding to this data, delete it from its original position, and then insert it into the head of the linked list.
  2. If this data is not in the cache linked list, it can be divided into two cases: undefined. If the cache is not full at this time, insert this node directly into the head of the linked list; Undefined if the cache is full at this time, the tail node of the linked list will be deleted and the new data node will be inserted into the head of the linked list.

Or idea two

    /**
     * If it doesn't exist 
     *   Insert if queue is not full tail
     *   Queue full remove head And insert from tail         
     * If present, remove from and insert from tail
     */ 
package com.s2.link;
public class LRULinkedList extends LinkedList {
    private static final int DEFAULT_LENGTH = 10;
    private final int length;
    private int used = 0;
    public LRULinkedList() {
        this(DEFAULT_LENGTH);
    }
    public LRULinkedList(int length) {
        this.length = length;
    }
    protected boolean isFull () {
        return this.used == this.length;
    }
    @Override
    public void add(Integer data) {
        /**
         * If it doesn't exist 
         *   Insert if queue is not full tail
         *   Queue full remove head And insert from tail         
         * If present, remove from and insert from tail
         */ 
        Object removeNode = this.remove(data);
        if (removeNode == null && this.isFull()) {
            this.removeFirst();     
        }
        this.addLast(data);
    }
    
    @Override
    public Object remove(Object obj) {
        Object removeObject = super.remove(obj);
        if (removeObject != null) {
            this.used--;
        }       
        return removeObject;
    }
    @Override
    public Object removeFirst() {
        Object removeObject = super.removeFirst();
        if (removeObject != null) {
            this.used--;
        }       
        return removeObject;
    }
    @Override
    public void addLast(Integer data) {
        super.addLast(data);
        used++;
    }
}

Thinking question: judge whether it is palindrome string

Find the intermediate node. According to the parity number.

If it is an odd number, separate them.

If it is an even number, it is considered that there are two midpoint and continue to separate.

Then get the head pointers at both ends and cycle. If the data of the node is inconsistent, it is determined that it is not a palindrome string. If the loop ends, the string is considered a palindrome string.

code snippet

    //Determine whether it is palindrome 
    public boolean palindrome() {
        // Find the intermediate node according to the speed pointer, but do not know whether the number of summary points is odd or even
        if (this.headNode == null) {
            return false;
        }
        if (this.headNode.next == null) {
            return true;
        }
        // Greater than two nodes
        Node slow = this.headNode;
        Node fast = this.headNode;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        //  slow  fast
        //  1     2
        //  1     2   3
        System.out.println("slow " + slow);
        System.out.println("fast " + fast);
        Node leftNode;
        Node rightNode;
        // Total odd number, one key point
        if (fast.next == null) {
            rightNode = slow.next;
            this.inverseLinkList(slow);
            leftNode = slow.next;
        } 
        //  Total even number, two midpoint 
        else {
            rightNode = slow.next;
            this.inverseLinkList(slow);
            leftNode = slow;
        }           
        return this.TFResult(leftNode, rightNode);
    }

My code

https://gitee.com/kaiLee/struct/tree/master/src/main/java/com/s2