Data Structure Series Learning (5) - Doubly Linked List (Double_Linked_List)

Table of contents

introduction:

study:

Code:

Header file (Double_Linked_List):

Structure design:

Function declaration:

The specific implementation of the function function in the source file (Double_Linked_List):

Initialization function (Init_dlist):

Clear function (Clear):

Destroy function (wireless header deletion) (Destroy1):

Destroy function (two-pointer cooperative release node) (Destroy2):

Print function (Show):

Find function (Search):

Get effective length function (Get_Length):

Empty judgment function (IsEmpty):

Head insertion function (Insert_head):

Tail insertion function (Insert_tail):

Interpolate function by position (Insert_pos):

Head delete function (Delete_head):

Tail delete function (Delete_tail):

Delete function by position (Delete_pos):

Delete function by value (Delete_val):

test:

Test initialization function, print function:

Test header plug-in function: 

Test tail interpolation function: 

Test the positional interpolation function:

Test header delete function:

 Test tail delete function:

Test the delete function by position:

Test the delete-by-value function: 

Test lookup function: 

Test clear function: 

Test destroy function 1: 

Test destruction function 2: 

Summarize:


introduction:

Data structure learning directory:

Data Structure Series Learning (1) - An Introduction to Data Structure

 Data structure series learning (2) - sequence table (Contiguous_List)

Data Structure Series Learning (3) - Singly Linked List (Linked_List)

Data Structure Series Learning (4) - Circular Linked List

In the previous article, we learned the theoretical knowledge of a single-item circular linked list and implemented it using code. There is another form of expression in the chained storage structure - a two-way linked list. In the singly linked list or one-way circular linked list we learned before, each node in the linked list saves the address of its successor node, but the doubly linked list we will introduce and learn today is different. The nodes in the doubly linked list It can not only save the address of its successor node, but also save the address of its predecessor node. This is why he is called a doubly linked list.

study:

The doubly linked list is different from the singly linked list. Each node of the doubly linked list not only saves the address of the successor node, but also saves the address of the predecessor node.

Why is there a doubly linked list?

In Yan Weimin's "Data Structure (C Language Edition)", it is said that there is only one pointer field indicating the direct successor in the chained storage structure. Therefore, starting from a certain node, you can only search for other nodes backward through the pointer. . In order to overcome the one-way shortcomings of the singly linked list, a doubly linked list is produced.

The significance of the existence of the doubly linked list: each node can find both the direct successor and the direct predecessor. And each node can either go backwards or forwards, which is equivalent to an improvement on the singly linked list, as shown in the figure:

Code:

The functions we want to implement in the doubly linked list (15):

initialization function (Init_dlist);

Clear function (Clear);

Destroy function (wireless header deletion) (Destroy1);

Destroy function (double pointer cooperative release node) (Destroy2);

print function (Show);

Find function (Search);

Get the effective length function (Get_Length);

Empty judgment function (IsEmpty);

Head insert function (Insert_head);

Tail insertion function (Insert_tail);

Interpolate function by position (Insert_pos);

Head delete function (Delete_head);

Tail delete function (Delete_tail);

Delete function by position (Delete_pos);

Delete function by value (Delete_val);

Header file (Double_Linked_List):

Structure design:

As the name implies, there are two pointer domains in the doubly linked list, which are the predecessor (prior domain) for storing the address of the previous node, the successor (next domain) for storing the address of the next node, and the data domain for storing data, as shown in the figure:

So we design the structure of the doubly linked list according to the structure of the doubly linked list:

typedef int Elem_type;
typedef struct DNode
{
    Elem_type data;
    struct DNode* next;
    struct DNode* prior;
}DNode, *PDnode;

Function declaration:

