在上一篇博客中在下介绍了单链表的一些操作,那么在这篇博客里边我就着重讲一下双向链表,至于为什么没有说链表的循环什么的,这个放在我的下一篇博客里说,在这里呢也给我自己立一个flag,下一篇的博客我就重点说一下单链表的循环和双向链表的循环,以及一些其他的问题比如说判断一个链表是不是回文链表等等。
好了废话不多说我们直接开始这篇博客的重点,就是双向链表的操作,双向链表呢,顾名思义就是可以两个方向进行操作的链表,我们在学习了单链表就会注意到一个问题,就是单链表我们可以从一个结点找到它的下一个结点,但是有的小伙伴就说了,我想倒着来,我想从一个结点找到它的上一个结点行不行,这个问题啊在单链表是无法实现的,所以我们的前辈们提出了双向链表的思想。
和单链表的区别就是,单链表的结点由两个部分组成,一个是数据域,另一个就是指针域,双向链表呢则是由三部分组成,一个数据域,两个指针域,这两个指针域一个是前驱指针域,另一个是后继指针域,前驱指针域指向的是该节点的上一个结点的地址,后继则是指向的该结点的下一个结点的地址,我们用下面的图片做一下理解。
那么基本的理论部分就差不多讲完了,我们开始实践一下代码部分的编写吧
在接触了链表后我们的第一件事肯定就是建立一个链表,那么我们的双向链表怎么来创建呢,从以下的代码我们进一步讲解:
typedef struct node
{
node *prior; //前驱
int data; //数据域
node *next; //后继
}node;
那么从上边的代码我们直接可以看出来,我们的双向链表的结点由三部分组成,前驱指针域,后继指针域和数据域,那么我们接下来给我们的双向链表插入数据,和我的上一篇博客一样我们的双向链表也采用循环赋值的语法进行添加,我们通过下边的代码进行进一步的理解
node *create()
{
int x = 0 , i = 0;
node *head , *p , *q; //准备阶段,我们准备一些变量等等
head = (node *)malloc(sizeof(node)); //给我们的头结点分配一个地址空间
while(1) //这里和上一篇博客一样,不懂的可以去上一篇博客看看
{
cout<<"please input data ";
cin>>x;
p = (node *)malloc(sizeof(node));
p->prior = NULL;
p->next = NULL;
p->data = x;
if(x == 0)
{
break;
}
if(++i == 1)
{
head->next = p;
}
else
{
p->prior = q; /*这里开始就是我们双向链表的核心算法,我们把上一个结点的地址存
在我们新的结点的前驱指针域,这是第一步*/
q->next = p; /*这里是第二步,我们把当前结点的地址给我
们上一个结点的next域*/
}
q = p; //代码执行到这里就和我们的单链表一样了,指针后移一位
}
q->next = NULL;
return head;
}
那么双向链表创建好了之后怎么查看表内的数据呢,和单链表的区别就是我们的双向链表有两种遍历方式,下面我们来详细说一下两种遍历方式,第一种就是从链表的头 节点开始遍历,直到遍历到链表的尾结点位置,第二种正好相反,就是从尾结点开始向前遍历,遍历到链表的头结点位置,具体的代码如下,我们首先来看正向 遍历算法:
void display2(node *head) //这个函数就是正向遍历的算法
{
node *p = head; //接收传进来的参数
if(head = NULL)
return;
while(1)
{
p = p->next; //因为进来的是头结点,是没有数据的,所以我们先进行指针后移
cout<<p->data<<" ";
if(p->next == NULL)
{
break; //判断是否到了尾节点的位置,到了的话直接退出
}
}
}
正向遍历的算法其实和单向链表差不了 多少,都是指针移位随后打印 ,再移位,再打印。。。那么我们来看一下我们的逆向遍历算法的实现,因为双向链表提供了前驱指针域,所以我们的逆向遍历就好做很多,步骤就是先找到整个链表的尾结点位置,然后再进行遍历,我们通过下边的代码具体了解一下:
void display(node *head)
{
node *p = head;
if(head = NULL)
return;
while(p->next != NULL) //这里就是我们通过循环找到最后的结点位置
{
p = p->next;
}
while(1)
{
cout<<p->data<<" "; //这里就简单了,打印并且指针前移
p = p->prior;
if(p->prior == NULL)
{
cout<<p->data<<" ";
break;
}
}
}
那么通过上边的两段代码我们就可以了解到在这个 双向链表中的两种遍历方式,不管是单链表还是双向链表我们都会接触到插入新数据的方式,就是头插、尾插、中间插入这么几个部分 ,在这篇博客里的话我们就不做具体的解释了,头插法和尾插法想了解的可以去我的上一篇博客做以了解,那么中间插入我们就在下一篇博客中做具体的阐述。
最后呢我们就来说一下链表的求长度,双向链表求长度其实是和单向链表是一样的,还是通过循环进行遍历,定义一个变量进行存储并进行返回,可以简单写一下代码可能会更加熟悉:
int double_legth(node *head)
{
node *p = head;
int i = 0;
if(head = NULL)
return;
while(p->next != NULL)
{
i++;
p = p->next;
}
return i;
}
那么我们今天的博客就先介绍到这里,双向链表在数据结构中也是相当重要的,如有问题欢迎留言讨论。
最后我把这次双向链表的创建等等代码贴出来:
#include<iostream>
#include<malloc.h>
using namespace std;
typedef struct node
{
node *prior; //前驱
int data; //数据域
node *next; //后继
}node;
node *create()
{
int x = 0 , i = 0;
node *head , *p , *q;
head = (node *)malloc(sizeof(node));
while(1)
{
cout<<"please input data ";
cin>>x;
p = (node *)malloc(sizeof(node));
p->prior = NULL;
p->next = NULL;
p->data = x;
if(x == 0)
{
break;
}
if(++i == 1)
{
head->next = p;
}
else
{
p->prior = q;
q->next = p;
}
q = p;
}
q->next = NULL;
return head;
}
void display(node *head)
{
node *p = head;
if(head = NULL)
return;
while(p->next != NULL)
{
p = p->next;
}
while(1)
{
cout<<p->data<<" ";
p = p->prior;
if(p->prior == NULL)
{
cout<<p->data<<" ";
break;
}
}
}
void display2(node *head)
{
node *p = head;
if(head = NULL)
return;
while(1)
{
p = p->next;
cout<<p->data<<" ";
if(p->next == NULL)
{
break;
}
}
}
int double_legth(node *head)
{
node *p = head;
int i = 0;
if(head = NULL)
return 0;
while(p->next != NULL)
{
i++;
p = p->next;
}
return i;
}
int main()
{
node *head = create();
display(head);
cout<<endl;
display2(head);
cout<<endl;
cout<<"长度是:"<<double_legth(head)<<endl;
return 0;
}
代码我的输入输出流运用了C++的语法,不过比较简单。