普通的二叉树增删改查没有什么意义,如果是存储数据则不如使用顺序表、链表。学习二叉树的初级操作,是为了学习后面更复杂的二叉树打基础(搜索二叉树、AVL树、红黑树、B树...)。
这里大多使用的是递归法,分而治之,还有非递归的方法,但是难度偏大。
一、二叉树的遍历
PS:这里使用'#'号表示空。
前序遍历: 根 左子树 右子树
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL) {
printf("# ");
return;
}
printf("%c ",root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
return;
}
中序遍历: 左子树 根 右子树
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL) {
printf("# ");
return;
}
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
return;
}
后序遍历: 左子树 右子树 根
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL) {
printf("# ");
return;
}
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ",root->_data);
return;
}
二、链式二叉树的创建
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
Q : 这里已知二叉树的前序遍历结果"ABD##E#H##CF##G##" ,请构建出二叉树。
BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
{
//不能在 if 里面++,不然每判断一次都要加一次。
if (a[*pi]=='#') {
(*pi)++;
return NULL;
}
BinaryTreeNode* root = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode));
root->_data = a[(*pi)++];
root->_left = BinaryTreeCreate(a,pi);
root->_right = BinaryTreeCreate(a,pi);
return root;
}
测试:
使用前序遍历查看一下。
PS: 这里变量 i 传参数要传入地址进去,如果只传入变量,其只是相当于其变量的拷贝。从父节点传入左右节点时,i 的值相等。会导致在同一个下标下赋值两个数据,导致错误。所以要传入地址,让 i 的值执行一次变一次
三、二叉树初级操作
1.求二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
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);
}
3.求二叉树的深度
int Depth(struct TreeNode* root){
if(root==NULL)
return 0;
int left = Depth(root->left);
int right = Depth(root->right);
return left > right ?left+1 :right+1;
}
4.求二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BinaryTreeLevelKSize(root->_left,k-1)+ BinaryTreeLevelKSize(root->_right,k-1);
}
求K层的节点数,进入到左右子树后,就是求K-1层的节点数,K==1就是其到了指定的层数。
5.二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
//使用前序最好,避免第一个节点就是要找的节点
if (root->_data == x)
return root;
BTNode* ret1 = BinaryTreeFind(root->_left, x);
if (ret1)
return ret1;
BTNode* ret2 = BinaryTreeFind(root->_right, x);
if (ret2)
return ret2;
return NULL;
}
这要返回节点的时候,用变量来接受了一下,因为这里还要判断一次,如果不为空,要返回给上个节点。如果使用下面这种写法,返回的时候会再递归一次,浪费效率。
//浪费效率
if (BinaryTreeFind(root->_left, x))
return BinaryTreeFind(root->_left, x);
6.二叉树的销毁
void BinaryTreeDestory(BTNode* root)
{
if (root==NULL)
{
return;
}
BinaryTreeDestory(root->_left);
BinaryTreeDestory(root->_right);
free(root);
}
释放节点要使用后序遍历的方式,因为前面还需要要释放的节点来找后面的节点,释放要节点要从最后面的节点开始释放。
7.层序遍历
层序遍历的意思是,一层一层遍历,一层遍历完后,再遍历二层。
这里要使用前面的队列的数据结构来实现。
其思路是,把二叉树根节点放入队列,然后访问队头,让其子节点带入队列。
这时A出队列,B变为队头,再把B的子节点带入队列。
这时B出队列,C变为队头,再把C的子节点带入队列。
这时C出队列,D变为队头,再把D的子节点带入队列。(这里D的子节点为空)依次按照这个方式到最后。
就会发现出队列的顺序就是二叉树的层序遍历。
代码:
PS:因为其要把节点的子节点带进去,所以要把二叉树的节点存入队列中去。
//存二叉树的节点数据
typedef struct BinaryTreeNode* Qdatatype;
struct QueNode
{
Qdatatype data;
struct QueNode* next;
};
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (QueueIsEmpty(&q)) {
QueuePush(&q,root);
}
while (!QueueIsEmpty(&q)) {
BTNode* front = QueueTop(&q);
QueuePop(&q);
printf("%c ", front->_data);
if (front->_left) {
QueuePush(&q, front->_left);
}
if(front->_right){
QueuePush(&q, front->_right);
}
}
QueueDestroy(&q);
}
测试:
8.判断二叉树是否是完全二叉树
这里要使用前面层序遍历的思路来判断,这里要把空节点一起入队,然后判断空节点是否连续,如果全部都是空节点,那么就是完全二叉树,如果中间有还有节点,那么就不是完全二叉树。
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (QueueIsEmpty(&q)) {
QueuePush(&q, root);
}
while (!QueueIsEmpty(&q)) {
BTNode* front = QueueTop(&q);
QueuePop(&q);
//这里遇到空指针,那么说明到最后一层了,节点都被带到队列中去了
if (front) {
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
else {
break;
}
}
while (!QueueIsEmpty(&q))
{
BTNode* front = QueueTop(&q);
QueuePop(&q);
if (front) {
QueueDestroy(&q); //注意这里退出的时候要释放内存。
return false;
}
}
QueueDestroy(&q);
return true;
}
测试:
四、初阶OJ题
1.判断是否是单值二叉树
如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时,才返回
true
;否则返回false
。
拿一个基准值来判断是否是否相等,如果每个节点都相等,那么就是单值二叉树。否则不是。
代码:
bool flag=true;
bool isSameNode(struct TreeNode* root,int val){
if(root==NULL)
return true;
if(root->val!=val)
flag=false;
isSameNode(root->left,val);
isSameNode(root->right,val);
return true;
}
bool isUnivalTree(struct TreeNode* root){
if(root==NULL)
return true;
flag=true;
isSameNode(root,root->val);
return flag;
}
2.检查两颗树是否相同
给你两棵二叉树的根节点
p
和q
,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
首先要判断节点数是否相同,如果相同,切节点的值都相等,那么两个树相同。
代码:
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);
}
3.判断另一个树,是自己的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
把问题分解为左右子树是否与另一个树相同,如果有一个子树相等,则说明是自己的子树。那么每个节点都要判断一下,要使用两个递归,一个是判断判断相同,一个是遍历节点。
代码:
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);
}
4.判断是否是对称二叉树
给你一个二叉树的根节点
root
, 检查它是否轴对称。
分解判断子树是否相等就可以,但是要注意,判断的方向,并不是单一的方向进行判断。
代码:
bool isSameTree(struct TreeNode* q,struct TreeNode* p)
{
if(p==NULL && q==NULL)
return true;
if(p==NULL || q==NULL)
return false;
if(p->val!=q->val)
return false;
return isSameSubTree(q->left,p->right)&&
isSameSubTree(q->right,p->left);
}
bool isSymmetric(struct TreeNode* root){
if(root==NULL)
return true;
return isSameSubTree(root->left,root->right);
}
总结:
会发现使用递归法,代码其实都比较简单,只不过思路要明确,其主要思想还是分而治之。