今天去考了科一 好久没学习了呜呜呜感觉有点忘了 !!写篇博客回忆一下-------------------------------
双向循环链表
顾名思义链表的一种,也叫双链表,他的每个数据节点中有两个指针 一个指向直接后继,一个指向后继前驱。
如图:👇👇👇
所以 在定义双向链表时 在一个节点中需要存储两个指针,一个next 保存下一个节点的地址,prev保存上一个节点的指针。另外,头指针要的prev指向最后一个节点,最后一个节点的next指向头节点。(头节点也可以称作哨兵位,不存储有效数据)
从增删查改的角度来实现双向带头循环链表;
接口如下:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdlib.h>
#include <stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
ListNode* BuyListNode(LTDataType x);
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
代码的实现:
1、创建一个要复用的创建节点的函数以及链表的初始化:
在初始化中,链表的头指向自己 尾也指向自己
ListNode* BuyListNode(LTDataType x)
{
ListNode* Node = (ListNode*)malloc(sizeof(ListNode));
if (Node == NULL)
{
perror("malloc fail");
exit(-1);
}
Node->_data = x;
Node->_next = NULL;
Node->_prev = NULL;
return Node;
}
ListNode* ListCreate()
{
ListNode* Phead = BuyListNode(-1);
Phead->_next = Phead;
Phead->_prev = Phead;
return Phead;
}
2、链表的头插和尾插
//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
//哨兵位的prev指向的就是最后一个节点的指针 链接在后面
ListNode* Tail = pHead->_prev;
Tail->_next = newnode;
newnode->_prev = Tail;
newnode->_next = pHead;
pHead->_prev = newnode;
}
//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
//保存原来节点的地址
ListNode* Next = pHead->_next;
//将新节点链接在哨兵位头节点之后
newnode->_next = Next;
Next->_prev = newnode;
pHead->_next = newnode;
newnode->_prev = pHead;
}
3、头删和尾删
//尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->_next != pHead);
ListNode* Tail = pHead->_prev;
ListNode* Prev = Tail->_prev;
Prev->_next = pHead;
pHead->_prev = Prev;
free(Tail);
}
//头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->_next != pHead);
ListNode* Next = pHead->_next;
ListNode* Listing = Next->_next;
pHead->_next = Listing;
Listing->_prev = pHead;
free(Next);
}
4、链表的查找
//查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->_next;
while (cur != pHead)
{
if (cur->_data == x)
{
//找到则返回该节点
return cur;
}
cur = cur->_next;
}
return NULL;
}
5、可以将上查找节点的地址存储在pos中 后在pos的前后进行插入 删除操作
//在pos位置插入
void ListInsert(ListNode* pos, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* Prev = pos->_prev;
Prev->_next = newnode;
newnode->_prev = Prev;
newnode->_next = pos;
pos->_prev = newnode;
}
//在pos位置删除
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* Prev = pos->_prev;
ListNode* Next = pos->_next;
Prev->_next = Next;
Next->_prev = Prev;
free(pos);
}
效果:
6、链表的打印
void ListPrint(ListNode* pHead)
{
ListNode* cur = pHead->_next;
//由于是循环链表 当cur走到phead(哨兵位)时 即走完一次链表,为停止条件
while (cur != pHead)
{
printf("%d ", cur->_data);
cur = cur->_next;
}
printf("\n");
}
相对于双向带头链表来说 优缺点有如下👇
优点:
(1)可以在任意位置插入删除 高效方便;时间复杂度O(1);
(2)可以按需要申请空间
缺点:
(1)不支持随机访问
(2)快排、二分查找的方法不合适