当前位置: 首页 > news >正文

《嵌入式数据结构笔记(六):二叉树》

1. ​​树数据结构的基本定义和属性​

树是一种重要的非线性数据结构,用于表示层次关系。

  • ​基本定义​​:树是由 n(n ≥ 0)个结点组成的有限集合。当 n = 0 时,称为空树;当 n > 0 时,树必须满足两个条件:
    • 有且仅有一个特定的根结点(root node)。
    • 当 n > 1 时,其余结点可划分为 m(m > 0)个互不相交的子集 T1, T2, ..., Tm,每个子集本身又是一棵树,称为子树(subtree)。
  • ​结点属性​​:
    • ​结点的度(degree)​​:指一个结点拥有的子树个数。例如,一个结点有 3 个子树,则其度为 3。
    • ​叶结点(leaf node)​​:度为 0 的结点,即没有子树的结点。
    • ​分支结点(branch node)​​:度不为 0 的结点,即至少有一个子树的结点。
  • ​树的整体属性​​:
    • ​树的度数​​:指树中所有结点的度的最大值。例如,如果所有结点度的最大值为 3,则树的度数为 3。
    • ​树的深度或高度​​:从根结点开始计算,根所在层为第 1 层,其子结点为第 2 层,依此类推。深度反映了树的层级结构。
  • ​树的性质强调​​:树的结构强调“互不相交”的子集,确保每个结点只属于一个父结点,避免循环或重复。

2. ​​树的存储结构​

  • ​顺序结构​​:将树结点存储在连续的内存位置(如数组),通过索引表示父子关系。优点是访问快速,但插入/删除操作可能低效。
  • ​链式结构​​:使用指针或引用来链接结点(如链表),每个结点包含数据域和指向子树的指针。优点是灵活,适用于动态树结构。


3. ​​二叉树的定义和类型​

二叉树是树的特例,具有严格的子树顺序规则。

  • ​基本定义​​:二叉树是 n 个结点的有限集合,它要么为空树,要么由一个根结点和两棵互不相交的左子树与右子树组成。关键特点包括:
    • 每个结点最多有两个子树(左子树和右子树)。
    • 左子树和右子树有固定顺序,不能颠倒(例如,交换左右子树会改变树结构)。
    • 如果结点只有一个子树,必须明确指定是左子树或右子树。
  • ​特殊二叉树类型​​:
    • ​斜树(skewed tree)​​:所有结点都只有左子树(左斜树)或只有右子树(右斜树)。结构类似线性链表。
    • ​满二叉树(full binary tree)​​:所有分支结点都有左右子树,且所有叶结点都在同一层上。结点总数达到最大值(2^k - 1,k 为深度)。
    • ​完全二叉树(complete binary tree)​​:对树按层序编号(根为 1),每个结点的编号与同深度的满二叉树中对应结点位置相同。不完全二叉树在编号上会有空缺。

4. ​​二叉树的数学特性和遍历方法​

  • ​数学特性​​:
    • 第 i 层(i ≥ 1)最多有 2^(i-1) 个结点。
    • 深度为 k 的二叉树(k ≥ 1)最多有 2^k - 1 个结点。
    • 关键公式:对于任意二叉树,叶子结点数(n0)和度为 2 的结点数(n2)满足 n0 = n2 + 1。
    • 对于有 n 个结点的完全二叉树,深度为 (log₂(n)) + 1。
  • ​遍历方法​​:遍历是访问树中所有结点的过程,三种主要顺序:
    • ​前序遍历(preorder traversal)​​:顺序为“根-左-右”。先访问根结点,然后递归访问左子树,最后右子树。
    • ​中序遍历(inorder traversal)​​:顺序为“左-根-右”。从根开始但不先访问根;先递归访问左子树,再访问根,最后右子树。常用于二叉搜索树(BST)。
    • ​后序遍历(postorder traversal)​​:顺序为“左-右-根”。从根开始但不先访问根;先递归访问左子树,然后右子树,最后根。
    • ​层序遍历(level order traversal)​​:按层从上到下、从左到右访问结点。
      遍历方法强调递归实现,适用于树结构的递归特性。

5. ​​GDB调试工具的常规使用步骤​

