題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑.從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑.
二叉樹中有兩條和為22的路徑:一條路徑經過結點10,5,7,另一條路徑經過結點10,12
由於路徑是從根結點出發到葉結點,也就是說路徑總是以根結點為起始點,因此我們要首先遍歷根結點.在樹的前序、中序、后序三種遍歷方式中,只有前序遍歷是訪問根結點的.
按照前序遍歷的順序遍歷圖中的二叉樹,在訪問結點10之后,就會訪問結點5.從二叉樹結點的定義可以看出,在本題的二叉樹結點中沒有指向父節點的指針,訪問到結點5的時候,我們是不知道前面經過了那些結點的,除非我們把經過的路徑上的結點保存下來.每訪問到一個結點的時候,我們都把當前的結點添加到路徑中去.到達結點5時,路徑中包含了兩個結點,它的值分別是10和5.接下來的遍歷到結點4,我們把這個結點也添加到路徑中去.這個時候已經到達葉結點,但路徑上三個結點的值之和是19.這個和不等於輸入的22,因此不是符號要求的路徑。
我們接着要遍歷其他的結點.在遍歷下一個結點之前,先要從結點4回到結點5,再去遍歷結點5和右子節點7。值得注意的是,回到結點5的時候,由於結點4已經不在前往結點7的路徑上,我們需要把結點4從路徑中刪除.接下來訪問到結點7的時候,再把該結點添加到路徑中.此時路徑中三個結點10,5,7之和剛好是22,是一條符合要求的路徑.
我們最后要遍歷的結點是12.在遍歷這個結點之前,需要先經歷結點5回到結點10.同樣,每一次當從子結點回到父節點的時候,我們都需要在路徑上刪除子結點.最后從結點10到達12的時候,路徑上的兩個結點的值之和也是22,因此這也是一條符合條件的路徑.
遍歷圖中二叉樹的過程
分析完前面具體的例子之后,我們就找到了一些規律。當用前序遍歷的方式訪問某一結點時,我們把該結點添加到路徑上,並累加該結點的值.如果該結點為葉結點並且路徑中結點值的和剛好等於輸入的整數,則當前的路徑符合要求,我們把它打印出來。如果當前結點不是葉結點,則繼續訪問它的子結點.當訪問結束后,遞歸函數將自動回到它的父結點。因此我們在函數退出之前要在路徑上刪除當前結點並減去當前結點的值,以確保返回父結點時路徑剛好從根結點到父結點的路徑。我們不難看出保存路徑的數據結構實際上就是一個棧,因為路徑要與遞歸調用狀態一致,而遞歸調用的本質就是一個壓棧和出棧的過程。
形成了清晰的思路之后,就可以動手寫代碼了。下面是參考代碼:
1 void FindPath(BinaryTreeNode* pRoot,int expectedSum) 2 { 3 if(pRoot==NULL) 4 return; 5 std::vector<int> path; 6 int currentSum=0; 7 FindPath(pRoot,expectedSum,path,currentSum); 8 } 9 10 void FindPath 11 ( 12 BinaryTreeNode* pRoot, 13 int expectedSum, 14 std::vector<int>& path, 15 int currentSum 16 ) 17 { 18 currentSum +=pRoot->value; 19 path.push_back(pRoot->value); 20 //如果是葉結點,並且路徑上結點的和等於輸入的值,打印出這條路徑 21 bool isLeaf =(pRoot->lchild==NULL&&pRoot->rchild==NULL); 22 if(currentSum==expectedSum&&isLeaf) 23 { 24 printf("A path is found:"); 25 std::vector<int>::iterator iter=path.begin(); 26 for(;iter!=path.end();++iter) 27 printf("%d\t",*iter); 28 printf("\n"); 29 } 30 //如果不是葉結點,則遍歷它的子結點 31 if(pRoot->lchild!=NULL) 32 FindPath(pRoot->lchild,exception,path,currentSum); 33 if(pRoot->rchild!=NULL) 34 FindPath(pRoot->rchild,expection,path,currentSum); 35 36 //在返回父結點之前,在路徑上刪除當前結點 37 path.pop_back(); 38 } 39
