《王道》树--PART2

《王道》树--PART2

4 树的应用

4.1 二叉排序树

    二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
        1. 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
        2. 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
        3. 它的左右子树也分别为二叉排序树。

    如下图所示:


    根据二叉排序树的定义,有左子树结点值<根结点值<右子树结点值,所以,对二叉排序树进行中序遍历,可以得到一个递增的有序序列。

    例3 判断一棵二叉树是不是二叉排序树

//判断一棵二叉树是不是二叉排序树  
//思路:二叉排序树的特点是,若左子树非空,则左子树上结点的值均小于根结点的值;  
//若右子树非空,则右子树上结点的值均大于根结点的值。所以根据这一特点,可以看出  
//二叉排序树的中序遍历是一个递增序列。  
#include<iostream>  
#include <limits.h>  
using namespace std;
int prev1 = INT_MIN;         //定义为最小的整数。宏定义INT_MIN在头文件limits.h中
typedef struct BiTreeNode
{
	int data;
	BiTreeNode* lchild;
	BiTreeNode* rchild;
	BiTreeNode(int x) : data(x), lchild(NULL), rchild(NULL) {}
};
int JudgeBST(BiTreeNode *root)
{
	int b1, b2;
	if (root == NULL)
		return 1;
	else
	{
		b1 = JudgeBST(root->lchild);
		if (b1 == 0 || prev1 >= root->data)
			return 0;
		prev1 = root->data;
		b2 = JudgeBST(root->rchild);
		return b2;
	}
}
//***********测试代码************  
//           10               
//         /                      
//        7              
//       /\                 
//      4  8  
//          \  
//           9  
int main()
{
	//生成一棵二叉树  
	BiTreeNode* Node1 = new BiTreeNode(10);//生成节点  
	BiTreeNode* Node2 = new BiTreeNode(7);
	BiTreeNode* Node3 = new BiTreeNode(4);
	BiTreeNode* Node4 = new BiTreeNode(8);
	BiTreeNode* Node5 = new BiTreeNode(9);

	Node1->lchild = Node2;//连接节点  
	Node2->lchild = Node3;
	Node2->rchild = Node4;
	Node5->rchild = Node5;

	int p = JudgeBST(Node1);
	cout << p << endl;
	system("pause");
	return 0;
}

4.2 平衡二叉树

    为了避免树的高度增长过快,降低二叉排序树的性能,我们规定插入和删除二叉树结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,并将这样的二叉树称为平衡二叉树(Balanced Binary Tree)又被称为AVL树。定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树的平衡因子的值只可能是-1,0,1。

    因此,平衡二叉树可定义为它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。例如,下图中的二叉树就是一棵平衡二叉树。


    例4 判断一棵二叉树是不是平衡二叉树

    有了求二叉树的深度的经验之后再解决这个问题,我们很容易就能想到一个思路:在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一棵平衡的二叉树。

//=============方法1==============
/*求二叉树的深度*/
int TreeDepth(BinaryTreeNode *pRoot){
	if (pRoot == NULL){
		return 0;
	}
	int nLeft = TreeDepth(pRoot->m_pLeft);
	int rRight = TreeDepth(pRoot->m_pRight);
	return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}

//需要重复遍历结点的算法,不够好
bool IsBalanced_Solution1(BinaryTreeNode *pRoot){
	if (pRoot == NULL)
		return true;
	int left = TreeeDepth(pRoot->m_pLeft);
	int right = TreeDepth(pRoot->m_pRight);
	int diff = left - right;
	if (diff > 1 || diff < -1)
		return false;
	return IsBalanced_Solution1(pRoot->m_pLeft) && IsBalanced_Solution1(pRoot->m_pRight)
}

    上面的代码固然简洁,但我们也要注意到由于一个结点会被重复遍历多次,这种思路的时间效率不高。例如在IsBalancedBinaryTree方法中输入上图中的二叉树,我们将首先判断根结点(结点1)是不是平衡的。此时我们往函数TreeDepth输入左子树的根结点(结点2)时,需要遍历结点4、5、7。接下来判断以结点2为根结点的子树是不是平衡树的时候,仍然会遍历结点4、5、7。毫无疑问,重复遍历同一个结点会影响性能。


    在上面的代码中,我们用后序遍历的方式遍历整棵二叉树。在遍历某结点的左右子结点之后,我们可以根据它的左右子结点的深度判断它是不是平衡的,并得到当前结点的深度。当最后遍历到树的根结点的时候,也就判断了整棵二叉树是不是平衡二叉树。

