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

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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
7
1 2 3 4 5 6 7
输出:
(打印出树)
      2
	1    4
	   3   5

	   4
     2    5
   1   3     6

        4
	 2		6
   1   3   5  7

关键:
1 总结:分析的目的是要解决旋转之后结点位置冲突问题
规律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

2 具体调整方式
	//左子树不平衡
	if(LEFT == unblancedFlag)
	{
		TreeNode* leftChild = notBalancedHead->_pLeft;
		if(NULL == leftChild)
		{
			return ;
		}
		TreeNode* grandSon = leftChild->_pRight;
		//需要判定不平衡的节点是否有父节点
		TreeNode* parent = notBalancedHead->_pParent;
		//开始调整,如果父节点为空,说明不平衡结点原先是树的顶点
		if(NULL == parent)
		{
			leftChild->_pParent = NULL;
		}
		//如果父节点不为空,父节点指向也需要修改
		else
		{
			parent->_pRight = leftChild;
			leftChild->_pParent = parent;
		}
		leftChild->_pRight = notBalancedHead;
		//重新设置父节点的指向
		notBalancedHead->_pParent = leftChild;
		//易错,孩子节点为空也要设置,因为不平衡结点左孩子为leftChild,不设置会陷入死循环
		notBalancedHead->_pLeft = grandSon;
	}

3关于构建平衡二叉树
	for(int i = 1 ; i < n ; i++)
	{
		TreeNode* node = createNode();
		node->_value = *( pArray + i );
		//插入节点
		insertNode(headNode , node);
		int height = checkHeight(headNode);
		//如果不是平衡二叉树,则需要调整
		if(height == -1)
		{
			//调整的时候,传入的是不平衡结点而不是顶点
			adjustTree(g_unbalancedNode , g_unbalancedFlag);
			//寻找调整后的根节点
			headNode = getHeadNode(headNode);
			//重新设置不平衡标记
			g_unbalancedFlag = UNKNOWN;
		}
	}

4 关于打印平衡二叉树
产生打印二叉树的字符串结果
由于如果每层相邻两个结点间隔的空格个数如果相同会导致两个结点连接在一起,如下图所示,结点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
*/
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];
}


int max(int a, int b)
{
	return a > b ? a : b ;
}

TreeNode* g_unbalancedNode; // 用于保存不平衡结点
enum UnbalancedFlag{LEFT , RIGHT , UNKNOWN};//必须记录是左孩子还是右孩子不平衡,枚举没有“=”
UnbalancedFlag g_unbalancedFlag;

//判断是否为二叉平衡树,如果是二叉平衡树,则返回该结点对应的树的高度;否则返回-1表示以该结点为顶点对应的二叉树不是平衡二叉树
int checkHeight(TreeNode* pHead)
{
	if(pHead == NULL)
	{
		return 0;
	}
	int leftHeight = checkHeight(pHead->_pLeft);
	if(leftHeight == -1)
	{
		//如果不平衡标记已经不等于UNKNOWN,说明已经找到了不平衡结点了,那么后续找到的不平衡结点不需要再赋值了
		if(g_unbalancedFlag == UNKNOWN)
		{
			g_unbalancedNode = pHead->_pLeft;
		}
		return -1;
	}
	int rightHeight = checkHeight(pHead->_pRight);
	if(rightHeight == -1)
	{
		if(g_unbalancedFlag == UNKNOWN)
		{
			g_unbalancedNode = pHead->_pRight;
		}
		return -1;
	}
	int heightDiff = abs(rightHeight - leftHeight);
	if(heightDiff > 1)
	{
		if(g_unbalancedFlag == UNKNOWN)
		{
			g_unbalancedNode = pHead;
			if(rightHeight > leftHeight)
			{
				g_unbalancedFlag = RIGHT;
			}
			else
			{
				g_unbalancedFlag = LEFT;
			}
		}
		return -1;
	}
	else
	{
		return max(leftHeight , rightHeight) + 1;
	}
}

