微軟Google等名企面試中關於二叉樹常見的一些問題匯總

來源:互聯網
上載者:User

序:二叉樹作為樹的一種,是一種重要的資料結構,常見的二叉樹有:
滿二叉樹:除葉子結點外,所有結點都有兩個結點,葉子結點的left,right為NULL.
哈夫曼樹:又稱為最優二叉數,是一種帶權路徑最短的樹。哈夫曼編碼就是哈夫曼樹的應用,可以用來進行編碼壓縮.哈夫曼樹的構造見哈夫曼樹的構造
完全二叉樹:除了最底層的葉子結點之外,其餘層全滿,而且葉子層集中在左端.堆是一種特殊的完全二叉樹(全滿或者差一個結點就全滿)
平衡二叉樹:所謂平衡二叉樹指的是,左右兩個子樹的高度差的絕對值不超過 1。包括AVL樹,紅/黑樹狀結構.
紅/黑樹狀結構:具體見紅/黑樹狀結構問題
下面是我總結的這幾天看過的一些常見二叉樹問題.

1.二叉搜尋樹的迭代構造二叉搜尋樹是一棵排好序的樹,當新插入一個節點的時候,小於當前根結點左走,大於當前根結點右走,直至走到NULL點,就是該結點的位置.

void IterativeInsert(Tree* T,node* z)//插入節點  {      node* y=NULL;      node* x=T->root;//管理兩個指標,父指標y,y的子樹指標x      while(x!=NULL)//一直向下遍曆到z應該插入的位置      {          y=x;          if(x->value < z->value)              x=x->right;          else x=x->left;      }      z->p=y;//先將z的父指標p指向y      if(y==NULL)//若樹為空白,樹根即為z          T->root=z;      else if(z->value < y->value)//否則分插入左邊還是右邊          y->left=z;      else y->right=z;  }

2.二叉搜尋樹的遞迴構造1.若是空樹,則插入至根結點位置2.若比當前根點,插入左邊.3.否則插入右邊

node* TreeInsert(node* root,node* z){if(!root)root=z;else if(root->value < z->value)root->right=TreeInsert(root->right,z);else root->left=TreeInsert(root->left,z);return root;}

3.二叉樹三種遞迴遍曆方式.

void InorderTreeWalk(node* root){if(!root ) return ;InorderTreeWalk(root->left);cout<<root->value<<' ';InorderTreeWalk(root->right);}void PriorTreeWalk(node* root){if(!root ) return ;cout<<root->value<<' ';PriorTreeWalk(root->left);PriorTreeWalk(root->right);}void PostTreeWalk(node* root){if(!root ) return ;PostTreeWalk(root->left);PostTreeWalk(root->right);cout<<root->value<<' ';}

4.二叉樹三種迭代遍曆方式.深度優先原則,輸出順序是'左子樹優先'

void IterativeInorderWalk(node *root)//迭代中序遍曆  {      node* p=root;      stack<node*> st;//利用棧      if(!p)  //if(!p)=if(p==null)        return ;      while(p || !st.empty())//當p不為空白 或者st不為空白      {          while(p)//沿著左孩子方向走到最左下。同時壓棧          {              st.push(p);              p=p->left;          }          p=st.top();//擷取棧頂元素          cout<<p->value<<" ";          st.pop();          p=p->right;      }  }  void IterativePriorTreeWalk(node* root)//迭代先序遍曆  {      node* p=root;      stack<node* > st;      if(!p)           return ;      while(p || !st.empty())      {          while(p)          {              cout<<p->value<<" ";              st.push(p);              p=p->left;          }          p=st.top();          st.pop();          p=p->right;      }  }  void IterativePostWalk(node* root)  {      node* p=root;      stack<node* > st;      node* pre=NULL;//pre表示最近一次訪問的結點      if(!p) return ;      while(p || !st.empty())      {          while(p)    //沿著左孩子方向走到最左下          {              st.push(p);              p=p->left;          }          p=st.top(); //擷取棧頂元素          if( !p->right || p->right ==pre)//如果p沒有右孩子或者其右孩子剛剛被訪問過.小竅門if (!p)表示如果p為空白          {              st.pop();              cout<<p->value<<' ';              pre=p;      //最近一次訪問的節點是p              p=NULL;     //使下一次迴圈不再執行p節點以下的壓棧操作          }          else              p=p->right;      }  }  

5.怎樣從根結點開始逐層列印二叉樹結點資料.廣度優先原則,需要用到隊列,當訪問一個當前節點CurrentNode的時候,將該結點出隊,同時將該CurrentNode的左右子結點入隊,重複這個操作,直至隊列為空白.步驟:1初始化隊列.2重複出隊入隊操作直至隊列為空白.

