目录
前言:暑期复习之双链表的简单理解
在基础数据结构(链表1)中复习了单链表的设计思路以及代码的具体详解,链表中还有一个双链表,也就是循环链表。
一·双链表的设计思路
双向链表也叫做双向表,是链表的一种,它由多个结点组成,顾名思义,也就是在双向链表的节点中有两个指针域,其中一指向直接后继,另一指向直接前驱,数据域用来存储数据。链表的头节点的数据域不存储数据指向前驱结点的指针域值为null,指向后继节点的指针域值指向第一个真正存储数据的结点
typedef int ElemType;
typedef struct DuLNode
{
ElemType data;//数据域
struct DuLNode* prev;//前驱指针
struct DuLNode* next;//后继指针
}DuLNode,*PDuLNode;
typedef struct
{
DuLNode* head;//PDuNode head
int cursize;//数据节点的个数
}DuLinkList;
二·双链表的具体代码详解
对链表的操作无非就是增删改查操作
1.初始化操作
由上图的逻辑图示可以很清楚的看到,前驱指针和后驱指针都会指向自身,head也指向新申请的这个节点
//双链表的初始化
void InitDuList(DuLinkList* pList)
{
assert(pList != NULL);
pList->cursize = 0;
DuLNode*s = (DuLNode*)malloc(sizeof(DuLNode));//从堆内申请空间
if (NULL == s)
{
exit(1);
}
s->next = s;
s->prev = s;
pList->head = s;
}
2.双链表的打印
双链表的打印操作:就是以结点的next域为判断条件,如果next域不是指回头节点,那么进入循环进行打印,然后指针向后移动,继续打印,如果next域指回头结点,那么说明链表中的数据结点已经全部打印完成,退出循环即可。
//双链表的打印
void PrintDuList(const DuLinkList* pList)
{
assert(pList != NULL);
DuLNode* p = pList->head->next;
while (p != pList->head)
{
printf("%d", p->data);
p = p->next;
}
printf("\n");
}
3.查询双链表
会打印就会查询,这两个函数非常相似,就不过多解释了。
//查询双链表的数据元素
DuLNode* FindValu(const DuLinkList* pList, ElemType val)
{
assert(pList != NULL);
DuLNode* p = pList->head->next;
while (p!=pList->head&&p->data!=val)
{
p = p->next;
}
if (p == pList->head)
{
p = NULL;
}
return p;
}
4.插入操作
插入操作包括了,头插,尾插,其余位置插入
(1)其余位置插入:
如图示:看图插入操作理解起来就比较方便
//在ptr指针前进行插入操作
bool Insert_Prev(DuLinkList* pList, DuLNode* p, ElemType val)
{
assert(pList != NULL);
if (p == NULL)
{
return false;
}
DuLNode* s = (DuLNode*)malloc(sizeof(DuLNode));
s->data = val;
s->prev = p->prev;
s->next = p;
p->prev = s;
s->prev->next = s;
pList->cursize += 1;
return true;
}
(2)头插操作:就是在第一个数据节点前插入一个新节点。
//头插
void Push_Front(DuLinkList* pList, ElemType val)
{
assert(pList != NULL);
Insert_Prev(pList, pList->head->next, val);
}
(3)尾插操作:尾插就是在头节点之前插入,反转过来相当于尾插。
//尾插
void Push_Back(DuLinkList* pList, ElemType val)
{
assert(pList != NULL);
Insert_Prev(pList, pList->head, val);
}
5.删除操作
删除操作包括头删,尾删,其余位置删除
(1)其余位置删除:其实删除操作非常简单,就是将要删除结点的前驱指针和后继指针的指向改变,结点的前一个结点指向后一个结点,将这个将要删除的结点绕过,然后free掉空间,数据节点总数减1.
//删除操作
Status Erase(DuLinkList* pList, DuLNode* ptr)
{
assert(pList != NULL);
if ( NULL== ptr)
{
return NULLPTR;
}
ptr->prev->next = ptr->next;
ptr->next->prev = ptr->prev;
free(ptr);
pList->cursize -= 1;
return OK;
}
(2)头删操作:在头节点之后的第一个数据结点
//头删
void Pop_Front(DuLinkList* pList)
{
assert(pList != NULL);
Erase(pList, pList->head->next);
}
(3)尾删操作:在头节点之前的结点
//尾删
void Pop_Back(DuLinkList* pList)
{
assert(pList != NULL);
Erase(pList, pList->head->prev);
}
6.清空与销毁函数
(1)清空:判断链表是否是一个空链表,在利用循环进行头删,也就是清空链表中的每一个结点数据
//清空函数
void ClearDuList(DuLinkList* pList)
{
assert(pList != NULL);
while (!IsEmpty(pList))
{
Pop_Front(pList);
}
}
(2)销毁:利用清空函数,只不过销毁是将整个链表,包括头节点也清空的函数
//摧毁函数
void DestroyDuList(DuLinkList* pList)
{
assert(pList != NULL);
ClearDuList(pList);
free(pList->head);
pList->head = NULL;
}
三·双链表的总结
(1)双链表有双指针域:前驱指针和后继指针,方便插入删除
(2)双链表的查询操作复杂度高
(3)动态链表在堆上申请空间