【数据结构】--- 双向链表的增删查改

前言:

经过了几个月的漫长岁月,回头时年迈的小编发现,数据结构的内容还没有写博客,于是小编赶紧停下手头的活动,补上博客以洗清身上的罪孽


目录

前言:

概念:

双链表的初始化

双链表的判空

双链表的打印

双链表的头插

双链表的尾插

双链表头删

双链表的尾删

双链表的查找

双链表在pos位置前进行插入

双链表删除pos位置

双链表的销毁

检查:

完整代码:

dlist.h

​​​​​​​ ​​​​​​​ dlist.c

​​​​​​​ ​​​​​​​ ​​​​​​​ test.c

总结:


概念:

双向链表的英文是 Doubly Linked List。双向链表是一种链表数据结构,其中每个节点包含三个部分:前驱指针、数据域和后继指针。前驱指针指向前一个节点,后继指针指向下一个节点。

双向链表的节点结构

双向链表的节点结构包括三个部分:

  1. 前驱指针域 (_prev):用于存放指向上一个节点的指针。

  2. 数据域 (_data):用于存储节点的数据元素。

  3. 后继指针域 (_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;
}
总结:

本篇关于双链表的讲解到这里就结束啦,后续小编会带来更多精彩实用的内容,对你有帮助的可以点个赞,欢迎各位队列交流学习

相关推荐
钢铁男儿几秒前
C# 方法(控制流和方法调用)
算法
heyCHEEMS6 分钟前
最大子段和 Java
java·开发语言·算法
我是一只鱼022320 分钟前
LeetCode算法题 (设计链表)Day16!!!C/C++
数据结构·c++·算法·leetcode·链表
蒟蒻小袁1 小时前
力扣面试150题--二叉树的最大深度
算法·leetcode·面试
bj32811 小时前
树的同构问题--Python
开发语言·python·算法
八股文领域大手子1 小时前
单机 vs 分布式:Java 后端限流的选择题
java·开发语言·数据结构·算法·spring
纪元A梦2 小时前
华为OD机试真题——告警抑制(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
java·c语言·javascript·c++·python·华为od
zl_dfq4 小时前
C语言 之 【栈的简介、栈的实现(初始化、销毁、入栈、出栈、判空、栈的大小、访问栈顶元素、打印)】
c语言·数据结构
geneculture4 小时前
融智学数学符号体系的系统解读(之一)
人工智能·算法·机器学习