详解双向循环带头节点链表——十分钟单手吊打链表

举报
乔乔家的龙龙 发表于 2022/04/15 09:27:23 2022/04/15
【摘要】 😎十分钟单手吊打双链表 带你一命通关,拿捏链表结构

@TOC

传统艺能😎

小编是双非本科大一计科菜鸟(打灰人 )不赘述,欢迎大佬指点江山(QQ:1319365055)
此前博客点我!点我!请搜索博主 【知晓天空之蓝】

过渡区🤣

10天军训终于完了,时间管理大师都没法在这段时间打理学习,中途用所剩无几的理性上传了一篇之前笔记随笔,阅读量却没让我失望,于是我就以最快的速度回来继续奋勇直追!
在这里插入图片描述

和单链表比较🤔

之前我讲过了单链表,也就是单向不带头不循环链表,看起来和现在的这个简直天差地别,但是——没有关系,我们必须知道一点:

单向不带头不循环链表是最简单的结构,但实现却比较复杂
双向循环带头节点链表是最复杂的结构,但实现却最简单

单链表一般出现在我们熟知的 OJ 题目中,而生活中实际运用却高度依赖于双链表,因为双链表效率高,实现方便,简单易懂,代码清爽,结构严谨,巴拉巴拉……

分类🤔

之前提到过三个分类:
在这里插入图片描述
双向就是后一个节点存有指向前一个和后一个的指针,既可以正向访问也可以逆向访问,相比单链表灵活性直接上了一个档次。

在这里插入图片描述
所谓带头,指的就是带哨兵位的头,这里的哨兵位相当于一个结构体。
在这里插入图片描述
循环就是指链表尾节点不指向 NULL 而是指向 phead,从而使头插头删尾插尾删等基本操作变得十分简单。

当然不止以上6种,两两排列组合可以得到更多的形式。

空间申请👏

老规矩,开辟空间肯定是第一步:

