資料結構面試之六——二叉樹的常見操作2
(非遞迴遍曆
&
二叉排序樹)
題註:《面試寶典》有相關習題,但思路相對不清晰,排版有錯誤,作者對此參考相關書籍和自己觀點進行了重寫,供大家參考。
六、二叉樹的基本操作(非遞迴遍曆)&二叉排序樹的操作
接上一節第五部分,主要分析二叉樹的非遞迴遍曆和二叉排序樹的操作。
1. 非遞迴中序遍曆
//1.依次將根節點root的左子樹入棧,直到lchild=NULL,執行2
//2.將棧的元素出棧、訪問;將當前指標指向節點的rchild,迴圈遍曆。直到棧空為止!
template<typenameelemType> voidbinaryTreeType<elemType>::noRecursionInorderTraversal() //非遞迴中序遍曆 { cout<< "noRecursionInorderTraversal--------------------------->"<< endl; linkedStackType<nodeType<elemType>* > stack; nodeType<elemType>*current = root; while(current!= NULL || !stack.isEmptyStack()) //或者|| { if(current!= NULL) { stack.push(current); current= current->llink; } else { stack.pop(current); cout<< current->info << "\t"; //出棧的時候訪問節點 current= current->rlink; } } cout<< endl; cout<< "<------------------------noRecursionInorderTraversal"<< endl; }
2. 非遞迴先序遍曆
//在中序遍曆的基礎上,訪問次序發生變化;
//先序遍曆,需要先逐個遍曆根節點,然後依次處理其左、右孩子節點。
template<typenameelemType> voidbinaryTreeType<elemType>::noRecursionPreorderTraversal() //非遞迴前序走訪 { cout<<"noRecursionPreorderTraversal--------------------------->"<< endl; linkedStackType<nodeType<elemType>* > stack; nodeType<elemType>*current = root; while(current!= NULL || !stack.isEmptyStack()) //或者|| { if(current!= NULL) { cout<< current->info << "\t"; //先訪問節點後入棧 stack.push(current); current= current->llink; } else { stack.pop(current); current= current->rlink; } } cout<< endl; cout<< "<------------------------noRecursionPreorderTraversal"<< endl; }
3. 非遞迴後序遍曆
由於訪問的順序為先左子樹、然後右子樹,最後根節點。並且對於每一個節點都是上述操作,所以,對於遍曆來講,需要識別當前節點類型是根(相對)、左孩子節點 、右孩子節點。故,我們設定了flag標記變數,flag=0初始標記,節點尚未入棧;在訪問左孩子之前將flag置為1;在訪問右孩子之前將flag置為2;並且在訪問右孩子之後,將flag置為0。
//後序非遞迴遍曆比較複雜..
template<typenameelemType>voidbinaryTreeType<elemType>::noRecursionPostorderTraversal() //非遞迴後序遍曆 { cout<<"noRecursionPostorderTraversal--------------------------->"<< endl; linkedStackType<nodeType<elemType>* > stack; linkedStackType<int>intStack; //標記位同步棧. nodeType<elemType>*current = root; intnflag = 0; //初始標記為0. if(current== NULL) { cout<< "The Stack is Empty!" << endl; } else { //1.將前端節點先入棧, stack.push(current); intStack.push(1); current = current->llink; //注意此處需要調整指向****** while(!stack.isEmptyStack()&& !intStack.isEmptyStack()) { if(current!= NULL && nflag == 0) { stack.push(current); intStack.push(1); //標記位為1,[在訪問左孩子之前,將其值置為1]。 current = current->llink; } else { stack.pop(current); intStack.pop(nflag); //此時的標記位為傳回值,需要根據其做判斷 if(nflag== 1) //說明下一步需要入棧的為右孩子. { stack.push(current); //繼續將該節點入棧, intStack.push(2); //但[在訪問右孩子之前,將其置為2]。 current= current->rlink; //訪問右節點, nflag= 0; //置標記位為0 } else { cout<< current->info << " "; //待左右子樹都為空白再訪問節點。 } } } cout<< endl; cout<< "<------------------------noRecursionPostorderTraversal"<< endl; } }
4. 二叉排序樹的搜尋操作
明確概念,國內、國外的著作裡提及的下三個概念等價,二叉搜尋樹=二叉尋找樹=二叉排序樹。
//二叉排序樹的尋找存在以下幾種情況:
//1.鏈表為空白,提示並返回;
//2.鏈表非空,需要迴圈尋找直到指標為空白,若存在,則bfound=true;否則尋找至最後bfound=預設false。
template <class elemType>boolbSearchTreeType<elemType>::search(const elemType& searchItem){ nodeType<elemType>*current = new nodeType<elemType>; boolbFound = false; if(root== NULL) { cout<< "The bSearchTree is NULL\n"; //case1: 鏈表為空白! returnfalse; } else { current= root; while(current!= NULL && !bFound) //case2:在鏈表中尋找,根據大小鎖定左、右子樹. { if(current->info== searchItem) { bFound= true; } elseif(current->info > searchItem) { current= current->llink; //左子樹 } elseif(current->info < searchItem) { current= current->rlink; //右子樹 } } } returnbFound;}
5. 二叉排序樹的插入存在以下幾種情況:
//1.鏈表為空白,插入元素即為根節點;
//2.鏈表非空,需要尋找插入位置後插入。
//2.1插入元素已經存在,則提示出錯。
//2.2總能找到大於或小於某節點的位置,記錄trailcurrent完成插入操作。
template <class elemType>voidbSearchTreeType<elemType>::insert(const elemType& insertItem){ nodeType<elemType>*newNode = new nodeType<elemType>; nodeType<elemType>*current; nodeType<elemType>*trailCurrent; newNode->info= insertItem; newNode->llink= NULL; newNode->rlink= NULL; if(root== NULL) { root= newNode; //case1:樹為空白. } else { current= root; while(current!= NULL) //case2,3,4搜尋才知道! { trailCurrent= current; if(current->info== insertItem) { cout<< "the elem is already exist!\n"; //case2:元素已經存在 return; } else { if(current->info> insertItem) { current= current->llink; //case3:鎖定左側位置... } else { current= current->rlink; //case4:鎖定右側位置... } } }//endwhile //case3,4根據大小進行連結 if(trailCurrent->info< insertItem) { trailCurrent->rlink= newNode; } else { trailCurrent->llink= newNode; } }//end else}
6. 二叉排序樹的刪除存在以下幾種情況【此處可能複雜些】:
//刪除一個節點,要首先判斷元素值在二叉排序樹中是否存在,
//若不存在則返回;
//若存在則需要鎖定其對應位置為1根節點;2分葉節點;3其餘節點。
//根據要刪除的節點是否含有左右子樹的不同,分為4種情況考慮,
//見deleteFromTree()函數。
template <class elemType>voidbSearchTreeType<elemType>::deleteNode(const elemType& deleteItem){ //1.尋找節點 //2.1找不到,不存在; //2.2找到,刪除,調用函數 nodeType<elemType>*current; nodeType<elemType>*trailCurrent; boolbFound = false; if(root== NULL) { cout<< "Can't delete an Empty BST" << endl; return; } else { current= root; trailCurrent= root; while(current != NULL && !bFound) { if(current->info== deleteItem) { bFound= true; } elseif(current->info > deleteItem) { trailCurrent= current; current= current->llink; //左 } else { trailCurrent= current; current= current->rlink; //右 } }//endwhile if(current== NULL) { cout<< deleteItem << " is not Exist in the BST!\n" <<endl; } elseif(bFound) { if(current== root) { deleteFromTree(root); //可能是根節點 } elseif(trailCurrent->info > deleteItem) { deleteFromTree(trailCurrent->llink);//左半分支,調整trailCurrent的指向 } elseif(trailCurrent->info < deleteItem) { deleteFromTree(trailCurrent->rlink); //右半分支,調整trailCurrent的指向 } }//endif bFound }//end else}
//[原理]:某節點的前驅是該節點左子樹的最右端的節點(中序遍曆的結果)
template <class elemType>voidbSearchTreeType<elemType>::deleteFromTree(nodeType<elemType>*&p){ nodeType<elemType>*temp; nodeType<elemType>*current; nodeType<elemType>*trailCurrent; if(p== NULL) { cout<< "The BST is NULL!" << endl; return; } if(p->llink== NULL && p->rlink == NULL) //情況1,左右節點都為空白(分葉節點) { temp= p; p= NULL; deletetemp; } elseif( p->rlink == NULL) //情況2,右子樹為空白,左非空 { temp= p; p= temp->llink; deletetemp; } elseif(p->llink == NULL) //情況3,左子樹為空白,右非空 { temp= p; p= temp->rlink; deletetemp; } else //情況4,左右都非空[用中序遍曆的前一個節點替換] { current= p->llink; trailCurrent= NULL; while(current->rlink!= NULL) { trailCurrent= current; //trailCurrent最終指向準備刪除節點的前一個節點 current= current->rlink; } p->info= current->info; //資訊賦值 if(trailCurrent== NULL) //僅一個左孩子節點 { p->rlink = current->llink; } else { trailCurrent->rlink= current->llink; //給刪除前點的前面一個節點調整指標指向 } deletecurrent; } }