Data Structures and Algorithms (2): Linked List

overview

definition

In computer science, a linked list is a linear collection of data elements, each of which points to the next element, and the element storage is not continuous

In computer science, a linked list is a linear collection of data elements whose order is not given by their physical placement in memory. Instead, each element points to the next.

Can be classified as [^5]

  • Singly linked list, each element only knows who the next element is

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-DJ6m944t-1679589982443)(.\imgs\image-20221110083407176.png)]

  • Doubly linked list, each element knows its previous element and next element

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-SZhvWbP1-1679589982443)(.\imgs\image-20221110083427372.png)]

  • In a circular linked list, the tail node tail of the usual linked list points to null, while the tail of the circular linked list points to the head node head

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-0v0nt7lE-1679589982443)(.\imgs\image-20221110083538273.png)]

There is also a special node in the linked list called a Sentinel node, also called a Dummy node, which does not store data and is usually used as the head and tail to simplify boundary judgment, as shown in the figure below

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-RUvDkzMz-1679589982444)(.\imgs\image-20221110084611550.png)]

random access performance

Search according to index, time complexity O ( n ) O(n)O ( n )

Insert or delete performance

  • Starting position: O ( 1 ) O(1)O(1)
  • End position: if the tail node is known is O ( 1 ) O(1)O ( 1 ) , don't know tail node isO(n) O(n)O ( n )
  • Middle position: lookup time according to index + O ( 1 ) O(1)O(1)

singly linked list

According to the definition of a one-way linked list, first define a class Node that stores value and next pointers, and a reference that describes the head node

public class SinglyLinkedList {
    
    
    
    private Node head; // 头部节点
    
    private static class Node {
    
     // 节点类
        int value;
        Node next;

        public Node(int value, Node next) {
    
    
            this.value = value;
            this.next = next;
        }
    }
}
  • Node is defined as an internal class to hide the implementation details from the outside , and there is no need for class users to care about the Node structure
  • Defined as a static inner class, because Node does not need to be related to the SinglyLinkedList instance, and multiple SinglyLinkedList instances can share the Node class definition

head added

public class SinglyLinkedList {
    
    
     /**
     * 头部添加一个节点
     * @param value
     */
    public void addFirst(int value) {
    
    
		this.head = new Node(value, this.head);
    }
}
  • If this.head == null, the new node points to null and serves as the new this.head
  • If this.head != null, the new node points to the original this.head and serves as the new this.head
    • Note that assignment operations are performed from right to left

while traversing

public class SinglyLinkedList {
    
    
    // ...
    public void loop() {
    
    
        Node curr = this.head;
        while (curr != null) {
    
    
            // 做一些事
            curr = curr.next;
        }
    }
}

You can pass in the function of what you want to do when traversing, using Consumer:

  • The rule of Consumer is a parameter and no return value , so methods like System.out::println are Consumers
  • When calling the Consumer, pass the current node curr.value to it as a parameter

code show as below:

/**
 * while遍历
 */
public void whileLoop(Consumer<Integer> consumer) {
    
    
    Node curr = this.head;
    while (curr != null) {
    
    
        // 做一些事
        consumer.accept(curr.value);
        curr = curr.next;
    }
}

The test call prints the traversed values:

public class SinglyLinkedList {
    
    
  
    public static void main(String[] args) {
    
    
        Ds02SinglyLinkedList singlyLinkedList = new Ds02SinglyLinkedList();
        singlyLinkedList.addFirst(4);
        singlyLinkedList.addFirst(3);
        singlyLinkedList.addFirst(2);
        singlyLinkedList.addFirst(1);
				// 使用lambda表达式
        singlyLinkedList.whileLoop(System.out::println);
    }
}

for traverse

/**
* for遍历
*/
public void forLoop(Consumer<Integer> consumer) {
    
    
    for (Node curr = this.head; curr != null ; curr = curr.next) {
    
    
        // 做一些事
        consumer.accept(curr.value);
    }
}

iterator traversal

