0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

二叉樹(shù)操作的相關(guān)知識(shí)和代碼詳解

Wildesbeast ? 來(lái)源:21IC ? 作者:21IC ? 2020-12-12 11:04 ? 次閱讀

樹(shù)是數(shù)據(jù)結(jié)構(gòu)中的重中之重,尤其以各類(lèi)二叉樹(shù)為學(xué)習(xí)的難點(diǎn)。在面試環(huán)節(jié)中,二叉樹(shù)也是必考的模塊。本文主要講二叉樹(shù)操作的相關(guān)知識(shí),梳理面試??嫉膬?nèi)容。請(qǐng)大家跟隨小編一起來(lái)復(fù)習(xí)吧。

本篇針對(duì)面試中常見(jiàn)的二叉樹(shù)操作作個(gè)總結(jié):

前序遍歷,中序遍歷,后序遍歷;

層次遍歷;

求樹(shù)的結(jié)點(diǎn)數(shù);

求樹(shù)的葉子數(shù);

求樹(shù)的深度;

求二叉樹(shù)第k層的結(jié)點(diǎn)個(gè)數(shù);

判斷兩棵二叉樹(shù)是否結(jié)構(gòu)相同;

求二叉樹(shù)的鏡像;

求兩個(gè)結(jié)點(diǎn)的最低公共祖先結(jié)點(diǎn);

求任意兩結(jié)點(diǎn)距離;

找出二叉樹(shù)中某個(gè)結(jié)點(diǎn)的所有祖先結(jié)點(diǎn);

不使用遞歸和棧遍歷二叉樹(shù);

二叉樹(shù)前序中序推后序;

判斷二叉樹(shù)是不是完全二叉樹(shù);

判斷是否是二叉查找樹(shù)的后序遍歷結(jié)果;

給定一個(gè)二叉查找樹(shù)中的結(jié)點(diǎn),找出在中序遍歷下它的后繼和前驅(qū);

二分查找樹(shù)轉(zhuǎn)化為排序的循環(huán)雙鏈表;

有序鏈表轉(zhuǎn)化為平衡的二分查找樹(shù);

判斷是否是二叉查找樹(shù)。

1 前序遍歷,中序遍歷,后序遍歷;

1.1 前序遍歷

#FormatImgID_0#對(duì)于當(dāng)前結(jié)點(diǎn),先輸出該結(jié)點(diǎn),然后輸出它的左孩子,最后輸出它的右孩子。以上圖為例,遞歸的過(guò)程如下:

輸出 1,接著左孩子;

輸出 2,接著左孩子;

輸出 4,左孩子為空,再接著右孩子;

輸出 6,左孩子為空,再接著右孩子;

輸出 7,左右孩子都為空,此時(shí) 2 的左子樹(shù)全部輸出,2 的右子樹(shù)為空,此時(shí) 1 的左子樹(shù)全部輸出,接著 1 的右子樹(shù);

輸出 3,接著左孩子;

輸出 5,左右孩子為空,此時(shí) 3 的左子樹(shù)全部輸出,3 的右子樹(shù)為空,至此 1 的右子樹(shù)全部輸出,結(jié)束。

而非遞歸版本只是利用 stack 模擬上述過(guò)程而已,遞歸的過(guò)程也就是出入棧的過(guò)程。

/*前序遍歷遞歸版*/

voidPreOrderRec(Node*node)

{

if(node==nullptr)

return;

cout<data<

PreOrderRec(node->left);//然后輸出左孩子

PreOrderRec(node->right);//最后輸出右孩子

}

/*前序遍歷非遞歸版*/

voidPreOrderNonRec(Node*node)

{

if(node==nullptr)

return;

stackS;

cout<data<

S.push(node);

node=node->left;

while(!S.empty()||node)

{

while(node)

{

cout<data<

S.push(node);

node=node->left;//然后輸出左孩子

}//while結(jié)束意味著左孩子已經(jīng)全部輸出

node=S.top()->right;//最后輸出右孩子

S.pop();

}

}

1.2 中序遍歷

對(duì)于當(dāng)前結(jié)點(diǎn),先輸出它的左孩子,然后輸出該結(jié)點(diǎn),最后輸出它的右孩子。以(1.1)圖為例:

1-->2-->4,4 的左孩子為空,輸出 4,接著右孩子;

6 的左孩子為空,輸出 6,接著右孩子;

7 的左孩子為空,輸出 7,右孩子也為空,此時(shí) 2 的左子樹(shù)全部輸出,輸出 2,2 的右孩子為空,此時(shí) 1 的左子樹(shù)全部輸出,輸出 1,接著 1 的右孩子;

3-->5,5 左孩子為空,輸出 5,右孩子也為空,此時(shí) 3 的左子樹(shù)全部輸出,而 3 的右孩子為空,至此 1 的右子樹(shù)全部輸出,結(jié)束。

/*中序遍歷遞歸版*/

voidInOrderRec(Node*node)

{

if(node==nullptr)

return;

InOrderRec(node->left);//先輸出左孩子

cout<data<

InOrderRec(node->right);//最后輸出右孩子

}

/*前序遍歷非遞歸版*/

voidInOrderNonRec(Node*node)

{

if(node==nullptr)

return;

stackS;

S.push(node);

node=node->left;

while(!S.empty()||node)

{

while(node)

{

S.push(node);

node=node->left;

}//while結(jié)束意味著左孩子為空

cout<data<

node=S.top()->right;//左孩子全部輸出,當(dāng)前結(jié)點(diǎn)也輸出后,最后輸出右孩子

S.pop();

}

}

