C语言数据结构双向链表之温故而知新

简介: 单向链表:http://blog.csdn.net/morixinguan/article/details/77756216单向链表理解了,那双向就非常简单了,没什么好说的,看图:双链表的引入是为了解决单链表的不足:(1)双链表可以往前遍历,也可以往后遍历,具有两个方向双链表的节点 = 有效...

单向链表:http://blog.csdn.net/morixinguan/article/details/77756216

单向链表理解了,那双向就非常简单了,没什么好说的,看图:

双链表的引入是为了解决单链表的不足:
(1)双链表可以往前遍历,也可以往后遍历,具有两个方向
双链表的节点 = 有效数据 + 两个指针(分别指向前一个节点和后一个节点)
双向链表的图形结构描述:


可以用一个简单的数据结构来描述上面的这个图:

struct double_list					struct double_list
{							{
	数据域;                            ep :------->     int data ;
	指针域(前向指针) ;  				    struct double_list *prev ;
	指针域(后向指针) ;				    struct double_list *next ;
};							};
2、双向链表的创建

struct list *create_node(int data) ;
创建步骤(与单链表类似,就是多了一个指针):
(1)给节点申请空间:
   ep : struct double_list *p = malloc(sizeof(struct double_list));
(2)初始化数据域
   ep : p->data = data ;
(3)初始化指针域
   ep : p->prev = NULL ; 
	    p->next = NULL ;
3、双向链表的尾插
双向链表尾插节点的函数可以定义如下:
void double_list_tail_insert(struct double_list *header , struct double_list *new) ;
尾插图示操作:


尾插的步骤:

(1)找到链表的尾节点
   ep : 和单链表差不多
	    DL *p = header ;
		while(NULL != p->next)
			p = p->next ;
(2)将新的节点连接到尾节点的后面成为新的节点
   1.原来的next指针指向新节点的首地址。			p->next = new ;
   2.新节点的prev指针指向原来的尾节点的首地址。 new->prev = p;
4、双向链表的头插
双向链表头插节点的函数可以定义如下:
void double_list_top_insert(struct double_list *header , struct double_list *new) ;


5、双向链表的遍历

5.1 正向遍历
	void double_list_for_each(DL *header)
	步骤:和单链表完全一致,没什么好写的。
	
	
5.2 反向遍历
	void double_list_for_each_nx(DL *header)
	步骤:(1)和单链表一样,先循环找到最后一个节点的地址
	      (2)再依靠prev指针循环往前移动
			 2.1 先打印最后一个数据  ep : printf("%d ",p->data);
			 2.2 向前循环遍历		 ep : p = p->prev ;
			 
			 判断条件:header->prev != p->prev,
			 header保存的是头节点的地址,
			 header->prev保存的是头节点的prev的地址,
			 header->next保存的是头节点的next的地址,
			 头节点在创建的时候:
			 header->prev = NULL ;
			 header->next = NULL ;
			 所以这个条件这样写header->prev = NULL也是对的。
6、双向链表节点的删除


假设需要删除节点1:	
	首先:
	(1)获取当前节点的地址: 
		ep : p = header;
	(2)遍历所有的节点,找到要删除的节点:
		ep : 
		while(NULL != p->next)
		{
			p = p->next ;
			if(p->data == data)
			{
			
			}
		}
	(3)找到要删除的数据以后,需要做判断,判断两种情况,这和单链表差不多
	3.1 如果找到当前节点的下一个节点不为空
	3.1.1	
		那就把当前节点的prev节点指向要删除的这个节点的prev
		因为当前的prev节点保存的是要删除的上一个节点的指针 
		p->next->prev = p->prev ;
	3.1.2	
		然后将当前节点的prev指针(也就是上一个节点的指针)指向当前节点(要删除的)的下一个节点:
		p->prev->next = p->next ;
	3.1.3	
		最后释放删除指针的空间:
		free(p);
		
	3.2 如果找到当前节点的下一个节点为空
	3.2.1   
	直接把当前指针(要删除的节点)的prev指针(保存着当前指针的上一个节点的地址)的下一个next指针设置为空。
	p->prev->next = NULL ;
	3.2.2
	将删除的指针的空间释放:
	free(p);
