
前言:
经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽
目录
dlist.c
test.c
概念:
双向链表的英文是 Doubly Linked List。双向链表是一种链表数据结构,其中每个节点包含三个部分:前驱指针、数据域和后继指针。前驱指针指向前一个节点,后继指针指向下一个节点。

双向链表的节点结构
双向链表的节点结构包括三个部分:
-
前驱指针域 (_prev):用于存放指向上一个节点的指针。
-
数据域 (_data):用于存储节点的数据元素。
-
后继指针域 (_next):用于存放指向下一个节点的指针。
cpp
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
双链表的基本操作
双链表是一种数据结构,它的每个节点除了存储数据外,还有两个指针,分别指向前一个节点和后一个节点。这种结构使得双链表在进行插入和删除操作时更为高效,因为可以直接访问任何节点的前驱和后继节点。
双链表的初始化
在开始对我们的双链表进行增删查改前,我们先要对链表进行初始化,和单链表差不多,需要一个创建新节点的函数,不同的是新节点多了一个前驱,也需要置空
然后创建链表的头节点,头节点的值我们取-1,当头节点创建成功的时候,我们将其前驱和后继指向自己
cpp
//创建新节点
ListNode* BuySListNode(LTDataType x) {
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL) {
perror("malloc fail");
return NULL;
}
newnode->_data = x;
newnode->_next = NULL;
newnode->_prev= NULL;
return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate() {
ListNode*head= BuySListNode(-1);
if (head != NULL) {
head->_prev = head;
head->_next = head;
}
return head;
}
双链表的判空
cpp
bool ListEmptyLTNode(ListNode* phead)
{
assert(phead);
/*
链表返回只剩头节点(链表已经被删空)为真
否则为假
*/
return phead->_next == phead;
}
双链表的打印
遍历链表
cpp
// 双向链表打印
void ListPrint(ListNode* pHead) {
assert(pHead);
ListNode* start = pHead->_next;
while (start!=pHead) {
printf("%d<=>", start->_data);
start = start->_next;
}
printf("\n");
}
双链表的头插

