二叉树

二叉树的 c++实现

二叉树节点定义

template<typename T>
struct BinTreeNode
{
	T data;
	BinTreeNode *leftChild, *rightChild;
	BinTreeNode() :leftChild(nullptr), rightChild(nullptr) {}
	BinTreeNode(T x, BinTreeNode<T> *l=nullptr, BinTreeNode<T> *r=nullptr) :data(x),leftChild(l),rightChild(r) {}
};

二叉树类

template<typename T>
class BinaryTree
{
public:
	BinaryTree() :root(nullptr),RefValue('#') {}
	BinaryTree(T value) : RefValue(value), root(nullptr) {}
	~BinaryTree() { DestroyTree(root) }
	// 创建二叉树
	//使用广义表创建二叉树,以'#'字符代表结束
	void CreateBinTree() { CreateBinTree(root); }
	//前序遍历创建二叉树(前序遍历的应用),用#表示空结点(用#将节点补成满二叉树)
	void CreateBinTree_PreOrder() { CreateBinTree_PreOrder(root); }
	//使用先序遍历和中序遍历创建二叉树
	void CreateBinTreeBy_Pre_In(const char *pre, const char *in)
	{
		int n = strlen(pre);
		CreateBinTreeBy_Pre_In(root, pre, in, n);
	}
	//使用后序遍历和中序遍历创建二叉树
	void CreateBinTreeBy_Post_In(const char *post, const char *in)
	{
		int n = strlen(post);
		CreateBinTreeBy_Post_In(root, post, in, n);
	}
	
	//==========二叉树的遍历==========//
	//先序遍历(递归)
	void PreOrder() { PreOrder(root); }

	//中序遍历(递归)
	void InOrder() { InOrder(root); }

	//后序遍历(递归)
	void PostOrder() { PostOrder(root); }

	//先序遍历(非递归)
	void PreOrder_NoRecurve() { PreOrder_NoRecurve(root); }

	//中序遍历(非递归)
	void InOrder_NoRecurve() { InOrder_NoRecurve(root); }

	//后序遍历(非递归)
	void PostOrder_NoRecurve() { PostOrder_NoRecurve(root); }

	//层次遍历(非递归)
	void LevelOrder() { LevelOrder(root); }

	//==========获取结点==========//
	//获取二叉树的根节点
	BinTreeNode<T> *getRoot() const
	{
		return root;
	}

	//获取current结点的父节点
	BinTreeNode<T> *Parent(BinTreeNode<T> *current)
	{
		//如果没有根节点或current结点就是root结点,就没有父节点
		return (root == nullptr || root == current) ? nullptr : Parent(root, current); 
	}

	//获取current结点的左结点
	BinTreeNode<T> *LeftChild(BinTreeNode<T> *current)
	{
		return (current != nullptr) ? current->leftChild : nullptr;
	}

	//获取current结点的右结点
	BinTreeNode<T> *RightChild(BinTreeNode<T> *current)
	{
		return (current != nullptr) ? current->rightChild : nullptr;
	}

	//==========成员函数==========//
	//销毁函数
	void Destroy() { Destroy(root); }

	//拷贝二叉树(前序遍历的应用)
	BinaryTree(BinaryTree<T> &s)
	{
		root = Copy(s.getRoot());
	}

	//判断两颗二叉树是否相等(前序遍历的应用)
	bool operator==(BinaryTree<T> &s)
	{
		return (equal(this->getRoot(), s.getRoot()));
	}

	//计算二叉树的结点的个数(后序遍历的应用)
	int Size() { return Size(root); }

	//计算二叉树的高度(后序遍历的应用)
	int Height() { return Height(root); }

	//判断二叉树是否为空
	bool Empty() { return (root == nullptr) ? true : false; }

	//以广义表的形式输出二叉树(前序遍历的应用)
	void PrintBinTree() { PrintBinTree(root); }

private:
	BinTreeNode<T> *root; //根节点
	T RefValue;           //数据输入停止的标志,需要一个构造函数
}

算法实现

使用广义表创建二叉树

从广义表A(B(D,E(G,)),C(,F))# 建立起来的二叉树。
image
实现思路

  1. 若是字母(假定以字母作为结点的值),则表示是结点的值,为它建立一个新的结点,并把该结点作为左子女(当k=1)或右子女(当k=2)链接到其父结点上。
  2. 若是左括号"(",则表明子表的开始,将k置为1;若遇到的是右括号")",则表明子表结束。
  3. 若遇到的是逗号",",则表示以左子女为根的子树处理完毕,应接着处理以右子女为根的子树,将k置为2。如此处理每一个字符,直到读入结束符“#”为止。