GDB(GNU Debugger)的实用指南,用于调试C/C++程序,特别是处理段错误和一般调试:

  • ​调试段错误(segmentation fault)​​:
    1. 编译时添加调试选项:使用 gcc -g *.c 生成带调试信息的可执行文件。
    2. 启动GDB:运行 gdb a.out
    3. 运行程序:在GDB中输入 r(run)命令执行程序。
    4. 重现错误:让程序运行到崩溃点。
    5. 定位错误:输入 wherebt 命令显示调用栈,找出段错误发生的具体位置(如文件和行号)。
  • ​一般调试流程​​:
    1. 设置断点:使用 b 命令(break),例如 b fun.c:36 在文件 fun.c 的第 36 行设置断点,或 b myfun 在函数 myfun 入口处设断点。
    2. 运行程序:输入 r 开始执行,程序会在断点处暂停。
    3. 单步执行:
      • n(next):步过,执行下一行代码(如果遇到函数,不进入函数内部)。
      • s(step):步入,执行下一行代码(如果遇到函数,进入函数内部)。
    4. 查看变量:使用 p(print)命令,例如 p a 显示变量 a 的值,或 p *ptr 显示指针 ptr 指向的数据。display a 可持续显示变量变化。
    5. 控制执行:
      • c(continue):从当前断点继续运行,直到下一个断点或程序结束。
      • return:强制从当前函数返回调用处。
    6. 辅助命令:set print elements 300 设置字符串显示长度(避免截断)。
  • ​关键优势​​:GDB 能通过 where 命令追踪函数调用栈,帮助快速定位内存错误或逻辑问题。

