二叉树的基本操作(C 语言版)

简介: 二叉树的基本操作(C 语言版)

1 二叉树的定义

二叉树的图长这样:

d859149850bd66502e566f2f7e191469.png

二叉树是每个结点最多有两个子树的树结构,常被用于实现二叉查找树和二叉堆。二叉树是链式存储结构,用的是二叉链,本质上是链表。二叉树通常以结构体的形式定义,如下,结构体内容包括三部分:本节点所存储的值、左孩子节点的指针、右孩子节点的指针。


struct TreeNode {//树的结点
     int data;//数据域
     struct TreeNode* lchild;//指向左孩子节点
     struct TreeNode* rchild;//指向右孩子节点 
 };

当然,我们也可以为我们的的树节点结构体重新定义一下名字,使用 C 语言中的 typedef 方法就可以了。


struct TreeNode {//树的结点
     int data;//数据域
     struct TreeNode* lchild;//指向左孩子节点
     struct TreeNode* rchild;//指向右孩子节点 
 } BiNode, *BiTree;

2 二叉树的建立

二叉树的操作通常使用递归方法,二叉树的操作可以分为两类,一类是需要改变二叉树的结构的,比如二叉树的创建、节点删除等等,这类操作,传入的二叉树的节点参数为二叉树指针的地址,这种参入传入,便于更改二叉树结构体的指针(即地址)。


如下是二叉数创建的函数,这里我们规定,节点值必须为大于 0 的数值,如果不是大于 0 的数,则表示结束继续往下创建子节点的操作。然后我们使用递归的方法以此创建左子树和右子树。


比如说,建立这个二叉树:


        5

       / \

      3   8

     /   / \  

    2   6   9

首先根据这个二叉树,我们先模拟一下:


先序输入:5 3 2 0 0 0 8 6 0 0 9 0 0


先序遍历输出:5 3 2 8 6 9


中序遍历输出:2 3 5 6 8 9


后序遍历输出:2 3 6 9 8 5


层次遍历输出:5 3 8 2 6 9


下面通过先序的方式建立二叉树:


第一种建立二叉树:使用一级指针


//先序建立二叉树
 BiTree CreateTree() {
     int data;
     scanf("%d", &data);//根节点数据
     BiTree root;
     if (data <= 0) {
         return NULL;
     } else {
         root = (BiTree)malloc(sizeof(BiNode));
         root->data = data;
         root->lchild = CreateTree();
         root->rchild = CreateTree();
     }
     return root;
 }


测试使用:


//测试
 int main() {
     //BiTree root;
     //CreateTree(&root);
     BiTree root = NULL; 
     root = CreateTree();//创建树 
     PreOrderTraverse(root);//先序遍历输出 
     return 0;
 }

第二种建立二叉树:使用二级指针


//先序建立二叉树
 void CreateTree(BiTree* root) {
     int data;
     scanf("%d", &data);//根节点数据
     if (data <= 0) {
         *root = NULL;
     } else {
         (*root) = (BiTree)malloc(sizeof(BiNode));
         (*root)->data = data;
         CreateTree(&((*root)->lchild));
         CreateTree(&((*root)->rchild));
     }
 }

测试使用:


//测试
 int main() {
     BiTree root;
     CreateTree(&root);
     //BiTree root = NULL; 
     //root = CreateTree();//创建树 
     PreOrderTraverse(root);//先序遍历输出 
     return 0;
 }

如果没有要求的话,我比较倾向于第一种!


3 二叉树的遍历

3.1 先序遍历

先序遍历的思路:


先序遍历的过程是首先访问根结点,然后先序遍历根的左子树,最后先序遍历根的右子树。对于根的左子树和右子树,遍历的过程相同。


方案一:递归


采用递归的方式来实现:


//先序遍历二叉树:递归实现 
 void PreOrderTraverse(BiTree root) {
     if (root) {
         printf("%d ", root->data);
         PreOrderTraverse(root->lchild);
         PreOrderTraverse(root->rchild);
     }   
 }

方案二:非递归


非递归实现:引入辅助栈