在算法中使用了一个栈s,在进入子表之前将根结点指针进栈,以便括号内的子女链接之用。在子表处理结束时退栈。

void CreateBinTree(BinTreeNode<T>* &BT)
{
	stack< BinTreeNode<T>* > s;
	BT = nullptr;
	BinTreeNode<T> *p, *t;    //p用来记住当前创建的节点,t用来记住栈顶的元素
	int k;    //k是处理左、右子树的标记
	T ch;
	cin >> ch;

	while (ch != RefValue)
	{
		switch(ch)
		{
		case '(':
			s.push(p);
			k=1;
			break;
		case ')':
			s.pop();
			break;
		case ',':
			k=2;
			break;
		default:
			p = new BinTreeNode<T>(ch);
			if(BT == nullptr) { BT = p; }
			else if (k==1) { //链入*t的左孩子
				t = s.top();
				t->leftChild = p;
			} else {	//链入*t的右孩子
				t = s.top();
				t->rightChild = p;
			}
		}
		cin >> ch;
	}
}

根据二叉树的前序遍历创建

必须对应二又树结点前序遍历的顺序,并约定以输入序列中不可能出现的值作为空结点的值以结束递归,此值通过构造函数存放在RefValue中。例如用“#”或表示字符序列或正整数序列空结点。

image

前序遍历所得到的前序序列为ABC##DE#G##F###。
实现思路:
每读入一个值,就为它建立结点。该结点作为根结点,其地址通过函数的引用型参数subTree直接链接到作为实际参数的指针中。然后,分别对根的左、右子树递归地建立子树,直到读入“#”建立空子树递归结束。

void CreateBinTree_PreOrder(BinTreeNode<T>* &subTree)
{
	T item;
	if (cin >> item)
	{
		if (item != RefValue)
		{
			subTree = new BinTreeNode<T>(item);    //构造结点
			if (subTree == nullptr)
			{
				cout << "内存空间分配错误!" << endl;
				exit(-1);
			}
			CreateBinTree_PreOrder(subTree->leftChild);
			CreateBinTree_PreOrder(subTree->rightChild);
		}
		else
		{
			subTree = nullptr;
		}
	}
}

根据前序遍历和中序遍历创建二叉树

根据前序遍历,先找到这棵树的根节点,也就是数组受中第一个结点的位置,创建根节点。
然后在中序遍历中找到根的值所在的下标,切出左右子树的前序和中序
注意:如果前序遍历的数组长度为0,说明是一棵空树。
下面举个例子详细讲解:
首先可以确定A是这棵树的根节点,然后根据中序遍历切出A的左右子树,得到BCD属于A的左子树,E属于A的右子树,如下图:
image
接着以B为根节点,在中序遍历中再次切出B的左右子树,得到CD为B的左子树,右子树为空。
image
再以C为根节点,结合中序遍历,得到D为C的右子树,左子树为空。
image
创建好的二叉树如下图所示:
image
代码实现(递归定义,递归实现):

void CreateBinTreeBy_Pre_In(BinTreeNode<T> *&cur, const char *pre, const char *in, int n)
{
	if (n <= 0)
	{
		cur = nullptr;
		return;
	}
	int k = 0;
	while (pre[0] != in[k]) //在中序中找到pre[0]的值
	{
		k++;
	}
	cur = new BinTreeNode<T>(in[k]); //创建结点
	CreateBinTreeBy_Pre_In(cur->leftChild, pre + 1, in, k);
	CreateBinTreeBy_Pre_In(cur->rightChild, pre + k + 1, in + k + 1, n - k - 1);
}

根据后续遍历和中序遍历创建二叉树

根据后序遍历,先找到这棵树的根节点的值,也就是数组中最后一个节点(数组长度-1)的位置,由此创建根节点。
然后在中序遍历中找到根的值所在的下标,切出左右子树的后续和中序。
注意:如果后序遍历的数组长度为0,说明是一棵空树。
下面举个例子详细讲解:
由后序遍历可以确定A是这棵树的根节点,然后根据中序遍历切出A的左右子树,得到CDB属于A的左子树,E属于A的右子树,如下图:
image
接着以B为根节点,在中序遍历中再次切出B的左右子树,得到CD为B的左子树,右子树为空。
image
再以C为根节点,结合中序遍历,得到D为C的右子树,左子树为空。
image
创建好的二叉树如下图所示:
image
代码实现

