1、 二叉树
1)二叉树有两种表示方式:链表与数组,在数组中存放信息、索引、左右节点,无疑二叉树的产生将减短查找时间。对二叉树的遍历使用广度优先遍历与深度优先遍历,在深度优先遍历中使用前序、中序、后序。一般的我们在进行深度优先遍历的时候采用的是递归的思想。
其一般的定义如下:
template<class T>
class BSTNode
{
public:
T key;
BSTNode *left,*right;
BSTNode()
{
left=right=0;
}
BSTNode(const T&el,BSTNode *l=0,BSTNode *r=0)
{
key=el;
left=l;
right=r;
}
};
2)但是使用递归的时候。递归调用不是纯粹表面上的函数自身调用,而是一个函数的实例调用另一个函数的实例,这些调用在内部表示为不同的记录,由操作系统进区分。
对于每个函数,它的状态是由函数的所有自动变量的内容、函数参数的值、表明从哪里返回到调用函数的返回地址所决定的,包含这些信息的数据区叫做记录或者栈结构。这个记录是函数的私有信息池,存储了程序正确执行并正确返回到调用它的函数所需的所有信息,活动记录的寿命一般很短,在函数开始执行时,对齐的空间分配是动态进行的。
由此可知,递归调用,系统内部付出的代价会很高,于是我们进行前序树遍历的非递归实现,使用一个栈进行,而在广度优先遍历的时候我们采用的是队列存储元素的数据。
3)但是当树在最坏的情况下,这个栈几乎要包含树中所有的信息。于是我们使用了一种叫做线索树的结构,每个节点有原来的左右节点,本身的信息,还有前驱后驱。为了简化,我们将前驱与左子树使用同个指针,另使用一个变量来进行说明区分,在右子树与后继一样的方式,如此完全可以不使用栈。其定义方式可以表示如下:
typedef enum PointerTag{Link,Thread};
typedef struct BiThrNode
{
int data;
struct BiThrNode *lchild,*rchild;
PointerTag LTag,RTag;
}BiThrNode,*BiThrTree;
4)由此我们可以看出,它利用了一个PointerTag域进行区分,但是可否不进行任何的多余的空间或者栈进行遍历呢?答案是肯定的。
MorrisInorder算法就是先将原来的树转为右子树的方式,重新构造一棵树,然后进行遍历,遍历之后再将这棵树进行恢复。
BSTNode<T>*p=root, *tmp;
while(p!=0)
{
if (p->left==0)//访问节点
{
visit(p);
p=p->right;
}
else
{
p=p->left;//在有左节点时,进行访问重新构造或者转换原来的树
while(tmp->right!=0&&tmp->right!=p)
tmp=tmp->right;
if (tmp->right==0)//在重新构建树,将最右下角的节点上提,
{
tmp->right=p;
tmp=tmp->right;
}
else
{
visit(p);//访问节点。
tmp->right=0;//将原来构造的树新添的右节点删除
p=p->right;
}
}
}
5)最后我们谈谈平衡树,在平衡树中。无疑AVL树与红黑树是非常流行的。首先我们先分析其各自的优势。红黑树,由于其性质的使然,很肯定的是它的插入、删除逻辑很复杂,但是针对随机数的效率较高,而AVL树,逻辑简单,可是每次插入或者删除的时候都要进行相应的判断,判断两边的高度。平衡性固然高。随机性降低。