不带头结点的双向链表

本文讲述了作者在学习双向链表过程中的经验,包括定义结构体、初始化、增删改操作,以及如何正确处理双向指针。作者强调了处理前后节点关系的重要性,并分享了链表的遍历方法和总结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

经过两个多星期的链表学习(别问为什么学了这么久,问就是前段时间一不小心走进舒适圈效率极低),发了超多篇相关博客,链表的学习也差不多到尾声啦,于是在十二月的第一天,有了这篇双向链表。
因为其实带不带头结点循不循环差别不是很大,再加上本人犯懒,所以双向链表不会有带头结点和循环篇了(嘻~)


讲正事

其实双向链表在处理上和单链表最大的不同就是要兼顾一个结点里的两个指针,但是最常见的错误方式,就是在处理的时候会很自然地忘记处理指向前一个结点的指针。

准备工作——各种结构体的定义

结点

typedef struct node {
	struct node* pre;
	int num;
	struct node* next;
}Node;

专用于储存链表信息的结构体

typedef struct linklist {
	Node* head;
	Node* tail;
}Linklist;

初始化

void Creat(Linklist* linklist) {
	Node* p = (Node*)malloc(sizeof(Node));
	printf("请输入第一个数据:");
	scanf("%d", &p->num );
	p->pre = NULL;
	p->next = NULL;
	linklist->head = p;
	linklist->tail = p;
}

其实可以选择在中间某个地方或者链表头插入新结点,但是由于我懒得写,就只展示了尾插的方法

void add(Linklist* linklist) {
	int flag = 0;
	while (1) {
		Node* p = (Node*)malloc(sizeof(Node));
		printf("请输入要添加的数字:");
		scanf("%d", &p->num);
		p->next = NULL;
		linklist->tail->next = p;//令前一个结点指向新结点
		p->pre = linklist->tail;//令新结点反指前一个结点(!!双向才有的哦)
		linklist->tail = p;//移动尾指针
		printf("继续输入请按1,结束请按0:");
		scanf("%d", &flag);
		if (!flag) {
			break;
		}
	}
}

在删除的时候,记得处理被删结点留下的痕迹!(断要断得干干净净才行哦)详见注释

void delete_sth(Linklist* linklist) {
	int n = 0;
	Node* p = linklist->head;
	printf("请输入你要删除的数据:");
	scanf("%d", &n);
	while (p->num != n && p->next) {
		p = p->next;
	}
	//这样子结束循环后,要么p停留在链表最后1个结点(不清楚是否找到数据),
	//要么找到数据停留在要删结点(该结点位于链表中间或最前面)
	
	if (p == linklist->head) {//先判断该结点是否为链表的第一个结点
		linklist->head = p->next;
		p->next->pre = NULL;//第二个结点的pre指向第一个结点,记得把这个关系抹掉
		free(p);
	}
	else if (p->next) {//这种是找到了要删结点,且结点不是头尾的情况
		p->pre->next = p->next;
		p->next->pre = p->pre;
		free(p);
	}
	else if (p->num == n) {		//这种是要删结点是尾的情况
		p->pre->next = NULL;//倒数第二个结点的next指向最后一个结点,记得也要抹掉这段关系
		linklist->tail = p->pre;
		free(p);
	}
	else {	//这种是压根没找到数据
		printf("未找到该数据\n");
	}
}

就改个数据没啥好说的,不说了

void amend(Linklist* linklist) {
	int n;
	Node* p = linklist->head;
	printf("请输入要修改的数字:");
	scanf("%d", &n);
	while (p) {
		if (p->num != n) {
			p = p->next;
		}
		else if (p->num == n) {
			break;
		}
	}
	if (!p) {
		printf("您输入的编号不存在\n");
	}
	else {
		printf("请输入改正后的数字:");
		scanf("%d", &p->num);
	}
}

逆置

逆置啥逆置,都双向了,正着能走反着也能走,这part跳过不逆了

遍历并输出链表

思想和单链表差不多,拿个工具指针从前往后指一个输出一个就行。
但是!!为了突出双向链表的特别,我特地从后面往前又遍历了一边(现在看我似乎有什么大病吃饱了撑的输出两遍)

void traversal(Linklist* linklist) {
	Node* p = linklist->head;
	//前一个往后一个遍历
	printf("%d ", p->num);
	while (p->next) {
		p = p->next;
		printf("%d ", p->num);
	}
	printf("\n");
	//后一个往前一个遍历
	printf("%d ", p->num);
	while (p->pre) {
		p = p->pre;
		printf("%d ", p->num);
	}
	printf("\n");
}

特别栏目——总结

学完双向链表之后我发现这真是个好东西(果然双向奔赴是最美好的),从头可以开始从尾也可以开始,就是处理上有点麻烦,容易忘记处理和前一个的关系(所以说和前任断要断得干净以绝后患啊)。
最后的最后,十二月到啦,带着十一月的未完成,在十二月奔跑起来!即使道路泥泞,也会收获遍野的烂漫!
藏着许多节日的十二月,也要全力以赴的开心~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值