【Data Structure Learning Record 4】—— Doubly Linked List

1. Brief introduction

The meaning of a doubly linked list is that in addition to the address of the next node, a node must additionally store the address of the previous node.
In this way, assuming you know the address of one of the nodes, we can easily traverse "forward" and "backward" instead of only "backward" traversal for singly linked lists.
Therefore, a double-linked list is more flexible than a singly-linked list. It will take slightly less time to traverse, but it will cost extra storage space. Some questions may be outrageous and will "advance and retreat", so the singly linked list cannot complete these functions. Double-linked list is a very typical "space for time".

Two. Idea diagram

2.1 Node

For creating a node, we must still be similar to a singly linked list, because it should contain prev data nextthree parts. prevPoint to the address of the previous node, nextpoint to the address of the next node.
Insert picture description here

2.2 Linked list structure

Because of the structure of the doubly linked list, I can't just store one 头地址, we have to declare one 尾地址. In the case of forward traversal, the boundary at which the traversal ends is *next = tail; in the case of reverse traversal, the boundary at which the traversal ends is *prev = head. Moreover, in order to save space, we can add a count (to count the length of the linked list). If we indexinsert it as an identifier, then we can indexget the traversal direction through the length and comparison of the linked list .
Of course, if you find it troublesome, you don't need to create a linked list, and you can directly use nodethe data of the first node to record the length of the linked list. This article uses the first method.

Insert picture description here

2.3 Initialization of the linked list

It is best to create a structure separately, which contains head *tail len = 0three parts, and assign initial values.
Insert picture description here

2.4 traversal of linked list

We can output in positive order or in reverse order, but the "beginning" and "process direction" of the traversal are different.
We'd better write a traversal function that can automatically determine the direction, so that for the following operations, we can take out the address of any node, and this code can be reused.

2.5 Insertion of linked list

The insertion of a doubly linked list is very simple, we only need to:

  1. The node to be inserted NewNodeis *prevchanged NextNodeto *prev; *nextchange LastNodeof *next.
  2. Then change LastNodethe *nextsum of the previous node prevto NewNodethe address of the inserted node , and you are done!
    Insert picture description here

2.6 Deletion of linked list

We need to modify the two nodes around it:

  1. To LastNodethe *nextchange of its*next
  2. To NextNodethe *prevchange it *prev
    so that you can not traverse it, then the node has been deleted
    Insert picture description here

2.7 Modification/Query of Linked List

This part is equivalent to traversal, you only need to modify the value.

Three. Code implementation

#include <stdio.h>
#include <stdlib.h>

#define OK      1
#define ERROR   0

typedef struct dataType{
    
    
    int data1;
    int data2;
}dataType;  // 数据类

typedef struct nodeType{
    
    
    struct nodeType *prev;
    struct nodeType *next;
    dataType data;
}nodeType; // 节点类

typedef struct listType{
    
    
    nodeType *head;
    nodeType *tail;
    int len;
}listType;  // 链表类

dataType DEFAULTDATA={
    
    0,0}; // 创建一个默认的类,作为数据结构

nodeType* NewNode(dataType data);
listType* ListInit(void);
nodeType* ListGetNode(listType *list, int index);
int ListShow(listType *list, int way);
int ListInsert(listType *list, dataType data, int index);
int ListDelete(listType *list, int index);
int ListModify(listType* list, dataType data, int index);
int ListQuery(listType* list, dataType data, int way);

int main()
{
    
    
    listType* list = ListInit();
    dataType test = {
    
    1,2};

    //主函数可以自己修改
    ListInsert(list, test, 0);
    test.data1 = 2;
    ListInsert(list, test, 1);
    test.data1 = 3;
    ListInsert(list,test,2);
    ListShow(list, 0);
    ListModify(list, test, 0);
    ListShow(list, 0);
    printf("i find in %d\n", ListQuery(list, test, 0));
    printf("i find in %d\n", ListQuery(list, test, 1));
    return 0;
}

nodeType* NewNode(dataType data)
{
    
    
    nodeType* node = (nodeType*)malloc(sizeof(nodeType));

    if(node != NULL)
    {
    
    
        node->data = data;  // 把数据的值赋值给节点
        node->prev = NULL;  // 并初始化节点的信息
        node->next = NULL;
        return node;
    }
    exit(1);
}

listType* ListInit(void)
{
    
    
    listType* list = (listType*)malloc(sizeof(listType));

    if (list != NULL)
    {
    
    
        list->head = NewNode(DEFAULTDATA); //   创建两个节点给头尾
        list->tail = NewNode(DEFAULTDATA);
        list->head->next = list->tail;  // 并将头的地址指向尾
        list->tail->prev = list->head;  // 尾的地址指向头
        list->len = 0;  // 初始化长度
        return list;
    }
    exit(1);
}

