一.双向循环带头链表
所谓双向循环带头链表可以分为以下3类来进行理解:
首先是双向:双向表示后一个节点可以找到自己的前驱节点,这一点和单链表有本质的区别。
其次是带头:若链表带头的话说明,链表有一个头结点,这个头结点也可称为哨兵位的头结点,该节点和普通节点一样,依然有自己的前驱和后继节点,但是头结点的data/val一般为随机值。
最后是循环:循环链表指的是链表的尾结点的后继节点是头结点,头结点的前驱节点是尾结点。构成了一个大大的环状结构。
二.双向循环链表代码实现
typedef int LTDateType;
typedef struct ListNode
{
LTDateType data;
struct ListNode* next;//指向下一个数据
struct ListNode* prev;//指向前一个数据
}LTNode;
三.常用函数的实现
节点初始化,由于是带头结点,因此一定要创建一个哨兵位头节点
1.节点初始化
L* ListInit()
{
//节点初始化 双向循环带头节点
L* head = (L*)malloc(sizeof(L));
head->next = head;
head->pre = head;
return head;
}
由于以后的增删改查都要涉及到节点的创建,所以实现一个这样的函数简化代码
2.创建新节点
L* CreateNode(LI x)
{
//创建新节点
L* newnode = (L*)malloc(sizeof(L));
newnode->next = NULL;
newnode->pre = NULL;
newnode->data = x;
return newnode;
}
3.遍历打印
void ListPrint(L* head)
{
assert(head);
//打印
L* cur = head->next;
if (cur == head || cur == head->next);
{
printf("头节点");
}
while (cur != head)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
4.尾插元素
void ListPushBack(L* head, LI x)
{
assert(head);
L* tail = head->pre;
L* nexttail = CreateNode(x);
//迭代
tail->next = nexttail;
nexttail->next = head;
nexttail->pre = tail;
head->pre = nexttail;
}
5.头插元素
void ListPushFront(L* head, LI x)
{
assert(head);
创建新节点
//L* newnode = CreateNode(x);
//L* nexthead = head->next;
前插 改变指向
//newnode->next = nexthead;
//newnode->pre = head;
//nexthead->pre = newnode;
//head->next = newnode;
//相当于头插
ListInsert(head->next, x);
}
6.尾删元素
void ListPopBack(L* head)
{
assert(head);
assert(head->next != head);
L* tail = head->pre;
L* pretail = tail->pre;
//改变指向 尾删
pretail->next = head;
head->pre = pretail;
free(tail);
}
7.头删元素
void ListPopFront(L* head)
{
assert(head);
assert(head->next != head);
L* headnext = head->next;
L* nextnext = headnext->next;
//改变指向 头删
nextnext->pre = head;
head->next = nextnext;
free(headnext);
}
8.查找值为X的节点(返回地址)
L* ListFind(L* head, LI x)
{
assert(head);
L* findnode = head->next;
while (findnode->next != head)
{
if (findnode->data == x)
{
return findnode;
}
//迭代
findnode = findnode->next;
}
return NULL;
}
9.插入节点到指定位置(头插)
void ListInsert(L* pos, LI x)
{
assert(pos);
L* insert = CreateNode(x);
L* prepos = pos->pre;
//往prepos 和pos 中间插入节点
prepos->next = insert;
insert->next = pos;
insert->pre = prepos;
pos->pre = insert;
}
10.删除指定位置的节点
void ListErase(L* pos)
{
assert(pos);
L* prepos = pos->pre;
L* nextpos = pos->next;
//改变pos两边节点指向
prepos->next = nextpos;
nextpos->pre = prepos;
//删除节点 释放内存
free(pos);
}
11.删除链表。释放内存
void destory(L* head)
{
assert(head);
L* cur = (head)->next;
while (cur != head)
{
L* next = cur->next;
free(cur);
cur = next;
}
free(head);
head = NULL;
}
**
注意事项:双向循环带头链表传参不需要传入二级指针,因为是带头节点,带头的节点(哨兵位)不需要发生改变。只需要改变哨兵位后面的节点就可以了。
**