數據結構之二叉樹(java語言版)
之前的都是線性結構,而樹結構在計算機應用中的應用更加廣泛。linux中的目錄結構,某些數據庫的底層存儲等,都是採用樹結構進行構架的。
樹的概念
線性表是一對一的關係,而樹是一對多的關係。
樹的結點:包含一個數據元素及若干指向子樹的分支;
孩子結點:結點的子樹的根稱爲該結點的孩子;
雙親結點:B 結點是A 結點的孩子,則A結點是B 結點的雙親;
兄弟結點:同一雙親的孩子結點; 堂兄結點:同一層上結點;
祖先結點: 從根到該結點的所經分支上的所有結點
子孫結點:以某結點爲根的子樹中任一結點都稱爲該結點的子孫
結點層:根結點的層定義爲1;根的孩子爲第二層結點,依此類推;
樹的深度:樹中最大的結點層
結點的度:結點子樹的個數
樹的度: 樹中最大的結點度。
葉子結點:也叫終端結點,是度爲 0 的結點;
分枝結點:度不爲0的結點;
有序樹:子樹有序的樹,如:家族樹;
無序樹:不考慮子樹的順序;
二叉樹
最常用的樹結構是二叉樹。在計算機科學中,二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”和“右子樹”。
1. 二叉樹的性質
二叉樹有以下幾個性質:TODO(上標和下標)
性質1:二叉樹第i層上的結點數目最多爲 2**{i-1}** (i≥1)。
性質2:深度爲k的二叉樹至多有2{k}-1個結點(k≥1)。
性質3:包含n個結點的二叉樹的高度至少爲log2 (n+1)。
性質4:在任意一棵二叉樹中,若終端結點的個數爲n0,度爲2的結點數爲n2,則n0=n2+1。
2、二叉樹的種類
完全二叉樹
若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h層有葉子結點,並且葉子結點都是從左到右依次排布,這就是完全二叉樹。
滿二叉樹
除了葉子結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹
平衡二叉樹
它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。
3、二叉樹的遍歷方式
先序遍歷
先訪問根節點,先序遍歷左子樹,先序遍歷右子樹
中序遍歷
中序遍歷左子樹,訪問根節點,中序遍歷右子樹
後序遍歷
後序遍歷左子樹,後序遍歷右子樹,訪問根節點
層次遍歷
即按照層次訪問,通常用隊列來做。訪問根,訪問子女,再訪問子女的子女
4、二叉樹的存儲方式
1、順序存儲
用一組連續的存儲單元存放二叉樹的數據元素。結點在數組中的相對位置蘊含着結點之間的關係。
比較浪費空間,基本不用。
2、鏈式存儲
鏈式存儲結構的每個結點由數據域、左指針域和右指針域組成。左指針和右指針分別指向下一層的二叉樹。
5、實現
節點類
class Node {// 節點
String data;
Node rchild;
Node lchild;
boolean isDel;// 標識是否被刪除
public Node(String data) {// 構造方法,傳入節點數據
this.data = data;
this.isDel = false;
}
public Node() {//構造方法
}
}
建立樹
樹默認使用先序遍歷的順序進行建立。
public class BinTree {
public BinTree() {// 構造方法
}
public Node init(Node root) {// 先序遍歷的格式初始化
String data;
System.out.println("輸入節點數據:");
Scanner scanner = new Scanner(System.in);
data = scanner.next(); //輸入節點存儲的數據
if (data.equals("0")) {//設0爲null,即不存在
System.out.println("該節點爲null");
} else {
System.out.println("data: " + data);
root = new Node(data);
root.lchild = init(root.lchild);//遞歸左孩子
root.rchild = init(root.rchild);//遞歸右孩子
}
return root;
}
}
遍歷
使用遞歸進行遍歷。
public void preOrder(Node root) {// 先序遍歷
if (root == null) {//遞歸終止條件,某個節點爲null時結束
return;
}
System.out.print(" " + root.data);//遍歷根節點
preOrder(root.lchild);//遍歷左節點,直到某個左節點爲null
preOrder(root.rchild);//遍歷右節點,直到某個右節點爲null
}
public void midOrder(Node root) {// 中序遍歷
if (root == null) {
return;
}
midOrder(root.lchild);
System.out.print(" " + root.data);
midOrder(root.rchild);
}
public void lastOrder(Node root) {// 後序遍歷
if (root == null) {
return;
}
lastOrder(root.lchild);
lastOrder(root.rchild);
System.out.print(" " + root.data);
}
測試
這是一個測試類,輸入節點數據建立一個樹後,進行遍歷。
public static void main(String[] args) {
BinTree binTree=new BinTree();
Node root=binTree.init(new Node());
System.out.println("先序遍歷樹:");
binTree.preOrder(root);
System.out.println("\n中序遍歷樹:");
binTree.midOrder(root);
System.out.println("\n後序遍歷樹:");
binTree.lastOrder(root);
}
就拿這個樹作爲模型測試。
輸入0代表節點不存在,所以按照先序建立樹,輸入數據應該是:
ABC000DE00F00
輸入數據:
輸入節點數據:
A
data: A
輸入節點數據:
B
data: B
輸入節點數據:
C
data: C
輸入節點數據:
0
該節點爲null
輸入節點數據:
0
該節點爲null
輸入節點數據:
0
該節點爲null
輸入節點數據:
D
data: D
輸入節點數據:
E
data: E
輸入節點數據:
0
該節點爲null
輸入節點數據:
0
該節點爲null
輸入節點數據:
F
data: F
輸入節點數據:
0
該節點爲null
輸入節點數據:
0
該節點爲null
輸出遍歷結果:
先序遍歷樹:
A B C D E F
中序遍歷樹:
C B A E D F
後序遍歷樹:
C B E F D A
插入刪除
關於插入和刪除,由於樹不是線性的,插入的位置有很多選擇。對於以上的測試二叉樹來說增加一個節點就有很多位置可以,若是插入那麼位置就更多了。
對於數據的增加和刪除需要更多的約束,避免產生極端的樹。
加入不同的約束會產生不同的樹,如哈夫曼樹,搜索樹等。
不同的樹有不同的優勢,用於滿足需求。