这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作。本篇文章利用C++实现了双向链表的基本操作,其中包括:

双向链表的基本操作C++语言实现

双向链表 实现的功能
头部插入结点建立链表 尾部插入结点建立链表
实现指定位置插入结点 查找给定数值是否存在
删除指定位置的结点 修改指定位置的结点
双向链表的长度 打印双向链表

定义双向链表的结点

因为双向链表的结点由三部分构成,用于指向当前节点的直接前驱节点的指针域,用于存储数据元素的数据域 ,以及用于指向当前节点的直接后继节点的指针域。

因此,首先我们需要封装一个结点类,定义了结点的三个要素,并利用构造函数实现初始化,另外,考虑到在双向链表中要用到结点类,所以将双向链表类定义为结点的友元类。

class doubleLinkedListNode
{
private:
doubleLinkedListNode<T> *prior;//双向结点前驱指针指向该结点的前驱结点
T data;//储存结点数据
doubleLinkedListNode<T> *next;//双向结点的后驱指针指向该结点的后继结点
//将双向链表类定义为结点的友元类
friend class doubleLinkedList<T>;
public:
//结点的无参构造函数,将结点指针域初始化为NULL
doubleLinkedListNode()
{
prior = NULL;
next = NULL;
}
//结点的有参构造函数,初始化指针域和数据域
doubleLinkedListNode(T _data,doubleLinkedListNode<T> *_prior = NULL,doubleLinkedListNode<T> *_next = NULL)
{
prior = _prior;//初始化前驱指针
data = _data;//初始化数据域
next = _next;//初始化后继指针
}
~doubleLinkedListNode()
{
prior = NULL;
next = NULL;
}
};

双向链表的基本操作

实现了双向链表头部插入结点, 尾部插入结点,指定位置插入结点建立链表, 查找给定数值的指定位置,删除指定位置的结点,修改指定位置的结点,双向链表的长度,打印双向链表,接下来逐一进行讲解实现:

头部插入结点建立链表

带头结点实现的双向链表,实现头部插入结点可分为两种情况,一种是只有一个头结点的时候,只需要使head和newNode的两个指针关联上即可,另外的两个指针依旧是NULL状态。另一种情况便是有结点的情况,这个时候跟在中间结点插入相似,需要调整四个指针,首先是让newNode与后继结点关联,最后让newNode与head结点关联。

因此,头部插入结点实现如下:

template<class T>
bool doubleLinkedList<T>::insertNodeByhead(T item)
{
//创建一个新的结点
doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
if (newNode == NULL){
cout << "内存分配失败,新结点无法创建" << endl;
return false;
}
//分两种情况,head的next是否为NULL,然后处理四个指针
if(head->next == NULL)
{
head->next = newNode;
newNode->prior = head;
return true;
}
else{
newNode->next = head->next;
head->next->prior = newNode;
newNode->prior = head;
head->next = newNode;
return true;
}
}

尾部插入结点建立链表

在尾部插入结点,当然第一步需要找到最后一个结点,然后在其后进行插入,双向链表因为两端的指针都是指向NULL的,所以在尾部插入也只需要调整两个指针就ok.

template<class T>
bool doubleLinkedList<T>::insertNodeBytail(T item)
{
//创建一个新的结点
doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
if (newNode == NULL){
cout << "内存分配失败,新结点无法创建" << endl;
return false;
}
//首先找到最后一个结点
doubleLinkedListNode<T>* lastNode = head;
while(lastNode->next != NULL)
{
lastNode = lastNode->next;//没找到就一直循环
}
//找到调整指针
lastNode->next = newNode;
newNode->prior = lastNode;
return true;
}

实现指定位置插入结点

在指定位置插入只需要两步走,首先也是找到指定的位置,然后就是插入新结点的指针的调整,中间插入是最复杂的,都逃不过调整四个指针,但是首先依旧是让新结点和后继结点建立上相关性,最后让新结点与前继结点建立关系,实现新结点的插入。

bool doubleLinkedList<T>::insertNode(T item,int n)
{
if(n<1){
cout<<"输入的非有效位置!"<<endl;
return false;
}
doubleLinkedListNode<T>* pMove = head;//创建一个新的指针,设置为游标指针
//首先找到插入位置
for(int i=1;i<n;i++)
{
pMove = pMove->next;
if(pMove == NULL&& i<=n)
{
cout<<"插入位置无效!"<<endl;
return false;
}
}
//创建一个新的结点
doubleLinkedListNode<T>* newNode = new doubleLinkedListNode<T>(item);
if (newNode == NULL){
cout << "内存分配失败,新结点无法创建" << endl;
return false;
}
//插入新的结点
newNode->next = pMove->next;
if (pMove->next != NULL)
{
pMove->next->prior = newNode;
}
newNode->prior = pMove;
pMove->next = newNode;
return true;
}