//從頂部開始逐層列印二叉樹結點資料,即廣度遍曆二叉樹,需要用到隊列void printHori(node* root){if(!root) return ;queue<node*> Q;Q.push(root);while(!Q.empty()){node* Front=Q.front();Q.pop();cout<<Front->value<<' ';if(Front->left)Q.push(Front->left);if(Front->right)Q.push(Front->right);}}

6.獲得樹的深度樹的深度=max(左子樹深度,右子樹深度)+1

//獲得樹的深度int GetTreeDepth(node* root){if(!root)return 0;int left=GetTreeDepth(root->left);int right=GetTreeDepth(root->right);return 1+(left > right? left:right);//max(左子樹,右子樹)+1為整棵樹高度}

7.如何判斷一棵二叉樹是否是平衡二叉樹.方法1:最直接的辦法:獲得左子樹深度,獲得左右子樹深度,判斷二者是否相差在1以內,如果是,則說明當前根結點root是平衡.否則可以判斷false.繼續用同樣的辦法判斷root的左右子樹是否也平衡.現在分析一下這種辦法的效率:GetTreeDepth()函數類似於樹的後序遍曆,T(n)=2*T(n/2)+1,由主定理:T(n)=o(n).即,要找到樹的深度需要將每個結點全掃描一遍.同樣求isTreeBalance(left) 和 isTreeBalance(right)也需要將左右子樹重複掃描一遍.這樣重重複復掃描樹的方式效率是多少?T(N)=2*T(N/2)+N,即T(n)=nlgn.方法2:為了克服多次掃描節點方法的缺陷,我們試圖用空間換時間的做法,或者是自底向上的思維方式.方法的方式是,先求根是否平衡,再求左右子樹是否平衡.現在,我可以換一個角度,先求左右子樹是否平衡,如果左右子樹平衡,再考慮求根結點,否則直接返回false.這種方式需要在左右子樹返回根時儲存樹的深度.即空間換時間.這種方法的複雜度為:T(n)=2*T(n/2)+1.T(n)=n.

//判斷樹是否平衡bool isTreeBalance(node* root){if(!root) return true ;int left=GetTreeDepth(root->left);int right=GetTreeDepth(root->right);if((left - right) > 1 || (right-left) > 1) return false;return isTreeBalance(root->left) && isTreeBalance(root->right);}//判斷樹是否平衡bool isBalanceTree(node* root,int& depth){if(!root){depth=0;return true;}int left,right;if(isBalanceTree(root->left,left) && isBalanceTree(root->right,right)){if(left-right <= 1 && right-left <= 1){depth=1+ (left > right ? left:right);return true;}}return false;}

8.求二叉樹的最低公用祖先LCA性質:如果兩條鏈有公用祖先,那麼公用祖先往上的結點都重合.因為如果x=x',那麼x->next=x'->next必然成立.可能性一:若是二叉搜尋樹.1如果x,y小於root,則在左邊找2如果x,y大於root,則在右邊找3如果x,y在root之間,則root就是LCA可能性二:不是二叉搜尋樹,甚至不是二叉樹,但是,每個一節點都有parent指標那麼解法有2:1:空間換時間:從x,y到root的鏈表可以儲存在棧中,找出最後一個相同結點即可.2.不用空間換時間,多重掃描法,x,y到root兩條鏈路可能一長一短,相差為n個結點,那麼長鏈表先前移n步,然後,二者同步前移,找到第一個相同結點
即可.(樹不含環,這種辦法有效.)可能性三:這是一棵只有left和right的平凡二叉樹.那麼需要輔助空間,空間換時間法,先調用16.中的GetNodePath()獲得兩條從root->x和root->y的鏈表路徑.然後比較兩條鏈表,找到最後一個相同的結點即可.
//若是二叉搜尋樹,返回x和y的公用最低祖先LCAnode* LowestCommonAncestor1(node* root,node* x,node* y){if(!root || !x || !y) return NULL;if(x->value < root->value && y->value < root->value)return LowestCommonAncestor1(root->left,x,y);else if(x->value > root->value && y->value > root->value)return LowestCommonAncestor1(root->right,x,y);else return root;}//若不是搜尋二叉樹,但是,每個結點都有父結點,空間換時間法,否則需要重重複復地掃描路徑node* LowestCommonAncestor2(node* x,node* y){stack<node*> st1,st2;while(x){st1.push(x);x=x->p;}while(y){st2.push(y);y=y->p;}node* pLCA=NULL;while(!st1.empty() && !st2.empty() && st1.top()==st2.top()){pLCA=st1.top();st1.pop();st2.pop();}return pLCA;}//不用空間換時間法int GetListLength(node* x){int Count=0;while(x){++Count;x=x->p;}return Count;}int Myabs(int val){return val > 0 ? val : -val;}node* LowestCommonAncestor3(node* x,node* y){int LengthX=GetListLength(x);int LengthY=GetListLength(y);node* pLong=x,*pShort=y;if(LengthX < LengthY){pLong=y;pShort=x;}for(int i=0;i<Myabs(LengthX-LengthY);++i)pLong=pLong->p;while( pLong && pShort && pLong !=pShort){pLong=pLong->p;pShort=pShort->p;}if(pLong == pShort)return pLong;return NULL;}//既不是二叉搜尋樹,也不沒有parent指標,只是一棵平凡的二叉樹bool GetNodePath(node* root, node* pNode, list<node*>& path);node* LowestCommonAncestor4(node* root,node* x,node* y){list<node*> path1;list<node*> path2;GetNodePath(root,x,path1);GetNodePath(root,y,path2);node* pLCA=NULL;list<node*>::const_iterator it1=path1.begin();list<node*>::const_iterator it2=path2.begin();while(it1 != path1.end() && it2 != path2.end() && *it1 == * it2){pLCA=*it1;++it1;++it2;}return pLCA;}
9.在二叉樹中找出和為某一值的所有路徑要求所有路徑,路徑即root到某一結點的結點之集合.這是一個深度優先原則的搜尋.我們很容易想到先序遍曆.為了跟蹤路徑和,我們需要一個額外的輔助棧來跟蹤遞迴調用棧的操作過程.在進入到下一個調用FindPath(left)和FindPath(right)時,遞迴棧會將root壓入棧,因此我們也模仿進棧.當FindPath(left)和FindPath(right)返回,FindPath(root)運行周期到之後,局部函數變數root會被析造,root會從遞迴棧中彈出,因此,我們也從輔助棧中彈出root,只需要在中間加上判斷條件,將滿足條件的結果輸出即可.改成迭代版也很簡單.

