双向链表代码

ops/2025/2/18 12:07:56/

在介绍双向链表之前,先介绍一下链表的分类:
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
单向或者双向:
在这里插入图片描述
带头或者不带头:
在这里插入图片描述
循环或者非循环:
在这里插入图片描述
看到有这么多的结构,你是不是以为链表链表会很难学呢,其实在实际当中最经常用的还是两种结构:

此行用于过渡

1.无头单向非循环链表:也就是我们做题中常见的单链表
2.带头双向循环链表:也就是接下来我们要讲到的双链表
在这里插入图片描述

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结
    构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向
    循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而
    简单了,后面我们代码实现了就知道了。

双向链表的代码实现和增删查改功能:

我们以带头双向循环链表为例来进行实现:
双向链表和单链表结构上的区别就在于双向链表多了一个指向上一个节点的指针。知道这个我们很快就能将双向链表的结构写出来:

typedef int LTDataType;
typedef struct ListNode
{LTDataType _data;struct ListNode* next;struct ListNode* prev;
}ListNode;

这里我们用三个文件来分别实现双向链表的声明,定义和测试功能——DSList.h,DSList.c,text.c

DSList.h:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DLTDateType;
typedef struct ListNode
{DLTDateType _data;struct ListNode* next;struct ListNode* prev;
}ListNode;//动态开辟一个节点:
ListNode* BuySListNode(DLTDateType x);
//链表的初始化——创建头节点
ListNode* DSLInit();
//尾插:
void DSLPushBack(ListNode* plist, DLTDateType x);
//尾删:
void DSLPopBack(ListNode* plist);
//头插:
void DSLPushFront(ListNode* plist, DLTDateType x);
//头删:
void DSLPopFront(ListNode* plist);
//查找:
ListNode* DSLFind(ListNode* plist, DLTDateType x);
//在任意位置后面插入:
void DSLInsert(ListNode* pos, DLTDateType x);
//在任意位置删除:
void DSLtErase(ListNode* pos);
//打印链表
void DSLPrint(ListNode* plist);

DSList.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"DSList.h"//动态开辟一个节点:
ListNode* BuySListNode(DLTDateType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc error");}newnode->_data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
//链表的初始化——创建头节点
ListNode* DSLInit()
{ListNode* head = BuySListNode(-1);head->prev = head;head->next = head;return head;
}//尾插:
void DSLPushBack(ListNode* plist, DLTDateType x)
{//如果没有节点:检查assert(plist);//找尾ListNode* tail = plist->prev;ListNode* newnode = BuySListNode(x);newnode->next = plist;tail->next = newnode;newnode->prev = tail;plist->prev = newnode;
}//尾删:
void DSLPopBack(ListNode* plist)
{assert(plist);//找尾:ListNode* tail = plist->prev;plist->prev = tail->prev;tail->prev->next = plist;free(tail);
}//头插:
void DSLPushFront(ListNode* plist, DLTDateType x)
{assert(plist);ListNode* newnode = BuySListNode(x);newnode->prev = plist;newnode->next = plist->next;plist->next = newnode;newnode->next->prev = newnode;
}//头删:
void DSLPopFront(ListNode* plist)
{assert(plist);ListNode* node = plist->next;plist->next->next->prev = plist;plist->next = plist->next->next;free(node);
}//查找:
ListNode* DSLFind(ListNode* plist, DLTDateType x)
{ListNode* find = plist->next;while (find != plist){if (find->_data == x)return find;find = find->next;}return NULL;
}// 在任意位置后面插入:
void DSLInsert(ListNode * pos, DLTDateType x)
{assert(pos);ListNode* newnode = BuySListNode(x);newnode->prev = pos;newnode->next = pos->next;pos->next = newnode;
}//在任意位置删除:
void DSLtErase(ListNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);
}//打印链表
void DSLPrint(ListNode* plist)
{ListNode* cur = plist->next;while (cur != plist){printf("%d<->", cur->_data);cur = cur->next;}printf("\n");
}

然后这里呢还有个小小的问题,为什么BuySListNode()和DSLInit()有返回值就可以修改实参的地址呢,我们不是说函数调用完就会被销毁,那么在函数里面创建的空间也会被销毁,但为什么还是可以这样写呢。因为我们在函数内部创建的空间是动态开辟的空间,这些空间会在程序运行时一直存在。
text.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"DSList.h"void DSListText()
{//初始化:ListNode* phead = NULL;phead = DSLInit();//测试尾插和尾删:DSLPushBack(phead, 1);DSLPushBack(phead, 2);DSLPushBack(phead, 3);DSLPushBack(phead, 4);DSLPushBack(phead, 5);DSLPrint(phead);DSLPopBack(phead);DSLPopBack(phead);DSLPopBack(phead);DSLPrint(phead);
}
void DSListText1()
{//初始化:ListNode* phead = NULL;phead = DSLInit();//测试头插和头删:DSLPushFront(phead,1);DSLPushFront(phead, 2);DSLPushFront(phead, 3);DSLPushFront(phead, 4);DSLPushFront(phead, 5);DSLPrint(phead);DSLPopFront(phead);DSLPopFront(phead);DSLPopFront(phead);DSLPrint(phead);
}void DSListText2()
{//初始化:ListNode* phead = NULL;phead = DSLInit();//尾插5个节点:DSLPushBack(phead, 1);DSLPushBack(phead, 2);DSLPushBack(phead, 3);DSLPushBack(phead, 4);DSLPushBack(phead, 5);DSLPrint(phead);//测试在任意位置后面插入和在任意位置删除:// 插入://找位置:ListNode* pos = DSLFind(phead, 1);DSLInsert(pos, 10);DSLPrint(phead);//删除://找位置:ListNode* pos1 = DSLFind(phead, 4);DSLtErase(pos1);DSLPrint(phead);
}int main()
{DSListText();printf("\n");DSListText1();printf("\n");DSListText2();return 0;
}