//先序遍历二叉树:非递归实现 
 void PreOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     if (root != NULL) {
         //根节点入栈
         top++;
         stack[top] = root;
         //栈不空时循环
         while (top > -1) {
             //出栈并访问该节点
             p = stack[top];
             top--;
             printf("%d ", p->data);
             //右孩子入栈
             if (p->rchild != NULL) {
                 top++;
                 stack[top] = p->rchild;
             }
             //左孩子入栈
             if (p->lchild != NULL) {
                 top++;
                 stack[top] = p->lchild;
             } 
         } 
     } 
 }


3.2 中序遍历

中序遍历的思路


中序遍历的过程是首先中序遍历左子树,然后访问根结点,最后中序遍历根的右子树。对于根的左子树和右子树,遍历的过程相同。


方案一:递归


采用递归的方式来实现:

//中序遍历二叉树:递归实现 
 void InOrderTraverse(BiTree root) {
     if (root) {
         InOrderTraverse(root->lchild);
         printf("%d ", root->data);
         InOrderTraverse(root->rchild);
     }
 }

方案二:非递归


非递归实现:引入辅助栈

//中序遍历二叉树:非递归实现 
 void InOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     if (root != NULL) {
         p = root;
         while (top > -1 || p != NULL) {
             //扫描p的所有左节点并入栈
             while (p != NULL) {
                 top++;
                 stack[top] = p;
                 p = p->lchild;
             } 
             if (top > -1) {
                 //出栈并访问节点
                 p = stack[top];
                 top--;
                 printf("%d ", p->data);
                 //扫描右孩子
                 p = p->rchild; 
             }
         }
     } 
 }


3.3 后序遍历

后序遍历的思路


后序遍历的过程是首先后序遍历左子树,然后后序遍历根的右子树,最后访问根结点。


方案一:递归


采用递归的方式来实现:

//后序遍历二叉树:递归实现 
 void PostOrderTraverse(BiTree root) {
     if (root) {
         PostOrderTraverse(root->lchild);
         PostOrderTraverse(root->rchild);
         printf("%d ", root->data);
     }
 }

方案二:非递归


非递归实现:引入辅助栈


//后序遍历二叉树:非递归实现 
 void PostOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     int sign; 
     if (root != NULL) {
         do {
             //root节点入栈
             while (root != NULL) {
                 top++;
                 stack[top] = root;
                 root = root->lchild;
             } 
             //p指向栈顶前一个已访问节点
             p = NULL;
             //置root为已访问
             sign = 1;
             while (top != -1 && sign) {
                 //取出栈顶节点
                 root = stack[top];
                 //右孩子不存在或右孩子已访问则访问root
                 if (root->rchild == p) {
                     printf("%d ", root->data);
                     top--;
                     //p指向被访问的节点
                      p = root; 
                  } else {
                     //root指向右孩子节点
                     root = root->rchild;
                     //置未访问标记
                     sign = 0; 
                  }
             }
         } while (top != -1);
     } 
 }

3.4 层次遍历

层次遍历的思路:


思路:在进行层次遍历时,对一层结点访问完后再按照它们的访问次序对各个结点的左孩子和右孩子顺序访问,这样一层一层地进行,先遇到的结点先访问,这棵二叉树的层次遍历序列为 5 3 8 2 6 9,先上到下,先左到右。实现层次遍历用队列比较方便,因为是先进先出(FIFO)。首先把 5 入队,然后再输出队首元素,并且把队首元素的左结点和右结点入队(如果有的话),以此类推,输出的序列就是层次遍历啦


采用非递归方式实现:引入队列

//层次遍历:非递归实现 
 void LevelOrderTraverseNonRec(BiTree root) {
     BiTree p;
     Push(root);
     while (!empty()) {//empty()判断队列是否为空
         p = Pop();//出队
         printf("%d ", p->data);//输出队首结点 
         if (p->lchild) {//把Pop掉的结点的左子结点加入队列 
             Push(p->lchild);
         }
         if (p->rchild) {//把Pop掉的结点的右子结点加入队列
             Push(p->rchild);
         }
     }
 }

附队列部分代码:

//队列结构体 
typedef struct queue {
  struct TreeNode* numQueue[MaxSize];
  int front;
  int rear; 
} Queue; 
Queue queue;//声明全局变量 
//初始化队列
void initQueue() {
  queue.front = 0;
  queue.rear = 0;
} 
//入队
void Push(BiTree root) {
  queue.numQueue[++queue.rear] = root;
} 
//出队
BiTree Pop() {
  return queue.numQueue[++queue.front];
}
//判断队列是否为空
int empty() {
  return queue.rear == queue.front;
}


