一、基本概念
二叉树的每个结点最多有两棵子树,左子树和右子树,次序不可以颠倒。
结点的层次(Level)从根开始定义,根为第一层,根的孩子为第二层。
二叉树的高度:树中结点的最大层次称为树的深度(Depth)或高度。
性质:
1、非空二叉树的第n层上至多有2^(n-1)个元素。
2、深度为h的二叉树至多有2^h-1个结点。
这个其实画个图也能明白
其他二叉树概念
满二叉树:所有终端都在同一层次,且非终端结点的度数为2。可以这样理解,除叶子结点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上.
在满二叉树中若其深度为h,则其所包含的结点数必为2^h-1。
度:子树就是二叉树的分支。度就是分支的数目。
完全二叉树:除了最大的层次即成为一颗满二叉树且层次最大那层所有的结点均向左靠齐,即集中在左面的位置上,不能有空位置。
对于顺序存储的完全二叉树,设一个结点为i则其父节点为i/2,2i为左子节点,2i+1为右子节点。
二、叉树的存储
顺序存储:
将数据结构存在一块固定的数组中。
#define LENGTH 100
typedef char datatype;
typedef struct node{
datatype data;
int lchild,rchild;
int parent;
}Node;
Node tree[LENGTH];
int length;
int root;
优点:存取速度快
缺点:好像除了上面的优点以外都是缺点了
链式存储
主流的存储方式是以链式存储。
链式存储:
typedef char datatype;
typedef struct BinNode{
datatype data;
struct BinNode* lchild;
struct BinNode* rchild;
}BinNode;
typedef BinNode* bintree; //bintree本身是个指向结点的指针 个人不喜欢这么做,因为有时候会忘记他是一个指针
三、遍历
二叉树的遍历方式有四种:前序遍历,中序遍历,后序遍历,层序遍历
前序遍历:根节点->左子树->右子树
中序遍历:左子树->根节点->右子树
后序遍历:左子树->右子树->根节点
层序遍历:按照层从上往下,每层从左往右遍历
区分前面三种遍历方式的方法: 左子树总是在右子树之前遍历,根节点分别为前中后
示例:对下列二叉树进行遍历
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
层序遍历:abcdfeg
遍历的代码实现
递归版本
void preorder( BinNode* root){
if(root){ //如果传入指针不是空,则继续
showdata(root->data); //展示数据的函数
preorder(root->lchild); //递归遍历左子树
preorder(root->rchild); //递归遍历右子树
}
}
上面的代码实现的是前序遍历,中序遍历和后序遍历只需要把展示数据的函数放到中间和后面即可
非递归版本
- 前序遍历
前序遍历,每遍历一个节点,如果有右子树,将右子树压进栈,当遇到当前遍历节点为叶子节点的时候,从栈弹出节点继续遍历,如果当前节点并且栈为空,结束
使用数组简单模拟的栈,无法使用标准库的时候临时使用
#define SIZE 100
typedef struct seqstack{
BinNode* data[SIZE];
int tag[SIZE]; //为后续遍历准备的
int top; //top为数组的下标
}seqstack;
void push(seqstack *s, BinNode* t){
if(s->top == SIZE){
printf("the stack is full\n");
}else{
s->top++;
s->data[s->top]=t;
}
}
BinNode* pop(seqstack *s){
if(s->top == -1){
return NULL;
}else{
s->top--;
return s->data[s->top+1];
}
}
前序遍历代码,这里使用c++ stack的栈
void preorder_dev( BinNode* root){
stack<int> stk; //定义栈
if(!root){
printf("the tree is empty\n");
}
else{
while(root || !stk.empty() ){
while(root){ //只要结点不为空就应该入栈保存,与其左右结点无关
showdata(root); //数据处理操作
stk.push(root);
root= root->lchild;
}
root=stk.top();
stk.pop();
root=root->rchild;
}
}
}
也可以这样 其实道理是一样的 看比较喜欢哪种了
void preorder_dev( BinNode* root){
stack<int> stk; //定义栈
if(!root){
printf("the tree is empty\n");
}
else{
while(root || !stk.empty() ){
while(root){
showdata(root); //数据处理操作
push(&stk,root->rchild);
stk.push(root->rchild);
root= root->lchild;
}
root=stk.top();
stk.pop();
}
}
}
中序遍历
void midorder(BinNode* root){
stack<int> stk; //定义栈
if(!root){
printf("the tree is empty!\n");
}else{
while(root ||!stk.empty() ){
while(root){
stk.push(root);
root= root->lchild;
}
root=stk.top();
stk.pop();
showdata(root);
root=root->rchild;
}
}
}
层序遍历
借助队列 先进先出
void level_tree(BinNode* root){
queue<int> q;
BinNode* temp;
if(!root){
printf("the tree is empty\n");
return ;
}
q.push(root);
while(!q.empty()){
root=q.front();
q.pop();
showdata(root);
if(root->lchild){
q.push(root->lchild);
}
if(root->rchild){
q.push(root->rchild);
}
}
}
求二叉树深度
int hight_tree(Bintree* root){
int left,right;
if(!root){
return 0;
}
left = hight_tree(root->lchild);
right = hight_tree(root->rchild);
return (left>right?left:right)+1;
}