一、双向带头链表的概念
双向带头链表是链表的一种,一个结点由数据部分、指向前一个结点的指针和指向后一个结点的指针三部分组成。
typedef struct ListNode{ //定义双链表结点类型
LTDataType data; //数据域
struct ListNode *prev,*next; //前驱和后继指针
}ListNode;
尽管看起来双向链表的结构较单向链表更为复杂,但是操作更为简便。双向链表相比单向链表在尾插、某个结点之前加入新结点、删除某结点等操作上,更加方便。
而头结点的存在,能减少对输入结点为空情况的检查,使操作更方便。
二、双向带头链表的基本操作
1.创建头结点
头结点创建以后数据域为空,其前驱指针和后继指针都指向自己,也是判断链表是否为空的依据。
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
if (head == NULL)
{
perror("malloc fail!");
exit(1);
}
head->next = head;
head->prev = head;
return head;
}
2.在pos之前插入结点
首先,需要申请一个新结点,考虑写成一个函数,后续直接调用
ListNode* BuyNewNode(LTDataType x)
{
ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NewNode->prev = NULL;
return NewNode;
}
然后,调整前后结点的指针指向,建议先完成新结点的前驱后驱指针指向,再修改前驱结点的后继指针和后继结点的前驱指针
void ListInsert(ListNode* pos, LTDataType x)
{
ListNode* cur = BuyNewNode(x);
cur->next = pos;
cur->prev = pos->prev;
pos->prev->next = cur;
pos->prev = cur;
}
3.删除pos结点
删除结点则只需修改其前驱后继结点的相关指针,然后free掉pos结点
void ListErase(ListNode* pos)
{
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
4.头插头删,尾插尾删以及链表摧毁
有了Insert和Erase函数以后,以上操作均可通过套用函数来完成,而链表的摧毁则仅需要一个循环,以链表是否为空为条件,不断删除头结点后的第一个结点,最后单独删除头结点即可。
5.打印链表
以链表是否为空为条件,对链表进行遍历,需要注意的是,头结点为空
void ListPrint(ListNode* pHead)
{
ListNode* head = pHead->next;
while (head != pHead)
{
printf("%d->", head->data);
head = head->next;
}
printf("\n");
}
6.找特定结点
同样使用链表是否为空为条件,对链表进行遍历,寻找符合条件的结点。
以上为带头双向链表的相关操作。作为数据结构经常会使用到的链表,相关操作一定要熟练,套用函数增加效率可行,但是背后的基本逻辑需掌握。