基本知识
树
-节点的度:一个节点含有的子树的个数称为该节点的度;
-叶节点或终端节点:度为0的节点称为叶节点;
-非终端节点或分支节点:度不为0的节点;
-父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
-子节点:一个节点含有的子树的根节点称为该节点的子节点;
-兄弟节点:具有相同父节点的节点互称为兄弟节点;
-树的度:一棵树中,最大的节点的度称为树的度;
-节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
-树的高度或深度:树中节点的最大层次;
-森林:由m(m>0)棵互不相交的多颗树的集合称为森林;
树的高度默认从1开始,是为了便于将空树高度以0表示。与之相对的,数组下标从0开始是为了方便数组名代表首元素,且能直接使用数组名加减数字找到目标元素位置。
最优一般树结构(孩子兄弟表示法):
只用两个指针表示任意多个子节点
typedef int DataType;
struct Node{
struct Node* firstChild;//指向当前节点的第一个孩子
struct Node* nextBrother;//指向当前节点“右边”的兄弟
DataType data;
};
二叉树
0.一棵二叉树是结点的一个有限集合,该集合由一个根节点加上两棵别称为左子树和右子树的二叉树组成。空树也是一种子树。
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。
满二叉树与完全二叉树
满二叉树:
-每一个层的结点数都达到最大值的二叉树。
-层数为k且结点总数为(2^k) -1的二叉树是满二叉树。
完全二叉树:
-满二叉树是特殊的完全二叉树。
-对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一 一对应时称之为完全二叉树。
-完全二叉树的叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
满二叉树必定是完全二叉树,而完全二叉树未必是满二叉树。
二叉树的性质
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2^h)-1.
3.对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0=n2+1
4.若规定根节点的层数为1,则具有n个结点的满二叉树的深度为:
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
(1)若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
(2)若2i+1<n,左孩子序号:2i+1,2i+1>=n,否则无左子节点
(3)若2i+2<n,右孩子序号:2i+2,2i+2>=n,否则无右子节点
二叉树的存储结构
1.顺序存储
-使用数组来存储。一般使用数组只适合表示完全二叉树,因为若不是完全二叉树会有空间浪费。
-实际应用中只有堆(是堆结构,不是内存堆区)才会使用数组来存储。
-顺序存储的二叉树在物理上是一个数组,在逻辑上是一颗二叉树。
2.链式存储
-用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
-通常的实现方式是链表中每个结点由三个域组成,数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
-链式结构又分为二叉链和三叉链,暂且只讨论二叉链。
二叉树遍历思路
已知:二叉树每个节点可以看作一个根,每个根有两个子树。空子树也算子树,只不过空树没有子树。
递归实现:
1.前序遍历/先根遍历:根、左子树、右子树
2.中序遍历/中根遍历:左子树、根、右子树
3.后序遍历/后根遍历:左子树、右子树、根
非递归实现:
4.层序遍历:用队列实现,从第一层开始,每层从左到右,逐层遍历
二叉树相关代码实现
队列实现代码此处略过,详见相应博文
二叉树相关功能实现:
#include<stdio.h>
#include<stdlib.h>
#include"Queue.h"
typedef char BTDataType;
typedef struct BinaryTreeNode {
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
BTNode* BuyBTNode(BTDataType x) {
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL) {
perror("malloc failed");
exit(-1);
}
node->_data = x;
node->_left = node->_right = NULL;
return node;
}
//前序遍历字符串构建二叉树,#代表空
BTNode* BinaryTreeCreate(BTDataType* str, int* pi) {
//i传地址,以保证++的是同一个i
//遇#返回空
if (str[*pi] == '#') {
(*pi)++;
return NULL;
}
//非#创建新节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->_data = str[(*pi)++];
root->_left = BinaryTreeCreate(str, pi);
root->_right = BinaryTreeCreate(str, pi);
return root;
}
//前序遍历
void PrevOrder(BTNode* root) {
//空树作为终止条件
if (root == NULL) {
printf("NULL ");
return;//返回到发起本次调用的栈帧,也就是递归的上一层。
//调用结束的栈帧会被销毁
}
//前序:根、左子树、右子树
//若非空,则访问当前节点
printf("%c ", root->_data);
//先从左子树的根向下递归,然后才是右子树
PrevOrder(root->_left);
PrevOrder(root->_right);
//从右子树的递归调用的栈帧返回本次调用的栈帧后,本次调用结束
//返回到发起本次调用的栈帧,也就是递归的上一层。
//调用结束的栈帧会被销毁
}
//中序遍历
void InOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
//中序:左子树、根、右子树
InOrder(root->_left);
printf("%c ", root->_data);
InOrder(root->_right);
}
//后序遍历
void PostOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
//后序:左子树、右子树、根
PostOrder(root->_left);
PostOrder(root->_right);
printf("%c ", root->_data);
}
//层序遍历-使用队列,一层带一层直至队列pop到空
void LevelOrder(BTNode* root) {
Queue q;
QueueInit(&q);
if (root) {
QueuePush(&q, root);//树根入队
}
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);//队头地址保存到front
printf("%c ", front->_data);//打印队头
QueuePop(&q);//队头出队
//通过front判断原队头是否存在子节点,存在则将子节点依次入队排在后面
if (front->_left) {
QueuePush(&q, front->_left);
}
if (front->_right) {
QueuePush(&q, front->_right);
}
}
printf("\n");
QueueDestroy(&q);
}
//二叉树销毁:使用后序遍历(最优)
void TreeDestroy(BTNode* root) {
if (root == NULL) {
return;
}
TreeDestroy(root->_left);
TreeDestroy(root->_right);
free(root);
//这个free可以不用跟置空,因为是形参
//由于没用二级指针,树指针的置空操作需要由调用者进行
}
//求二叉树节点个数
int TreeSize(BTNode* root) {
return root == NULL ? 0 :
TreeSize(root->_left) + TreeSize(root->_right) + 1;
}
//求二叉树叶节点个数
int TreeLeafSize(BTNode* root) {
if (root == NULL) {
return 0;
}
if (root->_left == NULL && root->_right == NULL) {
return 1;
}
return TreeLeafSize(root->_left) + TreeLeafSize(root->_right);
}
//求树的高度/深度
int TreeHeight(BTNode* root) {
if (root == NULL) {
return 0;
}
int leftHeight = TreeHeight(root->_left);
int rightHeight = TreeHeight(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//求第k层的节点个数,k>=1
int TreeKLevelSize(BTNode* root, BTDataType k) {
if (root == NULL) {
return 0;
}
if (k == 1) {
return 1;
}
return TreeKLevelSize(root->_left, k - 1) + TreeKLevelSize(root->_right, k - 1);
}
//查找值为x的节点,这里的x为字符
BTNode* TreeFind(BTNode* root, BTDataType x) {
if (root == NULL) {
return NULL;
}
if (root->_data == x) {
return root;
}
//每层递归的返回值都只会返回给上一层,要确保该值被上一层接收且能够继续向上传递
struct BinaryTreeNode* ret1 = TreeFind(root->_left, x);
if (ret1) {
return ret1;
}
struct BinaryTreeNode* ret2 = TreeFind(root->_right, x);
if (ret2) {
return ret2;
}
return NULL;
}
//判断完全二叉树:使用层序遍历思路
//相比前面的层序遍历,空节点也入队,便能在空节点出队时看后续是否为全空队列来判断完全二叉树
//不能通过节点数量判断,因为最后一层的节点可能不连续
bool TreeComplete(BTNode* root) {
Queue q;
QueueInit(&q);
if (root) {
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 != NULL) {
QueueDestroy(&q);
return false;//空后有非空则该树不是完全二叉树
}
}
QueueDestroy(&q);//即便到这一步队列后续必为空,也不省略destroy,
//,这样能确保即便是带哨兵位的队列也不会内存泄漏,使得本函数不受队列的实现方式影响
return true;
}
int main() {
//手动创建节并链接以构建树
BTNode* n1 = BuyBTNode('1');
BTNode* n2 = BuyBTNode('2');
BTNode* n3 = BuyBTNode('3');
BTNode* n4 = BuyBTNode('4');
BTNode* n5 = BuyBTNode('5');
BTNode* n6 = BuyBTNode('6');
BTNode* n7 = BuyBTNode('7');
n1->_left = n2;
n1->_right = n4;
n2->_left = n3;
n2->_right = n7;
n4->_left = n5;
n4->_right = n6;
/*BTDataType str[100] = "123##7##45##6##";
int i = 0;
BTNode* n1 = BinaryTreeCreate(str, &i);*/
PrevOrder(n1);
printf("\n");
InOrder(n1);
printf("\n");
PostOrder(n1);
printf("\n");
LevelOrder(n1);
printf("\n");
printf("TreeSize:%d\n", TreeSize(n1));
printf("TreeLeafSize:%d\n", TreeLeafSize(n1));
printf("TreeHeight:%d\n", TreeHeight(n1));
printf("Tree2LevelSize:%d\n", TreeKLevelSize(n1, 2));
printf("Tree3LevelSize:%d\n", TreeKLevelSize(n1, 3));
printf("Tree4LevelSize:%d\n", TreeKLevelSize(n1, 4));
printf("TreeFind '4':%d\n", (int)TreeFind(n1, '2'));
printf("TreeComplete:%d\n", TreeComplete(n1));
TreeDestroy(n1);
n1 = NULL;
return 0;
}
执行结果:
例题
单值二叉树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:
//递归判断每个子树的根与左右子节点是否相等,遇到空树直接返回
//先判断当前子树,再向下递归
bool isUnivalTree(struct TreeNode* root){
if(root==NULL){
return true;
}
if(root->left && root->left->val != root->val){
return false;
}
if(root->right && root->right->val != root->val){
return false;
}
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
判断两树是否相同
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:同时遍历并比较
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
//当前节点都为空则同
if(p==NULL && q==NULL){
return true;
}
//已判断当前节点并不都为空,故其中一个为空则异
if(p==NULL || q==NULL){
return false;
}
//至此,当前节点都不为空,遂判断值是否相异
if(p->val != q->val){
return false;
}
//同时递归遍历比较
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}
另一棵树的子树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:
//复用isSameTree函数来比较每一棵子树是否相同
//每个节点都可以看作一棵子树的根
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL && q==NULL){
return true;
}
if(p==NULL || q==NULL){
return false;
}
if(p->val != q->val){
return false;
}
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
if(root == NULL){
return false;
}
if(isSameTree(root,subRoot)){
return true;
}
return isSubtree(root->left,subRoot)
|| isSubtree(root->right,subRoot);
}
对称二叉树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:将一侧子树翻转后与另一侧子树比对(只在效果上是翻转的)
//用于比对的子函数
bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2){
if(root1 == NULL && root2 == NULL){
return true;
}
if(root1 == NULL || root2 == NULL){
return false;
}
if(root1->val != root2->val){
return false;
}
return _isSymmetric(root1->left,root2->right)
&& _isSymmetric(root1->right,root2->left);
}
bool isSymmetric(struct TreeNode* root){
return !root || _isSymmetric(root->left,root->right);
}