数据结构——树

一、二叉树

1.1 树
  考虑元素的有限集合,该集合或为空,或使其中一个元素为【root】,其余的元素组成了新的非空有限元素的集合,这种集合结构类型称为。一棵树除了根以外的集合构成了树的子树【subtree】。
  结点具有的子树的个数称为结点的,树中结点的度的最大值称为树的。若结点的度为0,则该节点是一个叶子结点,否则为分支结点。
  树中某个结点的子树的根称为该结点的子结点,该结点是其子结点的父结点。具有同一个父节点的子结点互为兄弟节点
  若结点 n i n_i ni是结点 n i + 1 n_{i+1} ni+1的父节点,则把 n 1 , . . . n k n_1, ...n_k n1,...nk称为 n 1 n_1 n1 n k n_k nk路径,其经过的边的个数称为路径长度
  在树中,如果有一条路径从结点 x x x通过子结点到达结点 y y y,则 x x x y y y祖先结点,而 y y y x x x子孙结点
  树的,也称层数描述了父子结点的层次关系,定义根节点的级为1且某个节点的子结点的级比该结点高1;树中所有节点中,最大的级称为树的高度
  若树的子结点是从左到右有序的,称其为有序树,否则为无序树
  由互不相交的树组成的集合称为森林
  如果两个树的结构相同,称为相似二叉树;若相似二叉树的对应信息也相同,称为等价二叉树

1.2 二叉树
  如果一棵树或为空,或其所有的结点都至多有两颗子树,分别为左子树右子树,该类型的树称为二叉树【Binary Tree】。
  二叉树的度不大于2,且子树有序,即使某个节点仅有一棵子树,也有左右之分。
  当一棵二叉树的所有结点都只有左子树,称其为左斜树;反之,称其为右斜树
  若一颗二叉树的所有分支结点都有两颗子树,且叶子结点都在最后一层,称其为满二叉树。高度为 K K K的满二叉树的结点个数为 2 K − 1 2^K-1 2K1
  对于高度相同的树与满二叉树,将其自顶向下,同一层自左向右的顺序编号,若树的编号与满二叉树的编号完全相同,且叶均在最后两层,那么称该树为完全二叉树
  完全二叉树的顺序储存结构为自顶向下,同一层自左向右的顺序连续编号。

1.3 二叉树的链式描述
  二叉树的链式描述中,二叉树的结点存放数据信息外,还设置两个指示左右子树的指针,并用一个指向根节点的指针标识这个二叉树,形成二叉链。其存储结构定义为

struct node{
    struct node *lchild;
    struct node *rchild;
    datatype data;
};
typedef struct node *BTree;

  此外,如果在链表描述的基础上增加指向父结点的指针,则形成了动态三叉链表

1.4 二叉树的遍历
  按照某种策略,按照一定的次序访问二叉树中的每一个结点,使每个结点被访问且仅被访问一次,该过程称为二叉树的遍历。遍历使得树的非线性结构线性化,得到二叉树结点的线性序列。
  遍历的策略包括:
  -先序遍历,首先访问根,再访问左右子树;
  -中序遍历,依次访问左子树、根、右子树;
  -后序遍历,首先访问左右子树,再访问根;
  -层次遍历,按照自顶向下,同一层自左向右的顺序访问。
  二叉树的遍历可以简单的递归,也可以通过栈进行迭代,先序的遍历非递归算法步骤为:
  1.初始化栈 s s s为空, r r r为指向根节点的指针;
  2.若 r r r不指向空,则访问 r r r-> d a t a data data,并将指向 r r r的指针压栈,令 r r r为指向左子树的指针,并迭代2,直到 r r r指向空;
  3.若 s s s不为空,则弹栈到 r r r,并令 r r r为指向右子树的指针,并迭代2,直到 s s s为空,遍历结束;
  此外,层序顺序遍历借助队列,其非递归算法步骤为:
  1.初始化队列 q q q为空,将树的根指针入队;
  2.若 q q q不为空,则出队到 r r r,访问 r r r-> d a t a data data,并将 r r r的左右子结点依次入队,并迭代2,直到 q q q为空。