1.3 后序遍歷

對(duì)于當(dāng)前結(jié)點(diǎn),先輸出它的左孩子,然后輸出它的右孩子,最后輸出該結(jié)點(diǎn)。依舊以(1.1)圖為例:

1->2->4->6->7,7 無(wú)左孩子,也無(wú)右孩子,輸出 7,此時(shí) 6 無(wú)左孩子,而 6 的右子樹(shù)也全部輸出,輸出 6,此時(shí) 4 無(wú)左子樹(shù),而 4 的右子樹(shù)已全部輸出,接著輸出 4,此時(shí) 2 的左子樹(shù)全部輸出,且 2 無(wú)右子樹(shù),輸出 2,此時(shí) 1 的左子樹(shù)全部輸出,接著轉(zhuǎn)向右子樹(shù);

3->5,5 無(wú)左孩子,也無(wú)右孩子,輸出 5,此時(shí) 3 的左子樹(shù)全部輸出,且 3 無(wú)右孩子,輸出 3,此時(shí) 1 的右子樹(shù)全部輸出,輸出 1,結(jié)束。

非遞歸版本中,對(duì)于一個(gè)結(jié)點(diǎn),如果我們要輸出它,只有它既沒(méi)有左孩子也沒(méi)有右孩子或者它有孩子但是它的孩子已經(jīng)被輸出(由此設(shè)置 pre 變量)。若非上述兩種情況,則將該結(jié)點(diǎn)的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時(shí)候,先依次遍歷左子樹(shù)和右子樹(shù)。

/*后序遍歷遞歸版*/

voidPostOrderRec(Node*node)

{

if(node==nullptr)

return;

PostOrderRec(node->left);//先輸出左孩子

PostOrderRec(node->right);//然后輸出右孩子

cout<data<

}

/*后序遍歷非遞歸版*/

voidPostOrderNonRec(Node*node)

{

if(node==nullptr)

return;

Node*pre=nullptr;

stackS;

S.push(node);

while(!S.empty())

{

node=S.top();

if((!node->left&&!node->right)||//第一個(gè)輸出的必是無(wú)左右孩子的葉子結(jié)點(diǎn),只要第一個(gè)結(jié)點(diǎn)輸出,

(pre &&(pre == node->left || pre == node->right)))//以后的 pre 就不會(huì)是空。此處的判斷語(yǔ)句加入一個(gè) pre,只是用來(lái)

{//確??梢哉_輸出第一個(gè)結(jié)點(diǎn)。

cout<data<

pre=node;

S.pop();

}

else

{

if(node->right)

S.push(node->right);//先進(jìn)右孩子,再進(jìn)左孩子,取出來(lái)的才是左孩子

if(node->left)

S.push(node->left);

}

}

}

2 層次遍歷

voidLevelOrder(Node*node)

{

Node*p=node;

queueQ;//隊(duì)列

Q.push(p);

while(!Q.empty())

{

p=Q.front();

cout<data<

Q.pop();

if(p->left)

Q.push(p->left);//注意順序,先進(jìn)左孩子

if(p->right)

Q.push(p->right);

}

}

3 求樹(shù)的結(jié)點(diǎn)數(shù)

intCountNodes(Node*node)

{

if(node==nullptr)

return0;

returnCountNodes(node->left)+CountNodes(node->right)+1;

}

4 求樹(shù)的葉子數(shù)

intCountLeaves(Node*node)

{

if(node==nullptr)

return0;

if(!node->left&&!node->right)

return1;

returnCountLeaves(node->left)+CountLeaves(node->right);

}

5 求樹(shù)的深度

intGetDepth(Node*node)

{

if(node==nullptr)

return0;

intleft_depth=GetDepth(node->left)+1;

intright_depth=GetDepth(node->right)+1;

returnleft_depth>right_depth?left_depth:right_depth;

}

6 求二叉樹(shù)第k層的結(jié)點(diǎn)個(gè)數(shù)

intGetKLevel(Node*node,intk)

{

if(node==nullptr)

return0;

if(k==1)

return1;

returnGetKLevel(node->left,k-1)+GetKLevel(node->right,k-1);

}

7 判斷兩棵二叉樹(shù)是否結(jié)構(gòu)相同

不考慮數(shù)據(jù)內(nèi)容。結(jié)構(gòu)相同意味著對(duì)應(yīng)的左子樹(shù)和對(duì)應(yīng)的右子樹(shù)都結(jié)構(gòu)相同。

boolStructureCmp(Node*node1,Node*node2)

{

if(node1==nullptr&&node2==nullptr)

returntrue;

elseif(node1==nullptr||node2==nullptr)

returnfalse;

returnStructureCmp(node1->left,node2->left)&&Str1uctureCmp(node1->right,node2->right);

}

8 求二叉樹(shù)的鏡像

對(duì)于每個(gè)結(jié)點(diǎn),我們交換它的左右孩子即可。

voidMirror(Node*node)

{

if(node==nullptr)

return;

Node*temp=node->left;

node->left=node->right;

node->right=temp;

Mirror(node->left);

Mirror(node->right);

}

9 求兩個(gè)結(jié)點(diǎn)的最低公共祖先結(jié)點(diǎn)