/*
获取不平衡结点,如果同时存在多个不平衡结点,那么就取最近的不平衡结点。
根据遍历的特点,由于遍历父节点的时候会优先判断父节点的孩子节点是否平衡。
因此第一次不平衡时候,就保存的不平衡结点就是我们寻找的
但找到后由于递归,退出成了问题
*/
TreeNode* getNotbalancedNode(TreeNode* head)
{
	return g_unbalancedNode;
}

//To-Do,获取根节点,只需要获取任意节点,向上遍历,如果某个节点的父节点为空,那么该节点就是根节点
TreeNode* getHeadNode(TreeNode* pNode)
{
	if(pNode == NULL)
	{
		return NULL;
	}
	if(pNode->_pParent == NULL)
	{
		return pNode;
	}
	return getHeadNode(pNode->_pParent);
}

/*
根据传入的不平衡结点,进行调整
左子树不平衡: 设不平衡结点为A,A的左孩子为B,B的右孩子为C
则将A变为B的右孩子,C变为A的左孩子

右子树不平衡:设不平衡结点为A,A的右孩子为B,B的左孩子为C
则将A变为B的左孩子,C变为A的右孩子

该函数无需返回根节点
*/
void adjustTree(TreeNode* notBalancedHead , UnbalancedFlag unblancedFlag)
{
	if(NULL == notBalancedHead)
	{
		return ;
	}

	//左子树不平衡
	if(LEFT == unblancedFlag)
	{
		TreeNode* leftChild = notBalancedHead->_pLeft;
		if(NULL == leftChild)
		{
			return ;
		}
		TreeNode* grandSon = leftChild->_pRight;
		//需要判定不平衡的节点是否有父节点
		TreeNode* parent = notBalancedHead->_pParent;
		//开始调整,如果父节点为空,说明不平衡结点原先是树的顶点
		if(NULL == parent)
		{
			leftChild->_pParent = NULL;
		}
		//如果父节点不为空,父节点指向也需要修改
		else
		{
			parent->_pRight = leftChild;
			leftChild->_pParent = parent;
		}
		leftChild->_pRight = notBalancedHead;
		//重新设置父节点的指向
		notBalancedHead->_pParent = leftChild;
		//易错,孩子节点为空也要设置,因为不平衡结点左孩子为leftChild,不设置会陷入死循环
		notBalancedHead->_pLeft = grandSon;
	}
	//右子树不平衡
	else if(RIGHT == unblancedFlag)
	{
		TreeNode* rightChild = notBalancedHead->_pRight;
		if(NULL == rightChild)
		{
			return ;
		}
		TreeNode* grandSon = rightChild->_pLeft;
		TreeNode* parent = notBalancedHead->_pParent;
		if(NULL == parent)
		{
			rightChild->_pParent = NULL;
		}
		else
		{
			rightChild->_pParent = parent;
			parent->_pRight = rightChild;
		}
		rightChild->_pLeft = notBalancedHead;
		notBalancedHead->_pParent = rightChild;
		notBalancedHead->_pRight = grandSon;
	}
}

//将节点node插入到以head为树的顶点的平衡二叉树中
void insertNode(TreeNode* head , TreeNode* node)
{
	//如果根节点为空,直接返回不做处理
	if(NULL == head)
	{
		return;
	}
	//如果当前节点小于节点,则插入在当前节点的左孩子
	if(node->_value < head->_value)
	{
		if(head->_pLeft == NULL)
		{
			head->_pLeft = node;
			node->_pParent = head;
		}
		//如果左孩子不空,令左孩子为根节点重复上述过程
		else
		{
			insertNode(head->_pLeft , node);
		}
	}
	else if(node->_value > head->_value)
	{
		if(head->_pRight == NULL)
		{
			head->_pRight = node;
			node->_pParent = head;
		}
		else
		{
			insertNode(head->_pRight , node);
		}
	}
}