//=========方法2==============
//后序每个结点只遍历一次,一边遍历一边记录它的深度
bool IsBalanced(BinaryTreeNode *pRoot, int *pDepth){
	if (pRoot == NULL){
		*pDepth = 0;
		return true;
	}
	int left, right;
	if (IsBanlanced(pRoot->m_pLeft,&left) && IsBanlanced(pRoot->m_pRight, &right)){
		int diff = left - right;
		if (diff <= 1 && diff >= -1){
			*pDepth = 1 + (left > right ? left : right);
			return true;
		}
	}
	return false;
}
bool IsBanlanced_Solution2(BinaryTreeNode* pRoot)
{
	int depth = 0;
	return IsBalanced(pRoot, &depth);
}
//=========方法3==============
//下面方法也是可行的,且形式更加简洁。求出根结点的最大深度与最小深度,则最大深度与最小深度之差dis就是树中任一子树深度差最大值,所以只要dis小于等于1,此树就是平衡二叉树。
int maxDepth(TreeNode *root){
	if (root == NULL)
		return 0;
	return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
int minDepth(TreeNode *root){
	if (root == NULL)
		return 0;
	return 1 + min(minDepth(root->left), minDepth(root->right));
}
bool isBanlanced(TreeNode *root){
	return (maxDepth(root) - minDepth(root) <= 1);
}

*平衡二叉树的插入问题

*红黑树

4.3 哈夫曼树及哈夫曼编码

    1.哈夫曼树的构造

     对于最优二叉树,权值越大的结点越接近树的根结点,权值越小的结点越远离树的根结点。

     最优二叉树的构造算法步骤:

     (1)根据给定的n个权值w1,w2,...,wn构成n棵二叉树森林F={T1,T2,...,Tn},其中每一棵二叉树Ti中都只有一个权为wi的根结点,其左、右子树为空。

     (2)在森林F中选出两棵根结点权值最小的树作为一棵新二叉树的左、右子树,新二叉树的根结点的权值为其左、右子树根结点的权值之和。

     (3)从F中删除这两棵二叉树,同时把新二叉树加入到F中。

     (4)重复步骤(2)、(3),直到F中只含有一棵树为止,此树便为最优二叉树。

     哈夫曼树的构造过程示意图如下:


    2.哈夫曼树的编码

        哈夫曼编码是一种变长编码。其定义如下:

       对于给定的字符集D={d1,d2,...,dn}及其频率分布F={w1,w2,...,wn},用d1,d2,...,dn作为叶结点,w1,w2,...,wn作为结点的权,利用哈夫曼算法构造一棵最优二叉树,将树中每个分支结点的左分支标上"0";右分支标上"1",把从根到每个叶子的路径符号("0"或"1")连接起来,作为该叶子的编码。

       哈夫曼编码是在哈夫曼树的基础上求出来的,其基本思想是:从叶子结点di(0<=i<n)出发,向上回溯至根结点,依次求出每个字符的编码。

       示例:对于字符集D={A,B,C,D},其频率(单位:千次)分布为F={12,6,2,18},下图给出D的哈夫曼编码图。


4.4 并查集

参考博客1

参考博客2

参考博客3





猜你喜欢

转载自blog.csdn.net/qq_27022241/article/details/80192717