程序员面试金典: 9.4树与图 4.3给定一个有序整数数组,元素各不相同且按升序排列,创建一颗高度最小的二叉查找树。 ---快速解法

#include <iostream>
#include <stdio.h>
#include <queue>
#include <vector>
#include <string>
#include <sstream>
#include <math.h>

using namespace std;

/*
问题:给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一颗高度最小的二叉查找树。
分析:二叉查找树是左孩子<结点<右孩子。二叉查找树关键的部分应该是调整树的结点,使得平衡。高度最小
     的二叉查找树,应该是平衡的二叉查找树。
	 中序遍历的结果是:左中右,那么遍历
	 问题转化为如何来建树。
建树的方式有:
	 方法1:创建结点数组,通过输入指定的父子节点来建立指向关系来建树;
这里只能根据二叉查找树的平衡方式进行处理:
1 在右子树添加右孩子,导致不平衡:
  此时应该找到不平衡的点,将不平衡的点向左旋转,将不平衡的右孩子代替原先不平衡的结点,
  并将不平衡的右孩子的左孩子旋转到不平衡的左孩子的右孩子
  设不平衡结点为A,不平衡结点右孩子为B,B的左孩子为C
  不平衡结点A向左旋转,称为B的左孩子,右孩子B代替A,如果C不为空,C变成B的右孩子。如下图所示,添加D,导致A失去平衡
     A                              B
   E     B        ----->        A      M   
	   C   M                  E   C       D
	         D
2 在右子树添加左孩子,导致不平衡:处理同上
3 在左子树添加左孩子,导致不平衡:将不平衡结点向右旋转,然后不平衡结点的左孩子代替原先不平衡结点,
                                  如果不平衡结点的左孩子有右孩子(会与旋转到右边的不平衡结点冲突,需要旋转到不平衡结点的左孩子)
总结:左子树添加左孩子导致不平衡,设不平衡结点为A,不平衡结点左孩子为B,B的右孩子为C
      将不平衡结点A向右旋转,B代替原先A的位置,如果C不为空,C变成A的左孩子处,如果C为空,则无需处理。
	  如下图所示,添加D,导致A失去平衡
	    A                          B
    B      E       ------>      M       A
  M   C                       D       C    E
D
4 在左子树添加右孩子导致不平衡:处理同上
	    A                          B
    B      E       ------>      M        A
  M   C                           D    C   E
    D



输入:
5(元素个数,接下来一行就是所有元素的值)
1 2 3 4 5
6
1 2 3 4 5 6
输出:
(打印出树)
       3
	1    4
	  2    5

	   3
     1     5
       2 4   6


关键:
1由于数组有序,这里构建高度最小的二叉查找树应该尽可能使得左右孩子节点个数尽量相等。
因此应该使用有序数组中间节点作为根节点,将有序数组左半部分作为左子树;同理右半部分
返回的根节点用于继续指向。
我没想到利用数组中间值作为根节点,牛逼。然后中间值左右两边继续划分
TreeNode* buildBinarySearchTree(TreeNode* head , int* pArray , int begin , int end)
{
	if(NULL == head || NULL == pArray || begin < 0 || end < 0)
	{
		return NULL;
	}
	int middle =  begin + (end - begin) / 2;
	//数组的中点用于建立根节点
	head->_value = *(pArray + middle);
	//说明只有一个元素,直接返回该根节点
	if(begin == end)
	{
		return head;
	}
	if(begin <= middle - 1)
	{
		TreeNode* leftChild = createNode();
		head->_pLeft = buildBinarySearchTree(leftChild , pArray , begin , middle - 1);
		leftChild->_pParent = head;
	}

2 第二种正规建平衡二叉树方式:
分析的目的是要解决旋转之后结点位置冲突问题
规律1:右子树添加孩子结点导致不平衡,
       设不平衡结点为A,不平衡结点右孩子为B,B的左孩子为C
       不平衡结点A向左旋转,称为B的左孩子,右孩子B代替A,C变成B的右孩子。如下图所示,添加D,导致A失去平衡
		 A                              B
	   E     B        ----->        A      M   
		   C   M                  E   C       D
				 D
规律2:左子树添加孩子结点导致不平衡,
	   设不平衡结点为A,不平衡结点左孩子为B,B的右孩子为C
       将不平衡结点A向右旋转,B代替原先A的位置,C变成A的左孩子处。
	   如下图所示,添加D,导致A失去平衡
				A                          B
			B      E       ------>      M       A
		  M   C                       D       C    E
		D

*/
const int MAXSIZE = 1000;
typedef struct TreeNode
{
	int _value;
	TreeNode* _pLeft;
	TreeNode* _pRight;
	TreeNode* _pParent;
	int _height;//用于打印二叉树某个节点的时候记录该节点所在行数
	int _spaceNum; //用于打印打印二叉树某个节点的时候记录该节点距离最左侧的空格个数
	bool _isVisited;//用于遍历的时候判断某个节点是否已经遍历过
	int _levelAdjacentSpaceNum; //当前层相邻节点空格个数
}TreeNode;
TreeNode g_treeNodeArray[MAXSIZE];
int g_index;
TreeNode* createNode()
{
	++g_index;
	g_treeNodeArray[g_index]._pLeft = g_treeNodeArray[g_index]._pRight = g_treeNodeArray[g_index]._pParent = NULL;
	//默认设置该节点未访问过
	g_treeNodeArray[g_index]._isVisited = false;
	return &g_treeNodeArray[g_index];
}


