吉法师的博客

不知道能否追到喜欢的人呀,今年努力下吧~ 2022.1.4

用C++实现一个二叉树

什么是二叉树

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。

二叉树的主要特点

查找较为方便,如果是左子树比节点小,右子树比节点大的这种方式,在查找的过程中其实就实现了一个”部分“的二分查找。这种二叉树只要不是偏二叉树的话,查找的效率会比线性表快很多。

二叉树不是线性表,因为它并不是线性结构,而是树形的。二叉树不能一次就遍历完整棵树,而是要通过递归的方式。

优点:搜索和排序效率极高 缺点:插入和删除略微麻烦,且实现起来比链表更难。

源码

二叉树的具体实现

1.类定义

class Node
{
public:
	int data;
	Node * left; //左右子树
	Node * right;
public:
	Node();
};

class BTree
{
public:
	Node * root;
};

二叉树的类定义和其他数据结构其实没有太大的区别,节点类包括元素值,左右子树的指针,而二叉树本身就只有一个根节点了。

2.构造函数

BTree::BTree()
{
	root = nullptr;
}

构造函数我个人喜欢简单的实现,这里只有最基本的初始化根节点的操作。

3.析构函数

BTree::~BTree()
{
	Node * p = root;
	destory(p);
}

析构函数其实是调用了一个销毁二叉树的方法,这个函数之后再说。

4.创建二叉树

//创建二叉树 递归
void BTree::create(Node * &p,Node * &temp)
{
    //保证二叉树满足 左子树小于根节点,右子树大于根节点
    //p是新的节点,temp是从root开始遍历的节点
	if(temp == nullptr)
	{
		temp = p;
	}
	else
	{
		//根节点有值了,判断大小
		if(temp->data == p->data )
		{
			cout<<"the same value is not allowed"<<endl;
		}
        else if(p->data < temp->data) //新节点比根节点小,就去左边递归
        {
            create(p,temp->left);
		}
        else //p->data > root->data //反之去右边递归
		{
            create(p,temp->right);
		}

	}

}

创建二叉树的过程稍微简单一点,我选择先输入根节点的值,创建二叉树,之后再调用这个函数去通过递归的方式创建各个子树。

外部代码如下:

    cout<<"enter the root's value"<<endl;
    tree.root = new Node;
    cin>>tree.root->data;
    while(1)
    {
        cout<<"enter the value or -999 to exit"<<endl;
        cin>>value;
        if(value == -999)
        {
            break;
        }

        Node * p =new Node;
        //

        p->data = value;
        p->left = nullptr;
        p->right = nullptr;
        tree.create(p,tree.root);

    }
    cout<<"the binary tree has been created"<<endl;

这样一来,就不需要判断二叉树是否为空,当前是否为根节点等等问题了,函数内部只需要关心创建的细节。

在创建的时候,我选择让二叉树的左子树全部小于根节点,右子树全部大于根节点,这就实现了一个二叉搜索树。同时不支持重复的节点,想看看能否通过代码去判断出这一问题。

5.遍历

//中序遍历
void BTree::showroot(Node * p)
{
	if(p!=nullptr)
	{
		showroot(p->left);
		cout<<p->data<<endl;
		showroot(p->right);
	}
}


//先序遍历
void BTree::showhead(Node *p)
{
	if(p!=nullptr)
	{
		cout<<p->data<<endl;
		showhead(p->left);
		showhead(p->right);

	}
}


//后序遍历
void BTree::showtail(Node *p)
{
	if(p!=nullptr)
	{

		showhead(p->left);
		showhead(p->right);
		cout<<p->data<<endl;
	}
}

二叉树的遍历是我见过最简单的数据结构代码,没有之一。通过递归调用,考虑清楚是先递归还是先输出根节点,就可以实现了。

6.插入

void BTree::insert(int value,Node * &p,Node * last)
{
	if(p == nullptr&&p==root) //如果根节点为空,表示这是一个空链表,需要new根节点了
	{
		root = new Node;
		root->data = value;
		return;
	}
	if(p!=nullptr)  //当前位置无法提供插入
	{
        cout<<"当前的值为"<<p->data<<endl;
        if(p->data > value) //当前节点比要插入的值大 往左边寻找空节点
		{
            insert(value,p->left,p);
		}
        else if(p->data < value) //反之去右边寻找
		{
            insert(value,p->right,p);
		}
		else //重复的元素暂不支持
		{
			cout<<"the same value is not be allowed"<<endl;
			return ;
		}
	}
	else if(p==nullptr)
	{
		p = new Node;
		p->data = value;
		if(p->data>last->data)
		{
			last->right = p;//左边比右边小
		}
		else
		{
			last ->left = p;
		}
		return ;

	}
}

插入也是用递归查找的方式,当目前的节点有值的时候,判断比这个值大还是小,就往左右子树进行遍历。

7.查询

Node * BTree::search(int value, Node * &p)
{
	if(p == nullptr)
	{
		cout<<"value not found"<<endl;
		return nullptr;
	}
	else if(p->data == value)
	{
		cout<<"value has been found "<<endl;
		cout<< p<<endl;
		return p;
	}
	else if(p->data<value)
	{
        return search(value,p->right);
	}
	else
	{
        return search(value,p->left);
	}
}