以上代码均为自己手搓,如果有错误还请大佬指点改正。


接下来有一道经典题:
1.给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。要求返回这个链表的深度拷贝。OJ链接
这道题是什么意思呢,就是有一个链表,每个节点包含一个数据和两个指针,一个指针指向下一个节点,另外一个指针指向NULL或者任意节点。要求将这个链表拷贝一份。
解题思路:在每个节点复制然后添加到对应节点的后面,这样每个新节点的random的指向就是原节点random指向的节点的下一个节点。
图解:
在这里插入图片描述
因为我们复制的节点在原节点的后面,所以原节点random指针指向的那个节点后面也存在一个该节点的复制节点,所以我们新节点的random指针指向原节点random指针指向的节点的下一个节点。例如:原节点13的random指针指向原节点7,那么我们新节点13的random指针指向原节点13random指针指向的节点的下一个节点。以此内推就可以将链表拷贝一份。
代码

struct Node* copyRandomList(struct Node* head) {struct Node* cur=head;struct Node*node[1000];int count=0;while(cur){struct Node* newnode=(struct Node*)malloc(sizeof(struct Node));node[count++]=newnode;newnode->val=cur->val;newnode->next=cur->next;cur->next=newnode;cur=newnode->next;}struct Node* cur1=head;while(cur1){if(cur1->random==NULL)cur1->next->random=NULL;else{cur1->next->random=cur1->random->next;}cur1=cur1->next->next;}for(int i=0;i<count-1;i++)node[i]->next=node[i+1];return node[0];
}

http://www.ppmy.cn/ops/158229.html

相关文章

顺序表(C)

1.顺序表的概念 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;通常借助数组来实现。它的特点是逻辑上相邻的元素在物理存储位置上也相邻&#xff0c;支持随机访问&#xff0c;可通过下标直接访问任意位置的元素。不过&#xff0c;顺序表在插入和…

pentaho-aggdesigner-algorithm-5.1.5-jhyde.jar

个人博客地址&#xff1a;pentaho-aggdesigner-algorithm-5.1.5-jhyde.jar | 一张假钞的真实世界 Maven编译时无法自动下载pentaho-aggdesigner-algorithm-5.1.5-jhyde.jar&#xff0c;需要手动下载并安装到本地仓库。安装命令&#xff1a; mvn install:install-file -Dfile.…

react 创建项目报错(react19)详细解决办法

一、问题描述 使用脚手架创建项目的时候报错如下&#xff1a; 二、原因及解决办法 打开项目查看 package.json 文件发现&#xff0c;使用的是最新的19版本&#xff0c;所以会出现版本不兼容的问题 所以我们需要换成18版本的 1、删除node_modules文件夹 2、package.json 中替…

java后端开发day14--之前练习的总结和思考

1.感受 这两天学点儿新的就直接上手打代码&#xff0c;真的是累死个人。我唯一的感受就是&#xff0c;课听完了&#xff0c;代码也跟着打完了&#xff08;是的&#xff0c;跟着打的&#xff0c;没自己打&#xff09;&#xff0c;感觉自己脑袋里乱乱的&#xff0c;对代码的分区…

AI向量数据库之LanceDB快速介绍

LanceDB LanceDB 是一个开源的向量搜索数据库&#xff0c;具备持久化存储功能&#xff0c;极大地简化了嵌入向量的检索、过滤和管理。 LanceDB的主要特点 LanceDB 的主要特点包括&#xff1a; 生产级向量搜索&#xff1a;无需管理服务器。 存储、查询和过滤向量、元数据以…

游戏引擎学习第97天

回顾昨天并计划今天 在这期节目中&#xff0c;主要讲解了光照的概念&#xff0c;并进一步讨论了法线贴图光照的实现。节目的内容大致分为几个部分&#xff1a; 光照的基础概述&#xff1a;讨论了光的工作原理以及如何在编程图形时需要考虑光照问题。尽管这些概念并没有深入到…

SpringBoot分布式应用程序和数据库在物理位置分配上、路由上和数量上的最佳实践是什么?

在设计和部署Spring Boot分布式应用程序时&#xff0c;物理位置分配、路由和数据库数量的最佳实践对系统性能、可用性和可维护性至关重要。以下是相关建议&#xff1a; 1. 物理位置分配 最佳实践&#xff1a; 靠近用户部署&#xff1a;将应用实例部署在靠近用户的数据中心&a…

Go 1.4操作符指针理解

对于初学者来说操作符指针类型、指针、取地址容易混淆&#xff0c;多练就好了。 只需要记住两个符号&#xff1a;&&#xff08;取内存地址&#xff09;和*&#xff08;解引用&#xff09;。 定义和使用&#xff1a;你可以使用 & 操作符获取一个变量的内存地址&#x…