/*
该建树的目的,是为了构建平衡的二叉查找树
由于数组有序,这里构建高度最小的二叉查找树应该尽可能使得左右孩子节点个数尽量相等。
因此应该使用有序数组中间节点作为根节点,将有序数组左半部分作为左子树;同理右半部分
返回的根节点用于继续指向
*/
TreeNode* buildBinarySearchTree(TreeNode* head , int* pArray , int begin , int end)
{
	if(NULL == head || NULL == pArray || begin < 0 || end < 0)
	{
		return NULL;
	}
	int middle =  begin + (end - begin) / 2;
	//数组的中点用于建立根节点
	head->_value = *(pArray + middle);
	//说明只有一个元素,直接返回该根节点
	if(begin == end)
	{
		return head;
	}
	if(begin <= middle - 1)
	{
		TreeNode* leftChild = createNode();
		head->_pLeft = buildBinarySearchTree(leftChild , pArray , begin , middle - 1);
		leftChild->_pParent = head;
	}
	if(middle + 1 <= end)
	{
		TreeNode* rightChild = createNode();
		head->_pRight = buildBinarySearchTree(rightChild , pArray , middle + 1 , end); 
		rightChild->_pParent = head;
	}
	return head;
}

/*
计算平衡二叉树高度,输入参数为二叉树结点个数
设共有n个节点,平衡二叉树的高度为h,则满足
n <= 2^h + 1,即 h >= log2(n+1),即 h = log2(n+1)向上取整
log a N = log c N / log c a

*/
int computeAVLBinaryTreeHeight(int nodeNum)
{
	//ceil函数向上取整,floor向下取整 , 换底公式 log a N = log c N / log c a
	 int height = ceil ( log(nodeNum + 1) / log(2) );
	 return height;
}

/*
产生打印二叉树的字符串结果
由于如果每层相邻两个结点间隔的空格个数如果相同会导致两个结点连接在一起,如下图所示,结点3和结点5连接在一起。
		       4
		     2   6
		   1   35   7
解决办法:从上层至下层结点,相邻两个结点间距要依次减少
设共有n个节点,平衡二叉树的高度为h,则满足
n <= 2^h + 1,即 h >= log2(n+1),即 h = log2(n+1)向上取整
另高度为1的相邻节点间隔空格个数为2^h
        2                        2^(h-1)
		...
		h                        2^0
即可
*/
void generatePrintResult(TreeNode* node, vector<string>& vecResult )
{
	if(node->_value >= 2700)
	{
		int a = 1;
	}
	//打印当前节点,如果该行待打印字符串还没有建立,则先创建
	if( node->_height >= vecResult.size() )
	{
		stringstream ss;
		//打印空格
		for(int i = 0 ; i < node->_spaceNum; i++)
		{
			ss << string(" ");
		}
		//打印结点值
		ss << node->_value;
		vecResult.push_back(ss.str());
	}
	//如果该行已经存在,根据广度优先遍历左孩子,再是右孩子,因此应该是取出字符串流,找到待打印空格位置后一位,打印结点值
	else
	{
		//这里必须获取引用
		string ss = vecResult.at(node->_height);
		int spaceNum = node->_spaceNum;
		string searchStr(" ");
		stringstream sStream;
		sStream << ss;
		//int appearSpaceNum = findStrAppearTimes(ss, searchStr);
		//为了解决打印的时候父节点的左右孩子节点相对于父节点不对称,默认出现的空格个数为字符串长度
		int appearSpaceNum = ss.length();
		/*
		如果空格的长度 > 字符串长度,说明此次添加的空格是在原有的基础上添加的,注意,易错,这里如果出现相等的情况
		比如二叉树
		       4
		     2   6
		   1   35   7
		   必须要解决3和5会连接在一起的情况,如果要用空格区分,会导致整体空格个数不太对,但是应该不影响
		*/
		if(spaceNum > appearSpaceNum)
		{
			for(int i = 0 ; i < spaceNum - appearSpaceNum; i++)
			{
				sStream << string(" ");
			}

			//注意是整形,不转化为字符串就只能要字符串流
			sStream << node->_value;
			ss = sStream.str();
		}
		//这里有问题,这个分支是当前计算出结点值的存放位置在已有字符串里面,应该不能添加,直接也是累加在后面,加上一个空格
		else
		{
			//更新当前结点的位置
			node->_spaceNum = ss.length() + 1;
			stringstream sss;
			sss << ss << " " << node->_value;
			ss = sss.str();
		}
		vecResult.at(node->_height) = ss;
	}
}

