树
树的一些术语与性质
树,是一个没有环路的无向联通图;
根,我们将树中一个不同的顶点视为树的根;
结点的度,结点的子树的数目,例如A的度为3,F的度为0;(与图的度不同?)
树的度,所有子树的度的最大值,例如上面树的度为3
父亲,儿子,兄弟;A是B的父节点,B是A的子节点,BC是兄弟节点
树叶:度为0的节点,例如上图树叶为K,L,F,G,M,I,J
树的高度:等于根节点的高度,等于最大的深度
高度:有的书将叶节点的高度定义为0,有的定义为1
二叉树:每个节点最多有两个儿子的树
性质:
- 一棵树有N个顶点,则这棵树有N-1条边
- 所有节点度的和 = 节点的个数-1 (这个1是由于根节点)
二叉树
二叉树的每个节点最多只有两个儿子,而且左儿子与右儿子不同
节点定义:
class TreeNode
{
ElemType Val;
TreeNode *Left;
TreeNode *Right;
};
二叉树有左右两个指针,分别指向两个二叉树
二叉树的性质:
- 第 i 层最多有2i-1 个节点
- 深度为k的二叉树最多有2k -1个节点 (k>=1) …… 这个根节点的深度为定义为1
- 度为2节点的个数 = 度为0节点(树叶)的个数 -1 (利用所有节点度的和为节点的个数减1这个性质)
表达树
根据后缀表达式创建一个表达式树,
创建步骤如下:
- 依次读取字符
- 如果该字符是操作数,创建操作数节点并入栈;
- 如果该字符是操作符,将栈顶的两个节点T1,T2出栈,并创建操作符节点,将T1,T2分别作为操作符节点的左右儿子,最后将操作符节点入栈
有了表达式树以后,利用树的前序遍历,中序遍历,后序遍历可以分别获得前序表达式,中缀表达式和后缀表达式。
二叉树的遍历
前序遍历,中序遍历,后序遍历
使用递归的方法对二叉树进行遍历:
前序遍历,中序遍历,后序遍历说的是根节点的位置。前序遍历,先输出当前子树的根节点,然后一次对左右子树进行递归遍历。中序遍历,先对左子树进行递归遍历,再输出根节点的值,然后对右子树进行递归遍历。
class TreeNode
{
public:
ElemType Val;
TreeNode *Left;
TreeNode *Right;
public:
TreeNode(ElemType X):Val(X),Left(nullptr),Right(nullptr){}
//前序遍历 Root->Left->Right
void traversalByPreOrder()
{
std::cout << Val << " ";
if(Left != nullptr)
Left->traversalByPreOrder();
if(Right != nullptr)
Right->traversalByPreOrder();
}
//中序遍历 Left -> Root -> Right
void traversalByInOrder()
{
if(Left != nullptr)
Left->traversalByInOrder();
std::cout << Val << " ";
if(Right != nullptr)
Right->traversalByInOrder();
}
//后续遍历 Left -> Right -> root
void traversalByPostOrder()
{
if(Left != nullptr)
Left->traversalByPostOrder();
if(Right != nullptr)
Right->traversalByPostOrder();
std::cout << Val << " ";
}
};
使用循环的方法进行中序遍历
void iter_inorder()
{
stack<TreeNode*> S;
TreeNode *p = root;
for (;;)
{
for (; p; p=p->Left)
S.push(p); //将左子树的左子树的……全入栈
if(S.size() == 0)
break; //栈为空表明遍历完成
p = S.top();
S.pop();
if(!p) //判断节点是否为null
break;
cout << p->Val <<" ";
p = p->Right; 指向其右子树做同样的操作
}
}
层序遍历
使用队列数据结构,算法步骤如下
- 首先将根节点入列,
- 将队列中的元素出列,打印输出,并将该元素的儿子节点依次入列
- 重复2,直到队列为空
void LevelOrderTaversal()
{
queue<TreeNode *> T;
T.push(root);
while(T.size() != 0 )
{
TreeNode* p = T.front();
T.pop();
std::cout << p->Val << " ";
if(p->Left != NULL)
{
T.push(p->Left);
}
if(p->Right != NULL)
{
T.push(p->Right);
}
}
}
Threaded Binary Trees(线索二叉树)
线索二叉树可以让二叉树以一种既定的方式进行遍历。
如果只给我们一个树叶节点,我们无法知道它的父节点是什么,而且next指向了null。从而也就无法依靠一个叶节点进行对树的遍历。而线索二叉树可以,它给一些节点添加了额外的信息,不用递归,甚至不用额外的存储空间就可以实现对线索二叉树的遍历。
线索二叉树的定义
对于一个二叉树,如果T->Right == null,则使T->Right指向以中序遍历为顺序的T节点的后一个节点;如果T->Left == null ,则使T->Left,指向以中序遍历为顺序的T节点的前一个节点。(如果存在的话)例如C的左右儿子的指向怎么判断?中序遍历的顺序为根节点中间输出,即A B C D E F G H I, 所以,C的左儿子指向B,右儿子指向D
这种定义方式,规定了线索二叉树是以中序方式进行遍历的。
Algorithm traverse(t):
Input: a pointer t to a node (or nil)
If t = nil, return.
Else:
traverse(left-child(t))
Visit t
traverse(right-child(t))
例如对节点D进行遍历,
会先对C进行遍历,而由于有了线索,并不会直接输出C,反而是对C左节点指向的B进行遍历,再会先对A先进行遍历。最终遍历输出的就是对F进行中序遍历输出的序列
二叉搜索树
定义
二叉搜索树的每个节点都包含一个key值,key值是不同的;
对于任意一个节点,若它的左儿子不为null,则左儿子的key值比它小;若它的右儿子不为null,则右儿子的key值比它大
左子树与右子树也一定是二叉搜索树
操作
Find
在一棵二叉树中找到key为X的节点。
递归查找,如果所找的元素比当前节点小,则对当前节点的左子树进行递归,如果所找的元素比当前节点大,则对当前节点的右子树进行递归,否则的话就是找到了该元素,直接返回。
不用递归的方法,T = root从根元素开始。如果,要找的元素比T->Element大,则将T移动到T->Right;小的话,就移动到T->Left.不断循环,知道移动到恰好相等的那个节点。
Position Find(int X,BSTree T)
{
if(T == null) return; //递归出口不要忘记
if(T->Element > X) return Find(X,T->Left);
if(T->Element < X) return Find(X,T->Right);
else return T;
}
Position Iter_Find( int X, BSTree T )
{
while ( T ) {
if ( X == T->Element ) return T ;
if ( X < T->Element )
T = T->Left ;
else
T = T-> Right ;
}
return NULL ;
}
FindMin与FindMax
找到一个二叉树中key值最大或最小的元素。
根据二叉搜索树定义,最小的元素一定在左子树上,不断的向左子树进行查找就可以找到。
//递归版本
Position FindMin(BSTree T)
{
if(T == null) return null; //空树的情况
if(T->Left == null) return T; //找到的最左边的元素
else return FindMin(T->Left);
}
//非递归版本
Position FindMin(BSTree T)
{
if(T)
while(T->Left != null)
T=T->Left;
return T;
}
插入
SearchTree Insert( ElementType X, SearchTree T )
{
if(T == null) T = TreeNode(X); //如果为空树,则直接新建一个节点
if(X < T->Element) T->Left = Insert(X, T->Left); //如果比T的元素值小,就向左子树插入
if(X > T->Element) T->Right = Insert(X, T->Right);
return T;
}
删除
情形1:左右儿子都存在的情况
从右子树中找到一个最小的节点,将值复制给要删除的节点,最后转变为删除右子树最小元素的那个节点
TmpCell = FindMin(T->Right);
T->Element = TmpCell->Element;
T->Right = DeleteNode(T->Element, T->Right);
情形二:只有一个儿子或没有儿子的情况
可以直接这样写:
Temp = T;
if(T->Left == null) T = T->Right;
else if(T->Right == null) T = T->Left;
free(Temp);