创建一个新节点,保存头节点的后继,令其为ne,让新节点的下一个指向ne,ne的前驱指向新节点,新节点的前驱指向头节点,头节点的后继指向新节点
cpp
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuySListNode(x);
ListNode* ne = pHead->_next;
newnode->_next = ne;
ne->_prev = newnode;
newnode->_prev = pHead;
pHead->_next = newnode;
}
双链表的尾插
cpp
// 在链表尾部插入新节点
void ListPushBack(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuySListNode(x); // 创建新节点
// 调整指针将新节点接入链表尾部
ListNode* pre = pHead->_prev; // 原尾节点
pre->_next = newnode; // 原尾节点的next指向新节点
newnode->_prev = pre; // 新节点的prev指向原尾节点
newnode->_next = pHead; // 新节点的next指向头节点
pHead->_prev = newnode; // 头节点的prev指向新尾节点
}
双链表头删
先看看链表是否为空,空链表不能进行尾删
然后保存头节点的后继的后继,让头节点的后继指向头节点的后继的后继,头节点的后继的后继的前驱指向头节点
cpp
// 双向链表头删
void ListPopFront(ListNode* pHead) {
assert(pHead);
if (!ListEmptyLTNode(pHead)) {
ListNode* check = pHead->_next->_next;
ListNode* tmp = pHead->_next;
pHead->_next = check;
check->_prev = pHead;
free(tmp);
}
}
双链表的尾删
先看看链表是否为空,空链表不能进行尾删
然后保存头节点的前驱的前驱,让头节点的前驱指向头节点的前驱的前驱,头节点的前驱的前驱的后继指向头节点
cpp
// 双向链表尾删
void ListPopBack(ListNode* pHead) {
assert(pHead);
if (!ListEmptyLTNode(pHead)) {
ListNode* tmp = pHead->_prev;
ListNode* pre = pHead->_prev->_prev;
pre->_next = pHead;
pHead->_prev = pre;
free(tmp);
}
}
双链表的查找
遍历链表
cpp
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* start = pHead->_next;
while (start != pHead) {
if (start->_data == x)return start;
start = start->_next;
}
return NULL;
}
双链表在pos位置前进行插入
先判断pos是否有效,创建一个新节点,然后将pos位置的前驱保存下来,让新节点的前驱指向pos的前驱新节点的后继指向pos,pos的前驱指向新节点,pos前驱的后继指向新节点
cpp
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuySListNode(x);
ListNode* pre = pos->_prev;
newnode->_next = pos;
pos->_prev = newnode;
pre->_next = newnode;
newnode->_prev = pre;
}
双链表删除pos位置
先判断pos是否有效,然后将pos位置的前驱和后继保存下来,让pos的前驱的后继指向pos的后继,pos后继的前驱指向pos的前驱
cpp
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* ne = pos->_next;
ListNode* pre = pos->_prev;
ne->_prev = pre;
pre->_next = ne;
free(pos);
}
双链表的销毁
先断言头节点有效,然后再创建一个start指针指向头节点的后继,当该指针不等于头节点时,不断遍历,先保存start指针的后继,然后释放掉start指针所指向的内存,再将原来保存的后继重新给到start指针,最后再释放头节点,需在外层置空指针,防止野指针问题。
cpp
// 双向链表销毁
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* start = pHead->_next;
while (start != pHead) {
ListNode* ne = start->_next;
free(start);
start = ne;
}
free(pHead);
//pHead = NULL;
}
检查:
cpp
#include "dlist.h"
int main() {
ListNode* head = ListCreate(); // 创建链表头节点
// 尾插入测试
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
printf("链表内容(尾插入后):");
ListPrint(head);
// 头插入测试
ListPushFront(head, 0);
printf("链表内容(头插入后):");
ListPrint(head);
// 查找测试
ListNode* found = ListFind(head, 2);
if (found) {
printf("找到节点:%d\n", found->_data);
}
else {
printf("未找到节点\n");
}
// 删除头节点测试
ListPopFront(head);
printf("链表内容(头删后):");
ListPrint(head);
// 删除尾节点测试
ListPopBack(head);
printf("链表内容(尾删后):");
ListPrint(head);
// 在指定位置插入测试
ListInsert(head->_next, 4); // 在第二个节点后插入
printf("链表内容(插入4后):");
ListPrint(head);
// 删除指定节点测试
ListErase(head->_next); // 删除第二个节点
printf("链表内容(删除第二个节点后):");
ListPrint(head);
// 销毁链表
ListDestory(head);
// printf("链表内容(销毁后):");
// ListPrint(head); // 应该输出空链表的状态
head=NULL;
return 0;
}
完整代码:
dlist.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
ListNode* BuySListNode(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);
bool ListEmptyLTNode(ListNode* phead);
dlist.c
cpp
#include "dlist.h"
ListNode* BuySListNode(LTDataType x) {
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL) {
perror("malloc fail");
return NULL;
}
newnode->_data = x;
newnode->_next = NULL;
newnode->_prev= NULL;
return newnode;
}
// 创建返回链表的头结点.
ListNode* ListCreate() {
ListNode*head= BuySListNode(-1);
if (head != NULL) {
head->_prev = head;
head->_next = head;
}
return head;
}
// 双向链表销毁
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* start = pHead->_next;
while (start != pHead) {
ListNode* ne = start->_next;
free(start);
start = ne;
}
free(pHead);
//pHead = NULL;
}
bool ListEmptyLTNode(ListNode* phead)
{
assert(phead);
/*
链表返回只剩头节点(链表已经被删空)为真
否则为假
*/
return phead->_next == phead;
}
// 双向链表打印
void ListPrint(ListNode* pHead) {
assert(pHead);
ListNode* start = pHead->_next;
while (start!=pHead) {
printf("%d<=>", start->_data);
start = start->_next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuySListNode(x);
ListNode* pre = pHead->_prev;
pre->_next = newnode;
newnode->_prev = pre;
newnode->_next = pHead;
pHead->_prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead) {
assert(pHead);
if (!ListEmptyLTNode(pHead)) {
ListNode* tmp = pHead->_prev;
ListNode* pre = pHead->_prev->_prev;
pre->_next = pHead;
pHead->_prev = pre;
free(tmp);
}
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* newnode = BuySListNode(x);
ListNode* ne = pHead->_next;
newnode->_next = ne;
ne->_prev = newnode;
newnode->_prev = pHead;
pHead->_next = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead) {
assert(pHead);
ListNode* check = pHead->_next->_next;
ListNode* tmp = pHead->_next;
pHead->_next = check;
check->_prev = pHead;
free(tmp);
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* start = pHead->_next;
while (start != pHead) {
if (start->_data == x)return start;
start = start->_next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuySListNode(x);
ListNode* pre = pos->_prev;
newnode->_next = pos;
pos->_prev = newnode;
pre->_next = newnode;
newnode->_prev = pre;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* ne = pos->_next;
ListNode* pre = pos->_prev;
ne->_prev = pre;
pre->_next = ne;
free(pos);
}
test.c
cpp
#include "dlist.h"
int main() {
ListNode* head = ListCreate(); // 创建链表头节点
// 尾插入测试
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
printf("链表内容(尾插入后):");
ListPrint(head);
// 头插入测试
ListPushFront(head, 0);
printf("链表内容(头插入后):");
ListPrint(head);
// 查找测试
ListNode* found = ListFind(head, 2);
if (found) {
printf("找到节点:%d\n", found->_data);
}
else {
printf("未找到节点\n");
}
// 删除头节点测试
ListPopFront(head);
printf("链表内容(头删后):");
ListPrint(head);
// 删除尾节点测试
ListPopBack(head);
printf("链表内容(尾删后):");
ListPrint(head);
// 在指定位置插入测试
ListInsert(head->_next, 4); // 在第二个节点后插入
printf("链表内容(插入4后):");
ListPrint(head);
// 删除指定节点测试
ListErase(head->_next); // 删除第二个节点
printf("链表内容(删除第二个节点后):");
ListPrint(head);
// 销毁链表
ListDestory(head);
// printf("链表内容(销毁后):");
// ListPrint(head); // 应该输出空链表的状态
head=NULL;
return 0;
}
总结:
本篇关于双链表的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习