数据结构(二叉树)

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

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

基本定义:

树是由 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 层,依此类推,深度反映了树的层级结构。

树的性质强调:

树的结构强调"互不相交"的子集,确保每个结点只属于一个父结点,避免循环或重复。

树的存储结构

顺序结构:

将树结点存储在连续的内存位置(如数组),通过索引表示父子关系。优点是访问快速,但插入或删除操作可能较低效。

链式结构:

使用指针或引用来链接结点(如链表),每个结点包含数据域和指向子树的指针。优点是灵活,适用于动态树结构。

二叉树的定义和类型

基本定义:

二叉树是 n 个结点的有限集合,它要么为空树,要么由一个根结点和两棵互不相交的左子树与右子树组成。关键特点包括:每个结点最多有两个子树(左子树和右子树);左子树和右子树有固定顺序,不能颠倒;如果结点只有一个子树,必须明确指定是左子树或右子树。

特殊二叉树类型:

斜树(skewed tree)是所有结点都只有左子树(左斜树)或只有右子树(右斜树),结构类似线性链表。满二叉树(full binary tree)是所有分支结点都有左右子树,且所有叶结点都在同一层上,结点总数达到最大值(2^k - 1,k 为深度)。完全二叉树(complete binary tree)是指对树按层序编号,每个结点的编号与同深度的满二叉树中对应结点位置相同,不完全二叉树在编号上会有空缺。

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

数学特性:

第 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)按"左-根-右"的顺序访问,常用于二叉搜索树;后序遍历(postorder traversal)按"左-右-根"的顺序访问;层序遍历(level order traversal)按层从上到下、从左到右访问结点。遍历方法通常采用递归实现,契合树的递归结构特点。

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

调试段错误(segmentation fault):编译时添加调试选项(如使用 gcc -g *.c)生成带调试信息的可执行文件。运行 gdb a.out 启动调试器,在 GDB 中输入 r(run)运行程序,重现错误。使用 where 或 bt 查看调用栈,定位段错误位置。

一般调试流程:

使用 b 命令设置断点(如 b fun.c:36 或 b myfun),输入 r 执行程序并在断点处暂停。使用 n(next)单步执行或 s(step)步入函数,使用 p 命令查看变量值(如 p a 或 p *ptr)。使用 c(continue)继续执行,使用 return 强制返回当前函数。使用 set print elements 可调整字符串显示长度避免被截断。

关键优势:

GDB 能通过 where 命令追踪函数调用栈,帮助快速定位内存错误或逻辑问题,适用于调试复杂的结构体或指针操作。

希尔排序算法

算法原理:希尔排序通过分组比较和插入来优化排序。它使用一个"间隙"(gap)序列,初始 gap 为数组长度的一半,逐步减小至 1,此时等价于插入排序。其核心在于减少逆序对,从而提升效率。

代码实现:

复制代码
void shell_sort(int a[], int len) {
    for (int gap = len / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < len; ++i) {
            int temp = a[i];
            int j = i;
            while (j >= gap && a[j - gap] > temp) {
                a[j] = a[j - gap];
                j -= gap;
            }
            a[j] = temp;
        }
    }
}

关键步骤:

外层循环控制 gap 的变化,内层循环对每个 gap 分组进行插入排序。元素依 gap 步长回退比较并插入到正确位置。

效率:

希尔排序的平均时间复杂度为 O(n log n),优于简单插入排序 O(n²),在实际应用中表现较好。

应用场景:

常用于嵌入式系统或内存受限环境,因其代码简洁且高效。

数据结构核心在于掌握树和二叉树的定义、属性、存储方式及遍历方法,强调结构特性如度和深度,二叉树中特殊类型也需理解。遍历是操作的基础,适用于递归实现。

调试工具 GDB 提供高效定位段错误和运行时错误的手段,掌握基本命令如 run、break、step、print 等是调试复杂程序的关键。