嵌入式全栈开发学习笔记---数据结构(双向循环链表)

目录

双向循环链表的定义

双向循环链表的初始化

双向循环链表插入操作

双向循环链表遍历操作

判断双向循环链表是否为空

求双向循环链表的长度

双向循环链表查找操作

双向循环链表删除操作

双向循环链表求前驱

双向链表的清空操作

双向链表的销毁操作


上一篇我们在单向链表的基础上完成了双向链表的代码,本节开始讲双向循环链表!

双向循环链表同时具有双向链表和循环链表的特性,是效率最高的一种链表。我们也是在单链表的代码基础上修改。

先复制单链表的代码复制到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

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vera工程师养成记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值