1,简单二叉树的实现
回忆链表的实现,我们定义了两个类模板,一个用于表示链表里的结点,另一个用于表示链表本身;但是在二叉树的实现中,我们只定义二叉树的结点,而不再为整个二叉树定义类模板。
这里实现的二叉树是最简单的二叉树,结点不区分叶结点与分支结点。即,即便是叶结点,在类中也同样有左子结点与右子结点,只不过左右子结点都是空指针。
文件结构如下:
BinNode.h定义二叉树结点的ADT,是基类;
BSTNode.h定义简单的二叉树结点,继承基类;
1.1 BinNode.h
/********************************************************/
// 用模板实现二叉树(Binary Node)结点的基类定义
/********************************************************/
#pragma once
#include "Public.h"
// 二叉树结点的抽象类,ADT
template<typename E>
class BinNode
{
public:
virtual ~BinNode() {};
//返回结点值
virtual E& element() = 0; //纯虚函数
//设置结点值
virtual void setElement(const E& e) = 0;
//返回左子结点
virtual BinNode* left() const = 0;
//设置左子结点
virtual void setLeft(const BinNode*) = 0;
//返回右子结点
virtual BinNode* right() const = 0;
//设置右子结点
virtual void setRight(const BinNode*) = 0;
};
1.2 BSTNode.h
/********************************************************/
// 用模板实现二叉树(Binary Node)结点的简单定义
// Pointer-Based Node
// 不区分叶结点和分支结点
/********************************************************/
#pragma once
#include "BinNode.h"
template<typename Key, typename E>
class BSTNode : public BinNode<E>
{
private:
Key k;
E it;
BSTNode* lc;
BSTNode* rc;
public:
BSTNode() :lc(NULL), rc(NULL) {}//若定义在局部作用域中,则k和it没有被初始化
BSTNode(Key K, E e, BSTNode* l=NULL, BSTNode* r=NULL) :k(K), it(e), lc(l), rc(r) {}
~BSTNode() {}
E& element() { return it; }
void setElement(const E& e) { it = e; }
//Key& key()const { return k; } ,error C2440: “return”: 无法从“const char”转换为“char &”
// c++ primer P377,不能从const成员函数返回指向类对象的普通引用
const Key& key()const { return k; }
void setKey(const Key& K) { k = K; }
BSTNode* left() const { return lc; }
BSTNode* right() const { return rc; }
//为什么输入参数为BinNode<E>*,而不直接用BSTNode*????????
//可能原因是基类中的setLeft输入参数类型就是BinNode<E>*
void setLeft(const BinNode<E>* b) { lc = (BSTNode*)b; }//???????????????????????????????
void setRight(const BinNode<E>* b) { rc = (BSTNode*)b; }//???????????????????????????????
void setAll(const BinNode<E>* bl, const BinNode<E>* br)
{
lc = (BSTNode*)bl;
rc = (BSTNode*)br;
}
//判断是否为叶结点
bool isLeaf() { return (lc == NULL) && (rc == NULL); }
};
这里有一个需要注意的地方,因为我们把返回key值的函数Key& key()定义成了const成员函数,又让其返回指向类成员的引用而不是值,那么其返回类型就必须只能是const引用而不能是普通引用。其实想想,const成员函数是不能改变成员的值的,但是如果返回的是普通引用,则对const成员函数返回值进行赋值,不就改变了类的成员?语法上的原因是,普通的成员函数的this指针是指向类对象的const指针,指针的值不能改变,但其指向的值可以改变;但是const成员函数的thsi指针是指向const对象的const指针。所以在const成员函数中,成员k的类型是const Key。详见《C++ Primer 3rd》P377:“不能从 const 成员函数返回指向类对象的普通引用。”
2,二叉树的3种遍历
3种遍历的定义见
前一篇博文。
2.1 traversal.h
/********************************************************/
// 实现简单二叉树的前序遍历、后序遍历与中序遍历
/********************************************************/
#pragma once
#include "Public.h"
#include "BSTNode.h"
//前序遍历
template<typename E> void preorder(BinNode<E>* root);
//后序遍历
template<typename E> void postorder(BinNode<E>* root);
//中序遍历
template<typename E> void midorder(BinNode<E>* root);
//前序遍历
template<typename Key, typename E> void preorder_2(BSTNode<Key,E>* root);
//后序遍历
template<typename Key, typename E> void postorder_2(BSTNode<Key, E>* root);
//中序遍历
template<typename Key, typename E> void midorder_2(BSTNode<Key, E>* root);
2.2 traversal_Def.h
/********************************************************/
// 实现简单二叉树的前序遍历、后序遍历与中序遍历
/********************************************************/
#include "BSTNode.h"
template<typename E>
void preorder(BinNode<E>* root)
{
if (root == NULL)
{
return;
}
cout << root->element() << '\t';
preorder(root->left());
preorder(root->right());
}
template<typename E>
void postorder(BinNode<E>* root)
{
if (root == NULL)
{
return;
}
postorder(root->left());
postorder(root->right());
cout << root->element() << '\t';
}
template<typename E>
void midorder(BinNode<E>* root)
{
if (root == NULL)
{
return;
}
midorder(root->left());
cout << root->element() << '\t';
midorder(root->right());
}
template<typename Key, typename E>
void preorder_2(BSTNode<Key, E>* root)
{
if (root == NULL)
{
return;
}
cout << root->key();
preorder_2(root->left());
preorder_2(root->right());
}
template<typename Key, typename E>
void postorder_2(BSTNode<Key, E>* root)
{
if (root == NULL)
{
return;
}
postorder_2(root->left());
postorder_2(root->right());
cout << root->key();
}
template<typename Key, typename E>
void midorder_2(BSTNode<Key, E>* root)
{
if (root == NULL)
{
return;
}
midorder_2(root->left());
cout << root->key();
midorder_2(root->right());
}
这里需要注意的一定是,定义模板函数与定义普通函数的文件结构是不一样的。不能写一个头文件放模板函数的声明,再写一个源文件放模板函数的定义,这样编译的时候是找不到模板函数的定义的;必须将模板函数的声明与定义都放在头文件中,并且在main()函数所在的文件中包含定义模板函数的头文件。详见
C++学习笔记60——模板编译模型
3.使用二叉树
/********************************************************/
// 主函数
// 用于测试编写的各函数与数据结构
/********************************************************/
#include "Public.h"
#include "Tools.h"
#include "traversal_Def.h"
#include "BSTNode.h"
int main()
{
/********************************************************/
// 7,简单的二叉树
///********************************************************/
BSTNode<char, int> root('A', 1);
BSTNode<char, int> NodeB('B', 2);
BSTNode<char, int> NodeC('C', 3);
BSTNode<char, int> NodeD('D', 4);
BSTNode<char, int> NodeE('E', 5);
BSTNode<char, int> NodeF('F', 6);
BSTNode<char, int> NodeG('G', 7);
BSTNode<char, int> NodeH('H', 8);
BSTNode<char, int> NodeI('I', 9);
root.setAll (&NodeB, &NodeC);
NodeB.setAll(NULL, &NodeD);
NodeC.setAll(&NodeE, &NodeF);
NodeE.setAll(&NodeG, NULL);
NodeF.setAll(&NodeH, &NodeI);
// 前序遍历
preorder_2(&root);
cout << endl;
// 后序遍历
postorder_2(&root);
cout << endl;
// 中序遍历
midorder_2(&root);
cout << endl;
system("pause");
return 0;
}
这里生成的二叉树就是《数据结构与算法分析》里图5.1给的二叉树例子。
运行的结果是:
与书本上给出的3种遍历的结果一致。