How to implement the doubly linked list in the linear structure

foreword

In the previous article, we mainly introduced the characteristics and principles of singly linked lists, but we did not practice through code. Today I will continue to use an article to explain to you the content of the doubly linked list, especially the operation of the linked list through code, I hope everyone will pay attention.


The full text is about [ 3500] words, no nonsense, just pure dry goods that allow you to learn techniques and understand principles! This article has a wealth of cases and videos with pictures, so that you can better understand and use the technical concepts in the article, and can bring you enough enlightening thinking...

1. Introduction to doubly linked list

1. Concept

In the previous article, when we introduced the types of linked lists, we mentioned doubly linked lists. Compared with the singly linked list, the doubly linked list has two pointers, front and back, in addition to the data field.
insert image description here
The structure term in doubly linked list can be interpreted as:

  • data : the data field stored in each node of the linked list;
  • next : the pointer field to the address of the next node contained in each node of the linked list;
  • prev : The pointer field of the address of the previous node contained in each node of the linked list.

2. Code definition

According to the above definition of the doubly linked list node, we give the Java definition implementation of the doubly linked list node structure:

class DNode{
    
    
    Object data;
    Node prev;
    Node next;
}

A doubly linked list is a real linked list consisting of multiple nodes. In actual programming, two special nodes of the linked list are usually marked, namely: the head node and the tail node . Use another variable size to represent the number of elements in the linked list.

  • Head node: the first node in the linked list
  • Tail node: the last element in the linked list

Therefore, there is a definition of the linked list class as follows:

public class DoubleLinkList{
    
    
    private int size;//大小
    private DNode head;//头结点
    private DNode last;//尾结点
}

In the rest of this article, we will use these DNode、DoubleLinkListtwo definitions to implement the operations of the doubly linked list.

2. Common operations

Because the doubly linked list is a structure extended on the basis of the singly linked list, many operations of the doubly linked list are the same as those of the singly linked list, such as: finding elements, updating elements, inserting elements, and deleting elements .

1. Find element

1.1 Find the head node

Because the head node head has been recorded in the DoubleLinkList, the search for the head node is very simple, as follows:

public Object getHead(){
    
    
    if(head == null){
    
    
        return null;
    }
    return head.data;
}

The time complexity is O(1).

1.2 Find the tail node

Similar to the head node, the time complexity of finding the tail node is also O(1), and the code is as follows:

public Object getLast(){
    
    
    if(last == null){
    
    
        return null;
    }
    return last.data;
}

1.3 Find the specified location node

When it is necessary to find the node element at the specified position, the implementation of the doubly linked list is different from that of the singly linked list . can be searched, so when searching for a specified location, in order to improve the search efficiency, it will first determine whether the location to be searched is in the first half or the second half of the linked list. Search forward from the tail node, the specific programming is as follows:

public Object get(int index){
    
    
    //排除边界异常
    if(index <0 || index>=size){
    
    
        return null;
    }
	//要查找的位置位于链表前半段
	if(index < (size>>1)){
    
    
        DNode x = head;
        for(int i = 0; i < index; i++){
    
    
            x = x.next;
        }
        return x.data;
    }else {
    
    //要查找的位置位于链表后半段
        DNode x = last;
        for(int i = size - 1; i >= index; i--){
    
    
            x = x.prev;
        }
        return x.data;
    }
}

In the above code, the writing of size >> 1 is relatively rare, and ">>" represents a shift operation in computer programming. There are two common shift operations:

Screenshot 2023-06-20 09.22.10.png

Through the actual programming verification, we can know that if the right shift operation is performed by 1 bit, the variable data will be reduced to 1/2 of the original. Shift left is the opposite. At the same time, we can analyze that the time complexity is O(n).

2. Update elements

Updating an element requires two steps:

  • Find the element to update
  • perform an update operation

According to different locations, the update operation can be divided into three situations: update the head node, update the tail node, and update the specified location node.

2.1 Update the head node

Updating the head node code is similar to finding the head node, as follows:

public boolean updateHead(Object obj){
    
    
    if(head == null){
    
    
        return false;
    }
	head.data = obj;
	return true;
}

The time complexity of updating the head node is O(1).

2.2 Update the tail node

public boolean updateLast(Object obj){
    
    
    if(last == null){
    
    
        return false;
    }
	last.data = obj;
}

