本篇着重讲述 二叉树 的相关知识
树是一种非线性数据结构,由节点和边组成。每个节点可以有零个或多个子节点,每个节点至多有一个父节点。树的顶部节点称为根节点,没有父节点的节点称为叶子节点。树的高度是从根节点到最深叶子节点的最长路径。二叉树是一种特殊的树,每个节点最多有两个子节点,分别称为左子节点和右子节点。
这是一棵树
这是一颗“倒树”
树的应用:
数据结构中树的概念源于自然界中的树,在人类社会中可以族谱、行政组织机构来表示,在计算机领域可以树来表述源程序的语法结构,在数据库中用树组织信息是常用手法,并且在算法分析中用树描述执行过程。
树的应用包括但不限于:搜索、排序、哈希、动态规划、图形算法等。在计算机科学中,树是一种非常重要的数据结构,被广泛应用于各种算法和数据结构中。例如,二叉搜索树是一种常用的数据结构,用于实现快速查找和排序。哈希表是一种基于树的数据结构,用于实现高效的键值对存储和查找。动态规划算法通常使用树来表示问题的解空间,以便更好地理解和优化算法。图形算法中,树被用来表示图的结构,以便更好地处理图的遍历和搜索。
类似jojo第五部黄金之风中 [热情] 黑帮行政组织图(暂时未找到)
树的术语非常之多,先慢慢来
-
根节点:没有父节点的结点,称之为 根结点
-
叶子节点:没有子节点的结点
-
节点的层数:有几层
-
节点的祖先:从根节点到该节点的所有节点
-
节点的度:该节点有几个孩子,就是几度
-
树的度:树中,节点的度 最大的 称之为 树的度
-
树的深度/高度:从根节点开始,长度最长的那条路径,有几个节点就是多高
树的性质
1.树中的节点数 等于所有节点的度数加1
2.度为 m 的树中,第i层上至多有 (i>=1)
3.高度为 h 的 m 叉树中,至多有个节点
二叉树
-
二叉树:每个节点最多含有两个子树的树称为二叉树
-
无序树 : 树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树
-
有序树 : 树中任意节点的子结点之间有顺序关系,这种树称为有序树
-
满二叉树:叶节点除外的所有节点均含有两个子树的树被称为满二叉树完全二叉树:除最后一层外,所有层都是满节点,且最后一层缺右边连续节点的二叉树称为完全二叉树
满二叉树
完全二叉树
二叉树的性质
性质一:在二叉树中的第i层上最多有2i-1个节点(i>0)
性质二:深度为k的二叉树,最多有2k-1个节点(k>0)
性质三:树上的节点数为n,则边数一定为n-1
性质四:任意一颗二叉树中,度为 0 的节点个数=度为 2 的节点个数+1
性质五:具有 n 个节点的完全二叉树的深度为⌊log2n⌋+1(向下取整 符号 ⌊⌋)
大顶堆和小顶堆
//大顶堆,每个根节点(所有的根节点)都大于他的子节点
ki>=k2i
ki>=k2i+1
//小顶堆,每个根节点(所有的根节点)都小于他的子节点
ki<=k2i
ki<=k2i+1
二叉树链式存储
二叉树链式存储是指使用指针来表示二叉树的存储结构,每个节点包含三个部分:数据域、左子树指针和右子树指针。下面的代码展示了如何定义一个二叉树节点,以及如何使用递归来遍历二叉树并计算节点数目和某一层的节点数目。
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
int countNodes(TreeNode* root) {
if (root == NULL) {
return 0;
}
return 1 + countNodes(root->left) + countNodes(root->right);
}
int countNodesLevel(TreeNode* root, int k) {
if (root == NULL) {
return 0;
}
if (k == 1) {
return 1;
}
return countNodesLevel(root->left, k-1) + countNodesLevel(root->right, k-1);
}
二叉树各个子节点的定义和连接关系
如果我们想形成这样一棵树
int main() {
//创建二叉树节点
BinaryNode nodeA= {'A',NULL,NULL};
BinaryNode nodeB= {'B',NULL,NULL};
BinaryNode nodeC= {'C',NULL,NULL};
BinaryNode nodeD= {'D',NULL,NULL};
BinaryNode nodeE= {'E',NULL,NULL};
BinaryNode nodeF= {'F',NULL,NULL};
BinaryNode nodeG= {'G',NULL,NULL};
BinaryNode nodeH= {'H',NULL,NULL};
BinaryNode nodeI= {'I',NULL,NULL};
BinaryNode nodeJ= {'J',NULL,NULL};
//或者手动输入节点
Node node[10];
for(int i=0;i<10;i++){
node[i].lchild=NULL;
node[i].rchild=NULL;
printf("输入第%d个数据:",i+1);
scanf("%c",&node[i].data);
getchar();
}
//建立节点之间的关系
nodeA.lChild=&nodeB;
nodeA.rChild=&nodeC;
nodeB.lChild=&nodeD;
nodeB.rChild=&nodeE;
nodeC.lChild=&nodeF;
nodeC.rChild=&nodeG;
nodeF.lChild=&nodeH;
nodeF.rChild=&nodeI;
nodeD.lChild=&nodeJ;
return 0;
}
二叉树有三种遍历方式
分别是先序遍历、中序遍历、后序遍历。
-
先序遍历:根左右
-
中序遍历:左根右
-
后序遍历:左右根
三种遍历的区别仅在于输出节点代码的位置不同
//先序遍历
void preOrder(Node *root){ //root根节点
//递归结束的条件,也就是当一个节点某个指针域为 NULL
if(root==NULL){
return;
}
printf("%c ", root->ch);
//递归调用左子树
preOrder(root->lchild);
//递归调用右子树
preOrder(root->rchild);
}
//中序遍历
void preOrder(Node *root){
//递归结束的条件,也就是当一个节点某个指针域为 NULL
if(root==NULL){
return;
}
//递归调用左子树
preOrder(root->lchild);
printf("%c ", root->ch);
//递归调用右子树
preOrder(root->rchild);
}
//后序遍历
void postOrder(Node *root){ //root根节点
//递归结束的条件,也就是当一个节点某个指针域为 NULL
if(root==NULL){
return;
}
//递归调用左子树
postOrder(root->lchild);
//递归调用右子树
postOrder(root->rchild);
printf("%c ", root->ch);
}
我们可以根据遍历的序列来确定二叉树的性状
1.先序 和 中序可以确定一颗二叉树
答:根据先序确定 根节点的位置,然后去 中序中找到该节点,该节点左边元素都是左子树,右边元素都是右子树,依次重复上述过程,即可根据序列确定 二叉树形状
(从前找根节点)
2.后序 和 中序可以确定一颗二叉树
答:根据先序确定 根节点的位置,然后去 中序中找到该节点,该节点左边元素都是左子树,右边元素都是右子树,依次重复上述过程,即可根据序列确定 二叉树形状
(从后找根节点)
3.先序 和 后序不可以确定一颗二叉树
二叉排序树
是一颗空树,或者是满足以下性质的二叉树
-
左子树非空,左边节点 小于 根节点
-
右子树非空,右边节点 大于 根节点
-
中序遍历可以实现二叉排序树从小到大排序
哈夫曼树(最小权值树)
-
路径:两个节点之间的 线 就是我们的路径
-
节点的路径:从根节点到此节点之间有几根线,路径长度就是多少
-
树的路径长度:所有节点路径长度之和 称之为 树的路径长度
-
权值:给这个节点 赋一个 数值
-
节点的带权值路径长度:路径长度 * 该节点的权值
-
树的带权路径长度(WPL):所有叶子节点的带权路径长度之和
-
重点:哈夫曼树构造
哈夫曼二叉树构造
1.每次从序列中找出 权值最小的两个节点(一般从左至右,从小到大)
2.生成一个新的节点,将这个节点放入 序列中,删除刚才的两个最小节点,再重复一过程
利用二叉树排序来求节点数量和高度
typedef struct Node {
char ch;
struct Node *lsun;
struct Node *rsun;
} Node,*TreeNode;
//求结点总数
int TreeNode1(TreeNode root) {
if(!root) {
return 0;
}
//+1 加的是每个子树的根节点
return TreeNode1(root->lsun)+TreeNode1(root->rsun)+1;
}
//求叶子结点总数
int TreeNode2(TreeNode root) {
if(!root) {
return 0;
}
if(root->lsun==NULL&&root->rsun==NULL) {
return 1;
}
return TreeNode2(root->lsun)+TreeNode2(root->rsun);
}
//求高度
int TreeNode3(TreeNode root) {
if(!root) {
return 0;
}
//求左子树高度
int LChildhigh=TreeNode3(root->lsun);
int RChildhigh=TreeNode3(root->rsun);
int High=LChildhigh>RChildhigh?LChildhigh+1:RChildhigh+1;
return High;
}
//先序遍历
int preOrder(TreeNode root) {
if(!root) {
return 0;
}
printf("%c ",root->ch);
preOrder(root->lsun);
preOrder(root->rsun);
}
int main() {
Node node[10];
for(int i=0; i<10; i++) {
node[i].lsun=NULL;
node[i].rsun=NULL;
printf("请输入第%d个元素:",i+1);
scanf("%c",&node[i].ch);
getchar();
}
node[0].lsun=&node[1];
node[0].rsun=&node[2];
node[1].lsun=&node[3];
node[1].rsun=&node[4];
node[2].lsun=&node[5];
node[2].rsun=&node[6];
node[3].lsun=&node[7];
node[3].rsun=&node[8];
node[4].lsun=&node[9];
preOrder(&node[0]);
printf("\n");
printf("节点数为:%d\n",TreeNode1(&node[0]));
printf("叶子节点数为:%d\n",TreeNode2(&node[0]));
printf("高度为:%d\n",TreeNode3(&node[0]));
}