【数据结构学习记录4】—— 双向链表

一.简述

双向链表的含义就是,一个节点除了下一个节点的地址,还要额外储存上一个节点的地址。
这样,假设你知道其中一个节点的地址的话,我们可以很方便的“向前”和“向后”遍历,而不是单链表的只能“向后”遍历。
所以,双链表比单链表更灵活,遍历的话,会花稍微更少的时间,但是会额外花费的储存空间。有些题可能比较离谱,会“进进退退”,所以单链表也无法完成这些功能。双链表是很典型的“空间换时间”。

二.思路图解

2.1 节点

对于创建一个节点,我们肯定还是类似于单链表,它因该包含prev data next三个部分。prev指向上一个节点的地址,next指向下一个节点的地址。
在这里插入图片描述

2.2 链表结构

因为双向链表的结构,所以我不能只存放一个头地址,我们还得声明一个尾地址。正向遍历的话,遍历结束的边界是*next = tail;反向遍历的话,遍历结束的边界是*prev = head。而且,为了节约空间,我们可以加一个计数(统计链表长度),假设我们通过index作为标示符插入,那么我们就可以通过链表长度和index对比,得出遍历方向。
当然,你嫌麻烦,可以不创建链表这个类,直接用首节点的node的数据来记录链表长也行,本文采用第一种方法。

在这里插入图片描述

2.3 链表的初始化

最好是单独创建一个结构体,包含head *tail len = 0三个部分,并赋上初值。
在这里插入图片描述

2.4 链表的遍历

我们可以正序输出,也可以倒序输出,就只是遍历的"开头"和"过程方向"不同。
我们最好写出一个能够自动判断方向的遍历函数,这样对后面的操作,我们可以取出任意一个节点的地址,这段代码可以被复用。

2.5 链表的插入

双向链表的插入很简单,我们只要:

  1. 把待插入节点NewNode*prev改成NextNode*prev*next改成LastNode*next
  2. 然后把前节点LastNode*next和改成插入节点的prev改成NewNode的地址,就完成了!
    在这里插入图片描述

2.6 链表的删除

我们需要对它周围的两个节点都进行修改:

  1. LastNode*next改成它的*next
  2. NextNode*prev改成它的*prev
    这样就遍历不到它了,然后节点就被删除了
    在这里插入图片描述

2.7 链表的修改/查询

这一部分等同于遍历,只需要修改值就可以了。

三.代码实现

#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;  // 反之同上,只是顺序变化了
    }
}

猜你喜欢

转载自blog.csdn.net/u011017694/article/details/109318559