数据结构之双向链表(及循环双向链表)

数据结构之双向链表

双向链表

双向链表和我之前介绍的单链表区别不是很大,只是多了一个prev结点指针让它可以指向上一个结点。

接下来介绍结点类型,和头结点类型(与单链表中不太一样,重温后发现这样子应该会更好)

结点类型

如图,结点由一个数值领,两个指针域组成。

data为数值域

next*为指针域(与单链表的next*一个作用)

prev*为指针域(具体继续往下看动图看完一目了然)

在这里插入图片描述

头结点类型

这里,和我写的单链表中不太一样,请各位仔细看,这是另一种结构体类型

头结点类型由一个int类型,两个指针类型组成

node_num: int 类型 用于记录链表中的结点个数

first* :Node* 类型 用于指向链表中的第一个结点

last* :Node* 类型 用于指向链表中的最后一个结点

如下图:

在这里插入图片描述

双向链表

头结点的first指向链表的首节点

头结点的last指向链表的尾结点

头结点的node_num记录链表的结点个数

链表拥有next指针指向下一个结点

拥有prev指向上一个结点

如下图:

在这里插入图片描述

这时我们观察得到,双向链表比单链表操作会复杂一些,

但是,双向链表的优势,

就是不再需要用一个指针记录一个结点的前一个结点进行插入和删除操作,

它本身就能很方便的实现插入和删除操作。

接下来,直接进入代码环节!(作者没有写插入操作,可以按照该代码,自己尝试写一下,如有需要评论区可以说一下,我评论区补发)

代码:

.h文件

#ifndef __LINKEDLIST_H__
#define __LINKEDLIST_H__

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

typedef int Elemtype;//方便后期修改类型

typedef struct node//结点结构体
{
	Elemtype data;
	struct node* next;	//指向后一个结点的指针
	struct node* prev;	//指向前一个结点的指针
}Node;

typedef struct head//头结点结构体
{
	Node* first;	//永远指向链表的第一个结点
	Node* last;		//永远指向最后一个结点
	int node_num;	//记录链表结点的数目
	//可以添加自己所需要的东西.....
}Head;	//头结点的类型

Node* create_node();	//创建结点

Head* create_head();	//创建头结点

Head* tail_insert();	//尾插法创建双向链表

Head* head_insert();	//头插法创建双向链表

Head* insert_sort();	//创建升序双向链表

Head* delete_node_x(Head*, Elemtype);		//删除第一个数据域为x的结点

Head* delete_all_x(Head*, Elemtype);		//删除所有数据域为x的结点

void printf_list(Head* h);	//打印双向链表

void reverse_printf_list(Head* h);	//反向打印双向链表

#endif // !__LINKEDLIST_H__

.c文件

创建结点
/*
* 创建结点
*/
Node* create_node()
{
	Node* p = (Node*)malloc(sizeof(Node));
	p->next = NULL;
	p->prev = NULL;
	return p;
}
创建头结点
/*
* 创建头结点
*/
Head* create_head()
{
	Head* head = (Head*)malloc(sizeof(Head));
	head->first = NULL;
	head->last = NULL;
	head->node_num = 0;
	return head;
}
尾插法

在使用该函数时,输入0结束,跳出循环,不再加入结点,这个可以按自行需求自行修改

/*
* 尾插法
* 参数:void
* 返回值:Head* 该双向链表的头结点
*/
Head* tail_insert()
{
	Head *head = create_head();
	Elemtype d;
	while (1)
	{
		
		scanf_s("%d", &d);
		if (d == 0)
			break;
		Node* p = create_node();
		p->data = d;
		if (head->first == NULL)//从无到有
		{
			head->first = p;
			head->last = p;
		}
		else//从少到多
		{
			head->last->next = p;
			p->prev = head->last;
			head->last = p;	
		}
		head->node_num++;//每插入一个,结点记数+1
	}
	return head;
}

在这里插入图片描述

如动图,即为尾插法,也就是单链表中,我为介绍介绍的方法,

