C语言---双向链表(详解)---数据结构

双向链表所需要头文件

首先重定义类型名 意义我前几篇讲过几次了,这里就不在赘述了,(顺序表,单链表的开头都有说明)

 

然后我们需要一个结构体

结构体包含 : 存储数据的 a 指向一个节点的指针 next 指向上一个节点的指针 prve

双向链表实现的函数功能有 :1.申请动态内存空间 2.初始化 3.双向链表的头,尾的插入与删除,在合法的任意位置进行插入和删除 4.释放链表,5.打印链表

1.申请动态内存空间

因为进行插入和初始化需要用到此函数 所以我们要先实现此功能

1.先申请一个动态内存,并名为newnode

2.把x存入newnode存放数据的a中

3.把newnode的上,下指针都置为空

4.最后再返回此申请的空间

2.初始化

我们先创建一个链表的头 并且把上,下指针置空,并且返回链表的头

并且我们再命名一个plist,用来接收,之后穿参数的时候,就可以直接用pilst

 3.双向链表的尾部插入

1.先创建一个指针 指向头的一个节点

2.申请一块内存 用来插入

因为双向链表的优势 ,我们不需要像单链表那样依次遍历找到最后一个节点,双向链表,表头,的上一个就是链表的最后一个节点

所以我们可以一步就找到表尾

尾插入原理:

找到表尾后,1.在表尾后再插入一个新数据(用next指针指向newnode(新数据)),2.把新数据指向原先的表尾(用prve指向原先的表尾)就完成了一次的连接

1.再让现在的表尾 (就是newnode(新数据))指向表头(newnode->next=phead(表头))  2.(再让表头指向上一个的指针(就是prve)指向newnode(新数据))     

这里可能会有点绕,需要读者慢慢理解,但是原理是简单的 就是让1个数据与它的上一个数据和下一个数据进行对接

4.双向链表的尾部删除

 原理:

1.找到表尾,再找到表尾的上一个数据(倒数第二个数据)

2.然后让倒数第二个数据与表头对接,倒数第二个数据就成为了表尾数据,

3.再把原先的表尾数据进行释放

end就是最后一个节点,cur就是倒数第二个节点

1.让cur指向下一个的指针指向表头 

2.把表头的指向上一个的指针指向cur

3.释放原先的表尾,并且把指针置空

 读者要搞清楚 next 和prve的作用 再搞清楚end和cur的位置,最后运用他们进行对接

5.双向链表的头部插入