The time complexity of updating the tail node is also O(1).

2.3 Update the specified location node

public boolean update(int index, Object obj){
    
    
    if(index < 0 || index >= size){
    
    
        return false;
    }
    if(index < (size>>1)){
    
    
        DNode x = head;
        for(int i = 0; i < index; i++){
    
    
            x = x.next;
        }
        x.data = obj;
    }else {
    
    
        DNode x = last;
        for(int i = size-1; i >= index; i--){
    
    
            x = last.prev;
        }
        x.data = obj;
    }
    return true;
}public boolean addHead(Object data){
    
    
    DNode h = head;
	DNode newNode = new DNode(null,data,h);
	head = newNode;
	if(h == null);{
    
    
        last = newNode;
    }else {
    
    
        h.prev = newNode;
    }
	size++;
	return true;
}

As shown in the above code, the algorithm used to modify the value of the specified node element is also: first determine whether the position to be operated is in the first half or the second half, then perform a precise search, and finally execute the modification operation.

The time complexity of the specified location modification operation is O(n).

3. Insert element

After analyzing the specific situation of the search element and update element operations, we can clearly analyze the specific situation of the insert element operation, which is actually divided into three specific scenarios: head node position insertion, tail node position insertion, specified The position to insert the element .

3.1 Head node position insertion

public boolean addHead(Object data){
    
    
    DNode h = head;
	DNode newNode = new DNode(null,data,h);
	head = newNode;
	if(h == null);{
    
    
        last = newNode;
    }else {
    
    
        h.prev = newNode;
    }
	size++;
	return true;
}

According to the above code, we can see that to insert a new element at the position of the head node, we only need to set the newly added node as the head node, and at the same time handle the pointing relationship between the new node and the head node in the original linked list. Can. Obviously, the time complexity of head node position insertion is O(1).

3.2 End node position insertion

The principle of tail node insertion is the same as that of head node insertion, only need to be replaced by tail node and pointer. As follows:

public boolean addLast(Object data){
    
    
    DNode l = last;
	DNode newNode = new DNode(l,data,null);
	last = newNode;
	if(last == null){
    
    
        head = last;
    }else {
    
    
        l.next = newNode;
    }
	size++;
	return true;
}

The time complexity is O(1).

3.3 Insert at specified position

When inserting at a specified position, the programming code is slightly more, because the following steps are required to complete:

  • Determine whether the inserted position is out of range
  • If the insertion position is at the end, execute the insertion logic at the end node
  • First, according to the position to be inserted, find and obtain the node element at the corresponding position
  • Then execute the insert logic
public boolean add(int index,Object data){
    
    
    if(index < 0 || index > size){
    
    
        return false;
    }
    if(index == size){
    
    
        addLast(data);
        return true;
    }else {
    
    
        //先找到要插入的指定位置的结点
        DNode x = index(index);
    	//执行插入操作
        DNode prevNode = x.prev;
        DNode newNode = new DNode(prevNode,data,x);
        x.prev = newNode;
        if(prevNode == null){
    
    
            head = newNode;
        }else {
    
    
            prevNode.next = newNode;
        }
        size++;
    }
}

//查找index位置上的结点并返回
public DNode index(int index){
    
    
    if( index < 0 || index >= size){
    
    
        return null;
    }
	if( index < (size>>1)){
    
    
        DNode x = head;
        for(int i = 0; i < index; i++){
    
    
            x = x.next;
        }
        return x;
    }else {
    
    
        DNode x = last;
        for(int i = size-1; i >= index; i--){
    
    
            x = x.prev;
        }
        return x;
    }
}

According to the above code, we can find that the code to insert the specified position needs to use the operation of finding the specified position, first search and then insert, so the time complexity is also O(n).

4. Delete elements

With the previous analysis experience, we can very naturally analyze that there are also three types of deletion operations: delete the head node, delete the tail node, and delete the specified node . Next, let's take a look at the details:

4.1 Delete the head node