这里就不再介绍头插法了,与单链表中大体一样,只不过多一个指针

例子:

输入:1,2,3,4,5,0(0为输入停止的标志,代码中可做修改)

输出:1->2->3->4->5(输出该单链表,需要打印看值)

头插法

在使用该函数时,输入0结束,跳出循环,不再加入结点,这个可以按自行需求自行修改

/*
* 头插法
* 参数:void
* 返回值:Head* 该双向链表的头结点
*/
Head* head_insert()
{
	Head* head = create_head();
	Elemtype d;
	while (1)
	{

		scanf_s("%d", &d);
		if (d == 0)
			break;
		Node* p = create_node();
		p->data = d;
		if (head->first == NULL)//从无到有
		{
			head->first = p;
			head->last = p;
		}
		else//从少到多
		{
			head->first->prev = p;
			p->next = head->first;
			head->first = p;
		}
		head->node_num++;//每插入一个,结点记数+1
	}
	return head;
}

例子:

输入:1,2,3,4,5,0(0为输入停止的标志,代码中可做修改)

输出:5->4->3->2->1(输出该单链表,需要打印看值)

创建升序双向链表

在使用该函数时,输入0结束,跳出循环,不再加入结点,这个可以按自行需求自行修改

/*
* 构建升序双向链表
* 参数:void
* 返回值:Head* 该双向链表的头结点
*/
Head* insert_sort()
{
	Head* head = create_head();
	Elemtype d;
	while (1)
	{
		scanf_s("%d", &d);
		if (d == 0)
			break;
		Node* p = create_node();
		p->data = d;
		if (head->first == NULL)//从无到有
		{
			head->first = p;
			head->last = p;
		}
		else//从少到多
		{
			Node* front = head->first;
			Node* behind = head->first;
			while (behind)//找位置
			{
				if (behind->data <= d)//比behind->data大往右边走
				{
					front = behind;
					behind = behind->next;
				}
				else//比behind小,插入,front和behind的中间
				{
					break;
				}
			}
			if (p->data < head->first->data)//最前面
			{
				p->next = head->first;
				head->first->prev = p;
				head->first = p;
			}
			else//中间及其后面
			{
				p->next = behind;
				p->prev = front;
				front->next = p;
				if (behind == NULL)//最后面的情况
				{
					head->last = p;
				}
				else
					behind->prev = p;
			}
		}
		head->node_num++;//每插入一个,结点记数+1
	}
	return head;
}

例子:

输入:3,5,2,1,6,8,0(输入结束标志)

输出:1->2->3->5->6->8(输出该单链表,可用打印出来查看)

删除第一个数据域为x的结点
/*
* 删除第一个数据域为x的结点
* 参数:第一个参数:Head* 类型,传入的是头结点
* 第二个参数:Elemtype 类型,传入的是x,要删除的数据域为x的第一个结点
* 返回值:Head* 该双向链表的头结点
*/
Head* delete_node_x(Head* head,Elemtype x)
{
	Node* p = head->first;	//待删除的结点p,若没找到则不需要删除
	while (p)//找位置
	{
		if (p->data == x)
			break;
		else
			p = p->next;
	}
	if(p==NULL)//不存在数据域为x的结点
		return head;
	else//存在数据域为x的结点,且此时为p
	{
		if (p == head->first)//删除第一个结点
		{
			head->first = p->next;
			p->next = NULL;
			p->prev = NULL;
			free(p);
		}
		else
		{
			if (p == head->last)//删除最后一个
			{
				head->last = p->prev;
				p->prev->next = p->next;

			}
			else//输出中间
			{
				p->prev->next = p->next;
				p->next->prev = p->prev;	
			}
			p->next = NULL;
			p->prev = NULL;
			free(p);
		}
		head->node_num--;
	}
	return head;
}

例子:

输入:1->2->3->4->5(单链表)

输出:1->2->4->5(单链表,需打印查看值)