4 求二叉树的最大深度

一棵树的最大深度,左子树和右子树的最大深度 + 1 即可.


采用递归的方式来实现:


//二叉树的最大深度
 int maxDepth(BiTree root) {
     if (root) {
         int maxLeft = maxDepth(root->lchild);
         int maxRight = maxDepth(root->rchild);
         if (maxLeft > maxRight) {
             return maxLeft + 1; 
         } else {
             return maxRight + 1;
         }
     }
     return 0;
 }

5 求二叉树的高度

采用递归的方式来实现

//二叉树高度
 int BiTreeHeight(BiTree root) {
     if (root) {
         int leftHeight = BiTreeHeight(root->lchild);
         int rightHeight = BiTreeHeight(root->rchild); 
         return (leftHeight > rightHeight) ? (leftHeight + 1) : (rightHeight + 1); 
     }
     return 0;
 }

6 求二叉树叶子节点的个数

一个节点的度就是一个节点的分支数,二叉树中的节点按照度来分类的话,分为三类,度分别为 0、1、2 的节点,我们将其数量表示为 n0、n1、n2,且我们将一棵树的总结点数量用 N 来表示。那么一个数的叶子节点的数量即为 n0,且有 N = n0 + n1 + n2。


如果我们按照一棵树的子节点数来计算一棵树的总结点数,那么一棵二叉树树的总结点数 N = 2 * n2 + n1 + 1,最后一个 1 表示树的根节点。我们将关于 N 的两个等式合并,则有结论:n0 = n2 + 1。


采用递归的方式来实现

//叶子节点
 int LeafNodeNum(BiTree root) {
     if (root == NULL) {
         return 0;
     }
     if (root->lchild == NULL && root->rchild == NULL) {
         return 1;
     } else {
         return LeafNodeNum(root->lchild) + LeafNodeNum(root->rchild);
     }
 }

7 求第 k 层节点的个数

采用递归的方式来实现:

//求第k层节点个数
int LevelNodeNum(BiTree root, int k) {
  if (root == NULL || k < 1) {
  return 0;
  }
  if (k == 1) {
  return 1;
  }
  return LevelNodeNum(root->lchild, k - 1) + LevelNodeNum(root->rchild, k - 1);
}

 

8 求二叉树总节点个数

采用递归的方式来实现:

//求二叉树总节点个数
 int CountNode(BiTree root) {
     if (root) {
         if ((root->lchild == NULL) && (root->rchild == NULL)) {
             return 1;
         } else {
             return CountNode(root->lchild) + CountNode(root->rchild) + 1;
         }
     }
     return 0;
 }

9 查找元素为 x 的节点

采用递归的方式来实现:


//查找元素为 x 的节点
 BiTree SearchNode(BiTree root, int x) {
     if (root) {
         if (root->data == x) {
             return root;
         } else {
             BiTree p;
             p = SearchNode(root->lchild, x);
             if (!p) {
                 p = SearchNode(root->rchild, x);
             }
             return p;
         }
     }
     return NULL;
 }


10 二叉树的操作完整代码