看来,双链表学起来比单链表容易多了!确实啊,多了一个方向,操作起来就更加容易了,但是多了一个方向,一维多了一个指针,相比增加了一定的复杂度,但是,只要牢记prev指针和next指针的指向,那么,手画一图,代码即可写出!

下面给一个案例实现一下双向链表:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//创建一个双链表的数据结构
typedef struct __double_list
{
	int data ;
	struct __double_list *prev ;
	struct __double_list *next ;
}DL ; 

//创建双向链表并插入一个节点 
DL *create_dl_node(int data)
{
	DL *p = malloc(sizeof(DL));
	if(NULL == p)
	{
		printf("create dl node fair!\n");
		return NULL ;
	}
	//初始化数据 
	p->data = data ;
	//初始化指针 
	p->next = NULL ;
	p->prev = NULL ;
}

//双向链表的尾插 
void double_list_tail_insert(DL *header , DL *new)
{
	//取得当前节点的地址 
	DL *p = header ;
	//找到链表的尾节点 
	while(NULL != p->next)
	{
		//找不到接着找 
		p = p->next ;
	}
	//找到了尾节点,指向新节点的首地址 
	p->next = new ;
	//新节点的prev指针指向原来的尾节点的首地址。
	new->prev = p;
}

//双向链表的头插(也就是插在两个节点之间的插入方式)
void double_list_top_insert(DL *header , DL *new)
{
	//新节点的next指针指向原来的节点一的地址
	new->next = header->next ; 
	//判断当前节点的下一个节点的地址是否为空
	if(NULL != header->next) 
		header->next->prev = new ; //节点1的prev指针指向新节点的地址 
	header->next = new ;
	new->prev = header ;
}

//双向链表的正向遍历 
void double_list_for_each(DL *header)
{
	DL *p = header ;
	while(NULL != p->next)
	{
		p = p->next ;
		printf("%d ",p->data);
	}
}

//双向链表的反向遍历 
void double_list_for_each_nx(DL *header)
{
	DL *p = header ;
	//先找到尾节点
	while(NULL != p->next)
	{
		p = p->next ;	
	} 
	//已经找到了尾节点,向前遍历,注意,遍历到头节点之前
	//限制条件: != 头结点 
	while(NULL != p->prev)
	{
		printf("%d ",p->data);
		p = p->prev ;
	}
}

//双向链表节点的删除
int double_list_delete_node(DL *header , int data)
{
	//取得当前节点 
	DL *p = header;
	//遍历所有的节点 
	while(NULL != p->next)
	{
		p = p->next ;
		//找到了对应要删除的数据了 
		if(p->data == data)
		{
			//一样存在两种情况
			//(1)当前节点的下一个节点不为空
			if(p->next != NULL)
			{
				//那就把当前节点的prev节点指向要删除的这个节点的prev
				//因为当前的prev节点保存的是要删除的上一个节点的指针 
				p->next->prev = p->prev ;
				//还要指定它的next节点 
				p->prev->next = p->next ;
				free(p);
			}
			//(2)当前节点的下一个节点为空 
			else
			{
				//把 
				p->prev->next = NULL ;
				free(p); 
			}
			return 0 ;
		}
	}
	printf("\n没有找到对应要删除的节点,或者节点已经被删除!\n");
	return -1 ;	
} 

int main(void)
{
	int i ;
	DL *header = create_dl_node(0);
	for(i = 0 ; i < 10 ; i++)
	{
		//双向链表的尾插 
		//double_list_tail_insert(header,create_dl_node(i));
		//双向链表的头插 
		double_list_top_insert(header,create_dl_node(i));
	}
	//双向链表的正向遍历 
	printf("双向链表的正向遍历:");
	double_list_delete_node(header,5);
	double_list_for_each(header);
//	double_list_delete_node(header,9);
	double_list_delete_node(header,5);
	putchar('\n');
	printf("双向链表的反向遍历:");
	double_list_for_each_nx(header);
	return 0 ;
}
运行结果:




目录
相关文章
|
10月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
377 1
|
7月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
184 30
|
7月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
288 25
|
7月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
8月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
8月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
315 5
|
9月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
9月前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
2776 6
|
10月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
262 5
|
10月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
245 1