C-数据结构-树状存储的基本实现

/*

理解和记忆递归的关键在于把握递归的本质和函数调用的过程。递归函数在每次调用时会把当前状态压入调用栈,直到满足终止条件后开始回溯。理解基准条件和递归步骤:每个递归函数都需要有基准条件(如节点为空时返回),并在每一步中递归调用自身处理子问题。

*/

main.c

c 复制代码
#include<stdio.h>
#include<stdlib.h>

#define NAMESIZE	32

struct score_st
{
	int id;
	char name[NAMESIZE];
	int math;
	int chinese;
};

struct node_st
{
	struct score_st data;
	struct node_st *l,*r;
};

print_s(struct score_st *d)
{
	printf("%d %s %d %d\n",d->id,d->d.name,d->math,d->chinese);
}


int insert(struct node_st **root,struct score_st *data)
{
	struct node_st *node;
	if(*root == NULL)
	{
		node = malloc(sizeof(*node));
		if(node == NULL)
			return -1;
		node->data = *data;
		node->l = NULL;
		node->r = NULL;
		*root = node;
		return 0;
	}
	if(data->id <= (*root)->data.id)
		return insert(&(*root)->l,data);
	else
		return insert(&(*root)->r,data);
}
struct score_st *find(struct node_st *root,int id)
{
	if(root == NULL)
		return NULL;
	if(id == root->data.id)
		return &root->data;
	if(id < root->data.id)
		return find(root->l,id);
	else
		return find(root->r,id);
}
void draw_(struct node_st *root,in level)
{
	int i;
	if(root == NULL)
		return ;
	draw_(root->t,level+1);
	for(i = 0;i<level;i++)
		printf("    ");
	print_s(&root->data);
	draw_(root->l,level+1);
}
void draw(struct node_st *root)
{
	draw_(root,0);
}

int main()
{
	int arr[] = {1,2,3,7,6,5,9,8,4};
	int i;
	struct score_st tmp,*datap;
	struct node_st *tree = NULL;
	
	for(i = 0;i<sizeof(arr)/sizeof(*arr);i++)
	{
		tmp.id = arr[i];
		snprintf(tmp.name,NAMESIZE,"stu%d",arr[i]);
		tmp.math = rand()%100;
		tmp.chinese = rand()%100;
		
		insert(&tree,&tmp);
	}
	draw(tree);


#if 0
	int tmpid = 2;
	datap = find(tree,tmpid);
	if(datap == NULL)
		printf("can not find the id %d\n",tmpid);
	else
		print_s(datap);
#endif
	exit(0);
}

补充说明1

为了理解二级指针的一级目标,我们需要明确指针和二级指针的概念。

指针和二级指针

  • 指针 :一个指针变量保存了另一个变量的地址。通过指针,我们可以访问或修改这个变量的值。例如,int *p 是一个指向整数的指针变量。
  • 二级指针 :一个二级指针变量保存了另一个指针变量的地址。通过二级指针,我们可以访问或修改这个指针变量的值。例如,int **pp 是一个指向指针变量的指针。

二级指针的一级目标

一级目标是指二级指针所指向的那个指针变量。

举个具体的例子,假设我们有以下定义:

c 复制代码
int a = 10;       // 普通变量
int *p = &a;      // 指向变量 a 的指针
int **pp = &p;    // 指向指针 p 的指针

在这个例子中:

  • p 是一个指向 a 的指针。
  • pp 是一个指向 p 的指针。

我们可以这样理解:

  • pp 保存了 p 的地址。
  • *pp 解析出 pp 所指向的内容,即 p
  • **pp 解析出 *pp 所指向的内容,即 a 的值。
    当然可以!以下是一个图形示例来说明 *aa = b 的含义:

假设:

  • aa 是一个指向指针的指针。
  • a 是一个指针,它最初指向一个地址。
  • b 是另一个指针,指向另一个地址。

初始状态:

+------+    +------+    +------+
|  aa  | -> |  a   | -> |  x   |  (假设 x 是 a 所指向的值)
+------+    +------+    +------+

+------+    +------+
|  b   | -> |  y   |  (假设 y 是 b 所指向的值)
+------+    +------+

执行 *aa = b 后的状态:

+------+    +------+
|  aa  | -> |  b   | -> |  y   |  (aa 现在指向 b,而 b 仍然指向 y)
+------+    +------+

这样做后,aa 所指向的指针 a 不再指向原来的地址,而是指向了 b 所指向的地址。

以下是更详细的图示:

  1. 初始状态:

    aa -> a -> [地址1: x]
    b -> [地址2: y]

  2. 执行 *aa = b 后:

    aa -> b -> [地址2: y]

