C语言数据结构:数据的逻辑结构和存储结构(迭代反转与递归反转)

数据的逻辑结构和存储结构

逻辑结构

数据的逻辑结构,简单地理解,就是指的数据之间的逻辑关系。
数据之间的逻辑关系可细分为三类,“一对一”、“一对多”和“多对多”:
“一对一”:
类似集合这类的数据,每个数据的左侧有且仅有一个数据与其相对
“多对一”:
父子关系是一个典型的“多对一”关系,一个父亲有多个孩子
“多对多”:
教师与学生关系是“多对多”关系,一个老师可以教许多学生,一个学生会有多个老师教导。
1.线性表用于存储具有“一对一”逻辑关系的数据;
2.树结构用于存储具有“一对多”关系的数据;
3.图结构用于存储具有“多对多”关系的数据;
我们通过分析数据之间的逻辑关系决定使用哪一种存储结构。
数据的存储结构,也就是物理结构,指的是数据在物理存储空间上选择集中存放还是分散存放。

数据结构和算法的关系和区别

1.分析问题,从问题中提取出有价值的数据,将其存储;
2.对存储的数据进行处理,最终得出问题的答案;
数据结构负责解决第一个问题,即数据的存储问题。算法,从表面意思来理解,即解决问题的方法。
数据结构用于解决数据存储问题,而算法用于处理和分析数据,它们是完全不同的两类学科。
顺序表和链表的优缺点
顺序表存储数据,需预先申请一整块足够大的存储空间,然后将数据按照次序逐一存储。
链表的存储方式与顺序表截然相反,什么时候存储数据,什么时候才申请存储空间,数据之间的逻辑关系依靠每个数据元素携带的指针维持。
开辟空间的方式
顺序表存储数据实行的是 “一次开辟,永久使用”,即存储数据之前先开辟好足够的存储空间,空间一旦开辟后期无法改变大小,这样的数据结构容易出现缓冲区溢出的危险。
链表则不同,链表存储数据时一次只开辟存储一个节点的物理空间,如果后期需要还可以再申请。
空间利用率
链表对所申请空间的利用率没有顺序表高。
时间复杂度
**顺序表:**1.问题中主要涉及访问元素的操作,元素的插入、删除和移动操作极少;
**链表:**2.问题中主要涉及元素的插入、删除和移动,访问元素的需求很少;

存储结构和存取结构区别详解

存储结构分为顺序存储结构和链式存储结构两类。
存取结构分为:顺序存取结构和随机存取结构。

单链表反转

迭代反转法、递归反转法、就地逆置法和头插法
迭代反转链表iteration_reverse
该算法的实现思想非常直接,就是从当前链表的首元节点开始,一直遍历至链表的最后一个节点,这期间会逐个改变所遍历到的节点的指针域,另其指向前一个节点。
首先我们定义 3 个指针并分别命名为 beg、mid、end
在这里插入图片描述
我们先改变 mid 所指节点的指针域指向,另其和 beg 相同(即改为 NULL),然后再将 3 个指针整体各向后移动一个节点。
在这里插入图片描述
先改变 mid 所指节点的指针域指向,另其和 beg 相同(指向节点 1 ),再将 3 个指针整体各向后移动一个节点。
在这里插入图片描述
先改变 mid 所指节点的指针域指向,另其和 beg 相同(指向节点 2 ),再将 3 个指针整体各向后移动一个节点。
在这里插入图片描述
虽然 mid 指向了原链表最后一个节点,但显然整个反转的操作还差一步,即需要最后修改一次 mid 所指节点的指针域指向,另其和 beg 相同(指向节点 3)
在这里插入图片描述
代码实现迭代反转链表

//迭代反转法,head 为无头节点链表的头指针

