Java數據結構(16)----實現簡單的二叉樹

你未必出類拔萃,但一定與衆不同

二叉樹

基本定義

二叉樹(Binary Tree)是n(n0n \geq 0)個結點的有限集合,該集合或者爲空集(稱爲空二叉樹),或者由一個根結點和兩顆互不相交的,分別稱爲根結點的左子樹和右子樹的二叉樹組成

二叉樹特點

  1. 每個結點最多有兩顆子樹,所以二叉樹中不存在度大於2的結點,注意不是隻有兩顆子樹,而是最多有。沒有子樹或者一顆子樹都是可以的。
  2. 左子樹和右子樹是有順序的,次序不能任意顛倒,就像是人是雙手雙腳,但顯然左手和右手是不同的。
  3. 即使樹種某結點只有一顆子樹,也要區分它是左子樹還是右子樹,樹1和樹2是同一顆樹,但是卻是不同的二叉樹,就好像朝左邊和朝右邊走路是不同的方向。

二叉樹具有五種基本形態

  1. 空二叉樹
  2. 只有一個根結點
  3. 根結點只有左子樹
  4. 根結點只有右子樹
  5. 根結點既有左子樹又有右子樹

特殊二叉樹

1.斜樹

所有的節點都只有左子樹的二叉樹叫左斜樹,所有結點都是隻有右子樹的二叉樹叫右斜樹,這兩個統稱爲斜樹就像下圖中的圖1和圖2就是斜樹,斜樹每一層都只有一個節點,節點的個數與二叉樹的深度相同
在這裏插入圖片描述

2.滿二叉樹

在一顆二叉樹中,如果所有分支結點都存在左子樹和右子樹,並且所有葉子都在同一層上,這樣的二叉樹稱爲滿二叉樹
單是每個結點都存在左右子樹,不能算是滿二叉樹,還必須要所有的葉子都在同一層上,這就做到了整棵樹的平衡

滿二叉樹的特點有:

  • 葉子只能出現在最下一層。出現在其他層就不可能打成平衡。
  • 非葉子結點的度一定是2
  • 在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子也最多。
圖3:

在這裏插入圖片描述

3.完全二叉樹

對一顆具有n個結點的二叉樹按層序編號,如果編號爲i(1in1 \leq i \leq n)的結點與同樣深度的滿二叉樹中編號爲i的結點在二叉樹中位置完全相同,則這顆二叉樹稱爲完全二叉樹。

如下圖4

在這裏插入圖片描述

完全二叉樹特點:

(1) 葉子結點只能出現在最下兩層。

(2)最下層的葉子一一定集中在左部連續位置。

(3)倒數二層,若有葉子結點,i一定都在右部連續位置。

(4)如果結點度爲1,則該結點只有左孩子,即不存在只有右子樹的情況。
(5)同樣結點數的二叉樹,完全二叉樹的深度最小。

從上面的例子,也給了我們一個判斷某二叉樹是否是完全一叉樹的辦法,那就是看着樹的示意圖,心中默默給每個結點按照滿二叉樹的結構逐層順序編號,如果編號出現空檔,就說明不是完全二叉樹,否則就是。

二叉樹的性質

性質1:在二叉樹的第i層上至多有2i12^{i-1}個結點(i>1)。如圖3

第一層是根結點,只有一個,所以2112^{1-1}=1.
第二層有兩個,2212^{2-1}=212^{1}=2。
第三層有四個,2312^{3-1}=222^{2}=4。
第四層有八個,2412^{4-1}=232^{3}=8。

通過數據歸納法的論證,可以很容易得出在二叉樹的第i層上至多有2i12^{i-1}個結點(i>1)的結論。

性質2:深度爲k的二叉樹至多有**2k12^{k}-1**個結點(k>1)。

是**2k12^{k}-1**
深度爲k意思就是有k層的二叉樹