void Init_dlist(PDnode dlist);
void Clear(PDnode dlist);
void Destroy(PDnode dlist);
void Destroy1(PDnode dlist);
void Show(PDnode dlist);
struct DNode* Search(PDnode dlist,Elem_type val);
int Get_Length(PDnode dlist);
bool IsEmpty(PDnode dlist);
bool Insert_head(PDnode dlist,Elem_type val);
bool Insert_tail(PDnode dlist,Elem_type val);
bool Insert_pos(PDnode dlist,int pos,Elem_type val);
bool Delete_head(PDnode dlist);
bool Delete_tail(PDnode dlist);
bool Delete_pos(PDnode dlist,int pos);
bool Delete_val(PDnode dlist,Elem_type val);

The specific implementation of the function function in the source file (Double_Linked_List):

Initialization function (Init_dlist):

Because the nodes of the doubly linked list are composed of three parts, namely: data domain, next domain (successor), prior domain (predecessor), when we initialize the doubly linked list, we first make it clear that there are no valid nodes in the initial stage, so the initial data The field does not store any data, and then the next field and the prior field can be assigned empty values.

void Init_dlist(PDnode dlist)
//如果没有有效节点,则双向链表的头节点,应该:头节点的数据域浪费掉,不使用,头节点的next域
{
    assert(dlist != nullptr);
    dlist->next = nullptr;
    dlist->prior = nullptr;
}

Clear function (Clear):

As mentioned above, clearing and destroying the linked list have the same meaning, so we can directly call the destroy function in the clearing function.

void Clear(PDnode dlist)
{
    Destroy(dlist);
}

Destroy function (wireless header deletion) (Destroy1):

The first way to destroy the linked list: when the linked list is not empty, we call the head delete function infinitely until all the nodes in the linked list are deleted.

void Destroy(PDnode dlist)//无线头删
{
    while(!IsEmpty(dlist)){
        Delete_head(dlist);
    }
}

Destroy function (two-pointer cooperative release node) (Destroy2):

The second way to destroy the linked list: two pointers cooperate to release the node. First, we disconnect the head node (make the next field of the head node empty), define the structure type pointer p to point to the first valid node, define the structure type pointer q and make it empty. Define a loop to traverse the linked list using the p pointer. Every time p points to a node, the next field of q is assigned to q, and then the q pointer is released, and then q is copied to p.

void Destroy1(PDnode dlist)//两个指针辅助
{
    assert(dlist != nullptr);
    PDnode p = dlist->next;
    PDnode q = nullptr;
    dlist->next = nullptr;
    while(p != nullptr){
        q = q->next;
        free(p);
        p = q;
    }
}

Print function (Show):

Define the structure type pointer p to point to the first effective node, and define the cycle. Every time p traverses a node, the value in the data field of the node pointed to by p is printed out.

void Show(PDnode dlist)
{
    assert(dlist != nullptr);
    PDnode p = dlist->next;
    for(;p != nullptr;p = p->next){
        printf("%5d",p->data);
    }
    printf("\n");
}

Find function (Search):

Define the structure type pointer p to point to the first valid node after the head node, define a loop, check whether the value in the data field pointed to by the pointer p is equal to the value to be searched in the process of p traversing the entire linked list, if Equal returns the address of the node, or a null address if not found.

struct DNode* Search(PDnode dlist,Elem_type val)
{
    assert(dlist != nullptr);
    PDnode p = dlist->next;
    for(;p->next != nullptr;p = p->next){
        if(p->data == val){
            return p;
        }
    }
    return nullptr;
}

Get effective length function (Get_Length):

Define the count integer value to record the number of valid nodes, define the structure type pointer p to point to the first valid node after the head node, define a loop, and the loop condition is that p is not equal to empty, and every time a valid node count is traversed Just add one, and the function returns the value of count at the end.

int Get_Length(PDnode dlist)
{
    assert(dlist != nullptr);
    int count = 0;
    PDnode p = dlist->next;
    for(;p != nullptr;p = p->next){
        count++;
    }
    return count;
}

Empty judgment function (IsEmpty):

When the next field of the head node is empty, it means that the entire linked list has no valid nodes, which means that the linked list is empty.

bool IsEmpty(PDnode dlist)
{
    return dlist->next == nullptr;
}

Head insertion function (Insert_head):

