前面已经介绍过了不带头节点,不带环的双向链表,以下将介绍带头节点,带环的双向链表的基本操作。
不带头结点时,用一个头指针代表整个链表。带头节点,则用头结点来表示整个链表。此时,头结点的数据域是没有意义的,对其任意赋值即可。如下图:
当链表是单向时,只有一个next指针指向下一个节点。而双向时,除有next指向下一个节点外,还有一个prev指针指向前一个结点。
当链表不带环时,链表的最后一个元素的next域为空,而带环时,链表的最后一个结点的next域要指向头结点,而头结点的prev域要指向链表的最后一个节点。这样,整个链表就呈一个环状。
通过上述分析,便可知道,当双向链表为空时,头结点的next域和prev域均指向头结点。
1. 链表的结点结构
上述分析已经知道,双向链表的一个节点包括三部分:数据域,next域,prev域,所以,结点结构定义如下:
typedef char DlinklistType;//将节点的数据域类型统一表示
//定义双向链表的节点结构
typedef struct DlinkNode
{
DlinklistType data;//数据域
struct DlinkNode* next;//指向下一个节点
struct DlinkNode* prev;//指向上一个节点
}DlinkNode;
2. 双向链表的初始化
因为该链表是带头节点的,所以初始化时,要创建一个头结点来表示整个链表,此时,还没有真正的节点,所以链表为空,所以将头结点的next指针和prev指针均指向自己,数据任意赋值。
因为初始化时,要使头指针指向头结点,即要改变头指针的指向,所以,在函数传参时,传的是头指针的地址,即二级指针(在测试函数中定义的是头指针,即DlinkNode*类型的变量)。
代码如下:
//创建结点
DlinkNode* CreateNode(DlinklistType value)
{
DlinkNode* new_node = (DlinkNode*)malloc(sizeof(DlinkNode));
new_node->data = value;
new_node->next = new_node;//初始时,使节点的两个指针域均指向自身
new_node->prev = new_node;
return new_node;
}
//初始化双向链表
void InitDlinklist(DlinkNode** phead)
{
if(phead == NULL)
{
//非法输入
return;
}
//申请头结点,使头指针指向新节点
*phead = CreateNode(0);
return;
}
因为再对头指针初始化后,头指针始终指向头结点,对链表进行以下操作(不包括销毁链表)时,改变的是头结点的相关指针域,而未改变头指针的指向,所以,在函数传参时传的均是头指针,即一级指针。
3. 对双向链表进行尾插
在尾插时,首先要找到尾节点,因为链表是带环的双向链表,所以,可以通过头结点的prev指针找到尾节点。然后在尾节点后面创建一个新节点进行插入。
在插入时,需要改变四个指针的指向,两两一组,分别为:
(1)原尾节点的next域要指向新节点,新节点的prev要指向原尾节点;
(2)新节点的next域要指向头结点,头结点的prev要指向新节点。
代码如下:
//对双向链表进行尾插
//head为双向链表的头指针,value为要插入的值
void DlinklistPushBack(DlinkNode* head,Dlinklis