void FindPath(node* root,int expectedSum,vector<int>& Path,int currentSum);//先序遍曆改裝版void FindPath(node* root,int expectedSum){int currentSum=0;vector<int> Path;FindPath(root,expectedSum,Path,currentSum);}void FindPath(node* root,int expectedSum,vector<int>& Path,int currentSum)//先序遍曆改裝版{if(!root)return ;//訪問根結點,同時將root->value加入輔助棧currentSum += root->value;Path.push_back(root->value);if(root->left==NULL && root->right ==NULL && currentSum == expectedSum){for(vector<int>::size_type i=0; i < Path.size(); ++i)cout<<Path[i]<<' ';cout<<endl;}FindPath(root->left,expectedSum,Path,currentSum);FindPath(root->right,expectedSum,Path,currentSum);//遞迴棧中,此時返回時,會將父結點銷毀,因為,局部函數生命週期已經到了.//所以輔助棧也需要和遞迴棧同步,將棧頂元素彈棧,同時當前路徑減去棧頂元素Path.pop_back();currentSum-=root->value;}

10.編寫一個程式,把一個有序整數數組放到二叉樹中這道題做法非常多,單純是這麼要求比較奇怪,因此,我選擇廣度優先插入,利用隊列實現,其實插成一個鏈表,或者隨便插入不知道可不可以?沒有其它要求真不知道怎麼弄.

node* HorizInsert(node* root,node* z){    if(!root)    {        root=z;        return root;    }    queue<node*> q;    q.push(root);    while(!q.empty())    {        node* Front=q.front();        q.pop();        if(Front->left==NULL)        {            Front->left=z;            return root;        }        if(Front->right==NULL)        {            Front->right=z;            return root;        }        if(Front->left)            q.push(Front->left);        if(Front->right)            q.push(Front->right);    }    return root;}

11.判斷整數序列是不是二叉搜尋樹的後序遍曆結果典型的遞迴思維,後序遍曆,根在最後,因此用根將二叉搜尋樹可以分成左右子樹,再遞迴處理左右子樹即可.

//判斷整數序列是不是二叉搜尋樹的後序遍曆結果  bool isPostTreeWalk(int* Last,int len){if( !Last || len <= 0)return false;int root=Last[len-1];int i=0;while(i < len-1 && Last[i] < root)//尋找左子樹劃分點++i;for(int j=i ; j < len-1 ; ++j){if(Last[j] < root)//如果右子樹中存在node<root,則無法構成後序結果return false;}bool isLastTree=true;if(i > 0) //判斷左邊是不是後序樹isLastTree=isPostTreeWalk(Last,i);//如果左邊是後序樹,繼續判斷右邊,如果左邊已經不是,則右邊已經無需判斷if(isLastTree && i < len-1 )isLastTree=isPostTreeWalk(Last+i,len-i-1);return isLastTree ;}

12.求二叉樹的鏡像畫出一個特例,我們發現只需要交換每個結點的左右指標(注意:不是數值)即可.因此在先序遍曆的時候換指標即可,沒有順序要求.

