【数据结构】--- 双向链表的增删查改
前言:
经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽
目录
前言:
概念:
双链表的初始化
双链表的判空
双链表的打印
双链表的头插
双链表的尾插
双链表头删
双链表的尾删
双链表的查找
双链表在pos位置前进行插入
双链表删除pos位置
双链表的销毁
检查:
完整代码:
dlist.h
dlist.c
test.c
总结:
概念:
双向链表的英文是 Doubly Linked List。双向链表是一种链表数据结构,其中每个节点包含三个部分:前驱指针、数据域和后继指针。前驱指针指向前一个节点,后继指针指向下一个节点。
双向链表的节点结构
双向链表的节点结构包括三个部分:
-
前驱指针域 (_prev):用于存放指向上一个节点的指针。
-
数据域 (_data):用于存储节点的数据元素。
-
后继指针域 (_next):用于存放指向下一个节点的指针。
typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* _next;struct ListNode* _prev;
}ListNode;
双链表的基本操作
双链表是一种数据结构,它的每个节点除了存储数据外,还有两个指针,分别指向前一个节点和后一个节点。这种结构使得双链表在进行插入和删除操作时更为高效,因为可以直接访问任何节点的前驱和后继节点。
双链表的初始化
在开始对我们的双链表进行增删查改前,我们先要对链表进行初始化,和单链表差不多,需要一个创建新节点的函数,不同的是新节点多了一个前驱,也需要置空
然后创建链表的头节点,头节点的值我们取-1,当头节点创建成功的时候,我们将其前驱和后继指向自己
//创建新节点
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;
}
双链表的判空
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");
}
双链表的头插
创建一个新节点,保存头节点的后继,令其为ne,让新节点的下一个指向ne,ne的前驱指向新节点,新节点的前驱指向头节点,头节点的后继指向新节点
// 双向链表头插
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 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指向新尾节点
}
双链表头删
先看看链表是否为空,空链表不能进行尾删
然后保存头节点的后继的后继,让头节点的后继指向头节点的后继的后继,头节点的后继的后继的前驱指向头节点
// 双向链表头删
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);}
}
双链表的尾删
先看看链表是否为空,空链表不能进行尾删
然后保存头节点的前驱的前驱,让头节点的前驱指向头节点的前驱的前驱,头节点的前驱的前驱的后继指向头节点
// 双向链表尾删
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);}}
双链表的查找
遍历链表
// 双向链表查找
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前驱的后继指向新节点
// 双向链表在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的前驱
// 双向链表删除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指针,最后再释放头节点,需在外层置空指针,防止野指针问题。
// 双向链表销毁
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;
}
检查:
#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
#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
#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
#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;
}
总结:
本篇关于双链表的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习