最低公共祖先,即 LCA(Lowest Common Ancestor),見(jiàn)下圖:#FormatImgID_1#結(jié)點(diǎn) 3 和結(jié)點(diǎn) 4 的最近公共祖先是結(jié)點(diǎn) 2,即 LCA(3,4)=2。在此,需要注意到當(dāng)兩個(gè)結(jié)點(diǎn)在同一棵子樹(shù)上的情況,如結(jié)點(diǎn) 3 和結(jié)點(diǎn) 2 的最近公共祖先為 2,即 LCA(3,2)=2。同理 LCA(5,6)=4,LCA(6,10)=1。

Node*FindLCA(Node*node,Node*target1,Node*target2)

{

if(node==nullptr)

returnnullptr;

if(node==target1||node==target2)

returnnode;

Node*left=FindLCA(node->left,target1,target2);

Node*right=FindLCA(node->right,target1,target2);

if(left&&right)//分別在左右子樹(shù)

returnnode;

returnleft?left:right;//都在左子樹(shù)或右子樹(shù)

}

10 求任意兩結(jié)點(diǎn)距離

#FormatImgID_2#首先找到兩個(gè)結(jié)點(diǎn)的 LCA,然后分別計(jì)算 LCA 與它們的距離,最后相加即可。

intFindLevel(Node*node,Node*target)

{

if(node==nullptr)

return-1;

if(node==target)

return0;

intlevel=FindLevel(node->left,target);//先在左子樹(shù)找

if(level==-1)

level=FindLevel(node->right,target);//如果左子樹(shù)沒(méi)找到,在右子樹(shù)找

if(level!=-1)//找到了,回溯

returnlevel+1;

return-1;//如果左右子樹(shù)都沒(méi)找到

}

intDistanceNodes(Node*node,Node*target1,Node*target2)

{

Node*lca=FindLCA(node,target1,target2);//找到最低公共祖先結(jié)點(diǎn)

intlevel1=FindLevel(lca,target1);

intlevel2=FindLevel(lca,target2);

returnlevel1+level2;

}

11 找出二叉樹(shù)中某個(gè)結(jié)點(diǎn)的所有祖先結(jié)點(diǎn)

#FormatImgID_3#如果給定結(jié)點(diǎn) 5,則其所有祖先結(jié)點(diǎn)為 4,2,1。

boolFindAllAncestors(Node*node,Node*target)

{

if(node==nullptr)

returnfalse;

if(node==target)

returntrue;

if(FindAllAncestors(node->left,target)||FindAllAncestors(node->right,target))//找到了

{

cout<data<

returntrue;//回溯

}

returnfalse;//如果左右子樹(shù)都沒(méi)找到

}

12 不使用遞歸和棧遍歷二叉樹(shù)

1968 年,高德納(Donald Knuth)提出一個(gè)問(wèn)題:是否存在一個(gè)算法,它不使用棧也不破壞二叉樹(shù)結(jié)構(gòu),但是可以完成對(duì)二叉樹(shù)的遍歷?隨后 1979 年,James H. Morris 提出了二叉樹(shù)線索化,解決了這個(gè)問(wèn)題。(根據(jù)這個(gè)概念我們又提出了一個(gè)新的數(shù)據(jù)結(jié)構(gòu),即線索二叉樹(shù),因線索二叉樹(shù)不是本文要介紹的內(nèi)容,所以有興趣的朋友請(qǐng)移步線索二叉樹(shù))

前序,中序,后序遍歷,不管是遞歸版本還是非遞歸版本,都用到了一個(gè)數(shù)據(jù)結(jié)構(gòu)--棧,為何要用棧?那是因?yàn)槠渌姆绞經(jīng)]法記錄當(dāng)前結(jié)點(diǎn)的 parent,而如果在每個(gè)結(jié)點(diǎn)的結(jié)構(gòu)里面加個(gè) parent 分量顯然是不現(xiàn)實(shí)的,而線索化正好解決了這個(gè)問(wèn)題,其含義就是利用結(jié)點(diǎn)的右孩子空指針,指向該結(jié)點(diǎn)在中序序列中的后繼。下面具體來(lái)看看如何使用線索化來(lái)完成對(duì)二叉樹(shù)的遍歷。#FormatImgID_4#先看前序遍歷,步驟如下:

如果當(dāng)前結(jié)點(diǎn)的左孩子為空,則輸出當(dāng)前結(jié)點(diǎn)并將其右孩子作為當(dāng)前結(jié)點(diǎn);

如果當(dāng)前結(jié)點(diǎn)的左孩子不為空,在當(dāng)前結(jié)點(diǎn)的左子樹(shù)中找到當(dāng)前結(jié)點(diǎn)在中序遍歷下的前驅(qū)結(jié)點(diǎn);

2.1如果前驅(qū)結(jié)點(diǎn)的右孩子為空,將它的右孩子設(shè)置為當(dāng)前結(jié)點(diǎn),輸出當(dāng)前結(jié)點(diǎn)并把當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的左孩子;

2.2如果前驅(qū)結(jié)點(diǎn)的右孩子為當(dāng)前結(jié)點(diǎn),將它的右孩子重新設(shè)為空,當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的右孩子;

重復(fù)以上步驟 1 和 2,直到當(dāng)前結(jié)點(diǎn)為空。

/*前序遍歷*/

voidPreOrderMorris(Node*root)

