1、二叉樹的性質
二叉樹是一個極爲重要的數據結構,對它的掌握程度反映了反映了我們數據結構的掌握程度,尤其是一些大公司在面試非科班出身的程序員的時候就會出一些類似於二叉樹恢復的題目來試探我們。
先來看一下這棵二叉樹,我們以他爲例
1
2 3
4 5 6 7
再看一下遍歷規則:
前序遍歷:根-左-右
中序遍歷:左-根-右
後續遍歷:左-右-根
遍歷結果:
前序遍歷:1 2 4 5 3 6 7
中序遍歷:4 2 5 1 6 3 7
後續遍歷:4 5 2 6 7 3 1
二叉樹不同方式的遍歷各有特點,主要是如下三點:
前序遍歷:第一個元素是根節點
中序遍歷:根節點左側爲左子樹,右側爲右子樹
後續遍歷:最後一個元素是根節點
2、根據前序和中序遍歷結果恢復二叉樹
2.1 手動推理
根據二叉樹的前序遍歷和中序遍歷的特點,我們可以使用遞歸方法對樹進行恢復
前序遍歷:1 2 4 5 3 6 7
中序遍歷:4 2 5 1 6 3 7
以上面舉例,根據前序遍歷,我們知道根節點爲1,左子樹元素爲 4 2 5,右子樹元素爲6 3 7
根據前序遍歷的結果和後序遍歷的結果,我們還知道,左子樹的前序遍歷和中序遍歷結果爲:
前序遍歷:2 4 5
中序遍歷:4 2 5
於是,我們知道根節點的左兒子爲左子樹的根節點,也就是2
我們構造到這一步的樹是這樣的
1
2
同樣,右子樹的前序遍歷和後序遍歷的結果集爲:
前序遍歷:3 6 7
中序遍歷:6 3 7
我們又得到結論,3是根節點的右子樹的的根節點。於是我們構造了這樣一個樹
1
2 3
然後,我們遞歸的進行樹的構造,就得到了一個完整的二叉樹。
可見問題的關鍵就是找到根節點,確定左右子樹,對左右子樹進行遞歸恢復!
2.2 Java實現
/**
*
* @param preOrder 先序遍歷結果集
* @param preStart 本段先序遍歷起始位置
* @param preEnd 本段先序遍歷終止位置
* @param inOrder 中序遍歷結果集
* @param inStart 本段中序遍歷起始位置
* @param inEnd 本段中序遍歷終止位置
* @return 構造的二叉樹
*/
public TreeNode rebuildTreeFromPreIn(int[] preOrder,int preStart,int preEnd,int[] inOrder,int inStart,int inEnd){
if(preStart>preEnd||inStart>inEnd){
return null;
}
int headVal=preOrder[preStart];
TreeNode head=new TreeNode(headVal);
int leftSize=0;//左子樹大小
for(int i=inStart;i<=inEnd;i++){//遍歷當前中序遍歷的結果,找到根節點,劃分左右子樹
if(inOrder[i]==headVal){//找到當前根節點位置
break;
}
leftSize++;//當前左子樹大小
}
//注意左子樹前序遍歷的起始位置爲當前左子樹起始位置+1,終止位置爲當前左子樹起始位置+左子樹大小
//左子樹中序遍歷起始位置爲中序遍歷的左子樹起始位置,結束位置爲原來根節點位置的前一個位置,也就是inStart+leftSize-1
head.left=rebuildTreeFromPreIn(preOrder,preStart+1,preStart+leftSize,inOrder,inStart,inStart+leftSize-1);
head.right=rebuildTreeFromPreIn(preOrder,preStart+leftSize+1,preEnd,inOrder,inStart+leftSize+1,inEnd);
return head;
}
3、根據中序和後序遍歷結果恢復二叉樹
根據中序和後序遍歷恢復二叉樹的方式和2中方法幾乎一致,區別在於後序遍歷的頭部是後序遍歷最後一個位置的元素。
/**
*
* @param inOrder 中序遍歷結果集
* @param inStart 本段中序遍歷起始位置
* @param inEnd 本段中序遍歷終止位置
* @param postOrder 後續遍歷結果集合
* @param postStart 本段後序遍歷起始位置
* @param postEnd 本段後序遍歷終止位置
* @return
*/
public TreeNode rebuildTreeFromInPost(int inOrder[],int inStart,int inEnd,int postOrder[],int postStart,int postEnd){
if(inStart>inEnd||postStart>postEnd){
return null;
}
int headVal=postOrder[postEnd];
TreeNode head=new TreeNode(headVal);
int leftSize=0;//左子樹元素個數
for (int i=inStart;i<=inEnd;i++){
if(inOrder[i]==headVal){//從中序遍歷找到根節點,左邊爲左子樹,右邊爲右子樹
break;
}
leftSize++;
}
head.left=rebuildTreeFromInPost(inOrder,inStart,inStart+leftSize,postOrder,postStart,postStart+leftSize-1);
head.right=rebuildTreeFromInPost(inOrder,inStart+leftSize+1,inEnd,postOrder,postStart+leftSize,postEnd-1);
return head;
}