数据结构---双向循环链表的实现

一. 内容概述

本次内容介绍一个常用的数据结构叫做双向循环链表,相较于单向循环链表它在每一个节点中额外用一段空间来存储上一个节点的地址,使其可以解决单向链表一旦遍历之后便不能再重新利用前面数据的弊端,可以让操作者可以更好的管理用链表存储的数据。本次内容包括通过插入节点为例子帮助大家理解双向链表的原理和双向循环链表的具体实现。

二. 原理解释

数据结构---双向循环链表的实现_第1张图片

画一张丑陋的图尽可能便于理解,该图表现得是双向链表的插入操作,图中有三个节点,prev和next节点及他们之间黄色的箭头表达得是在没插入pos结点之前该链表的结构,而插入操作做的就是去除黄色部分指针,添加红色部分指针,使整体串联起来,而其中具体操作为

next->prev = new_node;
new_node->next = next;
new_node->prev = prev;
prev->next = new_node;

节点中的prev是指该节点中存储的上一个节点的地址,next为存储的下一个节点的地址,总的来说就仅仅只是把节点中指针指向进行修改而已,且上述四句代码改变顺序并不会影响它的功能

三. 代码实现

1. 节点定义和插入删除函数

typedef int Element;
typedef struct _node
{
    Element val;
    struct _node *next;
    struct _node *prev;
}DoubleNode,DoubleList;//节点结构和头节点定义内容一致,可以用同一个结构体定义

static void addDoubleNode(DoubleNode* new_node, DoubleNode* prev, DoubleNode* next)
{
	next->prev = new_node;
	new_node->next = next;
	new_node->prev = prev;
	prev->next = new_node;
}//在第二部分有解释

static void delDoubleNode(DoubleNode* prev, DoubleNode* next)
{
	next->prev = prev;//将两边删除节点两边的节点指向对方
	prev->next = next;
}//该函数主要实现删除节点操作的指针改向

两操作函数定义原因是因为该类型链表(双向循环链表)的插入删除操作步骤完全一致,只需要提供插入/删除节点的上一个和下一个节点地址即可完成操作。

2. 初始化链表和释放链表

void initDoubleList(DoubleList* head)
{
    head->val = 0;//因为节点结构和头节点定义方式一致,所以头节点的数据存储val可以看作cnt(计数器)
    head->next = head->prev = head;//循环链表的初始化方式
}

void releaseDoubleList(DoubleList* head)
{
    DoubleNode* pos = head->next;//定义辅助节点来逐一遍历下一待释放节点
    DoubleNode* tmp = NULL;//定义辅助节点来逐一备份待释放节点
    while (pos != head)//循环链表终止条件:当前节点下一个节点不为头节点
    {
       tmp = pos->next;
       delDoubleNode(pos->prev, pos->next);//套用函数,实参为待删除结点的上一节点和下一节点地址
       pos = pos->next;//向后遍历
       free(tmp);//释放待释放空间节点
       head->val--;//计数器减一
    }
}

3. 头插/尾插节点

void insertDoubleListHeader(DoubleList* head, Element val)
{
    DoubleNode* new_node = malloc(sizeof(DoubleNode));//可以在后面加个申请是否成功判断
    new_node->val = val;//向新节点中存值
    addDoubleNode(new_node, head, head->next);//调用插值函数,实参为头节点和头节点下一个节点
    head->val++;
}

void insertDoubleListTail(DoubleList* head, Element val)
{
    DoubleNode* new_node = malloc(sizeof(DoubleNode));
    new_node->val = val;
    addDoubleNode(new_node, head->prev, head);//实参为尾节点(即头节点的前一个节点)和头节点
    head->val++;
}

4. 显示链表和删除节点

void showDoubleList(const DoubleList* head)
{
    DoubleNode* pos = head->next;//定义辅助节点地址为头节点下一个便于循环终止条件判断
    printf("Double List:\n");
    while (pos != head)
    {
       printf("%d\t", pos->val);//打印数据
       pos = pos->next;//指针后移
    }
    printf("\n");
    printf("count: %d\n", head->val);//输出节点数量
}

void deleteDoubleList(DoubleList* head, Element val)
{
    DoubleNode* pos = head->next;
    while (pos != head && pos->val != val)//搜索是否有val,将链表遍历一遍
    {
       pos = pos->next;
    }
    if (pos->val == val)
    {
       delDoubleNode(pos->prev, pos->next);
       pos->next = pos->prev = NULL;//将pos中指针指向改成NULL,该操作不必要
       free(pos);
       head->val--;
    }
    else//没有找到值后的执行语句
    {
       printf("Not found%d\n",val);
    }
}

四. 总结

本次关于双向循环链表的内容篇幅很少,并不代表它不重要,相反它还是日后链表结构中使用频率最高的链表之一,之所以篇幅较短,主要是从一开始单向链表的实现到现在,链表最主要的指针操作会在各类链表的学习中逐渐娴熟,而双向循环链表的操作也比较固化,所以写不出很多内容,且这也是双向循环链表本身的优势就是便于操作。至此,关于链表结构的大部分内容也就到此为止了,通过对链表各类结构的学习,一定能大大加深你对数据结构中链表章节逻辑的理解,同时通过用c语言将其实现也有助于对计算机空间的理解操作和加深对c语言的理解。希望大家可以从文章中学到些什么,能有所收获。在此希望看到这里的伙伴们可以点赞支持一下,谢谢大家!

你可能感兴趣的:(数据结构与算法学习,数据结构,链表,c语言,算法,网络)