在分析樹形結構以前,先看一下樹形結構在整個數據結構中的位置java
固然,沒有圖,如今尚未足夠的水平去分析圖這種太複雜的數據結構,表數據結構包含線性表跟鏈表,也就是常常說的數組,棧,隊列等,在前面的Java容器類框架中已經分析過了,上面任意一種數據結構在java中都有對應的實現,好比說ArrayList對應數組,LinkedList對應雙向鏈表,HashMap對應了散列表,JDK1.8以後的HashMap採用的是紅黑樹實現,下面單獨把二叉樹抽取出來:node
因爲樹的基本概念基本上你們都有所瞭解,如今重點介紹一下二叉樹算法
二叉樹(binary tree)是一棵樹,其中每一個節點都不能有多於兩個的兒子。
數組
對於徹底二叉樹,若某個節點數爲i,左子節點位2i+1,右子節點爲2i+2。bash
下面用代碼實現一個二叉樹數據結構
public class BinaryNode {
Object data;
BinaryNode left;
BinaryNode right;
}複製代碼
二叉樹的遍歷有兩種:按照節點遍歷與層次遍歷框架
構造徹底二叉樹
dom
就以上圖的二叉樹爲例,首先用代碼構造一棵徹底二叉樹
節點類TreeNode函數
public class TreeNode {
private int value;
private TreeNode leftChild;
private TreeNode rightChild;
public TreeNode(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public TreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(TreeNode leftChild) {
this.leftChild = leftChild;
}
public TreeNode getRightChild() {
return rightChild;
}
public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
}
}複製代碼
生成徹底二叉樹post
//數組
private int[] saveValue = {0,1, 2, 3, 4, 5, 6};
private ArrayList<TreeNode> list = new ArrayList<>();
public TreeNode createTree() {
//將全部的節點保存進一個List集合裏面
for (int i = 0; i < saveValue.length; i++) {
TreeNode treeNode = new TreeNode(saveValue[i]);
list.add(treeNode);
}
//根據徹底二叉樹的性質,i節點的左子樹是2*i+1,右節點字數是2*i+2
for (int i = 0; i < list.size() / 2; i++) {
TreeNode treeNode = list.get(i);
//判斷左子樹是否越界
if (i * 2 + 1 < list.size()) {
TreeNode leftTreeNode = list.get(i * 2 + 1);
treeNode.setLeftChild(leftTreeNode);
}
//判斷右子樹是否越界
if (i * 2 + 2 < list.size()) {
TreeNode rightTreeNode = list.get(i * 2 + 2);
treeNode.setRightChild(rightTreeNode);
}
}
return list.get(0);
}複製代碼
前序遍歷
//前序遍歷
public static void preOrder(TreeNode root) {
if (root == null)
return;
System.out.print(root.value + " ");
preOrder(root.left);
preOrder(root.right);
}複製代碼
遍歷結果: 0 1 3 4 2 5 6
中序遍歷
//中序遍歷
public static void midOrder(TreeNode root) {
if (root == null)
return;
midOrder(root.left);
System.out.print(root.value + " ");
midOrder(root.right);
}複製代碼
遍歷結果:3 1 4 0 5 2 6
後序遍歷
//後序遍歷
public static void postOrder(TreeNode root) {
if (root == null)
return;
postOrder(root.left);
postOrder(root.right);
System.out.print(root.value + " ");
}複製代碼
遍歷結果:3 4 1 5 6 2 0
上述三種方式都是採用遞歸的方式進行遍歷的,固然也能夠迭代,感受迭代比較麻煩,遞歸代碼比較簡潔,仔細觀察能夠發現,實際上三種遍歷方式都是同樣的,知識打印value的順序不同而已,是一種比較巧妙的方式。
二叉樹的層次遍歷能夠分爲深度優先遍歷跟廣度優先遍歷
深度優先遍歷
因爲上面的前序、中序和後續遍歷都是採用的遞歸的方式進行遍歷,下面就之前序遍歷爲例,採用非遞歸的方式進行遍歷
步驟
而後遍歷節點的時候先讓右子樹入棧,再讓左子樹入棧,這樣右子樹就會比左子樹後出棧,從而實現先序遍歷
//2.前序遍歷(迭代)
public static void preorderTraversal(TreeNode root) {
if(root == null){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while( !stack.isEmpty() ){
TreeNode cur = stack.pop(); // 出棧棧頂元素
System.out.print(cur.data + " ");
//先壓右子樹入棧
if(cur.right != null){
stack.push(cur.right);
}
//再壓左子樹入棧
if(cur.left != null){
stack.push(cur.left);
}
}
}複製代碼
遍歷結果:0 1 3 4 2 5 6
廣度優先遍歷
所謂廣度優先遍歷就是一層一層的遍歷,因此只須要按照每層的左右順序拿到二叉樹的節點,再依次輸出就OK了
步驟
//分層遍歷
public static void levelOrder(TreeNode root) {
if (root == null) {
return;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.push(root);
while (!queue.isEmpty()) {
//打印linkedList的第一次元素
TreeNode cur = queue.removeFirst();
System.out.print(cur.value + " ");
//依次添加每一個節點的左節點
if (cur.left != null) {
queue.add(cur.left);
}
//依次添加每一個節點的右節點
if (cur.right != null) {
queue.add(cur.right);
}
}
}複製代碼
遍歷結果:0 1 2 3 4 5 6 二叉堆是一棵徹底二叉樹或者是近似徹底二叉樹,同時二叉堆還知足堆的特性:父節點的鍵值老是保持固定的序關係於任何一個子節點的鍵值,且每一個節點的左子樹和右子樹都是一個二叉堆。
因爲二叉堆 的根節點老是存放着最大或者最小元素,因此常常被用來構造優先隊列(Priority Queue),當你須要找到隊列中最高優先級或者最低優先級的元素時,使用堆結構能夠幫助你快速的定位元素。
二叉堆能夠用數組實現也能夠用鏈表實現,觀察上述的徹底二叉樹能夠發現,是比較有規律的,因此徹底能夠使用一個數組而不須要使用鏈。下面用數組來表示上圖所對應的堆結。
對於數組中任意位置i的元素,其左兒子在位置2i上,右兒子在左兒子後的單元(2i+1)中,它的父親則在位置[i/2上面]
public class MaxHeap<Item extends Comparable> {
protected Item[] data;
protected int count;
protected int capacity;
// 構造函數, 構造一個空堆, 可容納capacity個元素
public MaxHeap(int capacity) {
data = (Item[]) new Comparable[capacity + 1];
count = 0;
this.capacity = capacity;
}
// 返回堆中的元素個數
public int size() {
return count;
}
// 返回一個布爾值, 表示堆中是否爲空
public boolean isEmpty() {
return count == 0;
}
// 像最大堆中插入一個新的元素 item
public void insert(Item item) {
assert count + 1 <= capacity;
data[count + 1] = item;
count++;
shiftUp(count);
}
// 交換堆中索引爲i和j的兩個元素
private void swap(int i, int j) {
Item t = data[i];
data[i] = data[j];
data[j] = t;
}
//調整堆中的元素,使其成爲一個最大堆
private void shiftUp(int k) {
// k/2表示k節點的父節點
while (k > 1 && data[k / 2].compareTo(data[k]) < 0) {
//子節點跟父節點進行比較,若是父節點小於子節點則進行交換
swap(k, k / 2);
k /= 2;
}
}
}複製代碼
// 測試 MaxHeap
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<>(100);
int N = 50; // 堆中元素個數
int M = 100; // 堆中元素取值範圍[0, M)
for (int i = 0; i < N; i++)
maxHeap.insert((int) (Math.random() * M));
System.out.println(maxHeap.size());
}複製代碼
public void insert(Item item) {
//從第1個元素開始賦值
assert count + 1 <= capacity;
data[count + 1] = item;
count++;
//上慮操做
shiftUp(count);
}複製代碼
先將新插入的元素加到數組尾部,而後跟父節點進行比較,若是比父節點大就進行交換
private void shiftUp(int k) {
// k/2表示k節點的父節點
while (k > 1 && data[k / 2].compareTo(data[k]) < 0) {
//子節點跟父節點進行比較,若是父節點小於子節點則進行交換
swap(k, k / 2);
k /= 2;
}
}複製代碼
public Integer extractMax(){
assert count > 0;
Integer ret = data[1];
swap( 1 , count );
count --;
//下慮操做
shiftDown(1);
return ret;
}複製代碼
先將根節點跟最後一個元素進行交換,而後將count減1,而後根節點再跟左子樹與右子樹之間的最大值進行比較
private void shiftDown(int k){
while( 2*k <= count ){
int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if( data[k].compareTo(data[j]) >= 0 )
//當前節點比根節點大,中斷循環
break;
//交換節點跟子樹
swap(k, j);
k = j;
}
}複製代碼
二叉查找樹:對於樹中的每一個節點X,它的左子樹中全部項的值小於X中的項,而它的右子樹中的全部項的值大於X中的項。
public class BST<Key extends Comparable<Key>, Value> {
private class Node {
private Key key;
private Value value;
private Node left, right;
public Node(Key key, Value value) {
this.key = key;
this.value = value;
left = right = null;
}
public Node(Node node){
this.key = node.key;
this.value = node.value;
this.left = node.left;
this.right = node.right;
}
}
// 構造函數, 默認構造一棵空二分搜索樹
public BST() {
root = null;
count = 0;
}
// 返回二分搜索樹的節點個數
public int size() {
return count;
}
// 返回二分搜索樹是否爲空
public boolean isEmpty() {
return count == 0;
}
}複製代碼
// 調用contains (root,key)
public boolean contain(Key key){
return contain(root, key);
}
// 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
private boolean contain(Node node, Key key){
if( node == null )
return false;
//根節點的key跟查詢的key相同,直接返回true
if( key.compareTo(node.key) == 0 )
return true;
//key小的話遍歷左子樹
else if( key.compareTo(node.key) < 0 )
return contain( node.left , key );
//key大的話遍歷右子樹
else // key > node->key
return contain( node.right , key );
}複製代碼
// 向二分搜索樹中插入一個新的(key, value)數據對
public void insert(Key key, Value value){
root = insert(root, key, value);
}
// 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
// 返回插入新節點後的二分搜索樹的根
private Node insert(Node node, Key key, Value value){
//空樹直接返回
if( node == null ){
count ++;
return new Node(key, value);
}
//若是須要插入的key跟當前的key相同,則將值替換成最新的值
if( key.compareTo(node.key) == 0 )
node.value = value;
//若是須要插入的key小於當前的key,從左子樹進行插入
else if( key.compareTo(node.key) < 0 )
node.left = insert( node.left , key, value);
else
//若是須要插入的key大於當前的key,從右子樹進行插入
node.right = insert( node.right, key, value);
return node;
}複製代碼
// 尋找二分搜索樹的最小的鍵值
public Key minimum(){
assert count != 0;
Node minNode = minimum( root );
return minNode.key;
}
// 返回以node爲根的二分搜索樹的最小鍵值所在的節點
private Node minimum(Node node){
//遞歸遍歷左子樹
if( node.left == null )
return node;
return minimum(node.left);
}複製代碼
// 尋找二分搜索樹的最大的鍵值
public Key maximum(){
assert count != 0;
Node maxNode = maximum(root);
return maxNode.key;
}
// 返回以node爲根的二分搜索樹的最大鍵值所在的節點
private Node maximum(Node node){
//遞歸遍歷右子樹
if( node.right == null )
return node;
return maximum(node.right);
}複製代碼
// 在二分搜索樹中搜索鍵key所對應的值。若是這個值不存在, 則返回null
public Value search(Key key){
return search( root , key );
}
// 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
// 若value不存在, 則返回NULL
private Value search(Node node, Key key){
if( node == null )
return null;
if( key.compareTo(node.key) == 0 )
return node.value;
else if( key.compareTo(node.key) < 0 )
return search( node.left , key );
else // key > node->key
return search( node.right, key );
}複製代碼
上面分析了三種比較特殊的樹形結構,二叉樹,二叉堆以及二叉搜索樹,尤爲是二叉堆跟二叉搜索樹,尤爲是二叉堆主要用於優先隊列,二叉搜索樹主要用來查找。