查找给定数值是否存在

查找给定元素,也就是一个遍历链表的过程,从头结点的下一个结点开始遍历,毕竟第一个头结点是没有储存数据项的。

template<class T>
bool doubleLinkedList<T>::findData(T item)
{
doubleLinkedListNode<T> *pMove = head->next; //设置游标指针
if(pMove == NULL)//链表为空
{
return false;
}
while(pMove)//遍历链表
{
if(pMove->data == item){
return true;
}
pMove = pMove->next;
}
return false;
}

删除指定位置的结点

删除指定的结点,第一步查找到删除的结点,需要定义一个删除指针临时指向将要删除的结点,最后指针处理删除之后别忘了释放该结点空间。

template<class T>
bool doubleLinkedList<T>::deleteData(int n)
{
if (n<1||n>getLength())
{
cout << "输入非有效位置" << endl;
return false;
}
doubleLinkedListNode<T> * pMove = head;//设置游标指针
doubleLinkedListNode<T> * pDelete;
//查找删除结点的位置
for (int i = 1; i <= n; i++)
{
pMove = pMove->next; //游标指针后移
}
//删除结点
pDelete = pMove;
pMove->prior->next = pDelete->next;
pMove->next->prior = pDelete->prior;
delete pDelete;//释放空间
return true;
}

修改指定位置的结点

修改指定位置的结点数据,当然还是得找到指定位置,然后对其进行修改,修改之后将原来的数据以引用的形式返回,具体的用法在测试函数中写到了的,不会的可以作为参考。

template<class T>
bool doubleLinkedList<T>::changeListElements(int n,T item,T &x)
{
if (n<1||n>getLength())
{
cout << "输入非有效位置" << endl;
return false;
}
doubleLinkedListNode<T> *pMove = head->next; //设置游标指针
for(int i=1;i<n;i++)//找到指定位置1
{
pMove = pMove->next;
}
x = pMove->data;
pMove->data = item;
return true;
}

双向链表的长度

计算双向链表的长度的函数,在双向链表的私有成员封装了一个变量length,以此来记录双向链表的长度,遍历双向链表,逐一进行计算结点数就是双向链表的长度。

template<class T>
int doubleLinkedList<T>::getLength()
{
doubleLinkedListNode<T> *pMove = head->next; //设置游标指针
int length=0;
//遍历链表,计算结点数
while(pMove!=NULL)
{
pMove = pMove->next; //游标指针后移
length++; //计算length
}
return length;
}

打印双向链表

打印双向链表,从第二个结点开始遍历链表,因为第一个为头结点是不含数据的,打印的过程也就是一个遍历的过程。

template<class T>
void doubleLinkedList<T>::printLinkedlist()
{
//从第二个结点开始打印,表头不含数据
doubleLinkedListNode<T>* pMove = head->next;
while(pMove)
{
cout<<pMove->data<<" ";
pMove = pMove->next;//移动指针
}
cout<<endl;
}

以上就是本次博文与大家分享的利用C++语言实现双向链表,在用C语言写了之后,感觉写起来就比较轻松,唯一不同的就是要利用类来进行封装。完整的代码,以及测试代码我已经Push到Github,喜欢的小伙伴欢迎Star! C++语言实现双向链表Github地址,如果还想要了解其他的数据结构实现的小伙伴也可以来我的Myblog,我们一起讨论,如果有写的不好的地方还请多多担待,也欢迎大家在评论区留言,我加以改正,共同进步!