如果有一層,至多1=2112^{1}-1個結點。
如果有二層,至多1+2=3=2212^{2}-1個結點。
如果有三層,至多1+2+4=7=2312^{3}-1個結點。
如果有四層,至多1+2+4+8=15-=2412^{4}-1個結點。

通過數據歸納法的論證,可以得出,如果有k層,此二叉樹至多有2k12^{k}-1個結點。

性質3:對任何一棵二叉樹T,如果其終端結點數爲n0,度爲2的結點數爲n2,則n0=n2+1。

終端結點數其實就是葉子結點數,而一棵二叉樹,除了葉子結點外,剩下的就是度爲1或2的結點數了,我們設n1爲度是1的結點數。則樹T結點總數n=no+n1+n2。
比如圖4,結點總數爲10,它是由A、B、C、D等度爲2結點,F、G、H、I、J等度爲0的葉子結點和E這個度爲1的結點組成。總和爲4+1+5=10。
在這裏插入圖片描述
再數數它的連接線數, 由於根結點只有分支出去,沒有分支進入所以分支線總數爲結點總數減去1,就是9個分支。對於A、BCD結點來說,它們都有兩個分支線出去,而E結點只有一個分支線出去。所以總分支線爲4* 2+1* 1=9。
用代數表達就是分支線總數=n- 1=n1+n2n_1+n_2。因爲剛纔我們有等式n=n0+n1+n2n=n_0+n_1+n_2,所以可推導出n0+n1+n21=n1+2n2n_0+n_1+n_2-1=n_1+2n_2結論就是n0=n2+1n_0=n_2+1

性質4::具有n個結點的完全二叉樹的深度爲

在這裏插入圖片描述
(符號表示不大於裏面的最大整數)
由滿二又樹的定義我們可以知道,深度爲k的滿二叉樹的結點數n定是2k12^{k}-1。因爲這是最多的結點個數。那麼對於n=2k1n=2^{k}-1 倒推得到滿二叉樹的深度爲k=log2(n+1)k=log_2({n+1}),比如結點數爲15的滿二叉樹,深度爲4。
完全二叉樹,它是棵具有n 個結點的二叉樹,若按層序編號後其編號與同樣深度的滿二叉樹中編號結點在二又叉樹中位置完全相同,那它就是完全二叉樹。也就是說,它的葉子結點只會出現在最下面的兩層。

它的結點數一定少於等於同樣深度的滿二叉樹的結點數2k12^{k}-1,但一定多於2k112^{k-1}-1。即滿足2k112^{k-1}-1<n≤2k12^{k}-1。由於結點數n是整數,n≤2k12^{k}-1意味着n<2k2^{k},
n>2k112^{k-1}-1,意味着n≥2k12^{k-1},所以2k12^{k-1}≤n<2k2^{k},不等式兩邊取對數,得到k-1≤log2n<k,而k作爲深度也是整數,因此k=[log2n]+1[log_2n]+1

(向下取整符號太難打 用[]代替了)

性質5:如果對一棵有n個結點的完全二叉樹(其深度爲[log2n]+1[log_2n]+1)的結點按層序編號(從第1層到第[log2n]+1[log_2n]+1 層,每層從左到右),對任一結點i (1in1 \leq i \leq n)有:

1.如果i=1, 則結點i是二叉樹的根,無雙親;如果i>1, 則其雙親是結點[i/2]。

2.如果2i>n,則結點i無左孩子(結點i爲葉子結點);否則其左孩子是結點2i.

3.如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1。

二叉樹的順序存儲結構

二叉樹是一種特殊的樹,由於它的特殊性,使得用順序存儲結構也可以實現。
二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點 ,並且結點的存儲位置,也就是數組的下標要能體現結點之間的邏輯關係,比如雙親與孩子的關係,左右兄弟的關係等。
在這裏插入圖片描述
將二叉樹存入數組中,相應的下標對應相應的位置
在這裏插入圖片描述

將數組中的數存入二叉樹(前中後)
public class ArrayTreeDemo1 {