link * iteration_reverse(link* head) {
    
    
 if (head == NULL || head->next == NULL) {
    
    
 	return head;
 	}
 	else {
    
    
 	link * beg = NULL;
 	link * mid = head;
 	link * end = head->next;
 	//一直遍历
 	while (1)
 	{
    
    
 	//修改 mid 所指节点的指向
 	mid->next = beg;
 	//此时判断 end 是否为 NULL,如果成立则退出循环
 	if (end == NULL) {
    
    
 	break;
 	}
 	//整体向后移动 3 个指针
 	beg = mid;
 	mid = end;
 	end = end->next;
 	}
 	//最后修改 head 头指针的指向
 	head = mid;
 	return head;
 	}

递归反转链表

和迭代反转法的思想恰好相反,递归反转法的实现思想是从链表的尾节点开始,依次向前遍历,遍历过程依次改变各节点的指向,即另其指向前一个节点。
过程一:
在这里插入图片描述
将 new_head 的指向继续作为函数的返回值,传给上一层的 new_head。
在这里插入图片描述
再退一层,此时 new_head 仍指向节点 4,而 head 退出一层后,指向的是节点 2。在此基础上执行 17、18 行代码,并最终将 new_head 的指向作为函数返回值,继续传给上一层的 new_head。
在这里插入图片描述
再退一层,此时 new_head 仍指向节点 4,而 head 退出一层后,指向的是节点 1。
在这里插入图片描述
head 由节点 1 进入递归,此时 head 的指向又返回到节点 1,整个递归过程结束。显然,以上过程已经实现了链表的反转,新反转链表的头指针为 new_head 。

代码实现递归反转链表:

link* recursive_reverse(link* head) {
    
    
 	//递归的出口
 	if (head == NULL || head->next == NULL)	// 空链或只有一个结点,直接返回头指针
 	{
    
    
 	return head;
 	}
 	else
 	{
    
    
 	//一直递归,找到链表中最后一个节点
 	link *new_head = recursive_reverse(head->next);
 	//当逐层退出时,new_head 的指向都不变,一直指向原链表中最后一个节点;
 	//递归每退出一层,函数中 head 指针的指向都会发生改变,都指向上一个节点。
 	//每退出一层,都需要改变 head->next 节点指针域的指向,同时令 head 所指节点的指针域为 NULL。
 	head->next->next = head;
 	head->next = NULL;
 	//每一层递归结束,都要将新的头指针返回给上一层。由此,即可保证整个递归过程中,能够一直找得到新链表的表头。
 	return new_head;
 	}

头插法反转链表

所谓头插法,是指在原有链表的基础上,依次将位于链表头部的节点摘下,然后采用从头部插入的方式生成一个新链表,则此链表即为原链表的反转版。
创建一个新的空链表
在这里插入图片描述
从原链表中摘除头部节点 1,并以头部插入的方式将该节点添加到新链表中。
在这里插入图片描述
从原链表中摘除头部节点 2,以头部插入的方式将该节点添加到新链表中。
在这里插入图片描述
继续重复以上工作,先后将节点 3、4 从原链表中摘除,并以头部插入的方式添加到新链表中。
在这里插入图片描述
由此,就实现了对原链表的反转,新反转链表的头指针为 new_head。
代码实现头插法反转链表:

link * head_reverse(link * head) {
    
    
 	link * new_head = NULL;
 	link * temp = NULL;
 	if (head == NULL || head->next == NULL) {
    
    
 	return head;
 	}
 	while (head != NULL)
 	{
    
    
 	temp = head;
 	// 将 temp 从 head 中 摘 除
 	head = head->next;
 	//将 temp 插入到 new_head 的头部
 	temp->next = new_head;
 	new_head = temp;
 	}
 	return new_head;

就地逆置法反转链表

就地逆置法和头插法的实现思想类似,唯一的区别在于,头插法是通过建立一个新链表实现的,而就地逆置法则是直接对原链表做修改,从而实现将原链表反转。
初始状态下,令 beg 指向第一个节点,end 指向 beg->next
在这里插入图片描述
将 end 所指节点 2 从链表上摘除,然后再添加至当前链表的头部。
在这里插入图片描述
将 end 指向 beg->next,然后将 end 所指节点 3 从链表摘除,再添加到当前链表的头部
在这里插入图片描述
将 end 指向 beg->next,再将 end 所示节点 4 从链表摘除,并添加到当前链表的头部。
在这里插入图片描述
代码实现就地逆置法反转链表:

link * local_reverse(link * head) {
    
    
 	link * beg = NULL;
 	link * end = NULL;
 	if (head == NULL || head->next == NULL) {
    
    
 	return head;
 	}
 	beg = head;
 	end = head->next;
 	while (end != NULL) {
    
    
 	//将 end 从链表中摘除
 	beg->next = end->next;
 	//将 end 移动至链表头
 	end->next = head;
 	head = end;
 	//调整 end 的指向,另其指向 beg 后的一个节点,为反转下一个节点做准备
 	end = beg->next;
 	}
 	return head;

猜你喜欢

转载自blog.csdn.net/qq_43332010/article/details/120810102
今日推荐