树的基本概念
利用表数据结构来进行数据操作时,其访问方式是线性的,当数据量较大时,需要的处理时间会很大,不宜使用,这时我们需要一种新的数据结构,来降低时间的开销,这种数据结构就是树(tree)。
树可以用几种方式定义,定义树的一种自然的方式是递归的方法:一棵树是一些节点的集合,这个集合可以是空集;若非空,则一棵树由称作根(root)的节点r以及0个或多个非空的(子)树T1、T2、…、Tk组成,这些子树中的每一颗的根都被来自根r的一条有向的边所连接。每一颗子树的根叫做根r的儿子(child),而r是每一颗子树的根的父亲(parent),没有儿子的节点称为树叶(leaf),具有相同父亲的节点称为兄弟(sibling)。
树结构中有几个概念,从节点n1到nk的路径(path)定义为节点n1、n2、…、nk的一个序列;这个路径的长(length)为该路径上的边的条数,即k-1;对任意节点ni,其深度为从根到ni的唯一路径的长;一棵树的高度等于它的根的高;一棵树的深度等于它的最深的树叶的深度,该深度总是等于这棵树的高。
树的实现
实现树的一种方法可以使在每一个节点除数据外还要有一些指针,使得该节点的每一个儿子都有一个指针指向它。
struct TreeNode
{
ElementType Element;
TreeNode FirstChild;
TreeNode NextSibling;
};
使用这种定义可应用在一般的树结构中,需要注意的是,每个结点中有两个指针,这两个指针不是指向节点的儿子,只有其中一个指向儿子节点,另一个指向的是它的兄弟节点。树的基本操作中有一项是遍历,对于树的遍历,有三种方式:先序、中序、后序,从名称就能看出来,这三种方式的不同之处就是对于节点内数据的取用顺序的先后。
二叉树
二叉树是树结构的一种,其中每个节点都不能多于两个的儿子。
二叉树的实现
因为一颗二叉树最多有两个儿子,所以可以直接指向它们,树节点的声明在结构上类似于双链表的声明,在声明中,一个节点就是由Key(关键字)信息加上两个指向其他节点的指针(Left和Right)组成的结构。
struct TreeNode
{
Element Element;
TreeNode Left;
TreeNode Right;
};
二叉树的一个重要的应用是在查找中的使用,又称为二叉查找树,是二叉树的一种特殊形式,它所具有的特殊性质是:对于树中的每个结点X,它的左子树中所有的关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值。这意味着该树所有的元素可以用某种统一的方式排序。
二叉查找树的基本操作
返回指向树T中具有关键字X的结点的指针,满足条件的结点不存在则返回NULL,依据二叉查找树的特性,递归的进行查找操作。
TreeNode *Find(ElementType X, TreeNode 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;
}
返回树中的最小元和最大元的位置,依据二叉查找树的特性,最小元从左子树向左搜寻,最大元从右子树向右搜寻,终止点就是最小或者最大的元素。
TreeNode *FindMin(TreeNode *T)
{
if (T == NULL)
return NULL;
else if ( T->Left == NULL)
return T;
else
return FindMin(T->Left);
}
TreeNode *FindMax(TreeNode *T)
{
if (T == NULL)
while (T->Right != NULL)
T = T->Right;
return T;
}
进行插入操作,找到有X存在的话什么也不用做,否则将X插入到遍历的路径上的最后一个点上。
TreeNode *Insert(ElementType X, TreeNode *T)
{
if (T == NULL)
{
T = new TreeNode;
if (T == NULL)
std::cout << “Out of space!!”;
else
{
T->Element = X;
T->Left = T->Right = NULL;
}
}
else if (X < T->Element)
T->Left = Insert(X, T->Left);
else if (X > T->Element)
T->Right = Insert(X, T->Right);
return T;
}
对于删除的操作,需要考虑几种不同的情况:如果节点是一篇树叶,那么它可以立即被删除;如果节点有一个儿子,则该节点可以在其父节点调整指针绕过该节点后被删除;如果节点有两个儿子,用其右子树的最小的数据代替该节点的数据并递归的删除那个节点。
TreeNode *Delete(ElementType X, TreeNode *T)
{
TreeNode *temp;
if (T == NULL)
std::cout << “Element not found”;
else if (X < T->Element)
T->Left = Delete(X, T->Left);
else if (X > T->Element)
T->Right = Delete(X, T->Left);
else if(T->Left && T->Right)
{
temp = FindMin(T->Right);
T->Element = temp->Element;
T->Right = Delete(T->Element, T->Right);
}
else
{
temp = T;
if (T->Left == NULL)
T = T->Right;
else if (T->Right == NULL)
T = T->Left;
delete temp;
}
return T;
}
在这种方式下进行大量Insert和Delete操作后,树的结构变得明显不平衡,所以就会添加平衡的附加条件来将这种二叉树变成平衡结构:任何节点的深度均不得超过深。有一种最古老的平衡查找树:AVL树,笔者将在下一篇笔记中来记录关于AVL树的情况。