树与二叉树
一、树概念及结构
1.1 树的概念
树是一种非线性数据结构,由n(n>=0)个有限节点组成的一个具有层次关系的集合。
1. 有一个特殊的节点,称为根节点,根节点没有前驱结点
2. 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1、T2、...、Tn,其中每一个集合Ti(1<=i<=m)又是一棵结构与树类似的子树。每颗子树的根节点有且只有一个前驱,可以有0个或者多个后继
3. 树是递归定义的
PS:树形结构中,子树之间不能有交集,否则就不是树形结构了
1.2 树的相关概念
节点的度: 一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点: 度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
非终端节点或分支节点: 度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
双亲节点或父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点: 一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点: 具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度: 一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次: 从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度: 树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点: 双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先: 从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙: 以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林: 由m(m>0)棵互不相交的树的集合称为森林;
1.3 树的表示
方式1: 假设说明了树的度为N
struct TreeNode
{
int data;
struct TreeNode* subs[N]; // 指针数组,数组的每个元素是指针
};
问题1:存在不少的空间浪费
问题2:万一没有限定树的度为多少呢?
方式2: 顺序表存
typedef struct TreeNode* SLDataType; // 顺序表存节点的指针
struct TreeNode
{
int data;
SeqList s; // vector
};
问题:结构复杂
方式3: 左孩子右兄弟表示法
typedef int DataType;
struct Node
{
struct Node* firstChild1; // 永远指向第一个孩子
struct Node* pNextBrother; // 指向孩子右边的兄弟
DataType data; // 节点中的数据域
};
二、二叉树的概念及结构
2.1 概念
一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出: - 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:
2.2 特殊的二叉树
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.3 二叉树的存储结构
2.3.1 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
left = 2 * parent + 1;
right = 2 * parent + 2;
parent = (child - 1) / 2
2.3.2 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。
普通的二叉树的增删查改没有意义,因为用来存数据太复杂了
价值体现:
在他的基础上增加一些性质,才有意义:
搜索二叉树——用来查找(最多查找高度次)->最坏情况下是O(N)
改进——平衡二叉树AVL红黑树等等->B树
huffman tree——文件压缩
三、二叉树的遍历
所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次,访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历
3.1 二叉树的存储结构
typedef int BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left; // 左孩子
struct BinaryTreeNode* right; // 右孩子
BTDataType data; // 节点中的数据域
} BTNode;
3.2 前序遍历
前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
根 左子树 右子树
A B D NULL NULL NULL C E NULL NULL F NULL NULL
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
3.3 中序遍历
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
访问A,要先访问A的左子树
NULL D NULL B NULL A NULL E NULL C NULL F NULL
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
3.4 后序遍历
后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
NULL NULL D NULL B NULL NULL E NULL NULL F C A
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
3.5 层序遍历
A B C D E F
先将当前的的根入队列,然后出队列,入左右孩子B,C;B出队列,带B的孩子D入队列,依次类推
思路:
1、先入根
2、当前节点出来,将孩子带进去
这样子,上一层节点出的时候,带入下一层,当队列为空,表明下一层没有节点了,层序遍历就结束了
void BinaryTreeLevelOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
QueueDestroy(&q);
}
3.5.1 判断二叉树是否为完全二叉树
完全二叉树若队列中某一项为NULL,则之后所有节点都为NULL
非完全二叉树若队列中某一项为NULL,则之后还有节点不为NULL
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
// 遇到空了以后,检查队列中剩下的节点
break;
}
else
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
// 剩下的全是空,则是完全二叉树,若存在非空,则不是完全二叉树
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front) // 若队头元素不为空,则说明不是完全二叉树
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
四、二叉树的节点个数及高度等
4.1 二叉树的节点个数
方式1:不带返回值
void BinaryTreeSize(BTNode* root, int* pn)
{
if (root == NULL)
{
return;
}
(* pn)++;
BinaryTreeSize(root->left, pn);
BinaryTreeSize(root->right, pn);
}
方式2:带返回值
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
4.2 二叉树的叶子节点个数
int BinaryTreeLeafSize(BTNode *root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
4.3 二叉树第K层节点个数
比如求A的第四层,可以转化为求左子树的第三层节点数量+右子树第三层节点数量
int BinaryTreeLevelKSize(BTNode *root, int k)
{
if (root == NULL)
return 0;
assert(k >= 1);
if (k == 1)
return 1;
// root不为空,k也不为1
// 转化为求左右子树第K-1层节点数量
return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
4.4 二叉树的深度/高度
当前树的高度/深度 = max(左子树的深度,右子树的深度)+1
在这里,空树给0,第一层高度为1
int BinaryTreeDepth(BTNode *root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = BinaryTreeDepth(root->left);
int rightDepth = BinaryTreeDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
4.5 二叉树查找值为x的节点
如果有多个x,返回第一个x
BTNode *BinaryTreeFind(BTNode *root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode *leftRet = BinaryTreeFind(root->left, x);
if (leftRet)
{
return leftRet;
}
BTNode *rightRet = BinaryTreeFind(root->right, x);
if (rightRet)
{
return rightRet;
}
return NULL;
}
五、二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeDestroy(root->left);
BinaryTreeDestroy(root->right);
free(root);
}