{

Node*cur=root;

Node*pre=nullptr;

while(cur)

{

if(cur->left==nullptr)//步驟1

{

cout<data<

cur=cur->right;

}

else

{

pre=cur->left;

while(pre->right&&pre->right!=cur)//步驟2,找到cur的前驅(qū)結(jié)點(diǎn)

pre=pre->right;

if(pre->right==nullptr)//步驟2.1,cur未被訪問(wèn),將cur結(jié)點(diǎn)作為其前驅(qū)結(jié)點(diǎn)的右孩子

{

cout<data<

pre->right=cur;

cur=cur->left;

}

else//步驟2.2,cur已被訪問(wèn),恢復(fù)樹(shù)的原有結(jié)構(gòu),更改right指針

{

pre->right=nullptr;

cur=cur->right;

}

}

}

}

再來(lái)看中序遍歷,和前序遍歷相比只改動(dòng)一句代碼,步驟如下:

如果當(dāng)前結(jié)點(diǎn)的左孩子為空,則輸出當(dāng)前結(jié)點(diǎn)并將其右孩子作為當(dāng)前結(jié)點(diǎn);

如果當(dāng)前結(jié)點(diǎn)的左孩子不為空,在當(dāng)前結(jié)點(diǎn)的左子樹(shù)中找到當(dāng)前結(jié)點(diǎn)在中序遍歷下的前驅(qū)結(jié)點(diǎn);

2.1. 如果前驅(qū)結(jié)點(diǎn)的右孩子為空,將它的右孩子設(shè)置為當(dāng)前結(jié)點(diǎn),當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的左孩子;

2.2. 如果前驅(qū)結(jié)點(diǎn)的右孩子為當(dāng)前結(jié)點(diǎn),將它的右孩子重新設(shè)為空,輸出當(dāng)前結(jié)點(diǎn),當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的右孩子;

重復(fù)以上步驟 1 和 2,直到當(dāng)前結(jié)點(diǎn)為空。

/*中序遍歷*/

voidInOrderMorris(Node*root)

{

Node*cur=root;

Node*pre=nullptr;

while(cur)

{

if(cur->left==nullptr)//(1)

{

cout<data<

cur=cur->right;

}

else

{

pre=cur->left;

while(pre->right&&pre->right!=cur)//(2),找到cur的前驅(qū)結(jié)點(diǎn)

pre=pre->right;

if(pre->right==nullptr)//(2.1),cur未被訪問(wèn),將cur結(jié)點(diǎn)作為其前驅(qū)結(jié)點(diǎn)的右孩子

{

pre->right=cur;

cur=cur->left;

}

else//(2.2),cur已被訪問(wèn),恢復(fù)樹(shù)的原有結(jié)構(gòu),更改right指針

{

cout<data<

pre->right=nullptr;

cur=cur->right;

}

}

}

}

最后看下后序遍歷,后序遍歷有點(diǎn)復(fù)雜,需要建立一個(gè)虛假根結(jié)點(diǎn) dummy,令其左孩子是 root。并且還需要一個(gè)子過(guò)程,就是倒序輸出某兩個(gè)結(jié)點(diǎn)之間路徑上的各個(gè)結(jié)點(diǎn)。#FormatImgID_5#步驟如下:

如果當(dāng)前結(jié)點(diǎn)的左孩子為空,則將其右孩子作為當(dāng)前結(jié)點(diǎn);

如果當(dāng)前結(jié)點(diǎn)的左孩子不為空,在當(dāng)前結(jié)點(diǎn)的左子樹(shù)中找到當(dāng)前結(jié)點(diǎn)在中序遍歷下的前驅(qū)結(jié)點(diǎn);

2.1. 如果前驅(qū)結(jié)點(diǎn)的右孩子為空,將它的右孩子設(shè)置為當(dāng)前結(jié)點(diǎn),當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的左孩子;

2.2. 如果前驅(qū)結(jié)點(diǎn)的右孩子為當(dāng)前結(jié)點(diǎn),將它的右孩子重新設(shè)為空,倒序輸出從當(dāng)前結(jié)點(diǎn)的左孩子到該前驅(qū)結(jié)點(diǎn)這條路徑上的所有結(jié)點(diǎn),當(dāng)前結(jié)點(diǎn)更新為當(dāng)前結(jié)點(diǎn)的右孩子;

重復(fù)以上步驟 1 和 2,直到當(dāng)前結(jié)點(diǎn)為空。

structNode

{

intdata;

Node*left;

Node*right;

Node(intdata_,Node*left_,Node*right_)

{

data=data_;

left=left_;

right=right_;

}

};

voidReversePrint(Node*from,Node*to)

{

if(from==to)

{

cout<data<

return;

}

ReversePrint(from->right,to);

cout<data<

}

voidPostOrderMorris(Node*root)

{

Node*dummy=newNode(-1,root,nullptr);//一個(gè)虛假根結(jié)點(diǎn)

Node*cur=dummy;

Node*pre=nullptr;

while(cur)

{

if(cur->left==nullptr)//步驟1

cur=cur->right;

else

{

pre=cur->left;

while(pre->right&&pre->right!=cur)//步驟2,找到cur的前驅(qū)結(jié)點(diǎn)

pre=pre->right;

if(pre->right==nullptr)//步驟2.1,cur未被訪問(wèn),將cur結(jié)點(diǎn)作為其前驅(qū)結(jié)點(diǎn)的右孩子

{

pre->right=cur;

cur=cur->left;

}

else//步驟2.2,cur已被訪問(wèn),恢復(fù)樹(shù)的原有結(jié)構(gòu),更改right指針

{

pre->right=nullptr;

ReversePrint(cur->left,pre);

cur=cur->right;

}

}

}

}

