數(shù)據(jù)結(jié)構(gòu) Java數(shù)據(jù)結(jié)構(gòu) 二叉樹

文章目錄

1. 樹形結(jié)構(gòu)

1.1 概念

樹是一種非線性的數(shù)據(jù)結(jié)構(gòu)?掠河,它是由n(n>=0)個有限結(jié)點組成一個具有層次關(guān)系的集合曙博。把它叫做樹是因為它看起來像一棵倒掛的樹碍脏,也就是說它是根朝上仿野,而葉朝下的?塞栅。它具有以下的特點:

有一個特殊的節(jié)點,稱為根節(jié)點趁俊,根節(jié)點沒有前驅(qū)節(jié)點

除根節(jié)點外域仇,其余節(jié)點被分成M(M > 0)個互不相交的集合T1、T2寺擂、…Tm暇务,其中每一個集合 Ti (1 <= i<= m) 又是一棵與樹類似的子樹。每棵子樹的根節(jié)點有且只有一個前驅(qū)怔软,可以有0個或多個后繼

樹是遞歸定義的垦细。

節(jié)點的度:一個節(jié)點含有的子樹的個數(shù)稱為該節(jié)點的度; 如上圖:A的為6

樹的度:一棵樹中挡逼,最大的節(jié)點的度稱為樹的度括改; 如上圖:樹的度為6

葉子節(jié)點或終端節(jié)點:度為0的節(jié)點稱為葉節(jié)點; 如上圖:B家坎、C嘱能、H吝梅、I…等節(jié)點為葉節(jié)點

雙親節(jié)點或父節(jié)點:若一個節(jié)點含有子節(jié)點,則這個節(jié)點稱為其子節(jié)點的父節(jié)點惹骂; 如上圖:A是B的父節(jié)點孩

子節(jié)點或子節(jié)點:一個節(jié)點含有的子樹的根節(jié)點稱為該節(jié)點的子節(jié)點苏携; 如上圖:B是A的孩子節(jié)點

根結(jié)點:一棵樹中,沒有雙親結(jié)點的結(jié)點对粪;如上圖:A

節(jié)點的層次:從根開始定義起右冻,根為第1層,根的子節(jié)點為第2層著拭,以此類推纱扭;

樹的高度或深度:樹中節(jié)點的最大層次; 如上圖:樹的高度為4

非終端節(jié)點或分支節(jié)點:度不為0的節(jié)點茫死; 如上圖:D跪但、E、F峦萎、G…等節(jié)點為分支節(jié)點

兄弟節(jié)點:具有相同父節(jié)點的節(jié)點互稱為兄弟節(jié)點; 如上圖:B忆首、C是兄弟節(jié)點

堂兄弟節(jié)點:雙親在同一層的節(jié)點互為堂兄弟爱榔;如上圖:H、I互為兄弟節(jié)點

節(jié)點的祖先:從根到該節(jié)點所經(jīng)分支上的所有節(jié)點糙及;如上圖:A是所有節(jié)點的祖先

子孫:以某節(jié)點為根的子樹中任一節(jié)點都稱為該節(jié)點的子孫详幽。如上圖:所有節(jié)點都是A的子孫

森林:由m(m>=0)棵互不相交的樹的集合稱為森林

1.2 樹的表示形式

樹結(jié)構(gòu)相對線性表就比較復(fù)雜了,要存儲表示起來就比較麻煩了浸锨,實際中樹有很多種表示方式唇聘,如:雙親表示法,孩子表示法柱搜、孩子兄弟表示法等等迟郎。我們這里就簡單的了解其中最常用的孩子兄弟表示法?。