Before writing the header insertion function, we need to consider how we should change the directivity of pnewnode and its front and back nodes after we apply for the memory space of pnewnode in the heap area?

As shown in the picture:

First of all, what we need to do is to modify the data field of pnewnode itself and the shower, put the value to be inserted into the data field of pnewnode, and assign the address stored in the next field of the original head node (that is, the address of the next valid node) to pnewnode next domain, and then we copy the address of the head node to the prior domain of pnewnode. At this time, the question comes, should we first modify the next domain of the head node or the prior domain of the second valid node? This problem was encountered before when we were in the single-linked list. When we add a node, if we first modify the next field of the node before the new node, the address of the subsequent node will be lost, and the computer will not be able to find the subsequent node. . Then for the same reason, in the doubly linked list, if we first modify the next field of the head node, it will inevitably cause the loss of the address of the subsequent node. So our correct approach should be to modify the prior field of the original first valid node, and then modify the next field of the head node.

Note: If there is only one head node in the linked list at this time, we only need to modify the next field of the head node and the prior field of pnewnode, and then assign the next field of pnewnode to be empty.

So in general, the steps to modify the pointer should be: the next field and the prior field of the new node, the prior field of the subsequent node, and the next field of the previous node. 

bool Insert_head(PDnode dlist,Elem_type val)
{
    assert(dlist != nullptr);
    PDnode pnewnode = (PDnode) malloc(1 * sizeof(DNode));
    //先修改pnewnode自身的两个域,再处理下一个节点的prior域,最后处理上一个节点的next域
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    pnewnode->next = dlist->next;//1
    pnewnode->prior = dlist;
    if(dlist->next != nullptr) {
        dlist->next->prior = pnewnode;
    }
    dlist->next = pnewnode;
    return true;
}

Tail insertion function (Insert_tail):

We first apply for the new node memory (pnewnode) in the heap area, save the data to be inserted in the data field of the new node applied for, define the structure type pointer p to point to the head node, and define a loop, the loop condition is that p is not equal to empty , copy the next field of p to p, and then p points to the end node. Then we modify the pointing of the pointer, assign the value of the next field (nullptr) of the original end node to the next field of pnewnode, assign the address of the original end node to the prior field of pnewnode, then we will copy the address of pnewnode to the original end node The next domain is enough, as shown in the figure:

bool Insert_tail(PDnode dlist,Elem_type val)//不存在特殊情况 每一种情况都是修改三个指针域
{
    assert(dlist != nullptr);
    PDnode pnewnode = (PDnode) malloc(1 * sizeof(DNode));
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    PDnode p = dlist;
    for(;p->next != nullptr;p = p->next);
    pnewnode->next = p->next;
    pnewnode->prior = p;
    p->next = pnewnode;
    return true;
}

Interpolate function by position (Insert_pos):

We first apply for the new node memory (pnewnode) to the heap area, and save the data to be inserted in the data field of the new node applied for. When pos is 0, we directly call the header insertion function we wrote before. When pos is equal to When the length of the entire linked list is -1, directly call the tail insertion function we wrote before. If neither of these two cases is the case, it is inserted in the middle position, define the structure type pointer p to point to the head node, define the loop positioning pointer p to the insertion position, and then we modify the pointing of the pointer, and set the next field of p (the node after the insertion position address) to the next field of pnewnode, then we copy the address of the p pointer (the address of the node before the insertion position) to the prior field of pnewnode, and then we assign the address of pnewnode itself to the prior field of the node after the insertion position , and then modify the next field of the p pointer (the node before the insertion position), and copy the address of pnewnode itself to the next field of p, as shown in the figure:

bool Insert_pos(PDnode dlist,int pos,Elem_type val)//当pos==0的时候是头插 当pos等于length的时候是尾插,其他位置pos >0 && pos < length 的时候是中间插入
{
    assert(dlist != nullptr);
    assert(pos >= 0 && pos <= Get_Length(dlist));
    PDnode pnewnode = (PDnode) malloc(1 * sizeof(DNode));
    assert(pnewnode != nullptr);
    pnewnode->data = val;
    if(pos == 0){
        return Insert_head(dlist,val);
    }
    else if(pos == Get_Length(dlist) - 1){
        return Insert_tail(dlist,val);
    }
    PDnode p = dlist;
    for(int i = 0;i < pos;i++){//当pos等于几
        p = p->next;
    }
    pnewnode->next = p->next;//1
    pnewnode->prior = p;//2
    p->next->prior = pnewnode;//4
    p->next = pnewnode;//3
    return true;
}

Head delete function (Delete_head):

Before writing the head delete function, we must first judge the linked list to be empty. At this time, we should consider the first case. If the linked list has only one valid node at this time, then we only need to empty the next field of the head node. If the number of valid nodes is greater than 1, define the structure type pointer p to point to the first valid node of the linked list, copy the next field of p (that is, the address of the second valid node) to the next field of the head node, and then Just assign the address of the head node to the prior field of the second valid node, as shown in the figure:

bool Delete_head(PDnode dlist)
{
    assert(dlist != nullptr);
    if(IsEmpty(dlist)){
        return false;
    }
    if(dlist->next->next == nullptr){
        dlist->next = nullptr;
    }
    PDnode p = dlist->next;
    dlist->next = p->next;
    p->next->prior = dlist;
    free(p);
    return true;
}

Tail delete function (Delete_tail):

First, empty the linked list, define the structure type pointer p to point to the head node, define the loop so that p points to the tail node, define the structure type pointer q to point to the head node, define the loop, the loop condition is that the next field of q is not equal to p, and then Assign the next field of q to p, and p points to the penultimate node. Now we assign the next field of p (that is, nullptr) to the next field of q, as shown in the figure:

bool Delete_tail(PDnode dlist)
{
    //尾删不存在特殊情况,因为待删除节点就是尾节点,且待删除节点的后一个节点永远永远不存在
    assert(dlist != nullptr);
    if(IsEmpty(dlist)){
        return false;
    }
    PDnode p = dlist;
    for(;p->next != nullptr;p = p->next);
    PDnode q = dlist;
    for(;q->next != p;q = q->next);
    q->next = p->next;
    free(p);
    return true;
}

Delete function by position (Delete_pos):

First, judge the linked list as empty. If pos is 0, just call the head delete function we wrote before. If pos is equal to the effective length of the linked list minus 1, just call the tail delete function we wrote before. If neither Head deletion is not tail deletion, that is, middle position deletion. Define the structure type pointer q to point to the head node, define a loop, the loop condition is i < pos, after the loop is completed, assign the next field of q to q, q points to the previous node at the position to be deleted, and the next field of q is Represents the next node to be deleted, define the structure type pointer p, and assign the next field of q to p. At this time, p points to the node to be deleted, and then we perform a cross-pointing operation, and we will next the node to be deleted The address of the node (that is, the next field of p) is assigned to the next field of a node on the node to be deleted (that is, the next field of q), and then we assign the address of the node pointed to by q to the prior field of p. As shown in the picture:

bool Delete_pos(PDnode dlist,int pos)
{
    assert(dlist != nullptr);
    assert(pos >= 0 && pos < Get_Length(dlist));
    if(IsEmpty(dlist)){
        return false;
    }
    //头删的情况
    if(pos == 0){
        return Delete_head(dlist);
    }
    //尾删的情况
    if(pos == Get_Length(dlist) - 1){
        return Delete_tail(dlist);
    }
    //既不是头删也不是尾删的情况——中间位置的删除,需要统一修改两个指针域
    PDnode q = dlist;
    for(int i = 0;i < pos;i++){
        q = q->next;
    }
    PDnode p = q->next;
    q->next = p->next;
    p->next->prior = q;
    free(p);
    return true;
}

Delete function by value (Delete_val):