#include <stdio.h>
 #include <stdlib.h>
 #define MaxSize 100 
 //树的结构 
 typedef struct TreeNode {
     int data;//数据域
     struct TreeNode* lchild;//指向左孩子节点
     struct TreeNode* rchild;//指向右孩子节点 
 } BiNode, *BiTree;
 //队列结构体 
 typedef struct queue {
     struct TreeNode* numQueue[MaxSize];
     int front;
     int rear; 
 } Queue; 
 Queue queue;//声明全局变量 
 //初始化队列
 void initQueue() {
     queue.front = 0;
     queue.rear = 0;
 } 
 //入队
 void Push(BiTree root) {
     queue.numQueue[++queue.rear] = root;
 } 
 //出队
 BiTree Pop() {
     return queue.numQueue[++queue.front];
 }
 //判断队列是否为空
 int empty() {
     return queue.rear == queue.front;
 } 
 //构造二叉树
 BiTree CreateTree() {
     int data;
     scanf("%d", &data);//根节点数据
     BiTree root;
     if (data <= 0) {
         return NULL;
     } else {
         root = (BiTree)malloc(sizeof(BiNode));
         root->data = data;
         //printf("请输入%d的左子树:", root->data);
         root->lchild = CreateTree();
         //printf("请输入%d的右子树:", root->data);
         root->rchild = CreateTree();
     }
     return root;
 }
 //先序遍历二叉树:递归实现 
 void PreOrderTraverse(BiTree root) {
     if (root) {
         printf("%d ", root->data);
         PreOrderTraverse(root->lchild);
         PreOrderTraverse(root->rchild);
     }   
 }
 //先序遍历二叉树:非递归实现 
 void PreOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     if (root != NULL) {
         //根节点入栈
         top++;
         stack[top] = root;
         //栈不空时循环
         while (top > -1) {
             //出栈并访问该节点
             p = stack[top];
             top--;
             printf("%d ", p->data);
             //右孩子入栈
             if (p->rchild != NULL) {
                 top++;
                 stack[top] = p->rchild;
             }
             //左孩子入栈
             if (p->lchild != NULL) {
                 top++;
                 stack[top] = p->lchild;
             } 
         } 
     } 
 }
 //中序遍历二叉树:递归实现 
 void InOrderTraverse(BiTree root) {
     if (root) {
         InOrderTraverse(root->lchild);
         printf("%d ", root->data);
         InOrderTraverse(root->rchild);
     }
 } 
 //中序遍历二叉树:非递归实现 
 void InOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     if (root != NULL) {
         p = root;
         while (top > -1 || p != NULL) {
             //扫描p的所有左节点并入栈
             while (p != NULL) {
                 top++;
                 stack[top] = p;
                 p = p->lchild;
             } 
             if (top > -1) {
                 //出栈并访问节点
                 p = stack[top];
                 top--;
                 printf("%d ", p->data);
                 //扫描右孩子
                 p = p->rchild; 
             }
         }
     } 
 }
 //后序遍历二叉树:递归实现 
 void PostOrderTraverse(BiTree root) {
     if (root) {
         PostOrderTraverse(root->lchild);
         PostOrderTraverse(root->rchild);
         printf("%d ", root->data);
     }
 } 
 //后序遍历二叉树:非递归实现 
 void PostOrderTraverseNonRec(BiTree root) {
     BiTree stack[MaxSize];
     BiTree p;
     int top = -1;
     int sign; 
     if (root != NULL) {
         do {
             //root节点入栈
             while (root != NULL) {
                 top++;
                 stack[top] = root;
                 root = root->lchild;
             } 
             //p指向栈顶前一个已访问节点
             p = NULL;
             //置root为已访问
             sign = 1;
             while (top != -1 && sign) {
                 //取出栈顶节点
                 root = stack[top];
                 //右孩子不存在或右孩子已访问则访问root
                 if (root->rchild == p) {
                     printf("%d ", root->data);
                     top--;
                     //p指向被访问的节点
                      p = root; 
                  } else {
                     //root指向右孩子节点
                     root = root->rchild;
                     //置未访问标记
                     sign = 0; 
                  }
             }
         } while (top != -1);
     } 
 }
 //层次遍历:非递归实现 
 void LevelOrderTraverseNonRec(BiTree root) {
     BiTree p;
     Push(root);
     while (!empty()) {//empty()判断队列是否为空
         p = Pop();//出队
         printf("%d ", p->data);//输出队首结点 
         if (p->lchild) {//把Pop掉的结点的左子结点加入队列 
             Push(p->lchild);
         }
         if (p->rchild) {//把Pop掉的结点的右子结点加入队列
             Push(p->rchild);
         }
     }
 } 
 //二叉树的最大深度
 int maxDepth(BiTree root) {
     if (root) {
         int maxLeft = maxDepth(root->lchild);
         int maxRight = maxDepth(root->rchild);
         if (maxLeft > maxRight) {
             return maxLeft + 1; 
         } else {
             return maxRight + 1;
         }
     }
     return 0;
 } 
 //二叉树高度
 int BiTreeHeight(BiTree root) {
     if (root) {
         int leftHeight = BiTreeHeight(root->lchild);
         int rightHeight = BiTreeHeight(root->rchild); 
         return (leftHeight > rightHeight) ? (leftHeight + 1) : (rightHeight + 1); 
     }
     return 0;
 }
 //叶子节点
 int LeafNodeNum(BiTree root) {
     if (root == NULL) {
         return 0;
     }
     if (root->lchild == NULL && root->rchild == NULL) {
         return 1;
     } else {
         return LeafNodeNum(root->lchild) + LeafNodeNum(root->rchild);
     }
 } 
 //求第k层节点个数 
 int LevelNodeNum(BiTree root, int k) {
     if (root == NULL || k < 1) {
         return 0;
     }
     if (k == 1) {
         return 1;
     }
     return LevelNodeNum(root->lchild, k - 1) + LevelNodeNum(root->rchild, k - 1);
 }  
 //求二叉树总节点个数
 int CountNode(BiTree root) {
     if (root) {
         if ((root->lchild == NULL) && (root->rchild == NULL)) {
             return 1;
         } else {
             return CountNode(root->lchild) + CountNode(root->rchild) + 1;
         }
     }
     return 0;
 } 
 //查找元素为 x 的节点
 BiTree SearchNode(BiTree root, int x) {
     if (root) {
         if (root->data == x) {
             return root;
         } else {
             BiTree p;
             p = SearchNode(root->lchild, x);
             if (!p) {
                 p = SearchNode(root->rchild, x);
             }
             return p;
         }
     }
     return NULL;
 } 
 //测试
 int main() {
     //测试数据:5 3 2 0 0 0 8 6 0 0 9 0 0 
     //BiTree root;
     //CreateTree(&root);
     BiTree root = NULL; 
     root = CreateTree();//创建树 
     printf("先序非递归遍历:");
     PreOrderTraverseNonRec(root);
     printf("\n中序非递归遍历:");
     InOrderTraverseNonRec(root);
     printf("\n后序非递归遍历:");
     PostOrderTraverseNonRec(root);
     printf("\n先序递归遍历:");
     PreOrderTraverse(root);//先序遍历输出
     printf("\n中序递归遍历:");
     InOrderTraverse(root);//中序遍历输出 
     printf("\n后序递归遍历:");
     PostOrderTraverse(root);//中序遍历输出 
     printf("\n层次非递归遍历:");
     LevelOrderTraverseNonRec(root);//层次遍历输出 
     printf("\n二叉树的深度为:%d",maxDepth(root));
     printf("\n二叉树的高度为:%d",BiTreeHeight(root));
     printf("\n叶子节点为:%d",LeafNodeNum(root));
     printf("\n总节点为:%d", CountNode(root));
     printf("\n第3层节点个数为:%d",LevelNodeNum(root, 3));
     BiTree q;
     q = SearchNode(root, 9);
     if (q) {
         printf("\n查找到了 :%d", q->data);
     } else {
         printf("\n没有查找到 9 ");
     }
     return 0;
 }