classNode{

intvalue;// 樹中存儲的數(shù)據(jù)NodefirstChild;// 第一個孩子引用NodenextBrother;// 下一個兄弟引用}

1.3 樹的應(yīng)用

文件系統(tǒng)管理(目錄和文件)

2. 二叉樹

2.1 概念

一棵二叉樹是結(jié)點的一個有限集合聪蘸,該集合或者為空宪肖,或者是由一個根節(jié)點加上兩棵別稱為左子樹和右子樹的二叉

樹組成。

二叉樹的特點:

1. 每個結(jié)點最多有兩棵子樹健爬,即二叉樹不存在度大于 2 的結(jié)點控乾。

2. 二叉樹的子樹有左右之分,其子樹的次序不能顛倒娜遵,因此二叉樹是有序樹蜕衡。

2.2 二叉樹的基本形態(tài)

一般二叉樹都是由以下幾個基本形態(tài)結(jié)合而形成的。

2.3 兩種特殊的二叉樹

滿二叉樹?: 一個二叉樹设拟,如果每一個層的結(jié)點數(shù)都達(dá)到最大值慨仿,則這個二叉樹就是滿二叉樹鸽扁。也就是說,如果一個二叉樹的層數(shù)為K镶骗,且結(jié)點總數(shù)是 桶现,則它就是滿二叉樹。

完全二叉樹?: 完全二叉樹是效率很高的數(shù)據(jù)結(jié)構(gòu)鼎姊,完全二叉樹是由滿二叉樹而引出來的骡和。對于深度為K的,有n個結(jié)點的二叉樹相寇,當(dāng)且僅當(dāng)其每一個結(jié)點都與深度為K的滿二叉樹中編號從1至n的結(jié)點一一對應(yīng)時稱之為完全二叉樹慰于。 要注意的是滿二叉樹是一種特殊的完全二叉樹。

2.4 二叉樹的性質(zhì)

若規(guī)定根節(jié)點的層數(shù)為1?唤衫,則一棵非空二叉樹的第i層上最多有 2i-1?(i>0)個結(jié)點

若規(guī)定只有根節(jié)點的二叉樹的深度為1?婆赠,則深度為K的二叉樹的最大結(jié)點數(shù)是 2k?-1(k>=0)

對任何一棵二叉樹, 如果其葉結(jié)點個數(shù)為 n0, 度為2的非葉結(jié)點個數(shù)為 n2,則有n0=n2+1

具有n個結(jié)點的完全二叉樹的深度k為 log2?(n+1) 上取整

對于具有n個結(jié)點的完全二叉樹?,如果按照從上至下從左至右的順序?qū)λ泄?jié)點從0開始編號?佳励,則對于序號為i的結(jié)點有?:

若i>0休里,雙親序號:(i-1)/2;i=0赃承,i為根節(jié)點編號妙黍,無雙親節(jié)點

若2i+1<n,左孩子序號:2i+1瞧剖,否則無左孩子

若2i+2<n拭嫁,右孩子序號:2i+2,否則無右孩子

比如:假設(shè)一棵完全二叉樹中總共有1000個?節(jié)點抓于,則該二叉樹中500個葉子節(jié)點做粤,500個非葉子節(jié)點,1個節(jié)點只有左孩子捉撮,0個只有右孩子怕品。

2.5 二叉樹的存儲

二叉樹的存儲結(jié)構(gòu)分為:順序存儲和類似于鏈表的鏈?zhǔn)酱鎯Α?/p>

順序存儲 存儲的是完全二叉樹

二叉樹的鏈?zhǔn)酱鎯κ峭ㄟ^一個一個的節(jié)點引用起來的,常見的表示方式有二叉和三叉表示方式呕缭,具體如下:

// 孩子表示法class Node {

intval;// 數(shù)據(jù)域Nodeleft;// 左孩子的引用堵泽,常常代表左孩子為根的整棵左子樹Noderight;// 右孩子的引用,常常代表右孩子為根的整棵右子樹}// 孩子雙親表示法class Node {

intval;// 數(shù)據(jù)域Nodeleft;// 左孩子的引用恢总,常常代表左孩子為根的整棵左子樹Noderight;// 右孩子的引用迎罗,常常代表右孩子為根的整棵右子樹Nodeparent;// 當(dāng)前節(jié)點的根節(jié)點}

2.6 二叉樹的遍歷

如果N代表根節(jié)點,L代表根節(jié)點的左子樹片仿,R代表根節(jié)點的右子樹纹安,則根據(jù)遍歷根節(jié)點的先后次序有以下遍歷方式:

NLR:前序遍歷(Preorder Traversal 亦稱先序遍歷)——訪問根結(jié)點—>根的左子樹—>根的右子樹。

LNR:中序遍歷(Inorder Traversal)——根的左子樹—>根節(jié)點—>根的右子樹。

LRN:后序遍歷(Postorder Traversal)——根的左子樹—>根的右子樹—>根節(jié)點厢岂。?

2.7 二叉樹的基本操作

第一步: 首先這里我們用窮舉法先來創(chuàng)建一個二叉樹來測試這些操作.

packageBinaryTree;class TreeNode{

publiccharval;publicTreeNodeleft;publicTreeNoderight;public TreeNode(char val){

this.val=val;}}publicclass BinaryTree {

public TreeNode createTree() {

TreeNodeA=newTreeNode('A');TreeNodeB=newTreeNode('B');TreeNodeC=newTreeNode('C');TreeNodeD=newTreeNode('D');TreeNodeE=newTreeNode('E');TreeNodeF=newTreeNode('F');TreeNodeG=newTreeNode('G');TreeNodeH=newTreeNode('H');A.left=B;A.right=C;B.left=D;B.right=E;E.right=H;C.left=F;C.right=G;returnA;}}

此時的二叉樹圖形如圖:

第二步: 用代碼實現(xiàn)3種遍歷二叉樹的方法.

// 前序遍歷voidpreOrderTraversal(TreeNoderoot){

if(root==null){

return;}System.out.print(root.val+" ");preOrderTraversal(root.left);preOrderTraversal(root.right);}// 中序遍歷voidinOrderTraversal(TreeNoderoot){

if(root==null){

return;}inOrderTraversal(root.left);System.out.print(root.val+" ");inOrderTraversal(root.right);}// 后序遍歷voidpostOrderTraversal(TreeNoderoot){

if(root==null){

return;}postOrderTraversal(root.left);postOrderTraversal(root.right);System.out.print(root.val+" ");}

第三步: 兩種方法求結(jié)點個數(shù)

// 遍歷思路-求結(jié)點個數(shù)staticintsize=0;void getSize1(TreeNode root){

if(root==null){

return;}size++;getSize1(root.left);getSize1(root.right);}// 子問題思路-求結(jié)點個數(shù)int getSize2(TreeNode root){

if(root==null){

return0;}returngetSize2(root.left)+getSize2(root.right)+1;}

第四步: 兩種方法求葉子結(jié)點的個數(shù)

// 遍歷思路-求葉子結(jié)點個數(shù)staticintleafSize=0;void getLeafSize1(TreeNode root){

if(root==null){

return;}if(root.left==null&&root.right==null){

leafSize++;}getLeafSize1(root.left);getLeafSize1(root.right);}// 子問題思路-求葉子結(jié)點個數(shù)int getLeafSize2(TreeNode root){

if(root==null){

return0;}if(root.left==null&&root.right==null){

return1;}returngetLeafSize2(root.left)+getLeafSize2(root.right);}

第五步: 求第 k 層結(jié)點個數(shù)

intgetKLevelSize(TreeNoderoot,intk){

if(root==null){

return0;}if(k==1){

return1;}returngetKLevelSize(root.left,k-1)+getKLevelSize(root.right,k-1);}

第六步: 查找val所在的位置

// 查找 val 所在結(jié)點光督,沒有找到返回 null// 按照 根 -> 左子樹 -> 右子樹的順序進(jìn)行查找// 一旦找到,立即返回塔粒,不需要繼續(xù)在其他位置查找TreeNodefind(TreeNoderoot,charval){

if(root==null){

returnnull;}if(root.val==val){

returnroot;}TreeNoderet=find(root.left,val);if(ret!=null){

returnret;}ret=find(root.right,val);if(ret!=null){

returnret;}returnnull;}

第七步: 獲取二叉樹的高度

// 獲取二叉樹的高度int getHeight(TreeNode root){

if(root==null){

return0;}intleftHeight=getHeight(root.left);intrightHeight=getHeight(root.right);returnleftHeight>rightHeight?leftHeight+1:rightHeight+1;}

第八步: 運行測試結(jié)果