我们先找到两个位置 一个是表头 一个是表头的下一个节点(这个节点才是真正存放数据的节点,表头只是其带头作用,里面只是存放0,并且打印链表都是从表头的一个节点开始打印

找到两个位置之后,创建一个动态内存 进行插入

接下来大致原理就是 让插入的数据表头表头的下一个节点在他们两个中间插入(就是对接)

表头原先指向的是表头的下一个节点1.现在我们让表头指向下一个节点的指针指向插入的数据

 2.让插入的数据指向上一个节点的指针指向表头  3.让插入的数据指向下一个节点的指针指向原先表头的下一个节点(进行对接)

6.双向链表的头部删除

头部删除也是删除表头的下一个节点

那么我们要找到两个位置 1.表头,2.表头的下一个节点的下一个节点(就是要删除的位置的下一个节点)

这里我们把表头命名为cur 把表头的下一个节点的下一个节点(就是要删除的位置的下一个节点)命名为end

1.然后我们让 cur和end进行对接

 2.然后释放要删除数据

把cur指向下一个节点的指向指向end 把end指向上一个节点的指针指向cur

7.双向链表的查找

 这里我们只需要从表头的下一个节点开始一直到表尾,进行遍历,如果找到就返回改位置。如果知道表尾都没找到(就是循环结束的时候)那么我们就返回空(NULL)

然后我们用pos来接收 

 7.对pos的前一个位置进行插入数据

我们首先需要先对pos进行判断,如果找到了,才开始插入,若找不到,就不执行函数

因为是插入数据 所以我们要先申请一个内存

又因为我们已经得到了pos的位置,所以我们可以直接找到pos的上一个数据

原理在于找两个位置 一个是pos 一个是pos的上一个数据

pos我们已经有了它的位置 pos的上一个位置我们只需要用指针指向它就可以了

pos的上一个位置命名为end

然后让新数据pos的上一个数据pos中间进行插入(就是对接) 

8.对pos这个位置进行删除

 因为我们有了pos的位置

那么我们用指针 next和prve找到 pos的上一个位置(命名为cur),和pos的下一个位置(end)进行对接

然后再释放掉pos,为把pos置空

 9.链表的打印

从表头的下一个数据开始,进行遍历,并依次打印

10.链表的释放 

因为链表的内存是一块块申请的,所以我们要遍历,并且释放

先从找到表头的下一个节点(命名为end),再找到表头的下一个节点的下一个节点(命名为cur)

先释放表头的下一个节点再把cur赋值给end,就此循环。就把表头以后的数据全释放完了,

最后把表头给释放,并且置空,就可以了。

双向链表的全部函数功能就实现好了!

这里添加一个注释

之所以双向链表传参不用(&)取地址 是因为SL* plist=ListCreate(); 这里是用指针接收,指针接收的就是地址,我们传参数的时候就是用地址传入了。

不用二级指针是因为我们不需要改变这个双向链表的哨兵位,只是改变它的指向,这个哨兵位是不会变的(不管头插、尾插、头删、尾删)

而单链表的头插,头删是会改变这个头的,比如(头删)释放这个头,让下一个节点成为头,这些都是会改变头的,所以我们要传二级指针。

接下来是代码段 ,需要自取。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int STDataType;

typedef struct linode
{
	STDataType a;
	struct linode* next;
	struct linode* prve;
}SL;

SL* Bylist(STDataType x)//申请动态内存
{
	SL* newnode = (SL*)malloc(sizeof(SL));
	newnode->a = x;
	newnode->next = NULL;
	newnode->prve = NULL;

	return newnode;
}

SL* ListCreate()//初始化
{
	SL* phead = Bylist(0);
	phead->next = phead;
	phead->prve = phead;

	return phead;
}

void ListDestory(SL* pHead)//释放链表
{
	SL* end = pHead->next;
	while (end != pHead)
	{
		SL* cur = end->next;
		free(end);
		end = cur;
	}
	free(pHead);
	pHead = NULL;
}

void ListPrint(SL* pHead)//打印链表
{
	SL* end = pHead->next;
	while (end != pHead)
	{
		printf("%d->", end->a);
		end = end->next;
	}
	printf("phead");
}

void ListPushBack(SL* pHead, STDataType x)//尾插
{
	SL* end = pHead->prve;
	SL* newnode = Bylist(x);
	//因为双向链表的优势 所以我们不需要依次遍历,头的上一个就是最后一个节点
	end->next = newnode;
	newnode->prve = end;
	newnode->next = pHead;
	pHead->prve = newnode;
}

void ListPopBack(SL* pHead)//尾删
{
	SL* end = pHead->prve;//找到最后一个节点
	SL* cur = end->prve;//最后一个节点的上一个
	cur->next = pHead;
	pHead->prve = cur;
	free(end);
	end = NULL;
}

void ListPushFront(SL* pHead, STDataType x)//头插
{
	SL* end = pHead;
	SL* cur = end->next;
	SL* newnode = Bylist(x);
	end->next = newnode;
	newnode->prve = end;
	newnode->next = cur;
	cur->prve = newnode;
}

void ListPopFront(SL* pHead)//头删
{
	SL* cur = pHead->next;
	SL* end = cur->next;
	pHead->next = end;
	end->prve = pHead;
	free(cur);
}

SL* ListFind(SL* pHead, STDataType x)//查找
{
	SL* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->a == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ListInsert(SL* pos, STDataType x)//插入
{
	SL* newnode = Bylist(x);
	SL* end = pos->prve;
	end->next = newnode;
	newnode->prve = end;
	newnode->next = pos;
	pos->prve = newnode;
}

void ListErase(SL* pos)//删除
{
	SL* cur = pos->prve;
	SL* end = pos->next;
	cur->next = end;
	end->prve = cur;
	free(pos);
	pos - NULL;
}

void Intenode()
{
	// 创建返回链表的头结点.
	SL* plist= ListCreate();


//	 双向链表尾插
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

//	 双向链表尾删
	ListPopBack(plist);

//	 双向链表头插
	ListPushFront(plist, 5);
	ListPushFront(plist, 6);
	ListPushFront(plist, 7);
	ListPushFront(plist, 8);

//	 双向链表头删
	ListPopFront(plist);

//	 双向链表查找
	SL* pos=ListFind(plist, 5);
	if (pos)
	{
	// 双向链表在pos的前面进行插入
		ListInsert(pos, 50);
	}

//	 双向链表删除pos位置的节点
	ListErase(pos);

//	// 双向链表打印
	ListPrint(plist);

//	// 双向链表销毁
	ListDestory(plist);
}

  • 36
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值