删除所有数据域为x的结点
/*
* 删除所有数据域为x的结点
* 参数:第一个参数:Head* 类型,传入的是头结点
* 第二个参数:Elemtype 类型,传入的是x,要删除的数据域为x的结点
* 返回值:Head* 该双向链表的头结点
*/
Head* delete_all_x(Head* head, Elemtype x)
{
	Node* front = head->first;	//待删除的结点p,若没找到则不需要删除
	Node* behind = head->first;
	while (behind)
	{
		if (behind->data == x)//若是behind->data==x,则需要删除behind
		{
			if (behind == head->first)//删除的是首结点
			{
				head->first = behind->next;
				if (head->first == NULL)
				{
					head->last = head->first;
				}
				else
					head->first->prev = NULL;
				behind->next = NULL;
				behind->prev = NULL;
				free(behind);
				behind = head->first;
			}
			else//删除的是中间及其尾结点
			{
				
				front->next = behind->next;
				if (behind == head->last)//删除的为尾结点
				{
					head->last = front;
				}
				else//删除的为中间结点
				{
					behind->next->prev = front;
				}
				behind->next = NULL;
				behind->prev = NULL;
				free(behind);
				behind = front->next;
			}
			head->node_num--;
		}
		else //不需要删除结点,behind往后移,front为behind的前一位
		{
			front = behind;
			behind = behind->next;
		}
	}
	return head;
}

例子:

输入:3->1->3->2->4->5->3(输入单链表)

输出:1->2->4->5(单链表,需打印查看值)

打印带头结点双向链表

利用next以及head->first来打印

/*
* 打印带头结点双向链表
* 参数:第一个参数:Head *类型,传入头结点
* 返回值:void
*/
void printf_list(Head* h)
{
	if (!h)//代表链表不存在
	{
		printf("链表不存在!\n");
		return;
	}
	if ((h->first == NULL))//代表空链表
	{
		printf("链表为空,无法打印!\n");
		return;
	}
	Node* p = h->first;
	while (p)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	putchar('\n');
	printf("链表结点个数:%d\n", h->node_num);
}
反向打印双向链表

由于双向链表的双向性,不必再写逆置函数(除了特殊原因)多此一举,直接利用prev以及head->last打印即可

/*
* 反向打印双向链表
*/
void reverse_printf_list(Head* h)
{
	if (!h)//代表链表不存在
	{
		printf("链表不存在!\n");
		return;
	}
	if ((h->last == NULL))//代表空链表
	{
		printf("链表为空,无法打印!\n");
		return;
	}
	Node* p = h->last;
	while (p)
	{
		printf("%d ", p->data);
		p = p->prev;
	}
	putchar('\n');
	printf("链表结点个数:%d\n", h->node_num);
}

主函数

#include"linkedlist.h"

int main()
{
	Head* head = tail_insert();	//尾插法创建双向链表
	//Head* head = head_insert();	//头插法创建双向链表
	//Head* head = insert_sort();	//创建升序双向链表
	//delete_node_x(head, 3);	//删除第一个data域为3的结点
	delete_all_x(head, 3);	//删除所有data域为3的结点
	printf_list(head);	//打印双向链表
	//reverse_printf_list(head);	//打印逆序双向链表
	return 0;
}

可自行操作实验。

以上就是一些双向链表的内容,感谢各位的观看,如有问题,请指出,谢谢!
intf("%d ", p->data);
p = p->prev;
}
putchar(‘\n’);
printf(“链表结点个数:%d\n”, h->node_num);
}


### 主函数

```c
#include"linkedlist.h"

int main()
{
	Head* head = tail_insert();	//尾插法创建双向链表
	//Head* head = head_insert();	//头插法创建双向链表
	//Head* head = insert_sort();	//创建升序双向链表
	//delete_node_x(head, 3);	//删除第一个data域为3的结点
	delete_all_x(head, 3);	//删除所有data域为3的结点
	printf_list(head);	//打印双向链表
	//reverse_printf_list(head);	//打印逆序双向链表
	return 0;
}

主函数可按自己的想法自行操作实验。

以上就是一些双向链表的内容,感谢各位的观看,如有问题,请指出,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值