1. 程式人生 > >資料結構(Java筆記)—樹(二叉樹)

資料結構(Java筆記)—樹(二叉樹)

樹(Tree)結構是一種描述非線性層次關係的的資料結構,樹中有一個根結點,根節點下分佈著一些互不交叉的子集合(子樹)

  • 在一個樹結構中,有且只有一個根結點,根結點沒有直接前驅
  • 每個結點有且只有一個直接前驅;
  • 每個結點可以有多個直接後繼

二叉樹:

在樹結構中,二叉樹是最簡單的一種形式,對樹結構的研究主要是二叉樹,二叉樹最多隻有兩個子結點,即左子樹右子樹;正因有左右之分,所以二叉樹是有序樹;二叉樹可分為滿二叉樹完全二叉樹

在資料結構中,完全二叉樹是主要的研究物件,如果二叉樹中包含n個結點,假設這些結點都按順序儲存,那麼,對於任意一個結點來說,具有如下性質:

  • 如果m!=1,則結點m的父節點編號為m/2;
  • 如果2*m<=n,則結點m的左子樹根結點的編號為2*m;若2*m>n,則無左子樹;
  • 如果2*m+1<=n,則結點m的右子樹根結點的編號為2*m+1;若2*m+1>n,則無右子樹;

上述性質只針對於二叉樹的順序結構,但順序儲存結構的缺點是浪費儲存空間,只適合滿二叉樹的儲存;對大部分二叉樹來講,鏈式儲存結構才是最適合的;

二叉樹的鏈式儲存:與線性結構的鏈式儲存類似,二叉樹的鏈式儲存結構包括結點元素以及分別指向左子樹和右子樹的引用,當然,為了計算方便,也可以儲存一個該節點的父節點的引用

二叉樹的鏈式結構設計步驟為:

  1. 定義二叉樹鏈式儲存結構(BTType)
  2. 初始化樹結構(btInit)
  3. 計算樹的深度(btDepth)
  4. 新增結點(查詢父節點——>新增至左(右)結點)(btAddNode)
  5. 查詢結點(btFindNode)
  6. 清空結點(btClearNode)
  7. 遍歷二叉樹(btClearNode)

程式碼實現:

一,定義結點

public class Data {//定義結點
	String name;
	int age;
}

二,二叉樹鏈式結構實現

public class BTree {
	BTType rootNode;// 定義全域性頭引用

	class BTType {// 定義二叉樹鏈式結構
		Data data = new Data();
		BTType leftNode;
		BTType rightNode;
		BTType fatherNode;
	}

	void btInit(Scanner input) {// 初始化樹結構,因為之後的方法用到了遞迴,所以根節點必須在初始化結構的時候就給出
		if ((rootNode = new BTType()) != null) {
			System.out.println("輸入根節點:姓名:");
			String name=input.next();
			System.out.println("輸入根節點:年齡:");
			int age=input.nextInt();
			Data d=new Data();
			d.name=name;
			d.age=age;
			rootNode.data = d;
			rootNode.leftNode = null;
			rootNode.rightNode = null;
			rootNode.fatherNode = null;
		}
	}

	int btDepth(BTType root) {// 計算樹深度,因為用到了遞迴,所以必須將根結點以引數傳入
		int leftdep;
		int rightdep;
		if(root==null){//如果沒有根節點,深度為零
			return 0;
		}else{
			leftdep=btDepth(root.leftNode);//遞迴計算左子樹的深度(最後一個結點返回0)
			rightdep=btDepth(root.rightNode);//遞迴計算右子樹的深度(最後一個結點返回0)
			if(leftdep>rightdep){//當遞迴完成後,判斷左右子樹深度大小
				return leftdep+1;
			}else{
				return rightdep+1;
			}
		}
	}