典型的二分查找,思路和插入是一样的。

8.插入根节点

void BTree::insertRoot(Node *&p, Node *&temp)
{

	//p是新的节点,temp是之前的head
	if(temp == nullptr)
	{
		root = p; //空树
	}
	else
	{
        if(search(p->data,temp))
        {
            cout<<"the same value is not allowed"<<endl;
            return;
        }
		if(p->data == temp->data)
		{
			cout<<"the same value is not allowed"<<endl;
		}
		else if(p->data>temp->data)//新的根节点比之前的大
		{
			p->left = temp;
			root = p;
		}
		else //新的根节点比之前的小,那么右子树就是原先的根节点了
		{
			p->right = temp;
			root = p;
		}
	}

}

一个没有太大用的功能。

9.求长度(节点的个数)

int BTree::getlength(Node *p)
{
	if(p == nullptr)
	{
		return 0;
	}
	else
	{
		return getlength(p->left)+1+getlength(p->right);
	}
}

通过递归的方式,每当这个节点有值,二叉树的节点个数就+1,并且递归到下一个节点

10.求深度

int BTree::getdepth(Node *p,int depth)
{
	if(p==nullptr)
	{
		return depth;
	}
	return max(getdepth(p->left,depth+1),getdepth(p->right,depth+1));
}

代码和求长度比较类似,但核心在于求出左右子树深度的最大值,而不是累加在一起。

11.删除某个节点

Node * BTree::deleteNode(int value, Node * &p)
{
	if(p == nullptr)
	{
		cout<<"data not found"<<endl;
		return p;//空节点不能删除
	}
	else if (value < p->data)
	{
        p->left = deleteNode(value,p->left); //删除后的值改对应链接
	}
	else if(value > p->data)
	{
        p->right = deleteNode(value,p->right);
	}
	else //查找到元素 value == p->data
	{
		cout<<"found"<<endl;
		if(p->left == nullptr && p->right == nullptr) //删除的节点是叶子
		{
			delete p;
			p = nullptr;
		}
		else if (root->data == value) //当为根节点时候
		{
            //也可以不让删除
			Node * tempR = root->right; //为了遍历出右子树的最大值
			Node * tempL = root->left;
			Node * temp = root->right;  //记录当前的右节点的指针
			if(tempR == nullptr && tempL != nullptr )
			{
				root  = tempL;
			}
			else if (tempR != nullptr && tempL == nullptr)
			{
				root  = temp;
			}
			else   //都有数据
			{
				while(tempR->left != nullptr)  //找到右子树最大的值
				{
					tempR = tempR->left;
				}
				tempR->left = tempL; //让右子树最大值的的左节点拼接最初的左节点
				root = temp;
			}
			return root;
		}
		else if(p->left == nullptr && p->right != nullptr) //当节点左空 右有 时
		{
			  Node * temp = p->right; //把右边节点记录下来
			  delete p;
			  return  temp; //返回上个节点的指向
//            Node * temp = p;
//            p = p->right;
//            delete temp;
//            temp = nullptr;
//            return  temp;
		}
		else if(p->right == nullptr && p->left != nullptr) //当节点右空 左有 时
		{
			 Node * temp = p->left;
			 delete p;
			 return  temp; //返回上个节点的指向
//            Node * temp = p;
//            p = p->left;
//            delete temp;
//            temp = nullptr;
//            return  temp;
		}
		else    //当两边都有节点时
		{

			Node * tempR = p->right; //为了遍历出右子树的最大值
			Node * tempL = p->left;
			Node * temp = p->right;  //记录当前的右节点的指针
			delete p;
			while(tempR->left != nullptr)  //找到右子树最大的值
			{
				tempR = tempR->left;
			}
			tempR->left = tempL; //让右子树最大值的的右节点拼接最初的左节点
			return temp;

//            while(p->left != nullptr) //找到最小的节点
//            {
//                p = p->left;
//            }
//            Node * temp = p;
//            p->data = temp->data;
//            p->right = deleteNode(temp->data,p->right);

		}

	}
	return p;
}

分四种情况:

1.该节点是叶子,直接置为空指针,释放内存空间即可。

2.该节点有左子树或右子树,删除该节点,下一个节点连接上去。

3.该节点有左右子树,最麻烦的一种情况,需要求出子树中的最大值,使他成为目前的节点。

4.找不到该元素。

由此可看出,删除二叉树节点的第一步,其实是遍历查找这个节点。

12.销毁该二叉树

其实就是博客前面提到的析构函数。

void BTree::destory(Node * &p)
{
	if(p != nullptr)
	{
		destory(p->left);
		destory(p->right);
		delete p;
		p = nullptr;
	}
}

相比之前的代码,算是比较简单的了。

总结

二叉树的实现相比链表,还是要复杂很多。文中实现的是一个二叉搜索树,比普通的二叉树稍微多了一点细节,但实用程度却大了很多。

通过学习二叉树,对递归的理解想必也可以得到加深了。


Share