1.5 线索二叉树
  在二叉链表描述的二叉树中,对于有n个节点的二叉树,只有n-1个指向子树的指针,而有n+1个空指针。线索二叉树利用了空指针的空间,将结点在无左子树的情况下指向中序遍历顺序的前驱,无右子树的情况下指向中序遍历顺序的后继,并使用标志位来区分指针指向子结点还是线索,当标志位置1时表示指向子结点。其存储结构定义为

struct node{
    struct node *lchild;
    struct node *rchild;
    datatype data;
    bool ltag;
    bool rtag;
};
typedef struct node *ThTree;

1.6 堆与选择树
  【Heap】是一种特殊形式的完全二叉树,当完全二叉树的任意一个结点都不小于其子结点,则称该完全二叉树为最大堆;反之,称为最小堆
  考虑一颗完全二叉树,其有n个外部结点与n-1个内部结点,且每个结点记录了其子结点的某种比较关系,称该类树为选择树。当记录了比较关系的正方,即“胜者”,则成为胜者树,反之称为败者树。选择树可以在 l o g ( n ) log(n) log(n)的时间内找到外部结点的最值。

二、树

2.1 一般树的父链描述
  由于每个非根结点都有唯一的父节点,故将各节点一般按层序储存在一维数组中,数组的元素为树结点,结点包括其储存的信息与父结点的索引,且根的父结点的索引为0。那么数组的元素结构定义为

struct node{
    datatype data;
    int parent;
};

而数组从下标1开始按照结点的层序索引对应储存结点。

2.2 一般树的子链描述
  将树的所有的结点被组织成线性表,每个结点的所有子结点又依次组织成线性表,使结点指向首个子结点,形成邻接表。
  邻接表的元素为结点,邻接表数组的索引与结点对应,且结点包括结点的数据与指向其首个子结点的指针;子结点包括该子结点的索引与指向下一个其兄弟节点的指针。邻接表的元素与表的结构定义为

struct CTnode{
    int child;
    CTnode *next;
};
struct CTBox{
    datatype data;
    CTnode *firstchild
};
struct {
    CTBox nodes[maxlength];
    int n,r;
}CTree;

在上述结构中加入父结点的信息,则构成父子链描述。

2.3 一般树的二叉链描述
  二叉树的二叉链存储了左右子树的指针,而为了表示一般树,由于结点的左子结点与右兄弟确定且唯一,那么一般树的二叉链存储左子结点的指针与右兄弟结点的指针。其结构定义为

struct CSnode{
    struct CSnode *firstchild;
    struct CSnode *rightsib;
    datatype data;
};
typedef struct CSnode *CSTree;

因此,一般树与二叉树之间存在着映射关系。

2.4 一般树的遍历操作
  一般树与二叉树的遍历策略相似,包括:
  -先序遍历,首先访问根,再按从左到右的顺序访问子树;
  -后序遍历,首先按从左到右的顺序访问子树,再访问根;
  -层次遍历,按照自顶向下,同一层自左向右的顺序访问。

2.5 森林与二叉树
  如果将森林中的数棵树看作兄弟结点,那么森林与二叉树之间存在着映射关系,并且任何一个森林对应着唯一的一棵二叉树,反之亦然。
  考虑森林 F = { T 1 , . . . T n } F = \{T_1, ... Tn\} F={T1,...Tn}与其对应的二叉树 B ( F ) B(F) B(F),那么非空森林转换成二叉树的递归算法步骤为:
  1. B ( F ) B(F) B(F)的根为 T 1 T_1 T1的根;
  2. B ( F ) B(F) B(F)的左子树是 T 1 T_1 T1的子树森林,递归 T 1 T_1 T1的子树森林转换成 B ( F ) B(F) B(F)的二叉树作为左子树;
  3. B ( F ) B(F) B(F)的右子树是 F F F T 1 T_1 T1之外的树森林,递归 F F F T 1 T_1 T1之外的树森林转换成 B ( F ) B(F) B(F)的二叉树作为右子树。