	int btAddNode(Data d, Scanner input) {// 新增結點
		BTType btemp, ftemp;//分別儲存新結點和父結點
		boolean nodeisfull = false;//定義父節點的左右結點是否為空
		int i;//用於儲存使用者輸入的子節點序號(1(新增到左子樹),2(新增到右子樹))
		
		if (rootNode.data == null) {
			rootNode.data = d;
			System.out.print("資料已儲存到根節點!");
			return 1;
		}
		// 儲存結點資料
		if ((btemp = new BTType()) != null) {
			btemp.data = d;
			btemp.leftNode = null;
			btemp.rightNode = null;
		}
		// 查詢父節點
		do {
			System.out.println("請輸入父節點:");
			String name = input.next();
			Data df = new Data();
			df.name = name;
			ftemp = btFindNode(rootNode, df);//呼叫了查詢結點的方法
			if (ftemp == null) {
				System.out.println("父節點不存在!");
				nodeisfull=true;//父節點不存在,設定nodeisfull=true,保證使用者可以重新輸入父節點
			}else{
			if (ftemp.leftNode != null && ftemp.rightNode != null) {
				System.out.print("子節點已滿!");
				nodeisfull = true;//子節點已滿,設定nodeisfull=true,保證使用者可以重新輸入父節點
			}else{
				nodeisfull=false;//父節點存在且左右子結點未滿,設定nodeisfull=false,退出循壞進行下一步
			}
			}
		} while (nodeisfull);
		// 確定目標子樹是否為空
		do {
			System.out.println("1(新增到左子樹),2(新增到右子樹)");
			i = input.nextInt();
			switch (i) {
			case 1:
				if (ftemp.leftNode != null) {
					System.out.println("左結點不為空");
					break;
				} else {
					ftemp.leftNode = btemp;// 左結點為空,新增資料到左結點
					return 1;//退出do...while迴圈
				}

			case 2:
				if (ftemp.rightNode != null) {
					System.out.println("右結點不為空");
					break;
				} else {
					ftemp.rightNode = btemp;// 右結點為空,新增資料到右結點
					return 1;//退出do...while迴圈
				}
			default:
				System.out.println("輸入序號不正確");
			}
		} while (i != 1 || i != 2);
		return 0;
	}

	BTType btFindNode(BTType root, Data d) {// 查詢結點,因為用到了遞迴,所以必須將根結點以引數傳入
		BTType btemp;//用於儲存每次遞迴查詢的臨時結點
		if (root== null) {//根結點為空,查詢失敗
			return null;
		} else {
			if (root.data.name.equals(d.name)) {
				return root;
			} else {
				if ((btemp = btFindNode(root.leftNode, d)) != null) {
					return btemp;
				}//當遞迴到最後一個root.leftNode返回null時,還未找到資料,遞迴停止,進行下一步
				if ((btemp = btFindNode(root.rightNode, d)) != null) {
					return btemp;
				}
			}
		}
		return null;
	}

	int btClearNode(BTType root) {// 清空結點,因為用到了遞迴,所以必須將根結點以引數傳入
		if(root!=null){
			btClearNode(root.leftNode);//先遞迴至葉子結點,再依次清空
			btClearNode(root.rightNode);
			root=null;
		}
		return 1;
	}

	void btGetAllNode(BTType root) {// 遍歷二叉樹,因為用到了遞迴,所以必須將根結點以引數傳入
		if(root!=null){//先序遍歷
			System.out.println("姓名:"+root.data.name);
			btGetAllNode(root.leftNode);
			btGetAllNode(root.rightNode);
			
		}
	}
}

三,執行測試

public static void main(String[] args) {

		Scanner in = new Scanner(System.in);
		BTree dt = new BTree();
		dt.btInit(in);
		while (true) {
			System.out.println("請輸入:0(退出),1(新增),2(清空),3(查詢),4(樹深度),5(遍歷)");
			int i = in.nextInt();
			switch (i) {
			case 0:
				return;
			case 1:
				Data d = new Data();
				System.out.print("新增姓名:");
				String name = in.next();
				System.out.print("新增年齡:");
				int age = in.nextInt();
				if (name == null || age == 0) {
					System.out.println("資訊不能為空");
					break;
				}
				d.age = age;
				d.name = name;
				int temp1 = dt.btAddNode(d,in);
				if (temp1 == 0) {
					System.out.println("新增失敗");
					break;
				}
				System.out.println("新增成功");
				break;
			case 2:
				dt.btClearNode(dt.rootNode);
				System.out.println("清空成功");
				break;
			case 3:
				System.out.println("你要查詢的結點:");
				String name3 = in.next();
				
				if (name3 == null) {
					System.out.println("資訊不能為空");
					break;
				}
				Data d3 = new Data();
				d3.name=name3;
				BTType btt= dt.btFindNode(dt.rootNode, d3);
				if(btt!=null){
				System.out.println("姓名:"+btt.data.name+"|"+"年齡:"+btt.data.age);
				}else{
					System.out.println("查詢失敗");
				}
				break;
			case 4:
				System.out.println("樹的深度:"+dt.btDepth(dt.rootNode));
				break;
			case 5:
				dt.btGetAllNode(dt.rootNode);
				break;
			default:
				break;
			}
		}
	}