6. ​​希尔排序算法​

  • ​算法原理​​:希尔排序通过分组比较和插入来优化排序。它使用一个“间隙”(gap)序列,初始 gap 为数组长度的一半,逐步减小 gap 至 1(此时等价于插入排序)。核心是减少逆序对,提升效率。
  • ​代码实现​​:
    void shell_sort(int a[], int len) 
    {for (int gap = len / 2; gap > 0; gap /= 2) {  // gap 从 len/2 开始,每次减半for (int i = gap; i < len; ++i) // 从 gap 位置开始遍历{         int temp = a[i];                      // 保存当前元素int j = i;while (j >= gap && a[j - gap] > temp) { // 比较并移动元素a[j] = a[j - gap];                // 将较大元素后移j -= gap;                         // 跳转到前一个 gap 位置}a[j] = temp;                          // 插入 temp 到正确位置}}
    }
  • ​关键步骤​​:
    1. ​外层循环​​:控制 gap 值(gap = len/2, gap/2, ..., 1)。
    2. ​内层循环​​:对每个 gap 组进行插入排序。从索引 gap 开始,将元素与同组前驱比较,移动元素以保持有序。
    3. ​效率​​:希尔排序平均时间复杂度为 O(n log n),优于简单插入排序 O(n²),适用于中等规模数据。
  • ​应用场景​​:常用于嵌入式系统或内存受限环境,因代码简洁高效。

代码实现:

1.创建数

2.三种遍历方法

3.以队列形式遍历

4..摧毁树

头文件

#ifndef _TREE_H_
#define TREE_H_typedef char DATATYPE;
typedef struct BiTNode /* 结点结构 */
{DATATYPE data;                   /* 结点数据 */struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode;//定义队列节点结构
typedef struct QueueNode 
{// 指向树节点的指针BiTNode *treeNode;// 指向下一个队列节点struct QueueNode *next;
} QueueNode;//定义队列结构
typedef struct Queue 
{// 队列头部QueueNode *head;// 队列尾部QueueNode *tail;
} Queue;extern void CreateTree(BiTNode **root);
extern void PreOrderTraverse(BiTNode *root);
extern void InOrderTraverse(BiTNode *root);
extern void PostOrderTraverse(BiTNode *root);
extern void DestroyTree(BiTNode *root); extern int isQueueEmpty(Queue *queue);
extern void enqueue(Queue *queue, BiTNode *treeNode);
extern BiTNode* dequeue(Queue *queue);
extern void each_of_tree(BiTNode *root);
extern void initQueue(Queue *queue);
#endif

函数

#include <stdio.h>
#include "tree.h"
#include <stdlib.h>char data[] = "Abd#g###ce#h##fi###";
int ind = 0;void CreateTree(BiTNode **root)
{char c = data[ind++];if ('#' == c){*root = NULL;return;}else{*root = malloc(sizeof(BiTNode));if (NULL == *root)               /*判断申请是否成功*/{printf("malloc error\n");*root = NULL;return;}  (*root)->data = c;CreateTree(&(*root)->lchild);    /*左*/CreateTree(&(*root)->rchild);    /*右*/}return;
}
//根左右(前序)
void PreOrderTraverse(BiTNode *root)
{if (NULL == root){return;}else{printf("%c", root->data);PreOrderTraverse(root->lchild);PreOrderTraverse(root->rchild);}return;
}
//左根右(中序)
void InOrderTraverse(BiTNode *root)
{if (NULL == root){return;}InOrderTraverse(root->lchild);printf("%c", root->data);InOrderTraverse(root->rchild);return;
}
//左右根(后序)
void PostOrderTraverse(BiTNode *root)
{if (NULL == root){return;}PostOrderTraverse(root->lchild);PostOrderTraverse(root->rchild);printf("%c", root->data);return;
}
void DestroyTree(BiTNode *root)
{if (NULL == root){return;}DestroyTree(root->lchild);DestroyTree(root->rchild);free(root);root = NULL;return;
}// 初始化队列
void initQueue(Queue *queue) 
{queue->head = queue->tail = NULL;
}//  判断队列是否为空
int isQueueEmpty(Queue *queue) 
{return queue->head == NULL;
}// 入队
void enqueue(Queue *queue, BiTNode *treeNode) 
{QueueNode *newNode = malloc(sizeof(QueueNode));newNode->treeNode = treeNode;newNode->next = NULL;if (isQueueEmpty(queue)) {queue->head = queue->tail = newNode;} else {queue->tail->next = newNode;queue->tail = newNode;}
}// 出队
BiTNode* dequeue(Queue *queue) 
{if (isQueueEmpty(queue)) {return NULL;}QueueNode *temp = queue->head;BiTNode *treeNode = temp->treeNode;queue->head = queue->head->next;if (queue->head == NULL) {queue->tail = NULL;}free(temp);return treeNode;
}// 层序遍历
void each_of_tree(BiTNode *root) 
{if (root == NULL) {return;}//定义对联Queue queue;// 初始化队列initQueue(&queue);// 根节点入队enqueue(&queue, root);while (!isQueueEmpty(&queue)) {// 出队BiTNode *current = dequeue(&queue);//访问节点printf("%c \n", current->data);// 左子节点入队if (current->lchild != NULL) {enqueue(&queue, current->lchild);}// 右子节点入队if (current->rchild != NULL) {enqueue(&queue, current->rchild);}}
}

主函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tree.h"int main(int argc, char **argv)
{BiTNode *root;CreateTree(&root);PreOrderTraverse(root);printf("\n");InOrderTraverse(root);printf("\n");PostOrderTraverse(root);printf("\n");DestroyTree(root);root = NULL;each_of_tree(root);return 0;
}

7. ​​整体总结​

  • ​数据结构核心​​:树和二叉树的定义、属性、存储及遍历是重点,强调树的结构特性(如度、深度)和二叉树的具体规则(如左右子树顺序)。数学特性(如结点数量公式)和遍历方法(前序、中序、后序)为算法实现奠定基础。
  • ​实用工具​​:GDB 调试部分提供 step-by-step 指南,解决段错误和一般调试问题,突出命令如 wherebp 的重要性。
  • ​算法应用​​:希尔排序作为高效排序算法,以代码示例展示其 gap 策略和插入优化。
http://www.dtcms.com/a/321895.html

相关文章:

  • 【C语言:一个整数分离出每一位数后求重新组合后接近于某个数的整数】
  • STM32传感器模块编程实践(十三)人脸识别模块简介及驱动
  • Redis缓存击穿、穿透雪崩
  • ADB 命令执行模块开发:双模式(普通模式Shell交互模式)实现、线程安全与资源管理优化
  • Linux系统层IO
  • Node.js 》》数据验证 Joi 、express-joi
  • 【数字图像处理系列笔记】Ch06:图像压缩
  • 数据结构5-哈希表
  • 板卡如何安装在主机系统(刀片服务器或计算节点)
  • Linux之shell脚本入门
  • Unity基于Recoder的API写了一个随时录屏的工具
  • http状态码403,404,500等是什么意思?
  • Cursor CLI 来了,准备 Build anything
  • Sum of Three Values(sorting and searching)
  • 全面了解selenium
  • RSA非对称加密
  • 除了腾讯会议,私有化有哪些选择?
  • 安科瑞EMS3.0源网荷储一体化解决方案 全面助力零碳园区建设
  • FreeSWITCH parse-all-invite-headers
  • 记一次lombok链式调用引发EasyExcel兼容性的问题
  • 记录网站突然报错503
  • 第六章第四节 PWM驱动LED呼吸灯 PWM驱动舵机 PWM驱动直流电机
  • 计算机网络:到底什么是可变长子网掩码VLSM?
  • win11中Qt5.14.0+msvc2019+opencv4.9配置
  • 全方位无限随机地图实现指南
  • 模块 PCB 技术在未来通信领域的创新突破方向
  • Docker 创建镜像错误记录
  • Java技术栈/面试题合集(21)-Docker篇
  • 如何动态执行 JS 脚本
  • 揭秘Java synchronize:轻量级锁升级与偏向锁