publicstaticvoidmain(String[]args){

BinaryTreebinaryTree=newBinaryTree();TreeNoderoot=binaryTree.createTree();System.out.print("前序遍歷結(jié)果:? ");binaryTree.preOrderTraversal(root);System.out.println();System.out.print("中序遍歷結(jié)果:? ");binaryTree.inOrderTraversal(root);System.out.println();System.out.print("后序遍歷結(jié)果:? ");binaryTree.postOrderTraversal(root);System.out.println();binaryTree.getSize1(root);System.out.println("結(jié)點數(shù): "+BinaryTree.size);intret=binaryTree.getSize2(root);System.out.println("結(jié)點數(shù): "+ret);binaryTree.getLeafSize1(root);System.out.println("葉子節(jié)點數(shù): "+BinaryTree.leafSize);intret1=binaryTree.getLeafSize2(root);System.out.println("葉子節(jié)點數(shù): "+ret1);intret2=binaryTree.getKLevelSize(root,3);System.out.println("求k層節(jié)點數(shù): "+ret2);TreeNodeb=binaryTree.find(root,'H');System.out.println(b.val);System.out.println("求二叉樹的深度: "+binaryTree.getHeight(root));}

運行結(jié)果:

2.8 層序遍歷

設(shè)二叉樹的根節(jié)點所在層數(shù)為1结借,層序遍歷就是從所在二叉樹的根節(jié)點出發(fā),首先訪問第一層的樹根節(jié)點卒茬,然后從左到右訪問第2層上的節(jié)點船老,接著是第三層的節(jié)點,以此類推圃酵,自上而下柳畔,自左至右逐層訪問樹的結(jié)點的過程就是層序遍歷。

a) 層序遍歷代碼實現(xiàn):

// 層序遍歷void levelOrderTraversal(TreeNode root){

if(root==null)return;Queue<TreeNode>queue=newLinkedList<>();queue.offer(root);while(!queue.isEmpty()){

TreeNodetop=queue.poll();System.out.print(top.val+" ");if(top.left!=null)queue.offer(top.left);if(top.right!=null)queue.offer(top.right);}System.out.println();}

b) 判斷一棵樹是不是完全二叉樹

方法一思路:

1. 將樹按照層序遍歷的方法,放入隊列中,不同的是將左右節(jié)點都放入隊列中,不論節(jié)點是否為空都放入

2. 循環(huán)取出隊首元素,如果隊首為null就結(jié)束循環(huán).

3. 如果此時隊列不為空.

①隊列還有節(jié)點,那么就不是完全二叉樹

②隊列全是null,那么就是完全二叉樹.

4. 循環(huán)結(jié)束那么就是true;

代碼實現(xiàn):