这意味着,aa 原本指向的指针 a,现在指向了 b,即 a 的地址已经被 b 的地址覆盖。

希望这个图形示例能帮助你更好地理解 *aa = b 的含义。如果还有其他问题,请随时告诉我!

补充说明2

你提到的情况是正确的。A 不出栈的原因是递归调用还没有完成。为了更清楚地解释递归调用的过程,让我们重新逐步分析每一个递归调用和返回的过程,并使用栈来跟踪函数调用。

示例二叉树

假设有如下二叉树:

      A
     / \
    B   C
   / \
  D   E

使用栈模拟执行过程

我们以栈的方式逐步模拟 draw 函数调用 draw_ 的过程。

  1. 初始状态

    • 栈为空。
    • 调用 draw(root),进入 draw_(root, 0)
  2. 处理根节点 A

    • 调用 draw_(A, 0)
    • 栈:[ draw_(A, 0) ]
  3. 递归处理右子树 C

    • 调用 draw_(C, 1)
    • 栈:[ draw_(A, 0), draw_(C, 1) ]
  4. 处理 C 节点

    • 右子树为空,返回。
    • 打印 C(缩进 4 空格)。
    • 左子树为空,返回。
    • 返回 draw_(A, 0)
    • 栈:[ draw_(A, 0) ]

    输出:

        C
    
  5. 打印根节点 A

    • 打印 A(无缩进)。
    • 栈:[ draw_(A, 0) ]

    输出:

        C
    A
    
  6. 递归处理左子树 B

    • 调用 draw_(B, 1)
    • 栈:[ draw_(A, 0), draw_(B, 1) ]
  7. 处理 B 节点

    • 调用 draw_(E, 2)(递归处理右子树)。
    • 栈:[ draw_(A, 0), draw_(B, 1), draw_(E, 2) ]
  8. 处理 E 节点

    • 右子树为空,返回。
    • 打印 E(缩进 8 空格)。
    • 左子树为空,返回。
    • 返回 draw_(B, 1)
    • 栈:[ draw_(A, 0), draw_(B, 1) ]

    输出:

        C
    A
            E
    
  9. 打印 B 节点

    • 打印 B(缩进 4 空格)。
    • 栈:[ draw_(A, 0), draw_(B, 1) ]

    输出:

        C
    A
            E
        B
    
  10. 递归处理左子树 D

    • 调用 draw_(D, 2)
    • 栈:[ draw_(A, 0), draw_(B, 1), draw_(D, 2) ]
  11. 处理 D 节点

    • 右子树为空,返回。
    • 打印 D(缩进 8 空格)。
    • 左子树为空,返回。
    • 返回 draw_(B, 1)
    • 栈:[ draw_(A, 0), draw_(B, 1) ]

    输出:

    C
    A
    E
    B
    D

  12. 完成 B 节点的处理

    • 返回 draw_(A, 0)
    • 栈:[ draw_(A, 0) ]
  13. 完成 A 节点的处理

    • 返回 draw(root)
    • 栈为空。

最终输出

    C
A
        E
    B
        D

在这个过程中,A 不出栈的原因是因为它的递归调用还没有完成。只有当左子树 B 的所有处理完成后,A 才会出栈,回到 draw(root),表示递归完全结束。这样我们可以准确地跟踪每一步的递归调用和返回,理解栈的作用。

希望这个解释能帮助你更好地理解代码的递归逻辑!如果还有其他问题,请随时告诉我。

相关推荐
熬夜学编程的小王14 分钟前
C++类与对象深度解析(一):从抽象到实践的全面入门指南
c++·git·算法
CV工程师小林16 分钟前
【算法】DFS 系列之 穷举/暴搜/深搜/回溯/剪枝(下篇)
数据结构·c++·算法·leetcode·深度优先·剪枝
Dylanioucn19 分钟前
【分布式微服务云原生】掌握 Redis Cluster架构解析、动态扩展原理以及哈希槽分片算法
算法·云原生·架构
繁依Fanyi28 分钟前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
罔闻_spider38 分钟前
爬虫prc技术----小红书爬取解决xs
爬虫·python·算法·机器学习·自然语言处理·中文分词
Themberfue1 小时前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
陈序缘2 小时前
LeetCode讲解篇之322. 零钱兑换
算法·leetcode·职场和发展
-$_$-2 小时前
【LeetCode HOT 100】详细题解之二叉树篇
数据结构·算法·leetcode
大白飞飞2 小时前
力扣203.移除链表元素
算法·leetcode·链表
尘心cx2 小时前
数据结构-栈
数据结构