目录
上一篇我们在单向链表的基础上完成了双向链表的代码,本节开始讲双向循环链表!
双向循环链表同时具有双向链表和循环链表的特性,是效率最高的一种链表。我们也是在单链表的代码基础上修改。
先复制单链表的代码复制到doublecircle文件夹下
补充命令12:cp -r 递归拷贝文件,将linklist文件夹下的文件复制到doublecircle文件夹下
双向循环链表的定义
我们首先在头文件link.h中做修改
双向循环链表的初始化
在双向循环链表中,从头到尾我们都不能出现NULL这个字眼。
初始化的时候,头结点的Prior和next指针域都初始化为它自己的地址。
所以初始化函数我们修改为
双向循环链表插入操作
插入操作我们没办法在单链表的代码改,直接重新写一个插入函数:
双向循环链表遍历操作
将遍历函数中修改为
判断双向循环链表是否为空
将empty函数修改为
求双向循环链表的长度
双向循环链表查找操作
双向循环链表也不需要反转操作,直接将反转函数注释掉。
双向循环链表删除操作
双向循环链表的删除操作也不好在单链表的代码基础上修改,直接重新写一个
双向循环链表求前驱
双向链表的清空操作
双向链表的销毁操作
注:*h表示取h这个指针指向的这块内存里面的值,即h指向了头指针,而头指针里面存放的是头结点的地址,所以*h取出来的是头结点的地址0x100。而**h表示取出来的是0x100这块地址里面存放的值,0x100里面存放的是头结点,所以**h取出来的是头结点。
运行结果:
完整代码:
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=*h;//将头指针指向的地址复制给头节点的next指针域
(*h)->prior=*h;//将头指针指向的地址复制给头节点的prior指针域
return SUCCESS;
}
//链表的插入操作
int insert_link(Node*h,int p,int n)//p接收的是要插入的位置
{
//入参判断
if(NULL==h)
{
return FAILURE;
}
//判断位置是否合法
if(p<=0||p>length_link(h)+1)//要插入的位置p小于0或者大于链表长度+1都不合法
{
return FAILURE;
}
//移动指针
Node *q=h;//q指向头节点
//将指针移动到要插入位置的前一个位置
int i;
for(i=0;i<p-1;i++)//先记录次数
{
q=q->next;//q指向第一个节点(指向要插入位置的前一个位置)
}
//产生新的节点
Node *m=(Node*)malloc(sizeof(Node)*1);//申请一个节点
if(NULL==m)
{
return FAILURE;//申请失败
}
m->data=n;//n要插入的数据
m->next=q->next;//将第二个节点的地址赋值给m的next
m->prior=q;//将第一个节点的地址赋值给m的prior
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!=h)//只要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==h)? SUCCESS:FAILURE;//如果是SUCCESS表示链表为空
}
//判断链表的长度
int length_link(Node*h)
{
if(NULL==h)
{
return FAILURE;//这里失败是因为传的参数错了
}
Node*q=h->next;//让q指向第一个节点
int length=0;
while(q!=h)//只要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!=h)//不回头
{
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;
}
//判断位置是否合法
if(p<=0||p>length_link(h))
{
return FAILURE;
}
//移动指针
int i;
Node *q=h;//将q指向头节点
for(i=0;i<p-1;i++)//将q指向要删除位置的前一个位置
{
q=q->next;//q移动到下一个节点
}
Node *n=q->next;//n指向要删除的节点
*num=n->data;//将要删除的数据记录下来
n->next->prior=q;//将要删除的节点的前一节点的地址存放在后一个节点的prior里面
q->next=n->next;//将后一个节点的地址存放在前一个节点的next里面
free(n);
return SUCCESS;
}
//判断前驱
int prior_link(Node*h, int num, int*p)
{
//入参判断
if(NULL==h||NULL==p)
{
return FAILURE;
}
//判断一下,如果是空链表或者只有一个节点的话肯定不存在前驱
if(h==h->next||h==h->next->next)//h->next是第一个节点,h->next->next是第一个节点的指针域
{
return FAILURE;
}
Node *k=h->next;//让k指向第一个节点
Node *q=k->next;//让q指向第二个节点
while(q!=h)//不回头
{
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!=h)//不回头
{
//释放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!=*h)//如果头节点的next不等于头节点的地址,说明还没有执行清空操作
{
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;//加一个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 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
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