First, the linked list is judged empty, and if the linked list is empty, it returns false. Define the structure type pointer p to save the address returned by the search function. If the saved address is empty, it will return false. If it is not empty, p points to the node to be deleted. Define the structure type pointer q to point to the head node, and define the loop , the loop condition is that the next field of q is not empty, we assign the next field of q to q, then q points to the address of the node before p node at this time, here we consider a special case, if only one in the linked list is valid node (that is, the next field of p is empty), then we can directly assign the next value of q to be empty. If the number of valid nodes is greater than 1, we will perform a cross-pointing operation and assign the address of the node after the node to be deleted Give the next domain of the previous node, and then copy the address of the previous node of the node to be deleted to the prior domain of the next node, as shown in the figure:

bool Delete_val(PDnode dlist,Elem_type val)
{
    assert(dlist != nullptr);
    if(IsEmpty(dlist)){
        return false;
    }
    PDnode p = Search(dlist,val);
    if(p == nullptr){
        return false;
    }
    PDnode q = dlist;
    for(;q->next != p;q = q->next);
    if(p->next == nullptr){
        q->next = nullptr;
    }
    else{
        q->next = p->next;
        p->next->prior = q;
    }
    free(p);
    return true;
}

test:

Test initialization function, print function:

#include<cstdio>
#include<cassert>
#include<cstdlib>
#include "Double_Linked_List.h"
int main()
{
    DNode head;
    Init_dlist(&head);
    for(int i = 0;i < 10;i++){
        Insert_pos(&head,i,i + 1);
    }
    printf("原始数据为:\n");
    Show(&head);
/*
    其他函数的测试代码在此添加...
*/

}

operation result:

Test header plug-in function: 

We insert 100 at the head of the linked list:

    printf("经过头插后的数据为:\n");
    Insert_head(&head,100);
    Show(&head);

operation result:

Test tail interpolation function: 

We insert 100 at the end of the linked list:

    printf("经过尾插后的数据为:\n");
    Insert_tail(&head,100);
    Show(&head);

 operation result:

Test the positional interpolation function:

We insert 100 after the second valid node in the linked list:

    printf("经过按位置插后的数据为:\n");
    Insert_pos(&head,2,100);
    Show(&head);

operation result:

Test header delete function:

We delete the head element of the linked list:

    printf("经过头删后的数据为:\n");
    Delete_head(&head);
    Show(&head);

operation result;

 Test tail delete function:

We delete the tail element of the linked list:

    printf("经过尾删后的数据为:\n");
    Delete_tail(&head);
    Show(&head);

operation result:

Test the delete function by position:

We delete elements after the fourth valid node of the linked list:

    printf("经过按位置删后的数据为:\n");
    Delete_pos(&head,4);
    Show(&head);

operation result:

Test the delete-by-value function: 

We want to delete element 2 in the linked list:

    printf("经过按值删后的数据为:\n");
    Delete_val(&head,2);
    Show(&head);

operation result;

Test lookup function: 

Find if there is element 4 in the linked list:

    PDnode p = Search(&head,4);
    printf("地址为:%p",p);

operation result:

Find if there is element 100 in the linked list:

    PDnode p = Search(&head,100);
    printf("地址为:%p",p);

operation result:

Test clear function: 

    Clear(&head);
    Show(&head);

 operation result:

Test destroy function 1: 

    Destroy(&head);
    Show(&head);

operation result:

Test destruction function 2: 

    Destroy1(&head);
    Show(&head);

operation result:

Summarize:

The doubly linked list is an optimization of the single linked list, which overcomes the disadvantage that the linked list can only go from front to back. In the doubly linked list, each node has both a prior field that can save the address of the previous node and a next field that can save the next node. Therefore, each node can go backwards or forwards. The meaning of the existence of the doubly linked list is that any node in the linked list can be accessed from any node. The difficulty of doubly linked list is higher than that of single linked list, but after learning and understanding the principle of doubly linked list, it is relatively easy to write code, and doubly linked list is also more important. In the process of Linux system, it is used The doubly linked list is used to save the address of the process, so as to achieve cross-access, so it is very important to master the knowledge of the doubly linked list and write the code independently.

Guess you like

Origin blog.csdn.net/weixin_45571585/article/details/127773893