/*
遍历二叉树,为二叉树中每个节点设置打印所在行数和距离最左侧空格个数,并打印
关键是即使知道某一行需要打印节点个数,但是需要放在同一行,因此需要设置一个字符串数组
来存放最终的打印结果

打印二叉树:必须包含 空格自动打印,按层次打印,用队列方式打印
首先需要从根节点出发,向左移动的次数n,然后设每向左移动一次的空格个数为m,
则计算出根节点距离左侧的空格个数=n * m
左孩子节点则在父节点距离最左侧总空格个数的基础上减去m即可

空格个数还需要考虑之前值的长度
*/
void visitTreeBFS(TreeNode* head , int spaceNum , vector<string>& vecResult)
{
	if(NULL == head)
	{
		return;
	}
	queue<TreeNode*> queueTree;
	int height = 0;
	head->_height = height;
	head->_spaceNum = spaceNum;
	head->_levelAdjacentSpaceNum = spaceNum;
	queueTree.push(head);
	while(!queueTree.empty())
	{
		TreeNode* node = queueTree.front();
		queueTree.pop();
		generatePrintResult(node , vecResult);

		//左孩子不空,且左孩子未遍历过,压入队列
		if(node->_pLeft != NULL && node->_pLeft->_isVisited == false)
		{
			//设置高度和空格个数
			node->_pLeft->_height = node->_height + 1;
			//设置当前层相邻结点空格个数为父节点所在层相邻结点空格个数的一半
			node->_pLeft->_levelAdjacentSpaceNum = node->_levelAdjacentSpaceNum / 2;
			node->_pLeft->_spaceNum = node->_spaceNum - node->_pLeft->_levelAdjacentSpaceNum;
			node->_pLeft->_isVisited = true;
			queueTree.push(node->_pLeft);
		}
		if(node->_pRight != NULL && node->_pRight->_isVisited == false)
		{
			//设置高度和空格个数
			node->_pRight->_height = node->_height + 1;
			node->_pRight->_levelAdjacentSpaceNum = node->_levelAdjacentSpaceNum / 2;
			//注意,右子树累加空格
			node->_pRight->_spaceNum = node->_spaceNum + node->_pRight->_levelAdjacentSpaceNum;
			node->_pRight->_isVisited = true;
			queueTree.push(node->_pRight);
		}
	}
}



//打印二叉树
void printTree(vector<string>& vecResult)
{
	if(vecResult.empty())
	{
		cout << "Null Tree!" << endl;
		return;
	}
	vector<string>::iterator it;
	stringstream ssResult;
	for(it = vecResult.begin() ; it != vecResult.end() ; it++)
	{
		string ss = *it;
		ssResult << ss << endl;
	}
	cout << ssResult.str();
}

int main(int argc, char* argv[])
{
	int n;
	while(cin >> n)
	{
		int * pArray = new int[n];
		for(int i = 0 ; i < n ; i++)
		{
			cin >> *(pArray + i);
		}
		TreeNode* head = createNode();
		head = buildBinarySearchTree(head , pArray , 0 , n-1);
		//打印二叉树
		vector<string> vecResult;
		int height = computeAVLBinaryTreeHeight(n);
		int spaceNum = (int) pow(2 , height); //设该平衡二叉树高度为h,则第一层相邻结点距离2^h
		visitTreeBFS(head , spaceNum , vecResult);
		printTree(vecResult);
		delete[] pArray;
	}
	getchar();
	return 0;
}

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/qingyuanluofeng/article/details/53911355
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