//求二叉樹的鏡像  void MirrorRotate(node* root){if(!root) return ;swap(root->left,root->right);MirrorRotate(root->right);MirrorRotate(root->left);}
13.一棵排序二叉樹(即二叉搜尋樹BST),令 f=(最大值+最小值)/2,設計一個演算法,找出距離f值最近、大於f值的結點。複雜度應儘可能低。BST中最大值是最右邊的值,最小值是最左邊的值,這樣就容易求出f,再求f的父指標或者右指標都可以?(我是這麼認為的).這裡求父指標.
//找出距離f值最近、大於f值的結點。node* FinClearF(node* root){if(!root) return NULL;node* max,*min;max=min=root;while(min->left)min=min->left;while(max->right)max=max->right;int F=(min->value + max->value) >> 1 ;while(1){if(root->value <= F)root=root->right;else{return root;}}}
14.把二叉搜尋樹轉變成排序的雙向鏈表其實就是中序遍曆的迭代版本,只是將中間的訪問結點代碼換成了調整指標.這種辦法返回的是鏈表的最後一個指標LastVist,因為是雙鏈表,這也可以接受.
//迭代法node* iTreeToList(node* root){node* p=root;stack<node*> st;node* LastVist=NULL;while(p || !st.empty()){while(p){st.push(p);p=p->left;}p=st.top();st.pop();//cout<<p->value<<' ';if(LastVist){LastVist->right=p;p->left=LastVist;}LastVist=p;p=p->right;}return LastVist;}
15.列印二叉樹中的所有路徑.和前邊的思路類似,用輔助棧記錄遞迴棧的運行過程,先序遍曆(深搜)的過程中,遇到葉子結點(!left && !right的結點)就輸出輔助棧的內容.

//輸出二叉樹的所有路徑void OutputTreePath(node* root,list<node*>& path){if(!root) return ; path.push_back(root);if(!root->left && !root->right){for(list<node*>::const_iterator it=path.begin();it !=path.end();++it)cout<<(*it)->value<<' ';cout<<endl;}OutputTreePath(root->left,path);OutputTreePath(root->right,path);//運行到這裡的時候,函數生命週期到,在返回上一層時//局部函數會析構root(當前根結點),對應地,也應該將root從path中的值彈出path.pop_back();}
16.求二叉樹中從根到某一結點的一條路徑.思路一如既往還是利用輔助棧來追蹤遞迴調用棧的運行過程,只是過程有所區別,將結點入棧,如果在結點的左邊能找到一條路徑,那麼不需要和遞迴棧同步(即彈棧),直接返回true,如果左邊沒找到這樣的一條路徑,再到右邊找,如果找到了,返回true.如果左右都找不到存在一條這樣的路徑,則說明在這個結點上不可能存在這樣的路徑,需要在輔助棧中彈出這個結點.再遍曆其它結點.實質上也是先序遍曆的改裝版.

//獲得二叉樹中從根到某一結點的一條路徑bool GetNodePath(node* root, node* pNode, list<node*>& path){if(root == pNode)//如果root==nNode,Done{path.push_back(pNode);return true;}path.push_back(root);//1用root實始化容器bool isFound = false;if(root->left)isFound = GetNodePath(root->left, pNode, path);if(!isFound && root->right)isFound = GetNodePath(root->right, pNode, path);if(!isFound)//如果1中的左右子樹都沒找到,說明在這個root上不存在這樣的路徑,需要將root彈出path.pop_back();return isFound;}

17判斷B子樹是否是A子樹的子結構這個似乎是有問題的,暫時還沒有找到好的解決方案.求大神相助.

//判斷B是否是A的子結構bool isAcontainB(node* rootA,node* rootB){if(!rootB)return true;if(!rootA)return false;if(rootA->value == rootB->value)return isAcontainB(rootA->left,rootB->left) && isAcontainB(rootA->right,rootB->right);return isAcontainB(rootA->left,rootB) || isAcontainB(rootA->right,rootB);}

18.利用先序和中序結果重建二叉樹在先序中找到根,利用根在中序中找到劃分位置,再從劃分位置把先序切成兩部分,再依次遞迴即可.

//利用先序和中序結果重建二叉樹node* RebuildTree(int* Prec,int * Post,int n){if(!Prec || !Post || n<1) return NULL;node* root=new node(Prec[0]);int i;for(i=0 ; i<n && Post[i] !=Prec[0] ; ++i );root->left=RebuildTree(Prec+1,Post,i);root->right=RebuildTree(Prec+1+i,Post+1+i,n-i-1);return root;}
19.求二叉樹中葉子結點的個數

void CountLeaves(node* root,int& Count){if(!root ) return ;if(!root->left && !root->right)++Count;CountLeaves(root->left,Count);CountLeaves(root->right,Count);}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.