赞
踩
目录
这次我们将学习双向循环链表,首先了解双向链表和循环链表的定义和讲解。
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,是单链表的进阶,比单链表的结点结构多出一个指向前驱的指针域,如下图所示:
循环链表是一种链式储存结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。方法是将尾结点中的指针域进行使用,指向首结点,如下图所示:
双向循环链表即是将上述二者联合起来使用,以达到完成各类工作的效用,在以后大多数链表结构的使用中,我们都会选择双向循环链表。
双向循环链表的循环方式是其尾结点的后继指针指向头结点(表头),而头结点的前置指针指向尾结点,达到双向循环的目的,这样不仅使得对链表尾部的操作更为简单,也减少了对NULL指针的引用。
新建头文件"dclist.h"(double circle list)对链表函数声明进行保存,方便我们后期查看及使用
- #pragma once
- #include<stdio.h>
- #include<string.h>
- #include<assert.h>
- #include<ctype.h>
- #include<stdlib.h>
-
- typedef int Element;
-
- typedef struct DNode
- {
- Element data;
- DNode* next;
- DNode* prev;
- }DNode, * PDNode;
-
- //初始化链表
- void InitList(PDNode plist);
-
- //购买结点
- PDNode BuyNode();
-
- //头插
- bool Push_Front(PDNode plist, Element val);
-
- //尾插
- bool Push_Back(PDNode plist, Element val);
-
- //按位置插
- bool Insert_Pos(PDNode plist, int pos, Element val);
-
- //头删
- bool Pop_Front(PDNode plist);
-
- //尾删
- bool Pop_Back(PDNode plist);
-
- //按位置删
- bool Erease_Pos(PDNode plist, int pos);
-
- //按值删
- bool Erease_Val(PDNode plist, Element val);
-
- //查找 返回结点地址
- PDNode Find(PDNode plist, Element val);
-
- //判空
- bool IsEmpty(PDNode plist);
-
- //获取元素个数
- int GetSize(PDNode plist);
-
- //打印链表
- void Print(PDNode plist);
-
- //清空
- void Clear(PDNode plist);
-
- //销毁
- void Destroy(PDNode plist);
在对使用链表进行数据的储存之前,我们需要进行链表的初始化
如图所示,带双向循环链表的结点由三部分构成:
1.数据域
2.指针域1:指向后继结点的指针
3.指针域2:指向前置结点的指针
- typedef int Element;//如此对数据类型进行改名操作,若要进行修改数据类型操作会更加方便
-
- typedef struct DNode
- {
- Element data;
- DNode* next;//后继指针
- DNode* prev;//前置指针
- }DNode, * PDNode;
与单链表不同的是,在双向循环链表不存在数据时,头结点的指针域不置为NULL,其后继指针与前置指针都指向头结点本身,以形成最基础的循环形态:
- //初始化链表
- void InitList(PDNode plist)
- {
- assert(plist != NULL);
- plist->next = plist->prev = plist;
- }
因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数
- //购买结点
- PDNode BuyNode()
- {
- PDNode node = (PDNode)malloc(sizeof(DNode));
- if (node == NULL)
- {
- exit(1);
- }
- memset(node, 0, sizeof(*node));//将申请的结点内存重置,防止数据残留
- return node;
- }
双向循环链表存在多个指针指向的断开和重新指向,我们在此先进行分析和讲解,如下图所示:
按照正常逻辑,我们再重新指定指针指向时,首先需要打破原有的指针指向,即上图中红叉部分,但实际编写程序中,我们会直接修改指针的指向,从而在代码中只会体现重新指向的过程。
在图中,已经标注出4个指针指向的修改,首先,我们应该对新申请的结点中指针(即图中34箭头的指向)进行指定,因为如果我们先对旧有指针进行修改,那么我们可能会丢失结点的地址信息,从而失去我们已经保存的数据。
其中指针具体的指向修改,我们不用太在意顺序,只需要记住先考虑新节点的指向,后修改旧结点的指向即可,下面的代码中,我们将采用4321的顺序:
我们先进行通用插入的实现:
- //按位置插
- bool Insert_Pos(PDNode plist, int pos, Element val)
- {
- assert(plist != NULL);
- //判断插入位置是否合法
- if (pos < 0 || pos > GetSize(plist))
- {
- return false;
- }
- PDNode node = plist;//将结点指针定位至需要插入数据位置之前的结点
- for (int i = 0; i < pos; i++)
- {
- node = node->next;
- }
- PDNode newnode = BuyNode();//申请新节点
- newnode->data = val;
- //以下为插入步骤
- newnode->next = node->next;//4
- newnode->prev = node;//3
- node->next->prev = newnode;//2
- node->next = newnode;//1
- return true;
- }
因为表头的前置指针指向尾结点,所以我们不用像单链表一样进行遍历后才能尾插。
- //尾插
- bool Push_Back(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode newnode = BuyNode();
- newnode->data = val;
- //插入
- newnode->next = plist;//4
- newnode->prev = plist->prev;//3
- plist->prev = newnode;//2
- plist->prev->next = newnode;//1
- return true;
- }
直接调用按位置插入,插入位置为0
- //头插
- bool Push_Front(PDNode plist, Element val)
- {
- assert(plist != NULL);
- return Insert_Pos(plist, 0, val);
- }
查找与普通链表的查找基本相同,但是在while循环中的结束条件不同,以结点指针是否指向表头来判断
- //查找 返回结点地址
- PDNode Find(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode node = plist->next;
- //遍历链表查找与val 相同的值
- while (node != plist && node->data != val)
- {
- node = node->next;
- }
- if (plist == node)
- {
- return NULL;
- }
- return node;
- }
双向循环链表的删除大致与单链表相同,但在两相隔结点的重新链接时需要连结两个指针,即被删除结点的前置结点的后继指针指向被删除结点的后继结点,而后继结点的前置指针指向前置结点
- //按位置删
- bool Erease_Pos(PDNode plist, int pos)
- {
- assert(plist != NULL);
- if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist))
- {
- return false;
- }
- PDNode delnode = plist;
- for (int i = 0; i <= pos; i++)
- {
- delnode = delnode->next;
- }
- //重新连结
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
这里调用了查找函数,找到val值所在的结点
- //按值删
- bool Erease_Val(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode delnode = Find(plist, val);
- if (delnode == NULL)
- {
- return false;
- }
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
同样的,因为表头的前置指针指向尾结点,则可以减少遍历链表所消耗的资源。
- //尾删
- bool Pop_Back(PDNode plist)
- {
- assert(plist != NULL);
- if (IsEmpty(plist))
- {
- return false;
- }
- PDNode delnode = plist->prev;
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
直接调用按位置删除,删除位置为0
- //头删
- bool Pop_Front(PDNode plist)
- {
- assert(plist != NULL);
- return Erease_Pos(plist, 0);
- }
这里我们使用比较简单的方法,一直头删即可,当然这里的尾删也可以使用,消耗资源相同。
- //清空
- void Clear(PDNode plist)
- {
- assert(plist != NULL);
- while (!IsEmpty(plist))
- {
- Pop_Front(plist);
- }
- }
调用清空函数,并使链表达到未初始化的状态。
- //销毁
- void Destroy(PDNode plist)
- {
- Clear(plist);
- plist->next = plist->prev = NULL;
- }
- #include "dclist.h"
-
- //初始化链表
- void InitList(PDNode plist)
- {
- assert(plist != NULL);
- plist->next = plist->prev = plist;
- }
-
- //购买结点
- PDNode BuyNode()
- {
- PDNode node = (PDNode)malloc(sizeof(DNode));
- if (node == NULL)
- {
- exit(1);
- }
- memset(node, 0, sizeof(*node));
- return node;
- }
-
- //头插
- bool Push_Front(PDNode plist, Element val)
- {
- assert(plist != NULL);
- return Insert_Pos(plist, 0, val);
- }
-
- //尾插
- bool Push_Back(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode newnode = BuyNode();
- newnode->data = val;
- newnode->prev = plist->prev;
- newnode->next = plist;
- plist->prev->next = newnode;
- plist->prev = newnode;
- return true;
- }
-
- //按位置插
- bool Insert_Pos(PDNode plist, int pos, Element val)
- {
- assert(plist != NULL);
- if (pos < 0 || pos > GetSize(plist))
- {
- return false;
- }
- PDNode node = plist;
- for (int i = 0; i < pos; i++)
- {
- node = node->next;
- }
- PDNode newnode = BuyNode();
- newnode->data = val;
- newnode->next = node->next;
- newnode->prev = node;
- node->next->prev = newnode;
- node->next = newnode;
- return true;
- }
-
- //头删
- bool Pop_Front(PDNode plist)
- {
- assert(plist != NULL);
- return Erease_Pos(plist, 0);
- }
-
- //尾删
- bool Pop_Back(PDNode plist)
- {
- assert(plist != NULL);
- if (IsEmpty(plist))
- {
- return false;
- }
- PDNode delnode = plist->prev;
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
-
- //按位置删
- bool Erease_Pos(PDNode plist, int pos)
- {
- assert(plist != NULL);
- if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist))
- {
- return false;
- }
- PDNode delnode = plist;
- for (int i = 0; i <= pos; i++)
- {
- delnode = delnode->next;
- }
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
-
- //按值删
- bool Erease_Val(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode delnode = Find(plist, val);
- if (delnode == NULL)
- {
- return false;
- }
- delnode->prev->next = delnode->next;
- delnode->next->prev = delnode->prev;
- free(delnode);
- delnode = NULL;
- return true;
- }
-
- //查找 返回结点地址
- PDNode Find(PDNode plist, Element val)
- {
- assert(plist != NULL);
- PDNode node = plist->next;
- while (node != plist && node->data != val)
- {
- node = node->next;
- }
- if (plist == node)
- {
- return NULL;
- }
- return node;
- }
-
- //判空
- bool IsEmpty(PDNode plist)
- {
- assert(plist != NULL);
- return (plist->next == plist->prev && plist->next == plist);
- }
-
- //获取元素个数
- int GetSize(PDNode plist)
- {
- assert(plist != NULL);
- PDNode node = plist->next;
- int count = 0;
- while (node != plist)
- {
- node = node->next;
- count++;
- }
- return count;
- }
-
- //打印链表
- void Print(PDNode plist)
- {
- assert(plist != NULL);
- PDNode node = plist->next;
- while (node != plist)
- {
- printf("%d ", node->data);
- node = node->next;
- }
- printf("\n");
- }
-
- //清空
- void Clear(PDNode plist)
- {
- assert(plist != NULL);
- while (!IsEmpty(plist))
- {
- Pop_Front(plist);
- }
- }
-
- //销毁
- void Destroy(PDNode plist)
- {
- Clear(plist);
- plist->next = plist->prev = NULL;
- }
以上为双向循环链表的实现和方法讲解,双向循环链表为比较常用的链表形式,学习并理解双向循环链表会方便以后的学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。