//该建树的目的,是为了构建平衡的二叉查找树
TreeNode* buildBinarySearchTree(TreeNode** pHead , int* pArray , int n)
{
	//如果根节点为空,那么无法构建二叉查找树,返回空
	if(pHead == NULL)
	{
		return NULL;
	}
	if(pArray == NULL || n <= 0)
	{
		return NULL;
	}

	TreeNode* headNode = *pHead;
	headNode->_value = pArray[0];
	g_unbalancedFlag = UNKNOWN;
	//除了首节点是直接能赋值就知道外,其余节点需要执行插入操作,插入操作需要知道根节点,需要不断和根节点比较才知道插在哪里
	for(int i = 1 ; i < n ; i++)
	{
		TreeNode* node = createNode();
		node->_value = *( pArray + i );
		//插入节点
		insertNode(headNode , node);
		int height = checkHeight(headNode);
		//如果不是平衡二叉树,则需要调整
		if(height == -1)
		{
			//调整的时候,传入的是不平衡结点而不是顶点
			adjustTree(g_unbalancedNode , g_unbalancedFlag);
			//寻找调整后的根节点
			headNode = getHeadNode(headNode);
			//重新设置不平衡标记
			g_unbalancedFlag = UNKNOWN;
		}
	}

	//重新设置根节点
	*pHead = headNode;
	return (*pHead);
}

int leftShiftTimes(TreeNode* head , int& times)
{
	if(NULL == head)
	{
		return 0;
	}
	//左孩子不空,继续向左递归处理
	if(head->_pLeft != NULL)
	{
		times++;
		return leftShiftTimes(head->_pLeft , times);
	}
	//左孩子为空,说明无需再继续统计了,直接返回向左移动次数
	else
	{
		return times;
	}
}

//找到某字符串在指定字符串中出现次数
int findStrAppearTimes(string& str, string& searchStr)
{
	int index = str.find_first_of(searchStr , 0);
	int count = 0;
	while(index != string::npos)
	{
		count++;
		index = str.find_first_of(searchStr , index + searchStr.length());
	}
	return count;
}

//寻找指定出现次数处字符串位置
int findStr(string& str, string& searchStr , int times)
{
	if(times <= 0)
	{
		return 0;
	}
	int index = str.find_first_of(searchStr , 0);
	int count = 0;
	while(index != string::npos)
	{
		count++;
		if(times == count)
		{
			//起始位置本身占据一个字符的长度,所以需要减去1,这里返回的就是指定次数处字符串出现位置最后一个字符对应位置
			return index + searchStr.length() - 1;
		}
		index = str.find_first_of(searchStr , index + searchStr.length());
	}
	//指定出现次数过大,直接返回0
	return 0;
}

/*
计算平衡二叉树高度,输入参数为二叉树结点个数
设共有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 ,int spaceNum , int leftShiftNum , vector& 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
		{
			//找到第spaceNum个空格,然后加上当前内容,在拼接剩余内容
			/*
			int index = findStr(ss, searchStr , spaceNum);
			string s1 = ss.substr(0 , index + 1);
			string s2 = ss.substr(index + 1 , ss.length() - (index + 1) );
			stringstream sss;
			sss << s1 << node->_value << s2;
			ss = sss.str();
			*/
			//更新当前结点的位置
			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 , int leftShiftNum , vector& vecResult)
{
	if(NULL == head)
	{
		return;
	}
	queue 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, spaceNum , leftShiftNum , 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& vecResult)
{
	if(vecResult.empty())
	{
		cout << "Null Tree!" << endl;
		return;
	}
	vector::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();
		TreeNode** pHead = &head;
		head = buildBinarySearchTree(pHead , pArray , n);
		//打印二叉树
		vector vecResult;
		int times = 0;
		int leftShiftNum = leftShiftTimes(head , times);
		int height = computeAVLBinaryTreeHeight(n);
		int spaceNum = (int) pow(2 , height); //设该平衡二叉树高度为h,则第一层相邻结点距离2^h
		visitTreeBFS(head , spaceNum , leftShiftNum , vecResult);
		printTree(vecResult);
		delete[] pArray;
	}
	getchar();
	return 0;
}


你可能感兴趣的:(程序员面试金典)