何为树
树(Tree)是一种非线性的数据结构,它由一组节点组成,节点之间通过边连接。树的结构类似于现实生活中的树,根节点相当于树的顶端,而其他节点则称为子节点。树的节点之间存在一对多的关系,一个节点可以有多个子节点,但每个节点最多只有一个父节点。
树的类型
树有多种类型,常见的包括二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)、平衡二叉树(Balanced Binary Tree)和B树(B-Tree)等。二叉树是一种特殊的树,每个节点最多有两个子节点;二叉搜索树是一种有序的二叉树,左子节点的值小于根节点的值,右子节点的值大于根节点的值;平衡二叉树是一种左右子树高度差不超过1的二叉树;B树是一种多路搜索树,用于在外部存储设备上组织和管理大量数据。
树的相关概念
度数:
一个节点的子树的个数称为该节点的度数,一棵树的度数是指该树中节点的最大度数。
高度(层数、深度):
节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。
路径:边数:
一个节点系列k1,k2, ……,ki,ki+1, ……,kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径,路径的
长度为j-1,即路径中的边数。路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。
叶子节点
度数为0的结点
二叉树
定义
二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。
性质
每个节点最多有两个子节点:二叉树的每个节点最多有两个子节点,分别是左子节点和右子节点。如果一个节点没有子节点,它就是叶子节点;如果一个节点有两个子节点,它就是内部节点。
左子节点小于根节点,右子节点大于根节点:对于二叉搜索树(Binary Search Tree),左子节点的值要小于根节点的值,右子节点的值要大于根节点的值。这个性质使得二叉搜索树可以进行高效的搜索和插入操作。
二叉树的高度:二叉树的高度是从根节点到最远叶子节点的最长路径上的节点数。树的高度也可以定义为空树的高度为0,只有一个根节点的二叉树的高度为 1。
二叉树的深度:二叉树的深度是从根节点到当前节点的路径上的节点数。根节点的深度为0,每往下一层深度加1。
二叉树的遍历:二叉树的遍历是指按照某种顺序访问二叉树的所有节点。常见的遍历方式包括前序遍历(根-左-右)、中序遍历(左-根-右)和后序遍历(左- 右-根)。
二叉树的平衡性:二叉树的平衡性是指左子树和右子树的高度差不超过1。平衡二叉树(Balanced Binary Tree)是一种高度平衡的二叉树,可以保证在最坏情况下的搜索和插入操作的时间复杂度为O(log n)。
普通的树转换为二叉树
树形结构的顺序储存
平均状况下顺序存储的空间利用率较低,所以不使用顺序存储,使用链式存储来存数据。
树形结构的链式储存
树形结构链式储存指的是使用链表来储存树形结构的数据。在树形结构链式储存中,每个节点都是一个链表节点,包含一个数据域和一个指针域。数据域用来存储节点的数据,指针域用来指向节点的子节点。
每个节点的指针域可以是一个指向子节点的指针,也可以是一个指向子节点链表的头指针。如果是二叉树,每个节点的指针域通常包含两个指针,分别指向左子节点和右子节点。如果是多叉树,每个节点的指针域可以包含多个指针,分别指向各个子节点。
使用链表来储存树形结构的优势在于灵活性和动态性。链表可以动态地分配内存空间,树的节点可以根据需要动态增加或删除。而且链表的节点可以通过指针域连接起来,形成任意复杂的树形结构。## 树的结点的定义
树的结点的定义
```c
typedef int data_type;
typedef struct bintreenode
{
struct bintreenode *Left;
data_type Data;
struct bintreenode *Right;
}BTree;
## 树的结点的插入
首先,找到插入节点的位置。从树的根节点开始,逐级向下比较节点的值,直到找到一个合适的位置。如果插入节点的值小于当前节点的值,则向左子树移动;如果插入节点的值大于当前节点的值,则向右子树移动。重复这个过程,直到到达一个叶子节点,即没有子节点的节点。
在找到插入位置后,创建一个新的节点,并将插入节点的值赋给新节点的数据域。
将新节点插入到树中。如果插入节点的值小于当前节点的值,则将新节点作为当前节点的左子节点;如果插入节点的值大于当前节点的值,则将新节点作为当前节点的右子节点。
插入完成后,更新树的相关属性,如节点数目、高度等。
```c
int InsertItem(BTree *pTree,data_type item, int *Flag)
{
BTree *pNew =NULL;//申请空间
BTree *pTmp =pTree;//指示插入位置
//入参判段
if(*Flag)
{
//直接将要插入的值,赋值给Data域
pTree->Data =item;
//把标志位置空
*Flag = 0;
return OK;
}
//其余次,插入
//申请空间
pNew =Creat();
//赋值给
pNew->Data =item;
//找到要插入位置
while(1)
{
if(item <= pTmp->Data)
{
if( NULL == pTmp->Left)
{
//找到了,插入
pTmp->Left =pNew;
return OK;
}
pTmp = pTmp->Left;
}
elsepr
{
if(NULL = pTmp->Right)
{
pTmp->Right =pNew;
return OK;
}
pTmp= pTmp->Right;
}
}
}
树的节点的遍历
常见的树的节点遍历方式有三种:前序遍历、中序遍历和后序遍历。下面分别介绍这三种遍历方式的特点和实现方法。
前序遍历(Preorder Traversal):按照根节点、左子树、右子树的顺序遍历节点。
遍历顺序:根节点 -> 左子树 -> 右子树
实现方法:递归或使用栈进行迭代
中序遍历(Inorder Traversal):按照左子树、根节点、右子树的顺序遍历节点。
遍历顺序:左子树 -> 根节点 -> 右子树
实现方法:递归或使用栈进行迭代
后序遍历(Postorder Traversal):按照左子树、右子树、根节点的顺序遍历节点。
遍历顺序:左子树 -> 右子树 -> 根节点
实现方法:递归或使用栈进行迭代
递归形式的遍历,非递归的用栈
先序遍历
void Fir_Travel(BTree *pTree)
{
//退出条件
if(NULL == pTree) return NULL;
/d_Trave/先访问根结点
printf("%p", pTree->Data);
//以先序便利的形式,访问左子树
Fri_Travel(pTree->Left);
//以先序便利的形式,访问右子树
Fri_Travel(pTree->Right);
}
中序遍历
void Mid_Travel(BTree *pTree)
{
//退出条件
if(NULL == pTree) return NULL;
Mid_Travel(pTree->Left);
printf("%p", pTree->Data);
Mid_Travel(pTree->Right);
}
后序遍历
void Fin_Travel(BTree *pTree)
{
//退出条件
if(NULL == pTree) return NULL;
Fin_Travel(pTree->Left);
Fin_Travel(pTree->Right);
printf("%p", pTree->Data);
}
层次遍历(Level Order Traversal)
从上到下、从左到右的顺序逐层遍历二叉树的节点
在层次遍历中,我们可以使用队列来实现。具体步骤如下:
首先,将根节点入队。
进入循环,直到队列为空:
从队列中取出一个节点,并访问该节点。
如果该节点有左子节点,将左子节点入队。
如果该节点有右子节点,将右子节点入队。
循环结束,表示遍历完成。
赫夫曼树(哈夫曼树、最优二叉树):
赫夫曼(Huffman)树,又称最优树,是带权路径长度最短的树,有着广泛的应用
从树中一个结点到另外一个结点的分支构成一条路径,分支的数目称为路径的长度。树的路径长度是指从树根到每
个结点的路径长度之和
进一步推广,考虑带权的结点。结点的带权路径长度指的是从树根到该结点的路径长度和结点上权的乘积。树的带
权路径长度是指所有叶子节点的带权路径长度之和,记作 WPL 。WPL最小的二叉树就是最优二叉树,又称为赫夫曼
树
线索二叉树(Threaded Binary Tree)
线索二叉树(Threaded Binary Tree)是对二叉树的一种改进,它在原有的二叉树结构上增加了一些额外的指针,使得遍历二叉树时可以更加高效。
在二叉树中,每个节点都有左子树和右子树,但在线索二叉树中,某些节点的左子树或右子树可能为空。这时,我们可以将这些空的子树指针指向该节点在某种遍历方式下的前驱节点或后继节点,这样就形成了线索。
根据线索的生成方式,线索二叉树可以分为前序线索二叉树、中序线索二叉树和后序线索二叉树。其中,中序线索二叉树是最常见的一种。
在中序线索二叉树中,对于任意一个节点,如果它的左子树为空,那么可以将它的左子树指针指向它的前驱节点;如果它的右子树为空,那么可以将它的右子树指针指向它的后继节点。这样,在遍历二叉树时,不需要使用递归或栈来回溯,可以直接通过线索找到下一个节点。