数据结构-带头结点双向循环链表ListNode详解(增删改查)
1.带头双向循环链表
这种数据结构很大程度上弥补了单链表的缺点,使增加和删除节点的时间复杂度减小,使用头节点也解决了函数传参的二级指针问题。
2头文件以及接口的实现
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
// 创建返回链表的头结点.
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);
//在堆空间申请一个节点
ListNode* create(LTDataType x);
2.1在堆空间申请一个节点
动态申请一个sizeof(ListNode)大小的节点并返回节点地址
ListNode* create(LTDataType x) {
ListNode* listnode =(ListNode*)malloc(sizeof(ListNode));
listnode->_data = x;
listnode->_next = NULL;
listnode->_prev = NULL;
return listnode;
}
2.2创建并返回链表的头节点
初始化链表,动态申请一个节点,节点的值可以是任意值(搜索的时候会做特殊判断,不会搜索到头节点,头节点的值是无效的),节点的prev和next必须指向自己,并且返回这个节点
ListNode* ListCreate() {
ListNode* listnode = create(-1);
listnode->_next = listnode;
listnode->_prev = listnode;
return listnode;
}
2.3双向链表的销毁
双向链表中的节点全都是通过malloc创建的,所以在销毁的时候需要全部free掉,但是由于外面仍然有变量指向头节点,所以将全部节点free之后需要exit。防止内存泄露。先free掉节点元素,最后free掉头节点。
void ListDestory(ListNode* pHead) {
ListNode* listnode = pHead->_next;
ListNode* tmp = NULL;
while (listnode!=pHead)
{
listnode->_prev->_next = listnode->_next;
listnode->_next->_prev = listnode->_prev;
tmp = listnode->_next;
free(listnode);
listnode = tmp;
}
free(pHead);
exit(0);
}
2.4双向链表的打印
从head->next节点开始打印,到head节点结束
void ListPrint(ListNode* pHead) {
ListNode* listnode = pHead->_next;
while (listnode!=pHead)
{
printf("%d<=>", listnode->_data);
listnode = listnode->_next;
}
printf("\n");
}
2.5 双向链表尾插
head->prev就是双向链表的最后一个节点,将新节点的prev指向双向链表的最后一个节点,新节点的next指向head,将没插入前的最后一个节点的next指向新节点,head节点的prev指向新节点。
void ListPushBack(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* listnode = create(x);
listnode->_next = pHead;
listnode->_prev = pHead->_prev;
pHead->_prev->_next = listnode;
pHead->_prev = listnode;
}
测试
void test1()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListDestory(plist);
}
2.6双向链表的的尾删
head->prev代表最后一个节点,将这个节点的前一个节点X的next指向head,将head的prev指向X,然后free掉最后一个节点即可
void ListPopBack(ListNode* pHead) {
assert(pHead->_next != pHead);
ListNode* listnode = pHead->_prev;
//printf("%d \n", listnode->_prev->_next->_data);
listnode->_prev->_next = pHead;
pHead->_prev = listnode->_prev;
}
测试
void test2()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
/*ListDestory(plist);*/
ListPopBack(plist);
ListPopBack(plist);
ListPrint(plist);
}
2.7双向链表的头插
head->next代表双向链表的第一个元素,在第一个元素前插入,首先将新元素的prev指向head,将新元素的next指向head->next,然后head->next->prev指向新节点,将head->next指向新节点
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* listnode = create(x);
listnode->_prev = pHead;
listnode->_next = pHead->_next;
pHead->_next->_prev = listnode;
pHead->_next = listnode;
}
test
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* listnode = create(x);
listnode->_prev = pHead;
listnode->_next = pHead->_next;
pHead->_next->_prev = listnode;
pHead->_next = listnode;
}
2.8双向链表的头删
删除head->next,将head->next->prev指向head,将head->prev指向head->next->next,在将head->next节点free掉
void ListPopFront(ListNode* pHead) {
assert(pHead);
assert(pHead != pHead->_next);
ListNode* listnode = pHead->_next;
listnode->_next->_prev = pHead;
pHead->_next = listnode->_next;
free(listnode);
}
test
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* listnode = create(x);
listnode->_prev = pHead;
listnode->_next = pHead->_next;
pHead->_next->_prev = listnode;
pHead->_next = listnode;
}
2.9双向链表的查找
从head->next位置开始循环遍历,循环结束条件为node==head,这样不会遍历头节点,所以头节点的值是无效的。找到返回节点,没找到返回NULL。
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* listnode = pHead->_next;
while (listnode!=pHead)
{
if (listnode->_data == x)return listnode;
listnode = listnode->_next;
}
return NULL;
}
test
void test5()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
/*ListDestory(plist);*/
printf("%d", ListFind(plist, 3)->_data);
//ListPrint(plist);
}
2.10双向链表在pos节点前插入
将新节点的next指向pos,新节点prev指向pos->prev,将pos->prev->next指向新节点,将pos->prev指向新节点。
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = create(x);
newnode->_prev = pos->_prev;
newnode->_next = pos;
pos->_prev->_next = newnode;
pos->_prev = newnode;
}
test
void test6()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 6);
/*ListDestory(plist);*/
ListPrint(plist);
ListInsert(ListFind(plist,1), 7);
ListInsert(ListFind(plist, 6), 5);
ListPrint(plist);
}
2.11双向链表删除一个节点
删除节点pos,将pos->prev->next指向pos->next,将pos->next->prev=pos->prev,free掉pos
void ListErase(ListNode* pos) {
assert(pos);
pos->_prev->_next = pos->_next;
pos->_next->_prev = pos->_prev;
free(pos);
}
test
void test7()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 6);
/*ListDestory(plist);*/
ListPrint(plist);
ListErase(ListFind(plist, 1));
ListErase(ListFind(plist, 3));
ListErase(ListFind(plist, 6));
ListPrint(plist);
}