	public static void main(String[] args) {
		

		int []arr = {1,2,3,4,5,6,7};
		ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
		System.out.println("先序遍歷");
		arrBinaryTree.preOrder(); 
		System.out.println("中序遍歷");
		arrBinaryTree.InOrder();
		System.out.println("後序遍歷");
		arrBinaryTree.PostOrder();
	}

}

//實現順序存儲二叉樹遍歷

class ArrBinaryTree {
	private int[] arr;//存儲數據結點的數組

	public ArrBinaryTree(int[] arr) {
		this.arr = arr;
	}
	
	//重載preOrder
	public void preOrder() {
		this.preOrder(0);
	}
	//重載InOrder
	public void InOrder() {
		this.InOrder(0);
	}
	//重載PostOrder
	public void PostOrder() {
		this.PostOrder(0);
	}
	
	//順序存儲二叉樹的前序遍歷
	public void preOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
		}
		System.out.println(arr[index]); 
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			preOrder(2 * index + 1 );
		}
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			preOrder(2 * index + 2);
		}
	}
	//順序存儲二叉樹的中序遍歷
	public void InOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的中序遍歷");
		}
		
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			InOrder(2 * index + 1 );
		}
		//輸出當前這個元素
		System.out.println(arr[index]); 
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			InOrder(2 * index + 2);
		}
	}
	//順序存儲二叉樹的後序遍歷
	public void PostOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的後序遍歷");
		}
		
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			PostOrder(2 * index + 1 );
		}
		
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			PostOrder(2 * index + 2);
		}
		//輸出當前這個元素
		System.out.println(arr[index]); 
	}
	
}

手動輸入數組存入二叉樹:

public class ArrayTree {

	public static void main(String[] args) {
		
		
		//手動輸入
		Scanner in = new Scanner(System.in);
		System.out.println("輸入二叉樹的深度");
		int depth = in.nextInt();
		char []arr = new char[(int) Math.pow(2, depth)-1];
		System.out.println("二叉樹的深度爲"+depth);
		System.out.println("請輸入根節點");
		arr[0] = in.next().charAt(0);
		for(int i =0;i<depth;i++) {
			if(arr[i]!=48) {
				//左孩子
				if((i * 2 + 1) < arr.length) {
					System.out.println("節點"+arr[i]+"的左孩子節點爲(爲空則輸入0):");
					arr[i * 2 + 1] = in.next().charAt(0);
				}
				//右孩子
				if((i * 2 + 2) < arr.length) {
					System.out.println("節點"+arr[i]+"的右孩子節點爲(爲空則輸入0):");
					arr[i * 2 + 2] = in.next().charAt(0);
				}
			}
			else {
				if((i * 2 + 1) < arr.length) {
					//System.out.println("節點"+arr[i]+"的左孩子節點爲(爲空則輸入0):");
					arr[i * 2 + 1] = 0;
				}
					//右孩子
				if((i * 2 + 2) < arr.length) {
					//System.out.println("節點"+arr[i]+"的右孩子節點爲(爲空則輸入0):");
					arr[i * 2 + 2] = 0;
				}
			}				 
		}
		ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
		System.out.println("先序遍歷");
		arrBinaryTree.preOrder(); 
		System.out.println("中序遍歷");
		arrBinaryTree.InOrder();
		System.out.println("後序遍歷");
		arrBinaryTree.PostOrder();
	}
	public static int log(int value, int base) {
		return (int) (Math.log(value) / Math.log(base));
	}

}

//編寫一個ArrayBinaryTree, 實現順序存儲二叉樹遍歷

class ArrBinaryTree {
	private char[] arr;//存儲數據結點的數組

	public ArrBinaryTree(char[] arr) {
		this.arr = arr;
	}
	
	//重載preOrder
	public void preOrder() {
		this.preOrder(0);
	}
	//重載InOrder
	public void InOrder() {
		this.InOrder(0);
	}
	//重載PostOrder
	public void PostOrder() {
		this.PostOrder(0);
	}
	