dummy 用的非常巧妙,建議讀者配合上面的圖模擬下算法流程。

13 二叉樹(shù)前序中序推后序

方式 序列
前序 [1 2 4 7 3 5 8 9 6]
中序 [4 7 2 1 8 5 9 3 6]
后序 [7 4 2 8 9 5 6 3 1]

以上面圖表為例,步驟如下:

根據(jù)前序可知根結(jié)點(diǎn)為1;

根據(jù)中序可知 4 7 2 為根結(jié)點(diǎn) 1 的左子樹(shù)和 8 5 9 3 6 為根結(jié)點(diǎn) 1 的右子樹(shù);

遞歸實(shí)現(xiàn),把 4 7 2 當(dāng)做新的一棵樹(shù)和 8 5 9 3 6 也當(dāng)做新的一棵樹(shù);

在遞歸的過(guò)程中輸出后序。

/*前序遍歷和中序遍歷結(jié)果以長(zhǎng)度為n的數(shù)組存儲(chǔ),pos1為前序數(shù)組下標(biāo),pos2為后序下標(biāo)*/

intpre_order_arry[n];

intin_order_arry[n];

voidPrintPostOrder(intpos1,intpos2,intn)

{

if(n==1)

{

cout<

return;

}

if(n==0)

return;

inti=0;

for(;pre_order_arry[pos1]!=in_order_arry[pos2+i];i++)

;

PrintPostOrder(pos1+1,pos2,i);

PrintPostOrder(pos1+i+1,pos2+i+1,n-i-1);

cout<

}

當(dāng)然我們也可以根據(jù)前序和中序構(gòu)造出二叉樹(shù),進(jìn)而求出后序。

/*該函數(shù)返回二叉樹(shù)的根結(jié)點(diǎn)*/

Node*Create(intpos1,intpos2,intn)

{

Node*p=nullptr;

for(inti=0;i

{

if(pre_order_arry[pos1]==in_order_arry[pos2+i])

{

p=newNode(pre_order_arry[pos1]);

p->left=Create(pos1+1,pos2,i);

p->right=Create(pos1+i+1,pos2+i+1,n-i-1);

returnp;

}

}

returnp;

}

14 判斷二叉樹(shù)是不是完全二叉樹(shù)

若設(shè)二叉樹(shù)的深度為 h,除第 h 層外,其它各層 (1~h-1) 的結(jié)點(diǎn)數(shù)都達(dá)到最大個(gè)數(shù),第 h 層所有的結(jié)點(diǎn)都連續(xù)集中在最左邊,這就是完全二叉樹(shù)(Complete Binary Tree)。如下圖:#FormatImgID_6#

首先若一個(gè)結(jié)點(diǎn)只有右孩子,肯定不是完全二叉樹(shù);其次若只有左孩子或沒(méi)有孩子,那么接下來(lái)的所有結(jié)點(diǎn)肯定都沒(méi)有孩子,否則就不是完全二叉樹(shù),因此設(shè)置 flag 標(biāo)記變量。

boolIsCBT(Node*node)

{

boolflag=false;

queueQ;

Q.push(node);

while(!Q.empty())

{

Node*p=Q.front();

Q.pop();

if(flag)

{

if(p->left||p->right)

returnfalse;

}

else

{

if(p->left&&p->right)

{

Q.push(p->left);

Q.push(p->right);

}

elseif(p->right)//只有右結(jié)點(diǎn)

returnfalse;

elseif(p->left)//只有左結(jié)點(diǎn)

{

Q.push(p->left);

flag=true;

}

else//沒(méi)有結(jié)點(diǎn)

flag=true;

}

}

returntrue;

}

15 判斷是否是二叉查找樹(shù)的后序遍歷結(jié)果

在后續(xù)遍歷得到的序列中,最后一個(gè)元素為樹(shù)的根結(jié)點(diǎn)。從頭開(kāi)始掃描這個(gè)序列,比根結(jié)點(diǎn)小的元素都應(yīng)該位于序列的左半部分;從第一個(gè)大于跟結(jié)點(diǎn)開(kāi)始到跟結(jié)點(diǎn)前面的一個(gè)元素為止,所有元素都應(yīng)該大于跟結(jié)點(diǎn),因?yàn)檫@部分元素對(duì)應(yīng)的是樹(shù)的右子樹(shù)。根據(jù)這樣的劃分,把序列劃分為左右兩部分,我們遞歸地確認(rèn)序列的左、右兩部分是不是都是二元查找樹(shù)。

intarray[n];//長(zhǎng)度為n的序列,注意begin和end遵循的是左閉右閉原則

boolIsSequenceOfBST(intbegin,intend)

