目录
树的概念与结构:
树的概念:
- 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
- 根节点较为特殊,没有前驱节点。
-
除根节点外,其余结点被分成X(X>0)个互不相交的集合T1、T2、……、Tn,其中每一个集合Ti(1<= i <= n)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱节点,可以有0个或多个后继节点。
-
树是递归定义的、子树之间不能有交集。
树的结构示例图:
-
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为4。
-
叶节点或终端节点:度为0的节点称为叶节点; 如上图:E、F、G、I、G、K节点为叶节点。
-
父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
-
子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的子节点。
-
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为4。
-
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
-
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
二叉树的性质:
二叉树是一个特殊的树,树的概念都能用于二叉树。
一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
-
二叉树不存在度大于2的结点
-
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
特殊的二叉树:
-
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是2^k - 1 ,则它就是满二叉树。(每一层都是满的)
-
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(前h-1层是满的,最后一层不一定满,但是从左到右是连续的)
假设树有N个节点,高度为h:
第h层节点数为:2^(h-1)
最多有(满二叉树):2^h-1 = N
h = log(N+1) (log以2为底)
完全二叉树高度为h,节点数范围为:(2^(h-1) , 2^h-1)
二叉树的基本功能实现:
手动构建一棵二叉树:
typedef int HTDateType;
typedef struct BinarTreeNode
{
HTDateType x;
struct BinarTreeNode* left;//记录左孩子
struct BinarTreeNode* right;//记录右孩子
}TreeNode;
TreeNode* BuyTreeNode(int n)
{
TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));
assert(Node);
Node->x = n;
Node->left = NULL;
Node->right = NULL;
return Node;
}
TreeNode* CreateTree()
{
TreeNode* Node1 = BuyTreeNode(1);
TreeNode* Node2 = BuyTreeNode(2);
TreeNode* Node3 = BuyTreeNode(3);
TreeNode* Node4 = BuyTreeNode(4);
TreeNode* Node5 = BuyTreeNode(5);
TreeNode* Node6 = BuyTreeNode(6);
TreeNode* Node7 = BuyTreeNode(7);
Node1->left = Node2;
Node1->right = Node4;
Node2->left = Node3;
Node4->left = Node5;
Node4->right = Node6;
Node5->left = Node7;
return Node1;
}
其概念图如下:
二叉树的遍历:
1. 前序、中序以及后序遍历(递归式):
前序遍历(Preorder Traversal):访问根结点的操作发生在遍历其左右子树之前。 即按照:根->左子树->右子树的顺序去遍历树,如上述的树,根为1、左为2、右为4。然后继续拆分为根为2、左为3、右为N(空NULL),按照根->左子树->右子树的顺序以此类推。
最后得出顺序为:1 2 3 N N N 4 5 7 N N N 6 N N
void PrevOrder(TreeNode* node)
{
if (node == NULL)
{
printf("N ");//当节点为空时最好也打印出来,方便判别
return;//当节点为空就不用继续往下了,没有左右子树可以直接返回了
}
printf("%d ", node->x);// 打印根节点的值
PrevOrder(node->left);// 继续往下遍历左子树与右子树
PrevOrder(node->right);
}
中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)。即按照:左子树 -> 根 -> 右子树的顺序进行遍历。
最后得出顺序为:N 3 N 2 N 1 N 7 N 5 N 4 N 6 N
void InOrder(TreeNode* node)
{
if (node == NULL)
{
printf("N ");
return;
}
// 按照左子树 根 右子树的顺序遍历
InOrder(node->left);
printf("%d ", node->x);
InOrder(node->right);
}
后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后。即按照:左子树 -> 右子树 -> 根的顺序进行遍历。
最后得出顺序为:N N 3 N 2 N N 7 N 5 N N 6 4 1
void PostOrder(TreeNode* node)
{
if (node == NULL)
{
printf("N ");
return;
}
// 按照左子树 右子树 根的顺序遍历
PostOrder(node->left);
PostOrder(node->right);
printf("%d ", node->x);
}
2.层序遍历:
层序遍历就是一层一层的去遍历每个节点,在这里需要运用到一些前面学过的队列的一些知识点,可以直接搬队列的代码过来用,代码是一样的。
- QueueInit :初始化队列
- QueuePush :插入数据
- QueueFront :返回队头的数据
- QueuePop :删除数据
- QueueDestroy :销毁队列
- QueueEmpty :判空
本质上是把每一层的节点都分为节点本身和节点的左右孩子,可以设计一个队列,将根节点插入队列中,然后根节点的左右孩子也依次入队,然后根节点的左孩子的左右孩子也依次入队,右孩子的左右孩子也同理,以此类推就能得到层序遍历。
void LevelOrder(TreeNode* node)
{
Queue q;
QueueInit(&q);
// 如果节点不为空,则将节点插入到队列中
if (node)
QueuePush(&q, node);
while (!QueueEmpty(&q))
{
// 取出队头数据
TreeNode* front = QueueFront(&q);
QueuePop(&q);
// 打印该节点
printf("%d ", front->x);
// 如果该节点的左右孩子不为空,则将该节点的左右孩子也插入队列中
if (front->left)
QueuePush(&q, front->left);
if (front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
求叶节点的个数:
int TreeLeafSize(TreeNode* node)
{
// 节点本身为空直接返回0
if (node == NULL)
{
return 0;
}
// 节点本身不为空且左右子节点为空则为叶节点
if (node->left == NULL && node->right == NULL)
{
return 1;
}
// 既不为空也不是叶节点则为枝干,则向下查找
return TreeLeafSize(node->left)
+ TreeLeafSize(node->right);
}
求树的高度:
int TreeHeight(TreeNode* node)
{
if (node == NULL)
{
return 0;
}
// 取左右两树的最大值加一(要算上根节点)
return fmax(TreeHeight(node->left), TreeHeight(node->right)) + 1;
}
返回第k层节点的个数:
int TreeLevelK(TreeNode* node, int k)
{
assert(k > 0);
// 当节点为空时返回0
if (node == NULL)
{
return 0;
}
// 当前节点属于第k层的节点时返回1
if (k == 1)
{
return 1;
}
// 不属于上述情况时往下查找
return TreeLevelK(node->left, k - 1) + TreeLevelK(node->right, k - 1);
}
查找值为n的节点:
TreeNode* TreeFind(TreeNode* node, HTDateType n)
{
// 如果节点为空,返回空
if (node == NULL)
return NULL;
// 找到该值,返回该节点
if (node->x == n)
return node;
// 创建ret1与ret2来接收递归调用的返回值
// 如果找到了就返回ret1/ret2
TreeNode* ret1 = TreeFind(node->left, n);
if (ret1)
return ret1;
TreeNode* ret2 = TreeFind(node->right, n);
if (ret2)
return ret2;
// 找不到就返回NULL
return NULL;
}
判断该二叉树是否为完全二叉树:
这里也需要运用到队列的一些知识,和上面层序遍历的思路的差不多。同样是先按照层的思路插入节点到队列中,然后依次往外取,如果遇到NULL后,后面还有数据的话那就不是完全二叉树,因为完全二叉树的前h-1层是满的,最后一层是连续的。
bool BinaryTreeComplete(TreeNode* node)
{
Queue q;
QueueInit(&q);
if (node)
QueuePush(&q, node);
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
// 遇到空就跳出去
if (front == NULL)
break;
// 未遇到就继续插入左右孩子
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
// 前面遇到空了,看后面还会不会有非空
while (!QueueEmpty(&q));
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
// 有非空,不是完全二叉树
if (front)
{
QueueDestroy(&q);
return false;
}
}
// 没有非空,是完全二叉树
QueueDestroy(&q);
return true;
}
二叉树的特点:
1.当根节点为第一层时,一棵非空二叉树的第x层上最多有2^(x-1)个节点
2.当根节点为第一层时,深度为h的二叉树的最大节点数为:(2^h) - 1
3.对任何一棵二叉树, 如果度为0的结点个数为m , 度为2的分支结点个数为n ,则有m = n+1
4.当根节点为第一层时,具有n个节点的满二叉树的高度为:h = log(n+1) (log以2为底)
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
2. 若2i+1=n否则无左孩子
3. 若2i+2=n否则无右孩子
6.一棵满二叉树的上下限为;[2^(h-1),2^h-1]