C++语言实现双向链表的更多相关文章

  1. 用C语言把双向链表中的两个结点交换位置,考虑各种边界问题。

    用C语言把双向链表中的两个结点交换位置,考虑各种边界问题. [参考] http://blog.csdn.net/silangquan/article/details/18051675

  2. C语言实现双向链表

    目前我们所学到的链表,无论是动态链表还是静态链表,表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表(或单链表). 虽然使用单链表能 100% 解决逻辑关系为 & ...

  3. C语言实现双向链表删除节点、插入节点、双向输出等操作

    #include<cstdio> #include<cstdlib> typedef struct DoubleLinkedList { int data; struct Do ...

  4. C语言数据结构----双向链表

    概括:主要说明双向链表的基本概念和具体操作以及源代码. 一.基本概念 1.有了单链表以后我们可以把内存中小块的空间联系在一起,并且把每一个小块都存储上我们想要存储的数值.但是单链表只有一个next,我 ...

  5. C语言一个双向链表的实现

    首先编写头文件,头文件里做相关的定义和声明,DList.h内容如下: #ifndef DList_H #define DList_H typedef int Item; typedef struct ...

  6. 大二作业——操作系统实验——C语言用双向链表,模拟实现动态分区式存储管理

    实验:动态分区式存储管理 实验内容: 编写程序模拟完成动态分区存储管理方式的内存分配和回收.实验具体包括:首先确定内存空闲分配表:然后采用最佳适应算法完成内存空间的分配和回收:最后编写主函数对所做工作 ...

  7. [算法天天练] - C语言实现双向链表(一)

    双向链表是比较常见的,主要是在链表的基础上添加prev指针,闲话少说直接上代码吧(这个也是网上一个大神的思路,真心不错,条理清楚,逻辑缜密) 主要也是为了学习,贴上我所调试成功的代码(Linux环境下 ...

  8. C语言双向链表讲解

    一.双向链表的概念 双向链表基于单链表.单链表是单向的,有一个头结点,一个尾结点,要访问任何结点,都必须知道头结点,不能逆着进行.而双链表添加了一个指针域,通过两个指针域,分别指向结点的前结点和后结点 ...

  9. Java中双向链表的代码实现

    写在前面: 双向链表是一种对称结构,它克服了单链表上指针单向性的缺点,其中每一个节点即可向前引用,也可向后引用,这样可以更方便的插入.删除数据元素. 由于双向链表需要同时维护两个方向的指针,因此添加节 ...

随机推荐

  1. vscode 自动修复 setting.json 修改完,得重启浏览器 # 新版(>1.41.0)配置 vscode #解决了

    vscode 自动修复 setting.json 修改完,得重启浏览器,不用 npm run dev 就能看到效果 "editor.codeActionsOnSave": { &q ...

  2. 数据挖掘算法——K-means算法

    k-means中文称为K均值聚类算法,在1967年就被提出  所谓聚类就是将物理或者抽象对象的集合分组成为由类似的对象组成的多个簇的过程 聚类生成的组成为簇 簇内部任意两个对象之间具有较高的相似度,不 ...

  3. JDBC封装-Java(新手)

    JDBC的封装,自己总结的自己总结的自己总结的 dao (代码分层)命名规范: 1.com.XXX.dao 存放dao相关的类型 例如 StudentDAOImpl 处理 数据库的链接 存取数据 2. ...

  4. IOptions、IOptionsMonitor以及IOptionsSnapshot

    背景 ASP.NET Core引入了Options模式,使用类来表示相关的设置组.简单的来说,就是用强类型的类来表达配置项,这带来了很多好处.初学者会发现这个框架有3个主要的面向消费者的接口:IOpt ...

  5. Effective Python读书笔记

    有些位置可能翻译理解的不到位,各位看官如有疑问,欢迎留言赐教. Pythonic Thinking 大家经常用Pythonic来形容python语法风格的编程方式:简单优美,没有之一:通过import ...

  6. Mass Spectrometry-Compatible Subcellular Fractionation for Proteomics 质谱兼容的蛋白质组学的亚细胞分离(解读人:王茹凯)

    文献名:Mass Spectrometry-Compatible Subcellular Fractionation for Proteomics(质谱兼容的蛋白质组学的亚细胞分离) 期刊名:Jpor ...

  7. java简单序列化和反序列化

    一.序列流 1.什么是序列流 序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推. 2.使用方式 整合两个: Seque ...

  8. mysql数据库表格之间的关系

    外键 昨日内容回顾: 字段类型 约束条件 创建表的完整语法 create table 表名( 字段名 字段类型[(宽度) 约束条件], 字段名 字段类型[(宽度) 约束条件], 字段名 字段类型[(宽 ...

  9. elasticsearch和kibana安装后,外网无法访问

    问题描述: 现在解压elasticsearch之后,启动,通过http://localhost:9200可以访问的到,但是http://ip:9200访问不到 解决方法: 修改elasticsearc ...

  10. Hive数据倾斜的原因及主要解决方法

    数据倾斜产生的原因 数据倾斜的原因很大部分是join倾斜和聚合倾斜两大类 Hive倾斜之group by聚合倾斜 原因: 分组的维度过少,每个维度的值过多,导致处理某值的reduce耗时很久: 对一些 ...