	//順序存儲二叉樹的前序遍歷
	public void preOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
		}
		//輸出當前這個元素
		if(arr[index]!=48) {
			System.out.println(arr[index]); 
		}
		
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			preOrder(2 * index + 1 );
		}
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			preOrder(2 * index + 2);
		}
	}
	//順序存儲二叉樹的前序遍歷
	public void InOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的中序遍歷");
		}
		
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			InOrder(2 * index + 1 );
		}
		//輸出當前這個元素
		if(arr[index]!=48) {
			System.out.println(arr[index]); 
		}
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			InOrder(2 * index + 2);
		}
	}
	//順序存儲二叉樹的前序遍歷
	public void PostOrder(int index) {
		//如果數組爲空,或者 arr.length = 0
		if(arr == null || arr.length == 0) {
			System.out.println("數組爲空,不能按照二叉樹的後序遍歷");
		}
		
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length) {
			PostOrder(2 * index + 1 );
		}
		
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			PostOrder(2 * index + 2);
		}
		//輸出當前這個元素
		if(arr[index]!=48) {
			System.out.println(arr[index]); 
		}
	}
	
}

遍歷二叉樹

二叉樹的三種遍歷方式

創建二叉樹(基本操作)

代碼如下:

public class BinaryTreeTest {

	public static void main(String[] args) {
		BinaryTree binaryTree = new BinaryTree();
		Student student1 = new Student(1, "趙一",20,"1801班");
		Student student2 = new Student(2, "錢二",20,"1802班");
		Student student3 = new Student(3, "張三",20,"1803班");
		Student student4 = new Student(4, "李四",20,"1804班");
		Student student5 = new Student(5, "王五",20,"1805班");
		Student student6 = new Student(6, "孫六",20,"1806班");
		Student student7 = new Student(7, "李七",20,"1807班");
		//創建需要的結點
		TreeNode root = new TreeNode(student1);
		TreeNode node2 = new TreeNode(student2);
		TreeNode node3 = new TreeNode(student3);
		TreeNode node4 = new TreeNode(student4);
		TreeNode node5 = new TreeNode(student5);
		TreeNode node6 = new TreeNode(student6);
		TreeNode node7 = new TreeNode(student7);
		
		//手動創建該二叉樹
		root.setLeftNode(node2);
		root.setRightNode(node3);
		node2.setLeftNode(node4);
		node2.setRightNode(node5);
		node3.setRightNode(node7);
		node3.setLeftNode(node6);
		binaryTree.setRootNode(root);
		
		//測試
		System.out.println("前序遍歷"); 
		binaryTree.preOrder();
		System.out.println("中序遍歷"); 
		binaryTree.InOrder();
		System.out.println("後序遍歷"); 
		binaryTree.PostOrder();
		System.out.println("刪除結點");
		binaryTree.delNode(2);
		System.out.println("前序遍歷"); 
		binaryTree.preOrder();
		System.out.println("前序查找");
		System.out.println(binaryTree.preOrderSearch(3));
	}

}
class BinaryTree{
	private TreeNode rootNode;

	public TreeNode getRootNode() {
		return rootNode;
	}

	public void setRootNode(TreeNode rootNode) {
		this.rootNode = rootNode;
	}
	//刪除結點
	public void delNode(int id) {
		if(rootNode != null) {
			if(rootNode.getStudent().getId() == id) {
				rootNode = null;
			} else {
				rootNode.delNode(id);
			}
		}else{
			System.out.println("空樹");
		}
	}
	//前序遍歷
	public void preOrder() {
		if(this.rootNode != null) {
				this.rootNode.PreOrder();
		}else {
				System.out.println("二叉樹爲空");
		}
	}

	//中序遍歷
	public void InOrder() {
		if(this.rootNode != null) {
			this.rootNode.InOrder();
		}else {
			System.out.println("二叉樹爲空");
		}
	}
	//後序遍歷
	public void PostOrder() {
		if(this.rootNode != null) {
			this.rootNode.PostOrder();
		}else {
			System.out.println("二叉樹爲空");
		}
	}	
	//前序查找
	public TreeNode preOrderSearch(int id) {
		if(rootNode != null) {
			return rootNode.preOrderSearch(id);
		} else {
			return null;
		}
	}
}
/**
 * 學生實體類
 */
