【双向链表】带头双向循环(1)

目录

链表的分类

Test.c主函数

test1头插

test2头删

test3尾插

test4尾删

test5查询

test6插入pos位置后一个

test7删除pos

Test.c总代码 

DList.h头文件&函数声明

头文件

函数声明

DList.h总代码

DList.c函数实现

Createnode创建节点

LTInit初始化

LTPrint打印

LTPushFront头插

LTPopFron头删

LTPushBack尾插

LTPopBack尾删

LTFind查询

LTInsertAfter在pos后面插入

LTErase删除pos数据

LTDestroy销毁释放

DList.c总代码


今天实现一个带头双向循环链表。

  • 判断为NULL的情况
  • 判断二级指针&一级指针(想要改变实参不仅可以用指针,而且可以用return
  • 判断先实现哪一步
  • ❌野指针的出现
  • ❌修改指针用二级指针
  • ❌修改结构体用一级指针
  • ❌内存泄露的问题,需要释放
  • ❌释放完空间,指针需要置NULL
  • 无头单项不循环链表适用OJ链表比较多
  • 带头双向不循环适用生活工作场景比较多
  • C++是兼容C
  • ❌【单链表】一般都不带头//需要二级指针处理//除了!链表分割
  • ❌【双向链表】需要带头
  • 【单链表】Singly linked list(SLT)
  • 【双链表】Double linked list(LT)
  • 【顺序表】Sequention table list(SL)

链表的分类

前面我们学习了单链表。但是链表并不仅限只有【单链表】。【链表】是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

【双向链表】带头双向循环(1)_第1张图片

 单向或者双向

【双向链表】带头双向循环(1)_第2张图片

带头或者不带头

 【双向链表】带头双向循环(1)_第3张图片

循环或者非循环

 【双向链表】带头双向循环(1)_第4张图片

 这些特征组合起来可以构成:8种形态的链表。

 【双向链表】带头双向循环(1)_第5张图片

 虽然有这么多的链表的结构,但是我们实际中最常用的还是有两种结构。

【无头单项非循环链表】&【带头双向循环链表】

【双向链表】带头双向循环(1)_第6张图片

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。实际生活中使用很多。

在前面我们已经学过 无头单项不循环链表,这里我们来实现 带头双向循环链表 

【双向链表】带头双向循环(1)_第7张图片

Test.c主函数

int main()
{
	LTNode* phead = LTInit();//初始化
	test1(phead);//头插
	test2(phead);//头删
	test3(phead);//尾插
	test4(phead);//尾删
    test5(phead);//查询某个数字

	test6(phead);//插入pos位置的前一个/后一个元素
	test7(phead);//删除pos位置的元素
	LTDestroy(phead);//空间释放/防止内存泄露
	return 0;
}

test1头插

#include"DList.h"
void test1(LTNode* phead)//头插
{
	LTPushFront(phead, 7);
	LTPushFront(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 7);
	LTPushFront(phead, 8);
	LTPushFront(phead, 9);
	LTPrint(phead);
}

test2头删

void test2(LTNode* phead)//头删
{
	LTPopFront(phead);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPrint(phead);
}

test3尾插

void test3(LTNode* phead)//尾插
{
	LTPushBack(phead, 77);
	LTPushBack(phead, 99);
	LTPrint(phead);
}

test4尾删

void test4(LTNode* phead)//尾删
{
	LTPopBack(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPrint(phead);
}

test5查询

void test5(LTNode* phead)//查询
{
	LTNode* node = LTFind(phead, 4);
	if (node == NULL)
	{
		printf("没找到");
	}
	else
	{
		printf("%d", node->val);
	}
}

test6插入pos位置后一个

void test6(LTNode* phead)//插入pos位置的前一个/后一个元素
{
	LTNode* pos = LTFind(phead, 4);
	LTInsertAfter(phead, pos, 77);
	LTInsertAfter(phead, pos, 66);
	LTInsertAfter(phead, pos, 99);
	LTPrint(phead);
}

test7删除pos

//删除pos位置的元素
void test7(LTNode* phead)
{
	LTNode* pos = LTFind(phead, 4);
    if(pos)//查询成功才进入
    {
	  LTErase(phead, pos);
      pos=NULL;
    }
	LTPrint(phead);
}

Test.c总代码 

#include"DList.h"
void test1(LTNode* phead)//头插
{
	LTPushFront(phead, 7);
	LTPushFront(phead, 3);
	LTPushFront(phead, 4);
	LTPushFront(phead, 7);
	LTPushFront(phead, 8);
	LTPushFront(phead, 9);
	LTPrint(phead);
}

void test2(LTNode* phead)//头删
{
	LTPopFront(phead);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPrint(phead);
}

void test3(LTNode* phead)//尾插
{
	LTPushBack(phead, 77);
	LTPushBack(phead, 99);
	LTPrint(phead);
}

void test4(LTNode* phead)//尾删
{
	LTPopBack(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPrint(phead);
}

void test5(LTNode* phead)//查询
{
	LTNode* node = LTFind(phead, 4);
	if (node == NULL)
	{
		printf("没找到");
	}
	else
	{
		printf("%d", node->val);
	}
}

void test6(LTNode* phead)//插入pos位置的前一个/后一个元素
{
	LTNode* pos = LTFind(phead, 4);
	LTInsertAfter(phead, pos, 77);
	LTInsertAfter(phead, pos, 66);
	LTInsertAfter(phead, pos, 99);
	LTPrint(phead);
}

//删除pos位置的元素
void test7(LTNode* phead)
{
	LTNode* pos = LTFind(phead, 4);
    if(pos)//查询成功才进入
    {
	  LTErase(phead, pos);
      pos=NULL;
    }
	LTPrint(phead);
}


int main()
{
	LTNode* phead = LTInit();//已经被初始化了
	test1(phead);//头插
	test2(phead);//头删
	test3(phead);//尾插
	test4(phead);//尾删
    test5(phead);//查询某个数字

	test6(phead);//插入pos位置的前一个/后一个元素
	test7(phead);//删除pos位置的元素
	LTDestroy(phead);//空间释放/防止内存泄露
    phead=NULL;
	return 0;
}

DList.h头文件&函数声明

头文件

#pragma once
#include
#include
#include
assert(//)//括号里是可以出现的情况为真 若括号里的表达式为假那就❌

函数声明

  • 声明节点 
typedef int LTDateType;
//声明节点
typedef struct DListNode
{
	LTDateType val;
	struct DListNode* prev;
	struct DListNode* next;
}LTNode;
  •  初始化(可以用指针修改实参/也可以用返回值改变实参)
LTNode* LTInit();
  • 头插
void LTPushFront(LTNode*phead, LTDateType x);
  • 头删
void LTPopFront(LTNode* phead);
  • 尾插
void LTPushBack(LTNode* phead,LTDateType x);
  • 尾删
void LTPopBack(LTNode* phead);
  • 打印
void LTPrint(LTNode* phead);
  • 查询
LTNode* LTFind(LTNode* phead);
  • 在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode* pos, LTDateType x);
  • 删除pos位置的元素
void LTErase(LTNode* phead, LTNode* pos);
  • 销毁释放 
void LTDestroy(LTNode* phead);

DList.h总代码

#pragma once
#include
#include
#include

typedef int LTDateType;

//定义节点
typedef struct DListNode
{
	LTDateType val;
	struct DListNode* prev;
	struct DListNode* next;
}LTNode;

//初始化
LTNode* LTInit();//不用二级指针/用返回值改变实参
void LTPushFront(LTNode*phead, LTDateType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTPushBack(LTNode* phead,LTDateType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印
LTNode* LTFind(LTNode* phead);//查询
void LTInsertAfter(LTNode* phead, LTNode* pos, LTDateType x);//在pos的后面插入一个元素
void LTErase(LTNode* phead, LTNode* pos);//删除pos位置的元素
void LTDestroy(LTNode* phead);//销毁释放

DList.c函数实现

//初始化
LTNode* LTInit()
{
	LTNode* phead = Createnode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}

Createnode创建节点

#include"DList.h"
//创造节点
LTNode* Createnode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);//程序停止
	}
	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

LTInit初始化

  • 通过形参改变实参可以使用 【指正】
  • 通过形参改变实参可以使用 【return】
//初始化
LTNode* LTInit()
{
	LTNode* phead = Createnode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}

【双向链表】带头双向循环(1)_第8张图片 

LTPrint打印

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead;
	printf("<=>");
    printf("%d<=>", cur->val);
	cur = cur->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

LTPushFront头插

//头插
void LTPushFront(LTNode* phead,LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

【双向链表】带头双向循环(1)_第9张图片

LTPopFron头删

  • 一定不能把头节点哨兵位给删除了会出现野指针的问题❌ 
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	LTNode* next = cur->next;
	assert(cur != phead);	//考虑如果链表为NULL
	phead->next = next;
	next->prev = phead;
	free(cur);
	cur = NULL;
}

【双向链表】带头双向循环(1)_第10张图片 

LTPushBack尾插

//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;
}

【双向链表】带头双向循环(1)_第11张图片 

LTPopBack尾删

  • 一定不能把头节点哨兵位给删除了会出现野指针的问题❌  
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->prev;
	LTNode* next = cur->prev;
	assert(cur != phead);
	phead->prev = next;
	next->next = phead;
	free(cur);
	cur = NULL;
}

【双向链表】带头双向循环(1)_第12张图片 

LTFind查询

  • 查询到某个数据的位置意味着可以修改这个位置前后的数据 
//查询
//查询某个数,存在返回节点地址,不存在返回NULL
LTNode* LTFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

LTInsertAfter在pos后面插入

//在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode*pos, LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
	newno

【双向链表】带头双向循环(1)_第13张图片 

LTErase删除pos数据

//删除pos位置的元素//
void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = pos->prev;
	LTNode* next = pos->next;
	cur->next = next;
	next->prev = cur;
	free(pos);
	pos = NULL;
}