nodeType* ListGetNode(listType *list, int index)
{
    
    
    nodeType *begin, *end;
    int cont = 0;

    if (list->len == 0)
    {
    
    
        return list->tail; // 如果链表为空,那么就返回尾节点
    }
    else
    {
    
    
        if (index > list->len / 2)  // 通过1/2判断从前还是后遍历
        {
    
    
            begin = list->tail;
            end = list->head;
            cont = list->len;   
            // 因为len刚好比index序号大一,所以尾节点的序号刚好等于len
            // 反之,如果是头节点,那么序号就是 -1
            while(begin != end && cont > index) // 遍历的边界就是链表的末尾,或者cont= index
            {
    
    
                begin = begin->prev;
                --cont; // 遍历节点与更新当前index
            }
            return begin;  // 此时的链表节点,就是index指向的节点,或者表头
        }
        else
        {
    
    
            begin = list->head;
            end = list->tail;
            cont = -1;
            // 头节点的序号就是-1
            while(begin != end && cont < index) // 遍历的边界就是链表末尾或者cont=index
            {
    
    
                begin = begin->next;
                ++cont; // 这部分和上面一样
            }
            return begin;
        }
    }
}

int ListShow(listType *list, int way)
{
    
    
    nodeType* begin;
    nodeType* end;
    int cont = 0;

    if (way == 1)   //way=1时,从后往前遍历,否则都是从前开始遍历
    {
    
    
        begin = list->tail->prev;
        end = list->head;
        cont = list->len - 1;
        // 此时的开始节点是尾节点的前一个,要么是有效的,要么就是头节点
        // 所以开始的节点序号是len-1
        while (begin != end)
        {
    
    
            printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
            begin = begin->prev;
            --cont;
        } // 遍历,并输出信息
        printf("----------------\n");
        return OK;
    }
    else
    {
    
    
        begin = list->head->next;
        end = list->tail;
        cont = 0;
        // 从头节点的后一个节点开始,所以序号因该是0
        while (begin != end)
        {
    
    
            printf("No.%d is %d %d\n", cont,begin->data.data1, begin->data.data2);
            begin = begin->next;
            ++cont;
        } // 同理 都是遍历
        printf("----------------\n");
        return OK;
    }
}


int ListInsert(listType *list, dataType data, int index)
{
    
    
    nodeType *newNode = NewNode(data);  // 先创建一个节点
    nodeType *nextNode = ListGetNode(list, index);  // 获得index节点的地址 
    nodeType *lastNode = nextNode->prev;    // 插入节点前的节点地址

    if (nextNode->prev != NULL)
    {
    
    
        newNode->prev = nextNode->prev;     // 修改插入节点的信息
        newNode->next = nextNode;
        lastNode->next = newNode;   // 更新插入节点的前节点信息
        nextNode->prev = newNode;   // 更新插入节点的后节点信息
        list->len += 1; // 一定不要忘记把长度加1
        return OK;
    }
    else
    {
    
    
        return ERROR;
    }

}

int ListDelete(listType* list, int index)
{
    
    
    nodeType *dNode;
    nodeType *nextNode;
    nodeType *lastNode;
    if (list->len == 0)
    {
    
    
        return ERROR;
    }
    else
    {
    
    
        dNode = ListGetNode(list, index);   // 找到要删除的节点
        if (dNode->next == NULL || dNode->prev == NULL)
        {
    
    
            return ERROR;   // 确保不是头节点或者尾节点
        }
        nextNode = dNode->next; //  更新前后节点的信息
        lastNode = dNode->prev;
        lastNode->next = nextNode;
        nextNode->prev = lastNode;
        list->len -= 1; // 一定不要忘记要修改链表的长度
        return OK;
    }
}

int ListModify(listType* list, dataType data, int index)
{
    
    
    nodeType *thisNode = ListGetNode(list, index);  //查找节点

    if (thisNode->next != NULL || thisNode->prev != NULL) // 确保不是首尾节点
    {
    
    
        thisNode->data = data;
        return OK;
    }
    else
    {
    
    
        return ERROR;
    }
}

int ListQuery(listType* list, dataType data, int way)
{
    
    
    nodeType *begin;
    nodeType *end;
    int cont = 0;
    
    if (way == 0)
    {
    
    
        begin = list->head->next; // 创建开始与结束节点
        end = list->tail;
        cont = 0;   // index计数
        while(begin != end)
        {
    
    
            if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
            {
    
    
                return cont;    //找到了
            }
            else
            {
    
    
                ++cont; // 没找到,更新节点信息
                begin = begin->next;
            }
        }
        return -1;  // 没找到
    }
    else
    {
    
    
        begin = list->tail->prev;
        end = list->head;
        cont = list->len - 1;
        while(begin != end)
        {
    
    
            if (begin->data.data1 == data.data1 && begin->data.data2 == data.data2)
            {
    
    
                return cont;
            }
            else
            {
    
    
                --cont;
                begin = begin->prev;
            }
        }
        return -1;  // 反之同上,只是顺序变化了
    }
}

Guess you like

Origin blog.csdn.net/u011017694/article/details/109318559