{

if(end-begin<=?0)

returntrue;

introot_data=array[end];//數(shù)組尾元素為根結(jié)點(diǎn)

inti=begin;

for(;array[i]

;

intj=i;

for(;j

if(array[j]< root_data)??//?此時(shí)右子樹(shù)應(yīng)該都大于根結(jié)點(diǎn);若存在小于,直接?return?false

returnfalse;

returnIsSequenceOfBST(begin,i-1)&&IsSequenceOfBST(i,end-1);//左右子樹(shù)是否都滿(mǎn)足

}

16 給定一個(gè)二叉查找樹(shù)中的結(jié)點(diǎn)(存在一個(gè)指向父親結(jié)點(diǎn)的指針),找出在中序遍歷下它的后繼和前驅(qū)

一棵二叉查找樹(shù)的中序遍歷序列,正好是升序序列。假如根結(jié)點(diǎn)的父結(jié)點(diǎn)為 nullptr,則:

如果當(dāng)前結(jié)點(diǎn)有右孩子,則后繼結(jié)點(diǎn)為這個(gè)右孩子的最左孩子;

如果當(dāng)前結(jié)點(diǎn)沒(méi)有右孩子;

2.1. 當(dāng)前結(jié)點(diǎn)為根結(jié)點(diǎn),返回 nullptr;

2.2. 當(dāng)前結(jié)點(diǎn)只是個(gè)普通結(jié)點(diǎn),也就是存在父結(jié)點(diǎn);

2.2.1. 當(dāng)前結(jié)點(diǎn)是父親結(jié)點(diǎn)的左孩子,則父親結(jié)點(diǎn)就是后繼結(jié)點(diǎn);

2.2.2. 當(dāng)前結(jié)點(diǎn)是父親結(jié)點(diǎn)的右孩子,沿著父親結(jié)點(diǎn)往上走,直到 n-1 代祖先是 n 代祖先的左孩子,則后繼為 n 代祖先或遍歷到根結(jié)點(diǎn)也沒(méi)找到符合的,則當(dāng)前結(jié)點(diǎn)就是中序遍歷的最后一個(gè)結(jié)點(diǎn),返回 nullptr。

/*求后繼結(jié)點(diǎn)*/

Node*Increment(Node*node)

{

if(node->right)//步驟1

{

node=node->right;

while(node->left)

node=node->left;

returnnode;

}

else//步驟2

{

if(node->parent==nullptr)//步驟2.1

returnnullptr;

Node*p=node->parent;//步驟2.2

if(p->left==node)//步驟2.2.1

returnp;

else//步驟2.2.2

{

while(p->right==node)

{

node=p;

p=p->parent;

if(p==nullptr)

returnnullptr;

}

returnp;

}

}

}

仔細(xì)觀察上述代碼,總覺(jué)得有點(diǎn)啰嗦。比如,過(guò)多的 return,步驟 2 的層次太多。綜合考慮所有情況,改進(jìn)代碼如下:

Node*Increment(Node*node)

{

if(node->right)

{

node=node->right;

while(node->left)

node=node->left;

}

else

{

Node*p=node->parent;

while(p&&p->right==node)

{

node=p;

p=p->parent;

}

node=p;

}

returnnode;

}

上述的代碼是基于結(jié)點(diǎn)有 parent 指針的,若題意要求沒(méi)有 parent 呢?網(wǎng)上也有人給出了答案,個(gè)人覺(jué)得沒(méi)有什么價(jià)值,有興趣的朋友可以到這里查看。

而求前驅(qū)結(jié)點(diǎn)的話(huà),只需把上述代碼的 left 與 right 互調(diào)即可,很簡(jiǎn)單。

17 二分查找樹(shù)轉(zhuǎn)化為排序的循環(huán)雙鏈表

二分查找樹(shù)的中序遍歷即為升序排列,問(wèn)題就在于如何在遍歷的時(shí)候更改指針的指向。一種簡(jiǎn)單的方法時(shí),遍歷二分查找樹(shù),將遍歷的結(jié)果放在一個(gè)數(shù)組中,之后再把該數(shù)組轉(zhuǎn)化為雙鏈表。如果題目要求只能使用 O(1)O(1) 內(nèi)存,則只能在遍歷的同時(shí)構(gòu)建雙鏈表,即進(jìn)行指針的替換。

我們需要用遞歸的方法來(lái)解決,假定每個(gè)遞歸調(diào)用都會(huì)返回構(gòu)建好的雙鏈表,可把問(wèn)題分解為左右兩個(gè)子樹(shù)。由于左右子樹(shù)都已經(jīng)是有序的,當(dāng)前結(jié)點(diǎn)作為中間的一個(gè)結(jié)點(diǎn),把左右子樹(shù)得到的鏈表連接起來(lái)即可。

/*合并兩個(gè)a,b兩個(gè)循環(huán)雙向鏈表*/

Node*Append(Node*a,Node*b)

{

if(a==nullptr)returnb;

if(b==nullptr)returna;

//分別得到兩個(gè)鏈表的最后一個(gè)元素

Node*a_last=a->left;

Node*b_last=b->left;

//將兩個(gè)鏈表頭尾相連

a_last->right=b;

b->left=a_last;

a->left=b_last;

b_last->right=a;

returna;

}

/*遞歸的解決二叉樹(shù)轉(zhuǎn)換為雙鏈表*/

Node*TreeToList(Node*node)

{

if(node==nullptr)returnnullptr;

//遞歸解決子樹(shù)

Node*left_list=TreeToList(node->left);

Node*right_list=TreeToList(node->right);

//把根結(jié)點(diǎn)轉(zhuǎn)換為一個(gè)結(jié)點(diǎn)的雙鏈表,方便后面的鏈表合并

node->left=node;

node->right=node;

//合并之后即為升序排列

left_list=Append(left_list,node);

left_list=Append(left_list,right_list);

returnleft_list;

}

18 有序鏈表轉(zhuǎn)化為平衡的二分查找樹(shù)(Binary Search Tree)

我們可以采用自頂向下的方法。先找到中間結(jié)點(diǎn)作為根結(jié)點(diǎn),然后遞歸左右兩部分。所以我們需要先找到中間結(jié)點(diǎn),對(duì)于單鏈表來(lái)說(shuō),必須要遍歷一邊,可以使用快慢指針加快查找速度。

structTreeNode

{

intdata;

TreeNode*left;

TreeNode*right;

TreeNode(intdata_){data=data_;left=right=nullptr;}

};

structListNode

{

intdata;

ListNode*next;

ListNode(intdata_){data=data_;next=nullptr;}

};

TreeNode*SortedListToBST(ListNode*list_node)

{

if(!list_node)returnnullptr;

if(!list_node->next)return(newTreeNode(list_node->data));

//用快慢指針找到中間結(jié)點(diǎn)

ListNode*pre_slow=nullptr;//記錄慢指針的前一個(gè)結(jié)點(diǎn)

ListNode*slow=list_node;//慢指針

ListNode*fast=list_node;//快指針

while(fast&&fast->next)

{

pre_slow=slow;

slow=slow->next;

fast=fast->next->next;

}

TreeNode*mid=newTreeNode(slow->data);

//分別遞歸左右兩部分

if(pre_slow)

{

pre_slow->next=nullptr;

mid->left=SortedListToBST(list_node);

}

mid->right=SortedListToBST(slow->next);

returnmid;

}

由 f(n)=2f(\frac n2)+\frac n2f(n)=2f(2n)+2n 得,所以上述算法的時(shí)間復(fù)雜度為 O(nlogn)O(nlogn)。

不妨換個(gè)思路,采用自底向上的方法:

TreeNode*SortedListToBSTRec(ListNode*&list,intstart,intend)

{

if(start>end)returnnullptr;

intmid=start+(end-start)/2;

TreeNode*left_child=SortedListToBSTRec(list,start,mid-1);//注意此處傳入的是引用

TreeNode*parent=newTreeNode(list->data);

parent->left=left_child;

list=list->next;

parent->right=SortedListToBSTRec(list,mid+1,end);

returnparent;

}

TreeNode*SortedListToBST(ListNode*node)

{

intn=0;

ListNode*p=node;

while(p)

{

n++;

p=p->next;

}

returnSortedListToBSTRec(node,0,n-1);

}

如此,時(shí)間復(fù)雜度降為 O(n)O(n)。

19 判斷是否是二叉查找樹(shù)

我們假定二叉樹(shù)沒(méi)有重復(fù)元素,即對(duì)于每個(gè)結(jié)點(diǎn),其左右孩子都是嚴(yán)格的小于和大于。

下面給出兩個(gè)方法:

方法 1:

boolIsBST(Node*node,intmin,intmax)

{

if(node==nullptr)

returntrue;

if(node->data<=?min?||?node->data>=max)

returnfalse;

returnIsBST(node->left,min,node->data)&&IsBST(node->right,node->data,max);

}

IsBST(node,INT_MIN,INT_MAX);

方法 2:

利用二叉查找樹(shù)中序遍歷時(shí)元素遞增來(lái)判斷。

boolIsBST(Node*node)

{

staticintpre=INT_MIN;

if(node==nullptr)

returntrue;

if(!IsBST(node->left))

returnfalse;

if(node->data

returnfalse;

pre=node->data;

returnIsBST(node->right);

}

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7613

    瀏覽量

    137239
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4808

    瀏覽量

    68808
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    計(jì)算機(jī)級(jí)二叉樹(shù)的問(wèn)題

    各位大神,本人馬上要考計(jì)算機(jī)級(jí)了,那個(gè)二叉樹(shù)老是弄不明白,比如一個(gè)題目,一棵二叉樹(shù)共有25個(gè)節(jié)點(diǎn),其中五個(gè)葉子節(jié)點(diǎn),則度為1的節(jié)點(diǎn)數(shù)為?
    發(fā)表于 09-04 09:45

    基于二叉樹(shù)的時(shí)序電路測(cè)試序列設(shè)計(jì)

    為了實(shí)現(xiàn)時(shí)序電路狀態(tài)驗(yàn)證和故障檢測(cè),需要事先設(shè)計(jì)一個(gè)輸入測(cè)試序列?;?b class='flag-5'>二叉樹(shù)節(jié)點(diǎn)和樹(shù)枝的特性,建立時(shí)序電路狀態(tài)二叉樹(shù),按照電路二叉樹(shù)節(jié)點(diǎn)(狀態(tài))與樹(shù)枝(輸入)的層次邏輯
    發(fā)表于 07-12 13:57 ?0次下載
    基于<b class='flag-5'>二叉樹(shù)</b>的時(shí)序電路測(cè)試序列設(shè)計(jì)

    二叉樹(shù)層次遍歷算法的驗(yàn)證

    實(shí)現(xiàn)二叉樹(shù)的層次遍歷算法,并對(duì)用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”創(chuàng)建的二叉樹(shù)進(jìn)行測(cè)試。
    發(fā)表于 11-28 01:05 ?2112次閱讀
    <b class='flag-5'>二叉樹(shù)</b>層次遍歷算法的驗(yàn)證

    二叉樹(shù),一種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)類(lèi)型

    然后我們?cè)俣x一棵深度也為 3 的二叉樹(shù),該二叉樹(shù)的 n 個(gè)結(jié)點(diǎn)(n≤7),當(dāng)從 1 到 n 的每個(gè)結(jié)點(diǎn)都與上圖中的編號(hào)結(jié)點(diǎn)一一對(duì)應(yīng)時(shí),這二叉樹(shù)就稱(chēng)為完全二叉樹(shù)。
    的頭像 發(fā)表于 04-13 10:48 ?4379次閱讀
    <b class='flag-5'>二叉樹(shù)</b>,一種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)類(lèi)型

    詳解電源二叉樹(shù)到底是什么

    作為數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),樹(shù)分很多種,像 AVL 樹(shù)、紅黑樹(shù)二叉搜索樹(shù)....今天我想分享的是關(guān)于二叉樹(shù)
    的頭像 發(fā)表于 06-06 15:05 ?1w次閱讀
    <b class='flag-5'>詳解</b>電源<b class='flag-5'>二叉樹(shù)</b>到底是什么

    C語(yǔ)言二叉樹(shù)代碼免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是C語(yǔ)言二叉樹(shù)代碼免費(fèi)下載。
    發(fā)表于 08-27 08:00 ?1次下載

    二叉樹(shù)的前序遍歷非遞歸實(shí)現(xiàn)

    我們之前說(shuō)了二叉樹(shù)基礎(chǔ)及二叉的幾種遍歷方式及練習(xí)題,今天我們來(lái)看一下二叉樹(shù)的前序遍歷非遞歸實(shí)現(xiàn)。 前序遍歷的順序是, 對(duì)于樹(shù)中的某節(jié)點(diǎn),先遍歷該節(jié)點(diǎn),然后再遍歷其左子樹(shù),最后遍歷其右子
    的頭像 發(fā)表于 05-28 13:59 ?1980次閱讀

    數(shù)據(jù)結(jié)構(gòu)與算法分析中的二叉樹(shù)與堆有關(guān)知識(shí)匯總

    該資料包括數(shù)據(jù)結(jié)構(gòu)與算法分析中的二叉樹(shù)與堆有關(guān)的一些知識(shí)
    發(fā)表于 11-03 09:37 ?0次下載

    C語(yǔ)言數(shù)據(jù)結(jié)構(gòu):什么是二叉樹(shù)?

    完全二叉樹(shù):完全二叉樹(shù)是效率很高的數(shù)據(jù)結(jié)構(gòu)。對(duì)于深度為K,有n個(gè)節(jié)點(diǎn)的二叉樹(shù),當(dāng)且僅當(dāng)每一個(gè)節(jié)點(diǎn)都與深度為K的滿(mǎn)二叉樹(shù)中編號(hào)從1至n的節(jié)點(diǎn)一一對(duì)應(yīng)時(shí),稱(chēng)為完全
    的頭像 發(fā)表于 04-21 16:20 ?2567次閱讀

    怎么就能構(gòu)造成二叉樹(shù)呢?

    一直跟著公眾號(hào)學(xué)算法的錄友 應(yīng)該知道,我在二叉樹(shù):構(gòu)造二叉樹(shù)登場(chǎng)!,已經(jīng)講過(guò),只有 中序與后序 和 中序和前序 可以確定一顆唯一的二叉樹(shù)。前序和后序是不能確定唯一的二叉樹(shù)的。
    的頭像 發(fā)表于 07-14 11:20 ?1631次閱讀

    二叉樹(shù)的最大深度

    精簡(jiǎn)之后的代碼根本看不出是哪種遍歷方式,也看不出遞歸三部曲的步驟,所以如果對(duì)二叉樹(shù)操作還不熟練,盡量不要直接照著精簡(jiǎn)代碼來(lái)學(xué)。
    的頭像 發(fā)表于 07-26 11:28 ?1109次閱讀

    使用C語(yǔ)言代碼實(shí)現(xiàn)平衡二叉樹(shù)

    這篇博客主要總結(jié)平衡二叉樹(shù),所以,二叉排序樹(shù)知識(shí)不會(huì)提及,但是會(huì)用到。
    的頭像 發(fā)表于 09-21 11:00 ?1127次閱讀

    二叉樹(shù)代碼實(shí)現(xiàn)

    二叉樹(shù)的主要操作有遍歷,例如有先序遍歷、中序遍歷、后序遍歷。在遍歷之前,就是創(chuàng)建一棵二叉樹(shù),當(dāng)然,還需要有刪除二叉樹(shù)的算法。
    的頭像 發(fā)表于 01-18 10:41 ?1251次閱讀
    <b class='flag-5'>二叉樹(shù)</b>的<b class='flag-5'>代碼</b>實(shí)現(xiàn)

    C++構(gòu)建并復(fù)制二叉樹(shù)

    使用C++構(gòu)建一個(gè)二叉樹(shù)并復(fù)制、輸出。
    的頭像 發(fā)表于 01-10 15:17 ?1050次閱讀
    C++構(gòu)建并復(fù)制<b class='flag-5'>二叉樹(shù)</b>

    C++自定義二叉樹(shù)并輸出二叉樹(shù)圖形

    使用C++構(gòu)建一個(gè)二叉樹(shù)并輸出。
    的頭像 發(fā)表于 01-10 16:29 ?1780次閱讀
    C++自定義<b class='flag-5'>二叉樹(shù)</b>并輸出<b class='flag-5'>二叉樹(shù)</b>圖形