双向循环链表
1.双向链表的创建
这里我使用的是创建一个头结点(哨兵节点),所以在创建链表的时候先创建一个头结点,这个头结点next指针和prev指针都先指向NULL,除此之外,这个节点并不存储其他任何值。
如果需要在后面添加新的值,需要创建一个新的节点,与头结点不同的是后续创建的节点都需要存储准确的值。
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//创建一个新的节点
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.双向链表的销毁
任何链表再使用完成后都需要进行销毁,因为双向链表是在堆上创建的,如果不及时进行销毁,会在成内存泄漏,可能会造成严重的损失。虽然现在的编译器能够尽量减少这种损失,但在后续使用完之后最好是及时销毁,减少不必要的损失。
注:这里创建的链表有头结点,所以最后只需要判断cur是否指向头结点即可
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
//因为有头结点,所以这里直接判断cur是否会访问到头结点即可
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(pHead);
}
3.双向链表的头插头删
(1)头插
这里采用三指针的方法,这样可以防止没有除头结点以外的节点所发生的错误,如果在有节点的情况下next就指向该节点。
- 在创建newnode的同时,创建一个next节点,并将phead->next指向next
- 将next->prev指向newnode,phead->next指向newnode,newnode->next指向next即可
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
ListNode* next = pHead->next;
//这里直接插入即可,没有单向链表那么麻烦
pHead->next = newnode;
newnode->next = next;
next->prev = newnode;
}
(2)头删
头删的方法跟头插的方法类似,因为这里使用了哨兵位头结点,所以最后只需要判断要删除的节点不是哨兵位节点即可
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->prev != pHead); //防止陷入循环
ListNode* first = pHead->next;
ListNode* second = first->next;
free(first);
pHead->next = second;
second->prev = pHead;
}
4.双向链表的查找
这里判断的方法就是判断查找的节点最后是否为哨兵位头节点,如果是则已经找了一遍整个链表,如果中途找到了就返回该节点,没找到就打印没有找到并返回NULL。
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
//因为带头结点,所以直接判断是否是哨兵位
while (cur != pHead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
printf("没有找到\n");
return NULL;
}
5.双向链表在指定位置的插入
这里直接创建一个新的节点,并先保存要插入位置的前一个位置的节点,因为这里是双向链表,这里直接连接前后节点即可。
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
//因为是双向链表,直接连接前后节点即可
prev->next = newnode;
pos->prev = newnode;
newnode->next = pos;
newnode->prev = prev;
}
6.双向链表在指定位置删除
- 先创建两个节点分别保存该节点的前后节点
- 再将这个节点的前后指针连接即可
- 最后释放该位置节点
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
代码总结
ListNode.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.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 ListEra
ListNode.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "ListNode.h"
//创建一个新的节点
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;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
//因为有头结点,所以这里直接判断cur是否会访问到头结点即可
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(pHead);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
//这里直接创建节点插入即可
ListNode* newnode = BuyListNode(x);
ListNode* tail = pHead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = pHead;
pHead->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->prev != pHead);
ListNode* tail = pHead->prev;
ListNode* tailprev = tail->prev;
pHead->prev = tailprev;
tailprev->next = pHead;
free(tail);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
ListNode* next = pHead->next;
//这里直接插入即可,没有单向链表那么麻烦
pHead->next = newnode;
newnode->next = next;
next->prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->prev != pHead); //防止陷入循环
ListNode* first = pHead->next;
ListNode* second = first->next;
free(first);
pHead->next = second;
second->prev = pHead;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
//因为带头结点,所以直接判断是否是哨兵位
while (cur != pHead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
printf("没有找到\n");
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
//因为是双向链表,直接连接前后节点即可
prev->next = newnode;
pos->prev = newnode;
newnode->next = pos;
newnode->prev = prev;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "ListNode.h"
int main()
{
ListNode* pHead = ListCreate();
ListPushBack(pHead, 1);
ListPushBack(pHead, 2);
ListPushBack(pHead, 3);
ListPushBack(pHead, 4);
ListPushBack(pHead, 5);
ListPopBack(pHead);
ListPushFront(pHead, 6);
ListNode* pos = ListFind(pHead, 2);
if (pos)
{
pos->data *= 5;
}
ListInsert(pos, 5);
ListErase(pos);
ListPrint(pHead);
return 0;
}