【说明】:
本文是左程云老师所著的《程序员面试代码指南》第一章中“构造数组的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、测试代码未使用二叉树的遍历算法,大家就凑合着看看吧。^_^ ^_^ ^_^
注:
转载请注明出处;
转载请注明源思路来自于左程云老师的《程序员代码面试指南》。