到目前为止,我们已经介绍了线性数据结构和表数据结构。这些数据结构一般不适用与描述具有层次结构的数据…
树
树是一个非空的有限元素的集合,其中一个元素为根(root),余下的元素组成t的子数(subtree)
层次中最高层元素为根,其下一级的元素是余下元素所构成的子数的根
二叉树
定义 : 二叉树(binary tree) t 是有限个元素的集合(可以为空)。当二叉树非空时,其中有一个称为根的元素,余下的元素(如果有的话)被组成两个二叉树,分别称为t的左子树和右子树
根本区别
- 二叉树可以为空
- 二叉树中每个元素都恰好有两个子树(其中一个或者两个可能为空)。树中每个元素可能有若干子树。
在二叉树中每个元素的子树都是有序的,也就是说可以用左子树和右子树来加以区别。而树的子树之间是无序的。
+ /\ * / /\ /\ a b c d
如上为数学表达式树,其含义为 (a*b)+(c/d)
二叉树的特性
特性1:包含n(n>0)个元素的二叉树的边数为n-1
特性2:若二叉树的高度为h,h>=0,则该二叉树至少有h个元素,最多有2^h-1个元素(即每一层至少有一个元素)
特性3:包含n个元素的二叉树的高度最大为n,最小为log(n-1)
特性4:设完全二叉树中一元素的序号为i,1<=i<=n则以下规则成立:
- 当i = 1时,该元素为二叉树的根。若i>1则该元素的父节点的编号为 i/2
- 当2i>n时,该元素无左孩子。否则其左孩子的编号为2i
- 若2i+1>n,该元素无右孩子。否则其右孩子的编号为2i+1
二叉树的描述
公式化描述
在公式化描述的方法中,将二叉树的元素存储在数组中,缺少的部分由null表示,当缺少的元素很少的时候这种表示方法十分有效
链表描述
这是一种二叉树最常用的描述方式,每个元素都是用一个或者两个指针域的节点表示,这两个域被称为LeftChild和RightChild,除了两个指针域外,每个节点还包含一个data域。
用一个变量来保存二叉树的根,并用该变量的名称来指称根节点或者整个二叉树。
在二叉树中不设置指向父节点的指针是没有问题的,因为大部分时候是不需要这个指针的。如果应用需要这个指针,可以在每个节点中增加一个指针域。
二叉树的常用操作
二叉树的常用操作为
- 确定其高度
- 确定其元素数目
- 复制
- 在屏幕或者纸上显示二叉树
- 确定两棵二叉树十否一样
- 删除整棵二叉树
- 若为数学表达式树,则计算这棵树
- 若为数学表达式树,则给出对应的带括号的表达式
二叉树的遍历
- 前序遍历
{
if(t)
{
visit(t); //访问根节点
PreOrder(t.left); //访问左子树
PreOrder(t.right);//访问右子树
}
}
- 中序遍历
void inOrder(binaryTreeNode<T> *t)
{
if(t)
{
inOrder(t.left); //访问左子树
visit(t); //访问根节点
inOrder(t.right);//访问右子树
}
}
- 后序遍历
void postOrder(binaryTreeNode<T> *t)
{
if(t)
{
postOrder(t.left); //访问左子树
postOrder(t.right);//访问右子树
visit(t); //访问根节点
}
}
- 逐层遍历
在逐层遍历的过程中,按从顶到底层的次序访问树中的元素,在同一层中,从左到右进行访问。由于遍历中所使用的数据结构是一个队列而不是一个栈,因此写一个按层遍历的递归程序是很困难的,下面的代码采用队列的数据结构,队列中的元素指向二叉树的节点。当然也可以采用公式化队列。
//--------------------------------------------------
// 广度优先
void bfs_order(btree* tree) {
btNode* p = tree;
if (NULL == p) {
return;
}
queue<btNode*> * qu = new queue<btNode*>();
qu->push(p);
while (!qu->empty()) {
p = qu->front();
qu->pop();
cout << p->data << " ";
if (NULL != p->left) {
qu->push(p->left);
}
if (NULL != p->right) {
qu->push(p->right);
}
}
delete qu;
}
//--------------------------------------------------
去递归处理
//--------------------------------------------------
// 非递归前序遍历
void pre_order(btree* tree) {
btNode * p = tree;
if (NULL == p)
return;
stack<btNode*>* st = new stack<btNode*>();
st->push(p);
while (!st->empty()) {
p = st->top();
st->pop();
cout << p->data << " ";
if (NULL != p->right) {
st->push(p->right);
}
if (NULL != p->left) {
st->push(p->left);
}
}
delete st;
}
// 非递归后续遍历
//需要注意的是 : 非递归后序遍历方法相对实现比较困难,主要原因就在于父子节点访问缺乏连续
// 解决思路是将前一个访问节点记忆下来,并判断和当前节点之间的关系再做处理
void post_order(btree* tree) {
btNode* curr = tree; // 记录当前执行的节点
btNode* prev = NULL; // 记录前一个访问的节点
if (NULL == curr) {
return;
}
stack<btNode*>* st = new stack<btNode*>();
st->push(curr);
while (!st->empty()) {
curr = st->top();
// 如果该节点没有孩子节点则可以直接访问
if (NULL == curr->left && NULL == curr->right) {
cout << curr->data << " ";
st->pop();
prev = curr;
continue;
}
// 如果该节点的孩子节点已经被访问过了,也可直接访问
if (NULL != prev && (prev == curr->right || prev == curr->left)) {
cout << curr->data << " ";
st->pop();
prev = curr;
continue;
}
// 除了上述情况则需要将孩子节点入栈
if (NULL != curr->right) {
st->push(curr->right);
}
if (NULL != curr->left) {
st->push(curr->left);
}
}
delete st;
}
// 非递归中序遍历
void in_order(btree* tree) {
btNode* p = tree;
if (NULL == p)
return;
stack<btNode*>* st = new stack<btNode*>();
while (NULL != p || !st->empty()) {
while (NULL != p) {
st->push(p);
p = p->left;
}
if (!st->empty()) {
p = st->top();
st->pop();
cout << p->data << " ";
p = p->right;
}
}
delete st;
}
代码引用自http://www.xuebuyuan.com/1612933.html