public Object removeHead(){
    
    
    if(head == null){
    
    
        return null;
    }
    DNode h = head;
    Object data = h.data;
    DNode next = h.next;
	//将原来头结点的数据域和指针域均赋值为null置空
    h.data = null;
    h.next = null;

    //将当前结点的next作为新的头结点
    head = next;

    //如果next为null,则说明当前链表只有一个节点,删除该节点,则链表的first、last都为null
    if(next == null){
    
    
        last = null;
    }else {
    
    
        // next要作为新的头节点,则其prev属性为null
        next.prev = null;
    }
    size--;
    return data;
}

Deleting the head node only involves the logical judgment and operation of the head node, so the time complexity of deleting the head node is O(1).

4.2 Delete the tail node

The principle is the same as deleting the head node, operating the tail node. code show as below:

public Object removeLast(){
    
    
    DNode l = last;
    if(l == null){
    
    
        return null;
    }
    Object data = l.data;
    DNode prev = l.prev;
	//将当前尾节点的属性赋值为null,为了GC清理
    l.data = null;
    l.prev = null;
	// 让当前尾节点的prev作为新的尾节点,赋值给last属性
    last = prev;
	// 如果prev为null,则说明当前链表只有一个节点,删除该节点,则链表的first、last都为null
    if(prev == null){
    
    
        head = null;
    }else {
    
    
        // prev要作为新的尾节点,则其next属性为null
        prev.next = null;
    }
    size--;
    return data;
}

Obviously, the time complexity of deleting the tail node is O(1).

4.3 Delete the specified node

The coding implementation of deleting the specified node is as follows:

public Object remove(int index){
    
    
    if(index < 0 || index >= size){
    
    
        return null;
    }
	//首先通过查找方法,查找到
	DNode node = index(index;
	//执行删除操作
	Object data = node.data;
	DNode next = node.next;
	DNode prev = node.prev;

	// 如果prev是null,则说明删除的是当前头节点,则将next作为新的头节点,赋值给head
	if(prev == null){
    
    
        head = prev;
    }else {
    
    
        // 如果删除的不是当前头节点,则将要删除节点的prev与next连接一起,即将prev的next属性赋值成next
        prev.next = next;
        // 如果prev不是null,则赋值为null
        node.prev = null;
    }

	// 如果next是null,则说明删除的是当前尾节点,则将prev作为新的尾节点,赋值给last
	if(next == null){
    
    
        last = prev;
    }else {
    
    
        // 如果删除的不是当前尾节点,则将要删除节点的prev与next连接一起,即将next的prev赋值成prev
        next.prev = prev;
        // 如果next不是null,则赋值为null
        node.next = null;
    }
	//将要删除的结点的data数据域设置为null
	node.data = null;
	//链表的结点个数-1操作
	size--;
	return data;
}

As shown in the above code, deleting the node element at the specified position also needs to execute index(index)the search algorithm first. As for the implementation of the index, it has already been implemented when introducing the operation of inserting the node at the specified position above, and it can be used directly here.

It is not difficult to analyze that the time complexity of deleting the node at the specified position is O(n).

3. Other operations

As a common data structure, in addition to some operations on its own node elements, there are also some acquisitions of the state of the linked list, such as the length of the linked list, whether the linked list is empty, etc. Here I will introduce some other operations of the doubly linked list.

1. The size of the linked list (the number of element nodes)

public int size(){
    
    
    return size;
}

2. Determine whether the linked list is empty

public boolean isEmpty(){
    
    
    return size == 0;
}

3. Get an array of linked list elements

public Object[] toArray(){
    
    
    Object[] result = new Object[size];
    int i = 0;
    for(DNode node = head; node != null; node = node.next){
    
    
        resunt[i++] = node.data;
    }
    return result;
}

4. Clear the linked list

public void clear(){
    
    
    for(DNode node = head; node != null; ){
    
    
        DNode next = node.next;
        node.data = null;
        node.next = null;
        node.prev = null;
        node = next;
    }
    head = last = null;
    size = 0;
}

4. Conclusion

So far, we have introduced the relevant knowledge of linked lists to you in two consecutive articles.

In the previous article, we mainly introduced the basic knowledge of linked list and the conventional operation of single linked list, and supplemented by diagrams to illustrate various operation situations. In this article, mainly from the perspective of Java programming as an entry point, to further explain some operations of the doubly linked list. In particular, a lot of code practice in this article requires everyone to be able to sort out the logical relationship. I hope you can practice it.

Guess you like

Origin blog.csdn.net/GUDUzhongliang/article/details/131299994