//使用后序遍历和中序遍历创建二叉树
void CreateBinTreeBy_Post_In(BinTreeNode<T> *&cur, const char *post, const char *in, int n)
{
	if (n == 0)
	{
		cur = nullptr;
		return;
	}
	char r = *(post + n - 1);    //根结点值
	cur = new BinTreeNode<T>(r); //构造当前结点
	int k = 0;
	const char *p = in;
	while (*p != r)
	{
		k++;
		p++;
	}
	CreateBinTreeBy_Post_In(cur->leftChild, post, in, k);
	CreateBinTreeBy_Post_In(cur->rightChild, post + k, p + 1, n - k - 1);
}

二叉树的递归遍历

void PreOrder(BinTreeNode<T> *&subTree)
{
	if(subTree != nullptr)
	{
		cout << subTree->data << " ";
		PreOrder(subTree->leftChild);
		PreOrder(subTree->rightChild);
	}
}

void InOrder(BinTreeNode<T>* &subTree)
{
	if(subTree != nullptr)
	{
		InOrder(subTree->leftChild);
		cout << subTree->data << " ";
		InOrder(subTree->rightChild);
	}
}

void PostOrder(BinTreeNode<T>* &subTree)
{
	if(subTree != nullptr)
	{
		PostOrder(subTree->leftChild);
		PostOrder(subTree->rightChild);
		cout << subTree->data << " ";
	}
}

二叉树遍历非递归实现

先序遍历

为了把一个递归过程改为非递归过程,一般需要利用一个工作栈,记录遍历时的回退路径。
image
利用栈实现前序遍历的过程。每次访问一个结点后,在向左子树遍历下去之前,利用这个栈记录该结点的右子女(如果有的话)结点的地址,以便在左子树退回时可以直接从栈顶取得右子树的根结点,继续其右子树的前序遍历。

void PreOrder_NoRecurve1(BinTreeNode<T> *p)
{
	stack<BinTreeNode<T> *> s;
	s.push(nullptr);    //最先push一个nullptr,到最后一个结点没有左右子树时,栈里只有一个nullptr了,令指针p指向这个nullptr,再判断就会结束循环
	while (p!=nullptr)
	{
		cout << p->data << " ";
		if(p->rightChild != nullptr)
		{
			s.push(p->rchild);
		}
		if(p->leftChild != nullptr)
		{
			p = p->leftChild;
		}
		else
		{
			p = s.top()
			s.pop();
		}
	}
}

另一种前序遍历的方法。为了保证先左子树后右子树的顺序,在进栈时是先进右子女结点地址,后进左子女结点地址,出栈时正好相反。

void PreOrder_NoRecurve2(BinTreeNode<T> *p)
{
	stack<BinTreeNode<T>*> s;
	BinTreeNode<T>* t;
	S.push(p);    //根节点进栈
	while(!s.empty())
	{
		t = s.top();
		s.pop();
		cout << t->data << " ";    //访问元素
		if(t->rightChild != nullptr) { s.push(t->rightChild) }
		if(t->leftChild != nullptr) { s.push(t->leftChild) }
	}
}

中序遍历

需要使用一个栈,以记录遍历过程中回退的路径。在一棵子树中首先访问的是中序下的第一个结点,它位于从根开始沿leftChild链走到最左下角的结点,该结点的leftChild指针为nullptr。访问它的数据之后,再遍历该结点的右子树。此右子树又是二叉树,重复执行上面的过程,直到该子树遍历完。
image

void InOrder_NoRecurve(BinTreeNode<T>* p)
{
	stack<BinTreeNode<T>*> s;
	do {
		while(p!=nullptr)
		{
			s.push(p);
			p = p->leftChild;
		}
		if (!s.empty())
		{
			p = s.top();
			s.pop();
			cout << p->data << " ";
			p = p->rightChild;
		}
	}while(p!=nullptr || !s.empty())
}

后续遍历

  1. 如果栈顶元素非空且左节点存在,将其压入栈中,如果栈顶元素存在左节点,将其左节点压栈,重复该过程。直到左结点不存在则进入第2步
  2. 判断上一次出栈节点是否是当前栈顶结点的右节点(就是右叶子结点,如:g,f结点),或者当前栈顶结点不存在右结点(如:g,f,a结点),将当前节点输出,并出栈。否则将当前栈顶结点右孩子节点压栈,再进入第1步