class Student{
	private int id;
	private String name;
	private int age;
	private String stu_class;
	public Student(int id,String name,int age,String stu_class) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.stu_class = stu_class;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getStu_class() {
		return stu_class;
	}
	public void setStu_class(String stu_class) {
		this.stu_class = stu_class;
	}
	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", age=" + age + ", stu_class=" + stu_class + "]";
	}
	
}
//二叉樹節點
class TreeNode{
	//數據域
	private Student student;
	//左孩子
	private TreeNode leftNode;
	//右孩子
	private TreeNode rightNode;
	
	public TreeNode(Student student) {
		this.student = student;
	}
	
	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	public TreeNode getLeftNode() {
		return leftNode;
	}

	public void setLeftNode(TreeNode leftNode) {
		this.leftNode = leftNode;
	}

	public TreeNode getRightNode() {
		return rightNode;
	}

	public void setRightNode(TreeNode rightNode) {
		this.rightNode = rightNode;
	}

	@Override
	public String toString() {
		return "TreeNode [student=" + student + ", leftNode=" + leftNode + ", rightNode=" + rightNode + "]";
	}
	//遞歸刪除結點
	//1.如果刪除的節點是葉子節點,則刪除該節點
	//2.如果刪除的節點是非葉子節點,則刪除該子樹
	public void delNode(int id) {
			
		//2. 如果當前結點的左子結點不爲空,並且左子結點 就是要刪除結點,就將this.left = null; 並且就返回(結束遞歸刪除)
		if(this.leftNode != null && this.leftNode.student.getId() == id) {
			this.leftNode = null;
			return;
		}
			//3.如果當前結點的右子結點不爲空,並且右子結點 就是要刪除結點,就將this.right= null ;並且就返回(結束遞歸刪除)
		if(this.rightNode != null && this.rightNode.student.getId() == id) {
			this.rightNode = null;
			return;
		}
		//4.我們就需要向左子樹進行遞歸刪除
		if(this.leftNode != null) {
			this.leftNode.delNode(id);
		}
		//5.則應當向右子樹進行遞歸刪除
		if(this.rightNode != null) {
			this.rightNode.delNode(id);
		}
	}	
	//前序遍歷 根左右
	public void PreOrder() {
		System.out.println(this);
		if(this.leftNode!=null) {
			this.leftNode.PreOrder();
		}
		if(this.rightNode!=null) {
			this.rightNode.PreOrder();
		}
	}
	//中序遍歷 左根右
	public void InOrder() {
		
		if(this.leftNode!=null) {
			this.leftNode.InOrder();
		}
		System.out.println(this);
		if(this.rightNode!=null) {
			this.rightNode.InOrder();
		}
	}
	//後序遍歷 左右根
	public void PostOrder() {
			
		if(this.leftNode!=null) {
			this.leftNode.PostOrder();
		}
			
		if(this.rightNode!=null) {
			this.rightNode.PostOrder();
		}
		System.out.println(this);
	}
	//前序遍歷查找
	public TreeNode preOrderSearch(int id) {
		if(this.student.getId() == id) {
			return this;
		}
			//1.則判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸前序查找
			//2.如果左遞歸前序查找,找到結點,則返回
		TreeNode resNode = null;
		if(this.leftNode != null) {
			resNode = this.leftNode.preOrderSearch(id);
		}
		if(resNode != null) {//說明我們左子樹找到
			return resNode;
		}
		//1.左遞歸前序查找,找到結點,則返回,否繼續判斷,
		//2.當前的結點的右子節點是否爲空,如果不空,則繼續向右遞歸前序查找
		if(this.rightNode != null) {
			resNode = this.rightNode.preOrderSearch(id);
		}
		return resNode;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章