【说明】:
本文是左程云老师所著的《程序员面试代码指南》第一章中“构造数组的MaxTree”这一题目的C++复现。
本文只包含问题描述、C++代码的实现以及简单的思路,不包含解析说明,具体的问题解析请参考原书。
感谢左程云老师的支持。
【题目】:
定义二叉树节点如下:

class Node
{
public:
Node(int data)
{
value = data;
left = NULL;
right = NULL;
}
public:
int value;
Node *left;
Node *right;
};
一个数组的 MaxTree 定义如下:
- 数组必须没有重复元素;
- MaxTree 是一棵二叉树,数组的每一个值对应一个二叉树节点;
- 包括 MaxTree 树在内且在其中一的每一棵子树上,值最大的节点都是树的头。
给定一个没有重复元素的数组 arr,写出生成这个数组的 MaxTree 的函数,要求如果数组长度为 N,则时间复杂度为 O(N),额外空间复杂度为 O(N)。
【思路】:
利用栈找到每个数左右两边第一个比它大的数,并且用hash_map保存。(估计这样说大家都不明白,大家可以看原书,或者分析代码喽)
【编译环境】:
CentOS6.7(x86_64)
gcc 4.4.7
【实现】:
实现及测试代码:

#include <sstream>
using namespace std;
using namespace __gnu_cxx;
class Node
{
public:
Node(int data)
{
value = data;
left = NULL;
right = NULL;
}
public:
int value;
Node *left;
Node *right;
};
/*
*函数说明:利用hash_map分别为每一个数的左边和右边第一个最大值(序号)设定关联
*输入参数:s为存放数组序号的栈;map为待建好的映射表
*输出参数:s为存放数组序号的栈;map为整理好的映射表
*返回值:
*/
void popStackSetMap(stack<int> &s,hash_map<int,string> &map)
{
int tmp = s.top();
s.pop();
stringstream ss;
string str;
if(s.empty())
{
map[tmp] = str;
}
else
{
ss << s.top();
ss >> str;
map[tmp] = str; //序号对应序号的关系
}
return ;
}
Node* getMaxTree(int a[],int len)
{
stack<int> s;
hash_map<int,string> lBigMap;
//为数组内的每个数据分别对应一个左端第一个最大值,没有则为空
for(int i=0; i<len; i++)
{
while(!s.empty() && a[s.top()] < a[i])
popStackSetMap(s,lBigMap);
s.push(i); //注意,这里保存的是数组序号,而不是数组值
}
while(!s.empty())
popStackSetMap(s,lBigMap);
//为数组内的每个数据分别对应一个右端第一个最大值,没有则为空
hash_map<int,string> rBigMap;
for(int i=len-1; i>=0; i--)
{
while(!s.empty() && a[s.top()] < a[i])
popStackSetMap(s,rBigMap);
s.push(i); //注意,这里保存的是数组序号,而不是数组值
}
while(!s.empty())
popStackSetMap(s,rBigMap);
//构造Node数组
Node* nArr[len];
for(int i=0;i<len;i++)
{
nArr[i] = new Node(a[i]);
}
//构造Node的MaxTree
Node *head = NULL;
for(int i=0;i<len;i++)
{
//调试代码
//cout << "nArr[" << i << "]->value = " << nArr[i]->value << endl;
//cout << "lBigMap = " << lBigMap[i] << endl;
//cout << "rBigMap = " << rBigMap[i] << endl;
string ls = lBigMap[i];
string rs = rBigMap[i];
if(ls.empty() && rs.empty())
{
head = nArr[i];
}
else if(ls.empty())
{
stringstream ss(rs);
int rID;
ss >> rID;
if(NULL == nArr[rID]->left)
nArr[rID]->left = nArr[i];
else
nArr[rID]->right = nArr[i];
}
else if(rs.empty())
{
stringstream ss(ls);
int lID;
ss >> lID;
if(NULL == nArr[lID]->left)
nArr[lID]->left = nArr[i];
else
nArr[lID]->right = nArr[i];
}
else
{
stringstream lss(ls);
int lID;
lss >> lID;
stringstream rss(rs);
int rID;
rss >> rID;
int pID = a[lID] < a[rID] ? lID : rID;
if(NULL == nArr[pID]->left)
nArr[pID]->left = nArr[i];
else
nArr[pID]->right = nArr[i];
}
}
return head;
}
int main()
{
int a[] = {3,4,5,1,2};
Node *head = getMaxTree(a,5);
Node *left = head->left;
Node *right = head->right;
cout << "head->value = "<< head->value << endl;
cout << head->value <<"->left->value = "<< left->value << endl;
cout << head->value <<"->right->value = "<< right->value << endl;
if(NULL != left->left)
cout << left->value << "->left->value = " << left->left->value << endl;
else
cout << left->value << "->left is empty!" << endl;
if(NULL != left->right)
cout << left->value << "->right->value = " << left->right->value << endl;
else
cout << left->value << "->right is empty!" << endl;
if(NULL != right->left)
cout << right->value << "->left->value = " << right->left->value << endl;
else
cout << right->value << "->left is empty!" << endl;
if(NULL != right->right)
cout << right->value << "->right->value = " << right->right->value << endl;
else
cout << right->value << "->right is empty!" << endl;
return 0;
}
【说明】
1、hash_map并不属于标准的STL,但是大部分的开发环境已经将其实现。在GCC中也可直接使用,但是需要声明命名空间为 __gnu_cxx;
2、C++中的hash_map的使用自定义的类型时,需要在类中实现hash函数和等于比较函数,所以我使用了string作为替代;
3、在string与int类型的互换中,我使用了stringstream这一辅助类型。
4、关键的思路还是参考了左程云老师提出的思路,具体实现有些差别(我是将数组的序号压入栈中,在hash_Map中将数组序号(int类型)与字符串类型(string)相对应),小伙伴们可以自行查看。
5、hash_map使用方法的参考文章:
6、我觉得这个题目十分像堆排序中的建立大顶堆这一问题,只不过在排序中自然不可能形成二叉树这么明显的结构了。堆排序代码如下:

1 void print(int a[], int n){
2 for(int j= 0; j<n; j++){
3 cout<<a[j] <<" ";
4 }
5 cout<<endl;
6 }
7
8
9
10 /**
11 * 已知H[s…m]除了H[s] 外均满足堆的定义
12 * 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,
13 *
14 * @param H是待调整的堆数组
15 * @param s是待调整的数组元素的位置
16 * @param length是数组的长度
17 *
18 */
19 void HeapAdjust(int H[],int s, int length)
20 {
21 int tmp = H[s];
22 int child = 2*s+1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)
23 while (child < length) {
24 if(child+1 <length && H[child]<H[child+1]) { // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
25 ++child ;
26 }
27 if(H[s]<H[child]) { // 如果较大的子结点大于父结点
28 H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点
29 s = child; // 重新设置s ,即待调整的下一个结点的位置
30 child = 2*s+1;
31 } else { // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
32 break;
33 }
34 H[s] = tmp; // 当前待调整的结点放到比其大的孩子结点位置上
35 }
36 print(H,length);
37 }
38
39
40 /**
41 * 初始堆进行调整
42 * 将H[0..length-1]建成堆
43 * 调整完之后第一个元素是序列的最小的元素
44 */
45 void BuildingHeap(int H[], int length)
46 {
47 //最后一个有孩子的节点的位置 i= (length/2 -2),此处和原文不同
48 for (int i = (length/2 -1) ; i >= 0; --i)
49 HeapAdjust(H,i,length);
50 }
51 /**
52 * 堆排序算法
53 */
54 void HeapSort(int H[],int length)
55 {
56 //初始堆
57 BuildingHeap(H, length);
58 //从最后一个元素开始对序列进行调整
59 for (int i = length - 1; i > 0; --i)
60 {
61 //交换堆顶元素H[0]和堆中最后一个元素
62 int temp = H[i]; H[i] = H[0]; H[0] = temp;
63 //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
64 HeapAdjust(H,0,i);
65 }
66 }
67
68 int main(){
69 int H[10] = {3,1,5,7,2,4,9,6,10,8};
70 cout<<"初始值:";
71 print(H,10);
72 HeapSort(H,10);
73 //selectSort(a, 8);
74 cout<<"结果:";
75 print(H,10);
76
77 }
堆排序的代码我是直接从 八大排序算法 这一文章内直接copy过来的,里面有一处不同(//最后一个有孩子的节点的位置 ,我认为应该时i= (length/2 -1) )。
利用大顶堆的方法实现MaxTree,代码如下:

1 /*
2 *文件名:bigTopHeap_MaxTree.cpp
3 *作者:
4 *摘要:利用大顶堆的方法实现MaxTree
5 */
6
7 #include <iostream>
8
9 using namespace std;
10
11 class Node
12 {
13 public:
14 Node(int data)
15 {
16 value = data;
17 left = NULL;
18 right = NULL;
19 }
20 public:
21 int value;
22 Node *left;
23 Node *right;
24 };
25
26 //利用数组建立大顶堆
27 void buildBTHeap(int arr[],int len)
28 {
29 int i = len/2 - 1;
30 for(; i >= 0; i--)
31 {
32 int fpos = i;
33 int fvalue = arr[fpos];
34 int child = 2*i + 1;
35 while(child < len)
36 {
37 if( child+1 < len && arr[child] < arr[child+1])
38 child++;
39 if(fvalue < arr[child])
40 {
41 arr[fpos] = arr[child];
42 fpos = child;
43 child = 2*fpos + 1;
44 }
45 else
46 break;
47 arr[fpos] = fvalue;
48 }
49 }
50 for(int i=0;i<len;i++)
51 cout << arr[i] << " " ;
52 cout << endl;
53 }
54
55 //利用数组建立二叉树(顺序建立)
56 Node* buildBinaryTree(int arr[],int len,int pos=0)
57 {
58 if(NULL == arr || 0 >= len)
59 return NULL;
60 if(pos >= len)
61 return NULL;
62 Node *head = new Node(arr[pos]);
63 head->left = buildBinaryTree(arr,len,2*pos+1);
64 head->right = buildBinaryTree(arr,len,2*pos+2);
65 return head;
66 }
67
68 Node *getMaxTree(int arr[],int len)
69 {
70 buildBTHeap(arr,len);
71 return buildBinaryTree(arr,len);
72 }
73
74 int main()
75 {
76 int a[] = {3,4,5,1,2};
77 Node *head = getMaxTree(a,5);
78 Node *left = head->left;
79 Node *right = head->right;
80 cout << "head->value = "<< head->value << endl;
81 cout << head->value <<"->left->value = "<< left->value << endl;
82 cout << head->value <<"->right->value = "<< right->value << endl;
83 if(NULL != left->left)
84 cout << left->value << "->left->value = " << left->left->value << endl;
85 else
86 cout << left->value << "->left is empty!" << endl;
87
88 if(NULL != left->right)
89 cout << left->value << "->right->value = " << left->right->value << endl;
90 else
91 cout << left->value << "->right is empty!" << endl;
92
93 if(NULL != right->left)
94 cout << right->value << "->left->value = " << right->left->value << endl;
95 else
96 cout << right->value << "->left is empty!" << endl;
97
98 if(NULL != right->right)
99 cout << right->value << "->right->value = " << right->right->value << endl;
100 else
101 cout << right->value << "->right is empty!" << endl;
102 return 0;
103 }
大顶堆的方法实现的 MaxTree 结构和栈方法实现的 MaxTree 结构略有不同,但也达到了要求。
7、测试代码未使用二叉树的遍历算法,大家就凑合着看看吧。^_^ ^_^ ^_^
注:
转载请注明出处;
转载请注明源思路来自于左程云老师的《程序员代码面试指南》。
来源:http://www.cnblogs.com/PrimeLife/p/5339965.html