void PostOrder_NoRecurve(BinTreeNode<T> *p)
{
	if (root == nullptr) return;
	stack<BinTreeNode<T> *> s;
	s.push(p);
	BinTreeNode<T> *lastPop = nullptr;
	while (!s.empty())
	{
		while (s.top()->leftChild != nullptr) s.push(s.top()->leftChild);

		while (!s.empty())
		{
			//右叶子结点 || 没有右结点
			if (lastPop == s.top()->rightChild || s.top()->rightChild == nullptr)
			{
				cout << s.top()->data << " ";
				lastPop = s.top();
				s.pop();
			}
			else if(s.top()->rightChild !=nullptr)
			{
				s.push(s.top()->rightChild);
				break;
			}
		}
	}
}

二叉树层次遍历

按层次顺序访问二叉树的处理需要利用一个队列。在访问二又树的某一层结点时,把下一层结点指针预先记忆在队列中,利用队列安排逐层访问的次序。因此,每当访问一个结点时,将它的子女依次加到队列的队尾,然后再访问已在队列队头的结点。这样可以实现二又树结点的按层访问。

void LevelOrder(BinTreeNode<T> *p)
{
	queue<BinTreeNode<T>*> Q;
	Q.push(p);
	BinTreeNode<T>* t;
	while(!Q.empty())
	{
		t = Q.front();
		Q.pop();
		cout << t->data << " ";    //访问队头元素的数据
		if (t->leftChild != nullptr) Q.push(t->leftChild);
		if (t->rightChild != nullptr) Q.push(t->rightChild);
	}
}

其他算法

二叉树结点个数

int Size(BinTreeNode<T> *subTree) const
{
	if (subTree == nullptr)    //递归结束,空树结点个数为0
	{
		return 0;
	}
	return 1 + Size(subTree->leftChild) + Size(subTree->rightChild);
}

二叉树高度

int Height(BinTreeNode<T> *subTree)
{
	if(subTree == nullptr) return 0
	int l = Height(subTree->leftChild);
	int r = Height(subTree->rightChild);
	return l>r ? l+1:r+1;
}

以广义表的形式输出二叉树

void PrintBinTree(BinTreeNode<T> *BT)
{
	if (BT != nullptr)    //树为空时结束递归
	{
		cout << BT->data;
		if (BT->leftChild != nullptr || BT->rightChild != nullptr)
		{
			 cout << '(';
			 if (BT->leftChild!=nullptr) PrintBinTree(BT->leftChild);
			 cout << ',';
			 if (BT->rightChild != nullptr) PrintBinTree(BT->rightChild);
			 cout << ')';
		}
	}
}

求二叉树某结点的父节点

//从结点subTree开始,搜索结点current的父节点,找到返回父节点的地址,找不到返回nullptr
BinTreeNode<T>* Parent(BinTreeNode<T>* subTree, BinTreeNode<T>* current)
{
	if (subTree == nullptr) return nullptr;
	if (subTree->leftChild == current || subTree->rightChild == current) return subTree;
	
	BinTreeNode<T>* p;
	if (p = Parent(subTree->leftChild, current) != nullptr) return p //递归在左子树中搜索
	else return Parent(subTree->rightChild, current);    //递归右子树中搜索
}

销毁二叉树

void Destroy(BinTreeNode<T> *&subTree)
{
	if (subTree != nullptr)
	{
		Destroy(subTree->leftChild);
		Destroy(subTree->rightChild);
		delete subTree;
		subTree = nullptr;
	}
}

判断两颗二叉树是否相等

bool equal(BinTreeNode<T> *a, BinTreeNode<T> *b)
{
	if (a == nullptr&&b == nullptr)    //两者都为空
	{
		return true;
	}
	//两者都不为空,且两者的结点数据相等,且两者的左右子树的结点都相等
	if (a != nullptr&&b != nullptr&&a->data == b->data&&equal(a->leftChild, b->leftChild) && equal(a->rightChild, b->rightChild))
	{
		return true;
	}
	return false;
}

判断是否是平衡二叉树

bool isBalanced(BinTreeNode<T> *root) {
    bool balanced = abs(Height(root->left) - Height(root->right)) > 1 ? false : true;
    return balanced && isBalanced(root->left) && isBalanced(root->right);
}
posted @ 2021-12-24 17:08  Logan_Xu  阅读(66)  评论(0编辑  收藏  举报