目录
相关文章
|
7月前
|
C语言
C语言写二叉树
C语言写二叉树
57 0
|
存储 算法 C语言
【C语言数据结构(基础版)】第五站:树和二叉树(上)
【C语言数据结构(基础版)】第五站:树和二叉树
118 0
|
存储 算法 Java
【数据结构】二叉树的前中后序遍历(C语言)
【数据结构】二叉树的前中后序遍历(C语言)
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
112 8
|
3月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
3月前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
3月前
|
存储 算法 C语言
C语言手撕实战代码_二叉树_构造二叉树_层序遍历二叉树_二叉树深度的超详细代码实现
这段代码和文本介绍了一系列二叉树相关的问题及其解决方案。其中包括根据前序和中序序列构建二叉树、通过层次遍历序列和中序序列创建二叉树、计算二叉树节点数量、叶子节点数量、度为1的节点数量、二叉树高度、特定节点子树深度、判断两棵树是否相似、将叶子节点链接成双向链表、计算算术表达式的值、判断是否为完全二叉树以及求二叉树的最大宽度等。每道题目均提供了详细的算法思路及相应的C/C++代码实现,帮助读者理解和掌握二叉树的基本操作与应用。
|
3月前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
4月前
|
机器学习/深度学习 存储 C语言
C语言的二叉树
1.树概念及结构 1.1树的概念 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 补充定义: 有一个特殊的结点,称为根结点,根节点没有前驱结点。除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。 1.2 树的相关概念 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6 叶节点或终端
|
7月前
|
C语言
【C语言/数据结构】二叉树(层序遍历|判断完全二叉树|性质)
【C语言/数据结构】二叉树(层序遍历|判断完全二叉树|性质)
342 52