非空二叉树转换成森林的算法类似。
  森林的遍历策略包括:
  -先根遍历,首先访问第一棵树的根,然后访问该树子森林,最后按先根遍历访问其余子树森林;
  -后根遍历,首先访问第一棵树的子树,然后访问树的根,最后按后根遍历访问其余子树森林;


三、树的应用

3.1 集合树
  假设集合的元素为 1 , 2 , . . . n 1,2, ... n 1,2,...n,其组成的集合互不相交,在这种结构中定义如下操作:
  - U n i o n ( S i , S j ) Union(S_i, S_j) Union(Si,Sj),作集合的交集;
  - F i n d ( i ) Find(i) Find(i),查找包含 i i i的集合;
  - I n i t i a l ( A , x ) Initial(A, x) Initial(A,x),建立集合 A = { x } A = \{x\} A={x}
在互不相交的集合定义如上操作,构成了一种特殊的数据类型,称为MFSET,使用树结构可以实现该数据结构。

3.2 判定树
  考虑这样的问题:假定有八枚硬币,已知其中一枚是伪造的,假币的重量与真币不同,使用最少的比较次数挑出假币。通过建立树结构的判定手段,可以在3次比较中得到结果。

3.3 最优二叉树
  考虑在二叉树中,对于每个结点,如果出现了空子树,则为其增加一个外结点,而原二叉树的结点称为内结点,从而得到了新的二叉树,称为扩充二叉树
  假设扩充二叉树的外结点 j j j对应了一个权重 w j w_j wj,外结点到根的距离为 l j l_j lj,那么称 w j l j w_jl_j wjlj为结点 j j j的加权路长,并令扩充二叉树的加权路长为 W P L = ∑ w j l j WPL = \sum w_jl_j WPL=wjlj。那么对于给定权重集合为 { w 1 , . . . , w n } \{w_1, ..., w_n\} {w1,...,wn}的有n个叶节点所构成的所有扩充二叉树中, W P L WPL WPL最小的称为最优二叉树,也称为哈夫曼【Huffman】
  哈夫曼树权值越大的叶结点越靠近根,且不会存在度为1的结点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Keil MDK中使用可以通过定义节点结构体和使用指针来实现。下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> // 定义节点结构体 typedef struct node { int data; struct node *left; struct node *right; } TreeNode; // 创建节点 TreeNode* create_node(int data) { TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode)); node->data = data; node->left = NULL; node->right = NULL; return node; } // 插入节点 TreeNode* insert_node(TreeNode* root, int data) { if (root == NULL) { return create_node(data); } if (data < root->data) { root->left = insert_node(root->left, data); } else { root->right = insert_node(root->right, data); } return root; } // 计算的深度 int tree_depth(TreeNode* root) { if (root == NULL) { return 0; } int left_depth = tree_depth(root->left); int right_depth = tree_depth(root->right); return (left_depth > right_depth ? left_depth : right_depth) + 1; } int main() { // 创建根节点 TreeNode* root = create_node(5); // 插入节点 root = insert_node(root, 3); root = insert_node(root, 7); root = insert_node(root, 1); root = insert_node(root, 4); root = insert_node(root, 6); root = insert_node(root, 8); // 计算的深度并输出 int depth = tree_depth(root); printf("Tree Depth: %d\n", depth); return 0; } ``` 在这个示例代码中,我们首先定义了一个节点结构体,包含了节点数据和左右子节点指针。然后我们定义了创建节点和插入节点的函数,使用递归的方式实现。最后我们定义了计算深度的函数,并在main函数中使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值