目录 2、二叉树 3、二叉查找树 |
树的基本概念:
节点的度:一个节点含有的子树的个数称为该节点的度;
树叶节点:度为0的节点称为树叶节点;
树的深度:对任意节点ni ,ni的深度为从根到ni的唯一路径长。根的深度为0。
树的高度:ni的高是从ni到一片树叶的最长路径长。因此所有的树叶的高度都是0。一颗树的高等于它的根的高
树的度:一棵树中,最大的节点的度称为树的度;
【概念】
二叉树是每个节点都不能有多于两个儿子节点的树。
二叉树的性质:平均二叉树的深度要比N小的多(N为节点个数)。
【应用】
二叉树的应用:
C编译器源代码中,二叉树的中序遍历形式被用来存放C 语言中的表达式。在游戏设计领域,许多棋类游戏的步骤都是按树型结构编写。此外还有很多就不一一列举了。
【表达式树分析】
1、什么是表达式树
表达式树:表达式树的树叶是操作数(operand),加常数或变量名字,而其他的结点为操作数(operator)。由于这里所有的操作都是二元的,因此这棵特定的树正好是二叉树,虽然这是最简单的情况,但是结点还是有可能含有多于两个的儿子,这里我们不讨论。
如下图是(a+b*c)+((d*e+f)*g)的表达式树。我们可以将通过递归计算左子树和右子树所得到的值应用在根处的运算符操作中而算出表达式树的值。
这里算术表达式有三种形式:后缀表达式[abc*+de*f+g*+],中缀表达式[(a+b*c)+((d*e+f)*g)],前缀表达式[++a*bc*+*defg](不太常用)。
我们可以先递归的产生一个带括号的左表达式,再打印出在根处的运算符,最后再递归的产生一个带括号的右表达式。通过对树的(左,节点,右)称为中序遍历(遍历的方式主要是根据中间节点访问的次序决定的)产生前缀表达式。
以此类推,可以得到后缀表达式和前缀表达式。
2、构造表达式树
后缀表达式严格按照从左到右进行计算的模式 符合计算机运行方式,而中缀表达式需要计算机遇到符号后向后扫描一位若为括号或优先级更高的操作符还需要向后继续扫描,计算机处理较为麻烦。
后缀表达式的特点就是计算机运算非常方便。而栈可以把一般的中缀表达式变成后缀表达式(中缀表达式转换为后缀表达式这里不讨论),并且计算后缀表达式得出结果,因此此应用在计算器中非常常用;计算机处理过程只需要顺序读入,如果遇到数字,则放入栈中,如果是运算符,则将两个栈中数字取出进行运算;比如1+2的后缀表达式为12+;
现在我们来分析后缀表达式的构造:
设输入为:ab+cde+**
前两个符号是操作数,因此创建两棵单结点树并将指向它们的指针压入栈中。
接着,”+”被读入,因此指向两棵树的指针被弹出,形成一棵新的树,并将指向它的指针压入栈中。
然后,c,d和e被读入,在单个结点树创建后,指向对应的树的指针被压入栈中。
接下来读入”+”号,因此两棵树合并。
继续进行,读入”*”号,因此,弹出两棵树的指针合并形成一棵新的树,”*”号是它的根。
最后,读入一个符号,两棵树合并,而指向最后的树的指针被留在栈中。
【表达式树程序】
输入后缀表达式,创建表达式树,并通过前中、后、序遍历将表达式树输出。
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
//树结点的设计
typedef struct node
{
//数字和运算符
union
{
char operatorr;
int data;
};
struct node *lchild;
struct node *rchild;
}TreeNode;
//树栈的设计
typedef struct
{
TreeNode *buf[MAX];
int n;//指示栈顶位置
}TreeStack;
//创建空栈
TreeStack *MakeEmpty_Stack()
{
TreeStack *pstack;
pstack = (TreeStack *)malloc(sizeof(TreeStack));
pstack->n = -1;
return pstack;
}
//入栈
int Push_Stack(TreeStack *p,TreeNode *data)
{
p->n ++;
p->buf[p->n] = data;
return 0;
}
//出栈
TreeNode *Pop_Stack(TreeStack *p)
{
TreeNode *data;
data = p->buf[p->n];
p->n --;
return data;
}
//判断空栈
int IsEmpty_Stack(TreeStack *p)
{
if(p->n == -1)
{
return 1;
}else{
return 0;
}
}
//后缀表达式树的创建
TreeNode *CreateExpressTree(char *str,TreeStack *p)
{
int i = 0;
TreeNode *current;
TreeNode *left,*right;
while(str[i])
{
if(str[i] == ' ')//跳过空格
{
i++;
continue;
}
if(str[i] >= '0' && str[i] <= '9')//如果表达式中的字符是数字,创建新的树节点,将其压入栈中
{
current = (TreeNode *)malloc(sizeof(TreeNode));
current->data = str[i] - '0';
current->lchild = current->rchild = NULL;
Push_Stack(p,current);
}else{//如果表达式中的字符是运算符,就从栈中弹出俩个节点分别作为运算符节点的右左子树,然后再将该运算符节点弹出
current = (TreeNode *)malloc(sizeof(TreeNode));
current->operatorr = str[i];
right = Pop_Stack(p);
left = Pop_Stack(p);
current->lchild = left;
current->rchild = right;
Push_Stack(p,current);
}
i++;
}
return p->buf[p->n];//返回树的根节点
}
//打印结点
void print_node(TreeNode *p)
{
if(p->lchild == NULL && p->rchild == NULL)
{
printf("%d ",p->data);
}else{
printf("%c ",p->operatorr);
}
return;
}
//访问结点
int vist_node(TreeNode *p)
{
print_node(p);
return 0;
}
//树的后序遍历
int PostOrder(TreeNode *p)
{
if(p != NULL)
{
PostOrder(p->lchild);//左
PostOrder(p->rchild);//右
vist_node(p);//根
}
return 0;
}
//树的中序遍历
int InOrder(TreeNode *p)
{
if(p != NULL)
{
InOrder(p->lchild);//左
vist_node(p);//根
InOrder(p->rchild);//右
}
return 0;
}
//树的前序遍历
int PreOrder(TreeNode *p)
{
if(p != NULL)
{
vist_node(p);//根
PreOrder(p->lchild);//左
PreOrder(p->rchild);//右
}
return 0;
}
int main()
{
TreeNode *treehead;//存放树的头结点
TreeStack *pstack;//栈用于读入表达式,输出树
int i = 0;
char buf[MAX];//存放输入的表达式
while((buf[i++] = getchar()) != '\n' && i < MAX);//输入后缀表达式
buf[i-1] = 0;
pstack = MakeEmpty_Stack();
treehead = CreateExpressTree(buf,pstack);//创建表达式树
printf("PostOrder: ");
PostOrder(treehead);//后序遍历表达式树
printf("\n");
printf("InOrder: ");
InOrder(treehead);//中序遍历表达式树
printf("\n");
printf("PreOrder: ");
PreOrder(treehead);//前序遍历表达式树
printf("\n");
system("pause");
return 0;
}
【概念】
使二叉树成为二叉查找树的性质是:对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中的所有关键字值大于X的关键字值。
二叉查找树的平均深度为O(logN)
【程序】
二叉查找树的相关操作程序实现:
#include "tree.h"
#include <stdlib.h>
#include "fatal.h"
struct TreeNode
{
ElementType Element;
SearchTree Left;
SearchTree Right;
};
SearchTree
MakeEmpty( SearchTree T )//将二叉查找树置空
{
if( T != NULL )
{
MakeEmpty( T->Left );
MakeEmpty( T->Right );
free( T );
}
return NULL;
}
Position
Find( ElementType X, SearchTree T )//利用二叉查找树的性质进行查找:左子树小于根,右子树大于根
{
if( T == NULL )
return NULL;
if( X < T->Element )
return Find( X, T->Left );
else
if( X > T->Element )
return Find( X, T->Right );
else
return T;
}
Position
FindMin( SearchTree T )
{
if( T == NULL )
return NULL;
else
if( T->Left == NULL )
return T;
else
return FindMin( T->Left );
}
Position
FindMax( SearchTree T )
{
if( T != NULL )
while( T->Right != NULL )
T = T->Right;
return T;
}
//二叉查找树中插入和删除较为复杂,主要还是要保持二叉查找树的性质
SearchTree
Insert( ElementType X, SearchTree T )
{
if( T == NULL )//当二叉查找树为空树时
{
T = malloc( sizeof( struct TreeNode ) );//为空树的根节点动态分配内存
if( T == NULL )
FatalError( "Out of space!!!" );
else
{
T->Element = X;//给根节点关键字赋值
T->Left = T->Right = NULL;//将左右子树置空
}
}
else
if( X < T->Element )//当待插入关键字的值小于节点T的关键字的值,则递归调用Insert( X, T->Left )
T->Left = Insert( X, T->Left );
else
if( X > T->Element )
T->Right = Insert( X, T->Right );
//如果X已经在树中,则直接结束,不做改动。
return T; //返回树的根节点
}
//正如许多数据结构一样,最困难的操作是删除。
SearchTree
Delete( ElementType X, SearchTree T )
{
Position TmpCell;
if( T == NULL )
Error( "Element not found" );
//先找到要删除的元素的位置:
else
if( X < T->Element ) /* Go left */
T->Left = Delete( X, T->Left );
else
if( X > T->Element ) /* Go right */
T->Right = Delete( X, T->Right );
//判断被删除元素的左右子树是否为空
else /* Found element to be deleted */
if( T->Left && T->Right ) /* Two children */
{
//被删除元素的左右子树均不为空,则用右子树中的最小元素代替被删元素,然后递归的删除右子树中的那个最小元素。
TmpCell = FindMin( T->Right );
T->Element = TmpCell->Element;
T->Right = Delete( T->Element, T->Right );
}
else //如果被删元素有一个子树为空,则直接用非空的子树根节点代替被删节点。如果左右子树均为空,则直接删除。
{
TmpCell = T;
if( T->Left == NULL ) /* Also handles 0 children */
T = T->Right;
else if( T->Right == NULL )
T = T->Left;
free( TmpCell );
}
return T;
}
ElementType
Retrieve( Position P )
{
return P->Element;
}
二叉查找树的平均深度为O(logN),但是当我们不断地进行删除插入操作后,二叉查找树将会变得非常的不平衡(例如:左子树很“深”,右子树很”浅“),这对于我们后续的操作的极为不利的。为了避免这样的情况发生,AVL树就诞生了。