Inherit ``Iterable 接口,实现其iterator` method, return an iterator.

/**
* 迭代器遍历
*/
@Override
public Iterator<Integer> iterator() {
    
    
    // 使用匿名内部类
    return new Iterator<Integer>() {
    
    
        Node p = head;
        @Override
        public boolean hasNext() {
    
    
          return p != null;
        }

        @Override
        public Integer next() {
    
    
            int v = p.value;
            p = p.next;
            return v;
        }
    };
}
  • hasNext is used to determine whether it is necessary to call next
  • next does two things
    • Returns the value of the current node
    • point to the next node

The test code is as follows:

public class SinglyLinkedList {
    
    
  
    public static void main(String[] args) {
    
    
        Ds02SinglyLinkedList singlyLinkedList = new Ds02SinglyLinkedList();
        singlyLinkedList.addFirst(4);
        singlyLinkedList.addFirst(3);
        singlyLinkedList.addFirst(2);
        singlyLinkedList.addFirst(1);
				for (Integer integer : singlyLinkedList) {
    
    
            System.out.println(integer);
        }
    }
}

recursive traversal

public class SinglyLinkedList implements Iterable<Integer> {
    
    
    // ...
    public void recursionLoop() {
    
    
        recursion(this.head);
    }

    private void recursion(Node curr) {
    
    
        if (curr == null) {
    
    
            return;
        }
        // 前面做些事
        recursion(curr.next);
        // 后面做些事
    }
}

Add at the end

In order to add an element at the tail, we must first find the node at the tail, and then insert a new node after the tail node.

Write a findLast()method to find the last node, and the method can be reused later.

public class SinglyLinkedList {
    
    
        /**
     * 寻找最后一个节点
     * @return
     */
    private Node findLast() {
    
    
        if (this.head == null) {
    
    
            return null;
        }
        Node curr;
        for (curr = this.head; curr.next != null; ) {
    
    
            curr = curr.next;
        }
        return curr;
    }

    /**
     * 在最后一个节点添加元素
     * @param value
     */
    public void addLast(int value) {
    
    
        Node last = findLast();
        // 如果未找到最后一个元素,说明链表为空,在头部添加即可
        if (last == null) {
    
    
            addFirst(value);
            return;
        }
        // 找到尾部元素后
        last.next = new Node(value, null);
    }
}
  • Note that to find the last node, the termination condition is curr.next == null

Add multiple at the end

public class SinglyLinkedList {
    
    
    // ...
	public void addLast(int first, int... rest) {
    
    
        
        Node sublist = new Node(first, null);
        Node curr = sublist;
        for (int value : rest) {
    
    
            curr.next = new Node(value, null);
            curr = curr.next;
        }
        
        Node last = findLast();
        if (last == null) {
    
    
            this.head = sublist;
            return;
        }
        last.next = sublist;
    }
}
  • First string into a string of sublist
  • then add as a whole

get by index

public class SinglyLinkedList {
    
    
      /**
     * 寻找指定索引位置的节点
     * @param index
     * @return
     */
    private Node findNode(int index) {
    
    
        int i = 0;
        for (Node curr = this.head; curr != null; curr = curr.next, i++) {
    
    
            if (index == i) {
    
    
                return curr;
            }
        }
        return null;
    }

    private IllegalArgumentException illegalIndex(int index) {
    
    
        return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));
    }

    /**
     * 获取指定位置的索引
     * @param index
     * @return
     */
    public int get(int index) {
    
    
        Node node = findNode(index);
        if (node != null) {
    
    
            return node.value;
        }
        throw illegalIndex(index);
    }
}
  • Similarly, sub-methods can be reused

insert

public class SinglyLinkedList {
    
    
       /**
     * 指定位置插入节点
     * @param index
     * @param value
     */
    public void insert(int index, int value) {
    
    

        if (index == 0) {
    
    
            addFirst(value);
            return;
        }
        // 找到上一个节点
        Node prev = findNode(index - 1); 
        if (prev == null) {
    
     // 找不到
            throw illegalIndex(index);
        }
        prev.next = new Node(value, prev.next);
    }
}
  • Insertion, including deletion below, must find the previous node

delete

public class SinglyLinkedList {
    
    
      /**
     * 删除指定位置的节点
     * @param index
     */
    public void remove(int index) {
    
    
        // 删除头节点
        if (index == 0) {
    
    
            if (this.head != null) {
    
    
                this.head = this.head.next;
                return;
            } else {
    
    
                throw illegalIndex(index);
            }
        }
        // 找到前一个节点
        Node prev = findNode(index - 1);
        Node curr;
        if (prev != null && (curr = prev.next) != null) {
    
    
            // 把前一个节点的下一个节点设置为下一个节点
            prev.next = curr.next;
        } else {
    
    
            throw illegalIndex(index);
        }
    }
}
  • The first if block corresponds to the removeFirst case
  • The last if block corresponds to the case of at least two nodes
    • Not only judge that the previous node is not empty, but also ensure that the current node is not empty

Singly linked list (with sentinels)

Observing the implementation of the one-way linked list before, I found that almost every method has the code to judge whether it is the head. Can it be simplified?

Use a special Node that does not participate in data storage as a sentinel, which is generally called a sentinel or a dummy, and a linked list with a sentinel node is called a leader linked list

public class SinglyLinkedListSentinel {
    
    
    // ...
    private Node head = new Node(Integer.MIN_VALUE, null);
}
  • It doesn't matter what value is stored, because its value will not be used

After joining the sentinel node, the code will become relatively simple, first look at a few tools and methods

public class SinglyLinkedListSentinel {
    
    
    // ...
    
    // 根据索引获取节点
    private Node findNode(int index) {
    
    
        int i = -1;
        for (Node curr = this.head; curr != null; curr = curr.next, i++) {
    
    
            if (i == index) {
    
    
                return curr;
            }
        }
        return null;
    }
    
    // 获取最后一个节点
    private Node findLast() {
    
    
        Node curr;
        for (curr = this.head; curr.next != null; ) {
    
    
            curr = curr.next;
        }
        return curr;
    }
}
  • findNode is similar to before, except that the initial value of i is set to -1 corresponding to the sentinel, and the actual index passed in is also [ − 1 , ∞ ) [-1, \infty)[1,)
  • findLast will never return null, even if there are no other nodes, it will return Sentinel as the last node

This way, the code simplifies to

public class SinglyLinkedListSentinel {
    
    
    // ...
    
    public void addLast(int value) {
    
    
        Node last = findLast();
        /*
        改动前
        if (last == null) {
            this.head = new Node(value, null);
            return;
        }
        */
        last.next = new Node(value, null);
    }
    
    public void insert(int index, int value) {
    
    
        /*
        改动前
        if (index == 0) {
            this.head = new Node(value, this.head);
            return;
        }
        */
        // index 传入 0 时,返回的是哨兵
        Node prev = findNode(index - 1);
        if (prev != null) {
    
    
            prev.next = new Node(value, prev.next);
        } else {
    
    
            throw illegalIndex(index);
        }
    }
    
    public void remove(int index) {
    
    
        /*
        改动前
        if (index == 0) {
            if (this.head != null) {
                this.head = this.head.next;
                return;
            } else {
                throw illegalIndex(index);
            }
        }
        */
        // index 传入 0 时,返回的是哨兵
        Node prev = findNode(index - 1);
        Node curr;
        if (prev != null && (curr = prev.next) != null) {
    
    
            prev.next = curr.next;
        } else {
    
    
            throw illegalIndex(index);
        }
    }
    
    public void addFirst(int value) {
    
    
        /*
        改动前
        this.head = new Node(value, this.head);
        */
		this.head.next = new Node(value, this.head.next);
        // 也可以视为 insert 的特例, 即 insert(0, value);
    }
}
  • For deletion, I said before [the last if block corresponds to the situation where at least two nodes are obtained], now that there are sentinels, two nodes are enough

Doubly linked list (with sentinels)

public class DoublyLinkedListSentinel implements Iterable<Integer> {
    
    

    private final Node head;
    private final Node tail;

    public DoublyLinkedListSentinel() {
    
    
        head = new Node(null, 666, null);
        tail = new Node(null, 888, null);
        head.next = tail;
        tail.prev = head;
    }

    private Node findNode(int index) {
    
    
        int i = -1;
        for (Node p = head; p != tail; p = p.next, i++) {
    
    
            if (i == index) {
    
    
                return p;
            }
        }
        return null;
    }

    public void addFirst(int value) {
    
    
        insert(0, value);
    }

    public void removeFirst() {
    
    
        remove(0);
    }

    public void addLast(int value) {
    
    
        Node prev = tail.prev;
        Node added = new Node(prev, value, tail);
        prev.next = added;
        tail.prev = added;
    }

    public void removeLast() {
    
    
        Node removed = tail.prev;
        if (removed == head) {
    
    
            throw illegalIndex(0);
        }
        Node prev = removed.prev;
        prev.next = tail;
        tail.prev = prev;
    }

    public void insert(int index, int value) {
    
    
        Node prev = findNode(index - 1);
        if (prev == null) {
    
    
            throw illegalIndex(index);
        }
        Node next = prev.next;
        Node inserted = new Node(prev, value, next);
        prev.next = inserted;
        next.prev = inserted;
    }

    public void remove(int index) {
    
    
        Node prev = findNode(index - 1);
        if (prev == null) {
    
    
            throw illegalIndex(index);
        }
        Node removed = prev.next;
        if (removed == tail) {
    
    
            throw illegalIndex(index);
        }
        Node next = removed.next;
        prev.next = next;
        next.prev = prev;
    }

    private IllegalArgumentException illegalIndex(int index) {
    
    
        return new IllegalArgumentException(
                String.format("index [%d] 不合法%n", index));
    }

    @Override
    public Iterator<Integer> iterator() {
    
    
        return new Iterator<Integer>() {
    
    
            Node p = head.next;

            @Override
            public boolean hasNext() {
    
    
                return p != tail;
            }

            @Override
            public Integer next() {
    
    
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    static class Node {
    
    
        Node prev;
        int value;
        Node next;

        public Node(Node prev, int value, Node next) {
    
    
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }
}

Ring linked list (with sentinels)

Two-way ring chain watch with sentinel, at this time the sentinel acts as both the head and the tail

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Q0Mf8Opu-1679589982444)(./imgs/image-20221229144232651.png)]

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-qIVfR2uc-1679589982444)(./imgs/image-20221229143756065.png)]

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-f6tHpFnn-1679589982444)(./imgs/image-20221229153338425.png)]

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-f5k2mR5K-1679589982444)(./imgs/image-20221229154248800.png)]

reference implementation

public class DoublyLinkedListSentinel implements Iterable<Integer> {
    
    

    @Override
    public Iterator<Integer> iterator() {
    
    
        return new Iterator<>() {
    
    
            Node p = sentinel.next;

            @Override
            public boolean hasNext() {
    
    
                return p != sentinel;
            }

            @Override
            public Integer next() {
    
    
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    static class Node {
    
    
        Node prev;
        int value;
        Node next;

        public Node(Node prev, int value, Node next) {
    
    
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    private final Node sentinel = new Node(null, -1, null); // 哨兵

    public DoublyLinkedListSentinel() {
    
    
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }

    /**
     * 添加到第一个
     * @param value 待添加值
     */
    public void addFirst(int value) {
    
    
        Node next = sentinel.next;
        Node prev = sentinel;
        Node added = new Node(prev, value, next);
        prev.next = added;
        next.prev = added;
    }

    /**
     * 添加到最后一个
     * @param value 待添加值
     */
    public void addLast(int value) {
    
    
        Node prev = sentinel.prev;
        Node next = sentinel;
        Node added = new Node(prev, value, next);
        prev.next = added;
        next.prev = added;
    }
    
    /**
     * 删除第一个
     */
    public void removeFirst() {
    
    
        Node removed = sentinel.next;
        if (removed == sentinel) {
    
    
            throw new IllegalArgumentException("非法");
        }
        Node a = sentinel;
        Node b = removed.next;
        a.next = b;
        b.prev = a;
    }

    /**
     * 删除最后一个
     */
    public void removeLast() {
    
    
        Node removed = sentinel.prev;
        if (removed == sentinel) {
    
    
            throw new IllegalArgumentException("非法");
        }
        Node a = removed.prev;
        Node b = sentinel;
        a.next = b;
        b.prev = a;
    }

    /**
     * 根据值删除节点
     * <p>假定 value 在链表中作为 key, 有唯一性</p>
     * @param value 待删除值
     */
    public void removeByValue(int value) {
    
    
        Node removed = findNodeByValue(value);
        if (removed != null) {
    
    
            Node prev = removed.prev;
            Node next = removed.next;
            prev.next = next;
            next.prev = prev;
        }
    }

    private Node findNodeByValue(int value) {
    
    
        Node p = sentinel.next;
        while (p != sentinel) {
    
    
            if (p.value == value) {
    
    
                return p;
            }
            p = p.next;
        }
        return null;
    }

}

Guess you like

Origin blog.csdn.net/qq_43745578/article/details/129742653