目录
本节开始学习双向链表!
我们先复制前面讲单链表的那个目录。
补充命令11:cp -rf (recursive、force)递归强制拷贝文件,如下,将linklist文件夹下的文件复制到doublelink文件夹下
将linklist文件夹下的文件复制到doublelink文件夹下后,链表的初始化、链表插入、链表的删除操作操作需要修改,其他的都不用改。其中链表的反转函数可以注释掉,因为双向链表不需要反转操作。
双向链表的定义
因为双向链表有两个指针域,所以link.h中表示结点的结构体需要修改为:
双向链表的初始化
然后link.c中链表的初始化函数需要修改为:
双向链表的插入操作
双向链表要插入节点时,前一个结点的next要改成被插入节点的地址,被插入节点的prior要填入前一个结点的地址,被插入节点的next要填入后一个结点的地址,而后一个结点的prior要填入被插入节点的地址。
在link.c中修改链表插入函数
双向链表的删除操作
比如我们要删除第二个节点,在删除之前我们得先把第三个结点的位置存放在第一个结点的next指针域中,然后将第一个结点的位置存放在第三个结点的prior指针域中,最后再释放掉第二个节点的空间。
在link.c中加上这一句
运行结果:
完整代码
Link.c
#include "link.h"
#include <stdlib.h>//malloc的头文件
#include <stdio.h>//printf的头文件
//链表的初始化
int init_link(Node **h)//用指针接收指针的地址
{
if(NULL==h)//判断空指针
{
return FAILURE;
}
*h=(Node*)malloc(sizeof(Node)*1);//头指针的地址存在h里面,则*h=head(头指针),在头指针指向的位置申请空间
if(NULL==(*h))//如果返回值是空的话说明内存用完了
{
return FAILURE;
}
(*h)->next=NULL;//头节点的后面一个指针域初始化为空
(*h)->prior=NULL;//头节点的前面一个指针域初始化为空
return SUCCESS;
}
//链表的插入操作
int insert_link(Node*h,int p,int n)//p接收的是要插入的位置
{
//入参判断
if(NULL==h)
{
return FAILURE;
}
Node *q=h;//q指向头节点
int k=1;//记录下移动的次数
while(k<p && q)//保证移动的次数小于p,并且要保证q不等于NULL,因为当p不合法时,比如p大于链表的长度+1,那q=q->next有可能给q赋值了NULL,导致程序死掉
{
q=q->next;//把q指针移动到要插入位置的前一个位置,此时q指向第一个节点
k++;//k++后就不小于p了,退出循环
}
//判断位置是否合法
if(q==NULL||k>p)//位置太大或者位置太小(比如p=0)都不合法
{
return FAILURE;
}
Node *m=(Node*)malloc(sizeof(Node)*1);//申请节点
if(NULL==m)
{
return FAILURE;//内存不足,申请失败
}
m->data=n;//将num存放在数据域
m->next=q->next;//将第一个节点的指针域里面存放的地址搬到此节点的指针域
m->prior=q;//将q所指向的地址(第一个节点的位置)赋值给m的prior
if(q->next)//考虑到尾插法的情况,当q->next不为空说明不是尾插法
{
q->next->prior=m;//将m赋值给原来第二个节点的prior指针域
}
q->next=m;//将m赋值给第一个节点的next指针域
return SUCCESS;
}
//遍历链表
void traverse_link(Node*h,void (*p)(int))//结构体指针,和函数指针(接收时要指明返回值和参数)
{
//入参判断
if(NULL==h)
{
return;//程序不会向下走了
}
Node *q=h->next;//q指向第一个节点
while(q)//只要q不为空
{
p(q->data);//由于p指向show函数,可以通过p调用show函数
q=q->next;//q继续指向下一个节点
}
}
//判断链表是否为空
int empty_link(Node*h)
{
if(NULL==h)
{
return FAILURE;//这里是失败是因为传的参数错了
}
return (h->next==NULL)? SUCCESS:FAILURE;//如果是SUCCESS表示链表为空
}
//判断链表的长度
int length_link(Node*h)
{
if(NULL==h)
{
return FAILURE;//这里失败是因为传的参数错了
}
Node*q=h->next;//让q指向第一个节点
int length=0;
while(q)//只要q不为空
{
length++;//累计
q=q->next;//q指向下一个节点
}
//q为空时退出
return length;
}
//链表查找操作
int find_link(Node*h,int n,int *r)
{
if(NULL==h||NULL==r)
{
return FAILURE;
}
Node *q=h->next;//让q指向第一个节点
int i=0,len=0,flag=0;
while(q)//只要q不为空
{
len++;//长度累计
if(q->data==n)//如果找到n
{
r[i++]=len;
flag=1;//找到
}
q=q->next;
}
if(flag==1)
{
return SUCCESS;
}
else
{
return FAILURE;
}
}
/*
//链表反转操作
int reverse_link(Node*h)
{
if(NULL==h)
return FAILURE;
Node *p=h->next;//让p指向第一个节点
h->next=NULL;//让头节点的指针域为空
while(p)
{
Node*q=p;//让q也指向第一个节点
p=p->next;//让p指向第二个节点
q->next=h->next;//让q即第一个节点的指针域为空
h->next=q;//把第一个节点的位置放到头节点的指针域中,即让h指向第一个节点
}
return SUCCESS;
}
*/
//链表删除操作
int delete_link(Node*h,int p,int *num)
{
//入参判断
if(NULL==h||NULL==num)
{
return FAILURE;
}
Node *q=h;//q指向头节点
int k=1;//记录下移动的次数
while(k<p && q)//保证移动的次数小于p,并且要保证q不等于NULL,因为当p不合法时,比如p大于链表的长度+1,那q=q->next有可能给q赋值了NULL,导致程序死掉
{
q=q->next;//把指针移动到要删除位置的前一个位置,即此时q指向第一个节点
k++;//k++后就不小于p了,退出循环
}
//判断位置是否合法
if(q==NULL||k>p||q->next==NULL)//位置太大或者位置太小(比如p=0)都不合法,当q->next指向的位置为NULL,就不合法,因为不能后面没有节点需要删除
{
return FAILURE;
}
//在释放掉p这个空间之前,先把它指针域中存放的下一个节点的地址挪到前一个节点的指针域中
Node*n=q->next;//此时q指向的是第一个节点,将第二个节点的位置存放在n中,n指向第二个节点
q->next=n->next;//将第三个节点的位置存放在第一个节点的next指针域中
if(n->next)//如果n->next即第三个节点不为空,则需要操作第三个节点的prior指针域,如果为空,即第三个节点不存在,也就不需要操作它的prior指针域了
{
n->next->prior=q;//将第一个节点的位置存放在第三个节点的prior指针域中
}
*num=n->data;//将第二个节点的数据赋值给num,即要删除的数据
free(n);//释放第二个节点
return SUCCESS;
}
//判断前驱
int prior_link(Node*h, int num, int*p)
{
//入参判断
if(NULL==h||NULL==p)
{
return FAILURE;
}
//判断一下,如果是空链表或者只有一个节点的话肯定不存在前驱
if(NULL==h->next||NULL==h->next->next)//h->next是第一个节点,h->next->next是第一个节点的指针域
{
return FAILURE;
}
Node *k=h->next;//让k指向第一个节点
Node *q=k->next;//让q指向第二个节点
while(q)//只要q不为空
{
if(q->data==num)//如果找到
{
*p=k->data;//记录下前驱
return SUCCESS;
}
q=q->next;//如果还没有找到,q继续往后走
k=k->next;//k也继续走
}
//如果当q等于NULL,退出while循环了还没有找到
return FAILURE;
}
//链表清空操作
int clear_link(Node*h)
{
//入参判断
if(NULL==h)
{
return FAILURE;
}
Node*q=h->next;//让q指向第一个节点
while(q)//只要q不为空
{
//释放q之前,先将指针域修改
h->next=q->next;//将第三个节点的地址存放到头节点
free(q);//释放第一个节点
q=h->next;//q指向第二个节点
}
return SUCCESS;
}
//链表销毁操作
int destroy_link(Node **h)//接收指针的地址
{
//入参判断
if(NULL==h)
{
return FAILURE;
}
//注意:在销毁操作前必须进行清空操作
if((*h)->next!=NULL)//说明头指针不为空,说明还没有执行清空操作
{
return FAILURE;
}
free(*h);//释放头节点所在的位置
*h=NULL;
return SUCCESS;
}
Link.h
#ifndef _LINK_H
#define _LINK_H
#define SUCCESS 1000
#define FAILURE 1001
//表示节点的结构体
typedef struct node
{
int data;
struct node *next;
struct node *prior;//多加一个指针指向前面一个节点
}Node;//将结构体重命名为Node
int init_link(Node **h);
int insert_link(Node*h,int p,int n);
void traverse_link(Node*h,void (*p)(int));
int empty_link(Node*h);
int length_link(Node*h);
int find_link(Node*h,int n,int *r);
//int reverse_link(Node*h);
int delete_link(Node*h,int p,int *num);
int prior_link(Node*h, int num, int*p);
int clear_link(Node*h);
int destroy_link(Node **h);
#endif
Main.c
#include <stdio.h>
#include "link.h"
#include <stdlib.h>
#include <time.h>//设置随机数的头文件
void show(int x)
{
printf("%d ",x);
}
int main()
{
//定义一个头指针
Node *head=NULL;
//初始化链表
int ret=init_link(&head);
if(SUCCESS==ret)//如果返回值是SUCCESS
{
printf("链表初始化成功\n");
}
else
{
printf("链表初始化失败\n");
}
srand(time(NULL));//设置种子,记得包含头文件time.h
//插入元素
int i,num;
for(i=0;i<10;i++)//插入10个元素
{
num=rand()%20+1;//插入随机数,数字范围在0~20
ret=insert_link(head,i+1,num);//这种插入的方式就像一个尾插法,head是头指针,头指针存放的是链表头节点的地址,也相当于链表的位置,类似数组名的作用
if(SUCCESS==ret)
{
printf("在第%d个位置插入%d成功\n",i+1,num);
}
else
{
printf("插入%d失败\n",num);
}
}
//遍历链表
traverse_link(head,show);//将头指针传过去,类似传数组名,将函数名show(即函数的地址)也传过去
printf("\n");//换行
//判断链表是否为空
ret=empty_link(head);
if(SUCCESS==ret)
{
printf("链表为空\n");
}
else
{
printf("链表不为空\n");
}
//判断链表的长度
printf("链表的长度是%d\n",length_link(head));
//链表查找操作
num=rand()%20+1;//随机生成一个数
int res[10]={0};//用来存放num的所有节点的位置
ret=find_link(head,num,res);
if(SUCCESS==ret)
{
printf("%d 出现的位置是 ",num);
for(i=0;i<10 && res[i]!=0;i++)
{
printf("%d",res[i]);
}
printf("\n");
}
else
{
printf("%d不存在\n",num);
}
/*
//反转链表
reverse_link(head);
//遍历链表,验证反转的结果
traverse_link(head,show);//将头指针传过去,类似传数组名,将函数名show(即函数的地址)也传过去
printf("\n");//换行
*/
//链表的删除
int p;//用来保存删除的位置
for(i=0;i<5;i++)//删除5个节点
{
p=rand()%15;
ret=delete_link(head,p,&num);//p是要删除的位置,num是要删除的数
if(SUCCESS==ret)
{
printf("删除第%d个元素成功 %d\n",p,num);
}
else
{
printf("删除第%d个元素失败\n",p);
}
}
//遍历链表,验证删除的结果
traverse_link(head,show);//将头指针传过去,类似传数组名,将函数名show(即函数的地址)也传过去
printf("\n");//换行
//判断链表的前驱
int prior;
num=rand()%30;
ret=prior_link(head,num,&prior);
if(SUCCESS==ret)
{
printf("元素%d的前驱是%d\n",num,prior);
}
else
{
printf("元素%d不存在前驱\n",num);
}
//链表清空操作
ret=clear_link(head);
if(SUCCESS==ret)
{
printf("链表清空成功\n");
}
else
{
printf("链表清空失败\n");
}
//遍历链表,验证清空的结果
traverse_link(head,show);//将头指针传过去,类似传数组名,将函数名show(即函数的地址)也传过去
printf("\n");//换行
//链表销毁操作
ret=destroy_link(&head);
if(SUCCESS==ret)
{
printf("链表销毁成功\n");
}
else
{
printf("链表销毁失败\n");
}
return 0;
}
下节开始讲双向循环链表!
QQ交流群:963138186
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