Seqlist* buymalloc(Seqtype x)
{
	Seqlist* newnode = (Seqlist*) malloc (sizeof(Seqlist));
	if (newnode == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

初始化👏

Seqlist* phead = NULL;
	phead=ListInit();

这里就开始设置哨兵位头节点:

Seqlist* init()
{
	Seqlist* phead = buymalloc(0);
	phead->next = phead;//指向下一个节点的指针指向自身
	phead->prev = phead;//指向上一个节点的指针指向自身
	return phead;
}

因为创建的结构体指针 phead 指向NULL,因此我们需要在初始化时创建一个哨兵位头结点,使 phead 指向这个头节点;在后续的使用中,plist始终是指向这个头结点,并不会被修改,因此不需要传入二级指针

存在哨兵位头结点时,改变的是哨兵位(结构体)节点,传入的是结构体的指针(地址),非哨兵位改变的是结构体指针那传入的就是结构体指针的指针(地址)

==注意==:这里开辟空间参数为 0,这里目的是只要让头结点不为空即可,不用在意值的大小。

尾插👏

以基本功能举两个栗子来实际体会一下和单链表的差距:

void pushback(Seqlist* phead, Seqtype x)
{
	assert(phead);
	Seqlist* newnode = buymalloc(x);
	Seqlist* tail = phead->prev;
	tail->next = newnode;
	tail = newnode->prev;
	newnode->next = phead;
	phead->prev = newnode;
}

用ppt手残一张图出来配合食用,勿喷捏:
在这里插入图片描述
注意各个步骤的顺序,前后的逻辑严格,不可随意调换,稍有差错就可能找不到前一个或者后一个节点的地址。

尾删👏

void popback(Seqlist* phead)
{
	assert(phead);
	assert(phead != phead->next);
	Seqlist* tail = phead->prev;
	Seqlist* tailprev = tail->prev;
	free(tail);
	tail = NULL;
	tailprev->next = phead;
	phead->prev = tailprev;

}

在这里插入图片描述

pos位插入👏

其实比起头插尾插,pos位插入其实更为简单,注意pos位插入是指 pos 的前一位插入,==思路即 pos 前前一个节点与pos前一个节点链接,再和 pos 后一个节点链接就行,中间步骤直接链接即可。==

void insert(Seqlist* pos, Seqtype x)
{
	assert(pos);
	Seqlist* newnode = buymalloc(x);
	Seqlist* posprev = pos->prev;//找到插入节点的正确位置
	newnode->next = pos;
	posprev->next = newnode;
	newnode->prev = posprev;
}

我们之前说过,头插头删尾插尾删其实是 pos 位插入删除的特殊实现情况,所以我们格局打开:写出 pos 位插入删除即可搞定所有插删操作。

那么头插就可以写成这样:

void pushfront(Seqlist* phead,Setype x)
{
assert(phead);
insert(phead->next,x);
}

尾插同理:

void pushback(Seqlist* phead,Setype x)
{
assert(phead);
insert(phead,x);
}

😎是不是突然就觉得头尾删插是个什么渣渣!😎
没错,这就是结构最复杂操作却最简单的双向带头循环链表。

源码奉上🎉

==Slist.h==

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int Seqtype;
typedef  struct Seqlist
{
	Seqtype data;
	struct Seqlist* next;
	struct Seqlist* prev;
}Seqlist;
Seqlist* buymalloc(Seqtype x);//开辟空间
Seqlist* init();//初始化
void print(Seqlist* phead);//打印
void pushback(Seqlist* phead, Seqtype x);//尾插
void popback(Seqlist* phead);//尾删
void popfront(Seqlist* phead);//头删
void pushfront(Seqlist* phead, Seqtype x);//头插
Seqlist* find(Seqlist* phead, Seqtype x);//查找
void insert(Seqlist* pos, Seqtype x);//pos位插入
void erase(Seqlist* pos);//pos位删除

==test.c==

# define _CRT_SECURE_NO_WARNINGS 
#include"Slist.h"
void test()
{
	Seqlist* phead = init();
	pushback(phead, 2);
	pushback(phead, 3);
	popback(phead);
    print(phead);
}
int main()
{
	test();
	return 0;
}

==Slist.c==

# define _CRT_SECURE_NO_WARNINGS 
#include"Slist.h"
Seqlist* buymalloc(Seqtype x)
{
	Seqlist* newnode = (Seqlist*) malloc (sizeof(Seqlist));
	if (newnode == NULL)
	{
		printf("fail!\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
void pushback(Seqlist* phead, Seqtype x)
{
	assert(phead);
	Seqlist* newnode = buymalloc(x);
	Seqlist* tail = phead->prev;
	tail->next = newnode;
	tail = newnode->prev;
	newnode->next = phead;
	phead->prev = newnode;

}
//哨兵位头结点,改变的是哨兵位(结构体)节点,
//传的是结构体的指针(地址),非哨兵位改变的是结构体指针那
//传的就是结构体指针的指针(地址)
void print(Seqlist* phead)
{
	assert(phead);
	Seqlist* newnode = phead->next;
	while(newnode != phead)
	{
		printf("%d ", newnode->data);
		newnode = newnode->next;
	}
}
Seqlist* init()
{
	Seqlist* phead = buymalloc(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
void popback(Seqlist* phead)
{
	assert(phead);
	assert(phead != phead->next);
	Seqlist* tail = phead->prev;
	Seqlist* tailprev = tail->prev;
	free(tail);
	tail = NULL;
	tailprev->next = phead;
	phead->prev = tailprev;

}
void insert(Seqlist* pos, Seqtype x)
{
	assert(pos);
	Seqlist* newnode = buymalloc(x);
	Seqlist* posprev = pos->prev;
	newnode->next = pos;
	posprev->next = newnode;
	newnode->prev = posprev;
}
void pushfront(Seqlist* phead, Seqtype x)
{
	assert(phead);
	insert(phead->next, x);
}

Seqlist* find(Seqlist* phead, Seqtype x)
{
	assert(phead);

	Seqlist* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

今天就到这里吧,摸了家人们。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。