【双向链表】带头双向循环(1)_第14张图片 

LTDestroy销毁释放

//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

DList.c总代码

#include"DList.h"
//创造节点
LTNode* Createnode(LTDateType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);//程序停止
	}
	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}
//初始化
LTNode* LTInit()
{
	LTNode* phead = Createnode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}
//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead;
	printf("<=>");
    printf("%d<=>", cur->val);
	cur = cur->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}
//头插
void LTPushFront(LTNode* phead,LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	LTNode* next = cur->next;
	assert(cur != phead);	//考虑如果链表为NULL
	phead->next = next;
	next->prev = phead;
	free(cur);
	cur = NULL;
}
//尾插
void LTPushBack(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->prev = phead->prev;
	phead->prev->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->prev;
	LTNode* next = cur->prev;
	assert(cur != phead);
	phead->prev = next;
	next->next = phead;
	free(cur);
	cur = NULL;
}
//查询
//查询某个数,存在返回节点地址,不存在返回NULL
LTNode* LTFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos的后面插入一个元素
void LTInsertAfter(LTNode* phead, LTNode*pos, LTDateType x)
{
	assert(phead);
	LTNode* newnode = Createnode(x);
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
	newnode->prev = pos;
}
//删除pos位置的元素//
void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = pos->prev;
	LTNode* next = pos->next;
	cur->next = next;
	next->prev = cur;
	free(pos);
	pos = NULL;
}
//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
    cur=NULL;
}
//形式参数这里就可以置NULL 
//实际参数到主函数里面置NULL


 【双向链表】带头双向循环(1)_第15张图片

✔✔✔✔✔最后感谢大家的阅读,若有错误和不足,欢迎指正!乖乖敲代码哦! 

代码---------→【唐棣棣 (TSQXG) - Gitee.com】

联系---------→【邮箱:[email protected]

你可能感兴趣的:(初阶数据结构,链表,数据结构,c语言,开发语言,1024程序员节)