boolean isCompleteTree(TreeNode root) {

if(root==null)returntrue;Queue<TreeNode>queue=newLinkedList<>();queue.offer(root);while(!queue.isEmpty()){

TreeNodetop=queue.poll();//左子樹 和 右子樹 都放入隊列(不論是不是null)if(top!=null){

queue.offer(top.left);queue.offer(top.right);}else{

//如果隊首為空就跳出循環(huán)break;}}//如果隊不為空,說明是因為break結(jié)束的循環(huán).那么就需要判斷隊里的元素while(!queue.isEmpty()){

TreeNodecur=queue.peek();//如果隊首為空,就出隊if(cur==null){

queue.poll();}else{

//遇到不為空的節(jié)點就表示不是完全二叉樹.returnfalse;}}//隊空既為true;returntrue;}

方法二思路:

1. 觀察完全二叉樹的圖形我們可以看出來,每個節(jié)點要么有2個子節(jié)點,要么沒有節(jié)點,要么就只有一個左節(jié)點沒有右節(jié)點.

2. 遍歷判斷

①如果左子樹和右子樹都不為空,就入隊.

②如果左子樹存在 右子樹不存在,那么進(jìn)行第二個判斷.

③如果左子樹不存在 右子樹存在,那么直接false

④如果左子樹右子樹都不存在,那么也進(jìn)入第二個判斷.

3. 第二個判斷,判斷是否接下來的左子樹和右子樹都為空,如果不為空,就是false;一直為空就是true;

代碼實現(xiàn):

boolean isCompleteTree1(TreeNode root) {

Queue<TreeNode>queue=newLinkedList<>();queue.offer(root);booleanisComplete=true;while(!queue.isEmpty()){

TreeNodetop=queue.poll();if(isComplete){

//1.都不為空 入隊if(top.left!=null&&top.right!=null){

queue.offer(top.left);queue.offer(top.right);}elseif(top.left!=null&&top.right==null){

//2.左子樹不為空,右子樹為空,進(jìn)入第二個判斷isComplete=false;queue.offer(top.left);}elseif(top.left==null&&top.right!=null){

//3.左子樹為空,右子樹不為空,不符合完全二叉樹概念falsereturnfalse;}else{

//4.左右子樹都為空,進(jìn)入第二個判斷isComplete=false;}}else{

//第2判斷//如果后面的節(jié)點還有子樹,那就不符合完全二叉樹概念 falseif(top.left!=null||top.right!=null){

returnfalse;}}}//循環(huán)遍歷結(jié)束,那就是滿足條件,返回true;returntrue;}

2.9 前中后序的非遞歸實現(xiàn)

①前序遍歷 (非遞歸)

// 前序遍歷voidpreOrderTraversal(TreeNoderoot){

if(root==null)return;TreeNodecur=root;Stack<TreeNode>stack=newStack<>();while(cur!=null||!stack.empty()){

while(cur!=null){

stack.push(cur);System.out.print(cur.val+" ");cur=cur.left;}TreeNodetop=stack.pop();cur=top.right;}}

②中序遍歷 (非遞歸)

// 中序遍歷voidinOrderTraversal(TreeNoderoot){

if(root==null)return;Stack<TreeNode>stack=newStack<>();TreeNodecur=root;while(cur!=null||!stack.empty()){

while(cur!=null){

stack.push(cur);cur=cur.left;}TreeNodetop=stack.pop();System.out.print(top.val+" ");cur=top.right;}}

③后序遍歷 (非遞歸)

// 后序遍歷voidpostOrderTraversal(TreeNoderoot){

if(root==null)return;TreeNodecur=root;TreeNodepre=null;//用來指向上一個被打印的元素Stack<TreeNode>stack=newStack<>();while(cur!=null||!stack.empty()){

while(cur!=null){

stack.push(cur);cur=cur.left;}cur=stack.peek();if(cur.right==null||pre==cur.right){

stack.pop();System.out.print(cur.val+" ");pre=cur;cur=null;}else{

cur=cur.right;}}}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郭赐,一起剝皮案震驚了整個濱河市薪韩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捌锭,老刑警劉巖俘陷,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舀锨,居然都是意外死亡岭洲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門坎匿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雷激,你說我怎么就攤上這事替蔬。” “怎么了屎暇?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵承桥,是天一觀的道長。 經(jīng)常有香客問我根悼,道長凶异,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任挤巡,我火速辦了婚禮剩彬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矿卑。我一直安慰自己喉恋,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轻黑,像睡著了一般糊肤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氓鄙,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天馆揉,我揣著相機與錄音,去河邊找鬼抖拦。 笑死升酣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蟋座。 我是一名探鬼主播拗踢,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼向臀!你這毒婦竟也來了巢墅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤券膀,失蹤者是張志新(化名)和其女友劉穎剿吻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谱仪,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡会涎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舒帮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片会喝。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玩郊,靈堂內(nèi)的尸體忽然破棺而出肢执,到底是詐尸還是另有隱情,我是刑警寧澤译红,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布预茄,位于F島的核電站,受9級特大地震影響侦厚,放射性物質(zhì)發(fā)生泄漏耻陕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一刨沦、第九天 我趴在偏房一處隱蔽的房頂上張望诗宣。 院中可真熱鬧,春花似錦已卷、人聲如沸梧田。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裁眯。三九已至鹉梨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穿稳,已是汗流浹背存皂。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逢艘,地道東北人旦袋。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像它改,于是被迫代替她去往敵國和親疤孕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容