一、实现的函数简介:
- 构造函数:分为数组构造函数和拷贝构造函数以及默认构造函数。
- 插入、删除函数:插入和删除一个int。
- 输出函数:输出二叉树中的数。
- 析构(清空)函数:清空整个二叉树(并且重置根节点。)
二、函数详解:
首先是节点的声明定义和二叉树类的声明:
struct node {
int ele;
node* left;
node* right;
node(int e):left(0), right(0){//初始化节点。即调用node::node()来初始化。
ele = e;
}
}
class BinaryTree {
private:
node* root;
// 此处四个函数均为辅助函数
static void MemoryDelete(node* p); // 内存处理的辅助函数
static void BuildTree(const node* Source_Root, node* &Target_Root); // 拷贝构造函数的辅助函数
static void BuildTree(const int* arr,int len, node* &root); // 数组构建树的辅助函数
static void preorder(const node* p); // 输出的辅助函数
public:
BinaryTree();
BinaryTree(const BinaryTree&);
BinaryTree(const int* arr, int len);
void ResetTree(const int* arr, int len); // 清空当前树并重置
~BinaryTree();//清除所有空间
void clear(); // 清空
void insert(int ele); // 按值插入
void Delete(int ele); // 按值删除
void print();//前序打印
};
正式函数和辅助函数:正式函数其实就是成员函数,类是对外的的接口。所以有很多判断条件的步骤在正式函数里面执行。辅助函数就是类中的普通的函数,进行一般操作,从成员函数中分块出来,便于修改。
1、构造函数
首先是根据数组构造二叉树。由于是静态函数所以不能直接调用插入函数,但是在用数组构造的情况和插入函数的情况基本相同,
都是一个一个向树里面插入数,除了要先置空对象中的root,辅助函数中自己建立一个根节点。直接复制插入函数部分的代码就可以了。
这里要注意的就是先自己建立根节点,同时要记得更新对象中的root值。以及重复数值的插入,如何跳过。
void BinaryTree::BuildTree(const int* arr,int len, node* &root){ // 数组构建树的辅助函数
root=new node(arr[0]);
root->left=NULL;
root->right=NULL;
for(int i=1;i<len;i++){
node *former=root,*save=NULL,*temp=NULL;
int flag=0;
while(former!=NULL){
if(arr[i] > former->ele){
save=former;
former=former->right;
}
else if(arr[i] < former->ele){
save=former;
former=former->left;
}
else {
flag=1;
break;
}}
if(flag==1) continue;
temp=new node(arr[i]);
temp->left=NULL;
temp->right=NULL;
if(arr[i] > save->ele)
save->right=temp;
else save->left=temp;
}
}
BinaryTree::BinaryTree(const int* arr, int len){//正式函数
root=NULL;
if(len!=0)
BuildTree(arr,len,root);
}
接下来是拷贝构造函数。正式函数就是置空root,和数组建立相同。辅助函数则是利用递归模拟赋值过程:遍历整个树,
同左同右的把每一个节点都建立赋值,然后利用递归把前的左右指针传给后节点,做到连起整个树的效果。(这里的递归还需要多思考)
void BinaryTree::BuildTree(const node* Source_Root, node* &Target_Root){ // 拷贝构造函数的辅助函数
if (Source_Root == NULL){
return;
}else{//遍历source,加给target插入值
Target_Root= new node(Source_Root->ele);//同左同右?
BuildTree(Source_Root->left,Target_Root->left);
BuildTree(Source_Root->right,Target_Root->right);
}
}
BinaryTree::BinaryTree(const BinaryTree& inp){//正式函数
root=NULL;
BuildTree(inp.root,root);
}
最后,这几个构造函数传递的参数都是const,所以一定要匹配着传递。
2、插入函数
单个插入和单个删除在二叉树里的模式基本相同:寻找节点(是否找到),插入和删除,根节点情况的root更新,重新做好链接关系。
插入函数的情况比较简单,要注意的就是根节点更新(根节点是否为空),和重复数值的判断。
void BinaryTree::insert(int inp){ // 按值插入
if(root==NULL){
root=new node(inp);
root->left=NULL;
root->right=NULL;
return; }
node *former=root,*save=root,*temp;
while(former!=NULL){
if(inp > former->ele){
save=former;
former=former->right;
continue;
}
else if(inp < former->ele){
save=former;
former=former->left;
continue;
}
else return;}
temp=new node(inp);
temp->left=NULL;
temp->right=NULL;
if(inp > save->ele)
save->right=temp;
else save->left=temp;
}
3、删除函数
删除函数的思路和插入函数差不多,不同的是多了很多特殊情况和有对子树的判断、不同情况的不同处理。
特殊情况一般就是树为空时、删除根节点时的处理。至于对子树的判断,如果链接时过于复杂可以直接把值进行替换然后删掉原来要顶替被删位的节点。
其他情况直接对父亲指向进行更改就好。
void BinaryTree::Delete(int inp){ // 按值删除
if(root==NULL)
return;
node *p=root,*p_dad=root,*p_left_son,*r=root,*r_dad=root;
if(p->left==NULL && p->right==NULL){
if(p->ele!=inp)
return;
root=NULL;
delete p;
return;}
while(p!=NULL){
if(inp > p->ele){
p_dad=p;
p=p->right;
}else if(inp < p->ele){
p_dad=p;
p=p->left;
}else break;
}
if(p==NULL)
return;
if(p->left!=NULL && p->left->right!=NULL)//有左子树,左子树有右子树
{
p_left_son=p->left;
r_dad=p_left_son;
r=r_dad->right;
while(r->right!=NULL){
r_dad=r;
r=r->right;}
if(p==root){
root->ele=r->ele;
r_dad->right=r->left;
delete r;
return;
}
p->ele=r->ele;
r_dad->right=r->left;//找r的父亲,把r的左孩子作为r父亲的右孩子
delete r;
return;
}
if(p->left!=NULL && p->left->right==NULL)//有左子树,左子树无右子树
{
p_left_son=p->left;
if(p==root){
root=p_left_son;
root->right=p->right;
delete p;
return;}
if(p_dad->ele > p->ele)//左子树代替p
p_dad->left=p_left_son;
else p_dad->right=p_left_son;
p_left_son->right=p->right;
delete p;
return;
}
if(p->left==NULL)//无左子树
{ if(p==root){
root=p->right;
delete p;
return;
}
if(p_dad->ele > p->ele)
p_dad->left=p->right;//用p的右孩子代替
else p_dad->right=p->right;
delete p;
return;
}
}
删除的操作比插入要情况多、操作复杂,容易忘记最后链接上下的树或者多连少连,以及要记得删除申请的空间。
4、输出函数以及析构(清空)函数
输出函数和清空的函数就是利用遍历的方法,直接遍历一遍然后输出/删除即可。除了判断一下树是否为空之外,没有什么需要注意的。
void BinaryTree::MemoryDelete(node* p){ // 内存处理的辅助函数
if (p != NULL){
MemoryDelete(p->left);
MemoryDelete(p->right);
delete p;
}
}
BinaryTree::~BinaryTree(){//析构
if(root!=NULL)
clear();
}
void BinaryTree::clear(){ // 清空
if(root!=NULL)
MemoryDelete(root);
root=NULL;
}
void BinaryTree::print(){//输出函数
const node *p=root;
if(p!=NULL)
preorder(p);
std::cout << std::endl;
}
三、小结:
首先列出一下遍历的三种方法。
void preorder(Treenode *p) {//先序遍历:根节点,左子树,右子树
if (p != NULL){
visit(p);
preorder(p->left);
preorder(p->right);
}
}
void inorder(Treenode *p) {//中序遍历:左子树,根节点,右子树
if (p != NULL){
inorder(p->left);
visit(p);
inorder(p->right);
}
}
void postorder(Treenode *p) {//后序遍历:左子树,右子树,根节点
if (p != NULL){
postorder(p->left);
postorder(p->right);
visit(p);
}
}
二叉树的建立和操作涉及内存的分配和删除,还有三种遍历(主要是递归难)方法,所以建立一个simple的二叉树类整个的步骤有些复杂,
也需要处理很多细节和特殊情况。