简单介绍一下双向循环链表
双向循环链表和单链表比较类似,只是比单链表多了一个prev的结构体指针,为什么要定义一个prev的结构体指针呢,用这种方式定义链表会不会更麻烦呢,其实恰恰相反,我们发现在对单链表进行增删的时候往往要保留上一个节点的结构体指针,但是使用双向循环链表之后就不用保留上一个节点的结构体指针,还有对单链表的尾插时需要遍历链表才能尾插,而双向循环链表只需要对哨兵位前一个节点进行插入就可以了,双向循环链表最大的优点就是在对链表进行头插时不用使用复杂的二级指针,可以让代码看起来更加简单易懂。
定义双向循环链表的初步工作
定义一个结构体
首先定义一个结构体作为链表的一个节点
typedef int LTDataType;//自定义
typedef struct ListNode
{
LTDataType data;//链表元素
struct ListNode* next;//指向下一个链表的节点
struct ListNode* prev;//指向上一个链表
}ListNode;
定义一个创建空间的函数
每当我们想要插入一个节点时,就需要开辟一个节点,为了方便使用,我们在这里创建一个函数
ListNode* BuyLTNode(LTDataType x)//申请空间
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
return newnode;
}
创建并初始化一个哨兵位
创建一个函数对哨兵位初始化,使哨兵位的next和prev指针都指向自己,初步形成一个环状。
ListNode* ListCreate()//返回链表的头结点
{
ListNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
链表的实现
我们还是从头插,头删,尾插,尾删,在pos之前增加,删除pos节点这几个方面实现。
void ListPushBack(ListNode* phead, LTDataType x);//双向链表的尾插
void ListPopBack(ListNode* phead)//链表的尾删
void ListPushFront(ListNode* phead, LTDataType x);//双向链表的头插
void ListPopFront(ListNode* phead)//链表的头删
void ListInsert(ListNode* pos, LTDataType x);// 双向链表在pos的前面进行插入
void ListErase(ListNode* pos)// 双向链表删除pos位置的节点
链表的尾插
双向循环链表的尾插,表面上尾插,是进行尾插实际上在(phead)哨兵位之前进行插入,在插入时也有一些细节需要注意,在连接时应该先连接(phead)哨兵位的prev进行链接再对phead进行链接,不然的话就会导致对phead进行链接后就会找不到哨兵位的上一个节点,从而导致链表断开,从而出现bug
下面是代码
void ListPushBack(ListNode* phead, LTDataType x)//双向链表的尾插
{
ListNode* newnode = BuyLTNode(x);//创建一个新的节点
ListNode* prev = phead->prev;//哨兵位的前一个节点
//将新的节点和链表的哨兵位的上一个节点链接
prev->next = newnode;
newnode->prev = prev;
//新的节点和哨兵位链接
newnode->next = phead;
phead->prev = newnode;
}
链表的尾删
尾删时需要记录哨兵位的上一个节点并将哨兵位上一个节点的上一个节点和哨兵位链接,最后释放头结点上一个节点的空间
void ListPopBack(ListNode* phead)//链表的尾删
{
ListNode* tail = phead->prev;//记录哨兵位的上一个节点
assert(tail != phead);//断言判断链表是否为空
ListNode* prev = tail->prev;
//哨兵位上一个节点的上一个节点和哨兵位链接
phead->prev = prev;
prev->next = phead;
free(tail);//释放空间
}
由于双向循环链表的头删,头插,和尾删,尾插,极其相似,下面我就不多做介绍了
链表的头插
void ListPushFront(ListNode* phead, LTDataType x)//双向链表的头插
{
ListNode* newhead = BuyLTNode(x);//申请空间
//先连接后面
newhead->next = phead->next;
phead->next->prev = newhead;
//再链接前面
phead->next = newhead;
newhead->prev = phead;
}
链表的头删
void ListPopFront(ListNode* phead)//链表的头删
{
ListNode* head = phead->next;
assert(head != phead);
//创建哨兵位下一个节点的下一个节点的指针
ListNode* next = head -> next;
//将哨兵位下一个节点的下一个节点和哨兵位链接
phead->next = next;
next->prev = phead;
//释放空间
free(head);
}
下面也是比较好理解的
在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)// 双向链表在pos的前面进行插入
{
ListNode* newnode = BuyLTNode(x);//申请空间
ListNode* prev = pos->prev;//创建节点
//先和pos前面的前面的节点链接
newnode->prev = prev;
prev->next = newnode;
//和pos前面的节点链接
newnode->next = pos;
pos->prev = newnode;
}
删除pos节点
void ListErase(ListNode* pos)// 双向链表删除pos位置的节点
{
assert(pos->next != pos)//断言链表不为空
pos->prev->next = pos->next;//将pos后面的节点设为pos前面节点的next
pos->next->prev = pos->prev;//将pos前面的节点设为pos后面节点的prev
free(pos);
}
总结
以上就是今天的·全部内容感谢大家的支持!