**首先來了解一下樹的一些術語:
(圖片來源:中國大學mooc —— 浙大數據結構)
二叉樹定義:
1、每個結點最多有兩顆子樹,結點的度最大爲2。
2、左子樹和右子樹是有順序的,次序不能顛倒。
3、即使某結點只有一個子樹,也要區分左右子樹。
二叉樹的幾種狀態:
特殊二叉樹:
下面放一張錯誤的完全二叉樹
一些公式做筆試題用的:
二叉樹介紹完了,下面介紹一下二叉搜索樹,此案例代碼展示的也是搜索樹二叉搜索樹其實就是在二叉樹的基礎上加個條件:
左節點的數比根節點的數小,右邊的數要比更節點數大
案例中提供的方法以及實現原理:
find:(就是在遍歷過程中加個判斷)
反覆與當前根節點比較,比起小向左找,比起大向右找
找到就返回,找不到(==null)返回false
findMax 最大的節點是樹的最右葉子結點
findMin 最小的節點是樹的最左葉子結點
insert:
如果是根節點直接插
不是就先判斷,比跟小往左,比跟大往右,然後不斷循環,直到葉子結點,在比較,選擇插到葉點的左還是右
delete
* 二叉樹的刪除要考慮4種情況:還要判斷要刪除的變量是parent的左還右
*
* 1.被刪除的節點是葉子結點 :直接賦爲null
* 2.被刪除的節點只有左子節點:左子節點上來
* 3.被刪除的節點只有右子節點:右子節點上來
* 4.被刪除的結點有兩個節點:取其右子節點的左葉子節點(最後一個)上來(也就是右子樹中的最小元素)
* (如果其右子節點是葉子結點或者沒有左子節點,直接將右子節點提上來)
display 遍歷
先序遍歷:(圖中的數字代表打印順序)
先序遍歷是先訪問根結點,再左子樹,再右子樹
遞歸思想:遍歷到當前元素先打印,然後走向左右子節點
*if ( current != null)
{
print
left
right
}*
數字是打印順序
以上圖爲例:一路打印到左葉子結點(A -> B ->D),下面沒有啦,D轉向右,右也沒有,函數返回,到上一個節點B,發現又右節點F(b.right ->F),打印,再left到E,打印,E沒有右,函數返回到F,F沒有右,再返回到B,B的right執行完畢,返回到A,A的left執行完,執行right,打印,left。。。
非遞歸:和中序一樣,就是打印順序不同
中序遍歷和
有了先序的經驗,這兩個就很好理解了,中序是先訪問左子樹, 再根結點,再右子樹,
遞歸思想:先left一直到葉子結點,然後print,print完,然後right,沒有右就返回上一節點,右就重複上面操作,
if (current != null )
{
left
print
right
}
非遞歸
後序遍歷
後序是先訪問左子樹, 再右子樹,再根結點。
其實前中後的遞歸遍歷思想基本一樣,就是print的時機不同而已
if ( current != null )
{
left
right
print
}
非遞歸:不會=_=
層次遍歷:即每一層從左向右輸出
元素需要儲存有先進先出的特性,所以選用隊列存儲。
思想:先節點入隊,然後出隊打印,將左右節點入隊
下面是代碼:
/*
* 二叉搜索樹
*
* 左節點的數比根節點的數小,右邊的數要比更節點數大
*/
public class binaryTree
{
private Node root ; //根節點
private Node current ;//記錄當前遍歷到哪個節點了
private Node parent ;//記錄遍歷節點的上一個節點
public binaryTree ()
{
root = null ;
current = null ;
}
/*
* 按照規則:左邊的數比根節點小,右邊的數比根節點大
* 先進行反覆比較,一直到最後和葉子節點比較,選擇插在左還是右
*/
public void insert ( int data )
{
Node newNode = new Node ( data ) ;
//如果根節點爲空,直接將新節點付給根節點
if ( root == null )
{
root = newNode ;
return ;
}
current = root ;
while ( current != null )
{
parent = current ;
//左子節點通常比根節點小,右節點大
//新數據比當前節點數據小,往左
if ( current.data > data )
{
current = current.left ;
//爲空代表到底了
if ( current == null )
{
parent.left = newNode ;
}
}
//比他大,往右
else
{
current = current.right ;
if ( current == null )
{
parent.right = newNode ;
}
}
}
}
public Node find ( int data )
{
current = root ;
if ( root != null )
while ( current != null )
{
//要查找的數據比當前節點數據小,往左
if ( current.data > data )
{
current = current.left ;
}
//往右
else if ( current.data < data )
{
current = current.right ;
}
//既不大又不小,那就是=嘍,返回此節點
else
{
return current ;
}
}
else
{
System.out.println ( "樹爲空" );
return null ;
}
//執行到這,說明current已爲null , 沒找到
return null ;
}
//找最大,通常情況下最大值在樹的最右分支上
public Node findMax ()
{
current = root ;
parent = root ;
while ( current != null )
{
parent = current ;
current = current.right ;
}
return parent ;
}
public Node findMin ()
{
current = root ;
parent = root ;
while ( current != null )
{
parent = current ;
current = current.left ;
}
return parent ;
}
/*
*
* 二叉樹的刪除要考慮4種情況:
*
* 1.被刪除的節點是葉子結點 :直接賦爲null
* 2.被刪除的節點只有左子節點:左子節點上來
* 3.被刪除的節點只有右子節點:右子節點上來
* 4.被刪除的結點有兩個節點:取其右子節點的左葉子節點(最後一個)上來
* (如果其右子節點是葉子結點或者沒有左子節點,直接將右子節點提上來)
*/
public boolean delete ( int data )
{
current = root ;
parent = root ;
boolean isLeft = true ;//用來判斷當前的節點是否是左節點(根據此變量來更改parent對下一個結點引用)
while ( current.data != data )
{
parent = current ;
//爲空代表沒找到
if ( current == null )
return false ;
//往左
if ( current.data > data )
{
current = current.left ;
isLeft = true ;
}
//往右
else
{
current = current.right ;
isLeft = false ;
}
}
//跳出循環代表找到要刪除的數據
//1.判斷是否是第一種情況
if ( current.left == null && current.right == null )
{
//判斷是不是根節點
if ( current == root )
{
root = null ;
return true ;
}
else
{
//isLeft是相對於parent而言,判斷current是parent的左右節點,依次來改變parent中的引用
if ( isLeft == true )
{
parent.left = null ;
}
else
{
parent.right = null ;
}
current = null ;
return true ;
}
}
//2.判斷是否是第二種
if ( current.left != null && current.right == null )
{
if ( current == root )
{
root = current.left ;
return true ;
}
else
{
if ( isLeft == true )
{
parent.left = current.left ;
}
else
{
parent.right = current.left ;
}
return true ;
}
}
//3.判斷是否爲第三種
if ( current.left == null && current.right != null )
{
if ( current == root )
{
root = current.right ;
return true ;
}
else
{
if ( isLeft == true )
{
parent.left = current.left ;
}
else
{
parent.right = current.left ;
}
return true ;
}
}
//4 。第四種 (最麻煩的一種)
if ( current.left != null && current.right != null )
{
//parent 是current的父節點
//current:要刪除的節點
//sonp:用來替換節點的父節點
//son用來遍歷,遍歷到最後變成用來替換的節點
Node son = current.right ;
Node sonp = current.right;
boolean isNull = false ;//用於判斷要刪除的節點的右子節點是否左子節點
if ( son.left == null )
{
isNull = true ;
}
//如果條件成立,代表此時要刪除的節點的右子節點沒有左子節點
if ( isNull == true )
{
//判斷current是父節點的左還是右節點
if ( isLeft == true )
{
parent.left = son ;
return true ;
}
else
{
parent.right = son ;
return true ;
}
}
//執行到這說明有左子節點,找到右節點的葉左葉子結點(也叫中序後繼節點)
while ( son.left != null )
{
sonp = son ;
son = son.left ;
}
//循環結束,代表找到了此時son就是最後的節點
sonp.left = null ;//抹除son父節點對其的引用
son.right = current.right ;//將要刪除的節點的右引用轉移到son身上來
son.left = current.left ;
if ( current == root )
{
root = son ;
return true ;
}
else
{
if ( isLeft == true )
{
parent.left = son ;
}
else
{
parent.right = son ;
}
return true ;
}
}
//如果真能執行到這說明上面代碼都不符合只能false了
return false ;
}
public void display ()
{
//
// System.out.println ( "先序遍歷" );
// frontdisplay (root) ;
// System.out.println ( );
// front (root) ;
// System.out.println ( "中序遍歷" );
// middisplay (root) ;
// mid(root) ;
// System.out.println ( "後序遍歷" );
// lastdisplay (root) ;
// System.out.println ( );
// last ( root ) ;
levelDisplay () ;
}
//使用遞歸遍歷
public void frontdisplay ( Node next )
{
//先序遍歷(根,左,右)
if ( next != null )
{
System.out.print ( next.data + " ");
frontdisplay ( next.left ) ;
frontdisplay ( next.right ) ;
}
}
//中序:左根右
public void middisplay ( Node next )
{
if ( next != null )
{
middisplay ( next.left ) ;
System.out.print ( next.data + " " );
middisplay ( next.right ) ;
}
}
//後序:左右根
public void lastdisplay ( Node next )
{
if ( next != null )
{
lastdisplay ( next.left ) ;
lastdisplay ( next.right ) ;
System.out.print ( next.data + " " );
}
}
public void levelDisplay ()
{
Node current = root ;
queue q = new queue () ;
q.add ( current );
while ( !q.isEmpty () )
{
current = q.delete () ;
System.out.print ( current.data + " " );
if ( current.left != null )
{
q.add ( current.left );
}
if ( current.right != null )
{
q.add ( current.right );
}
}
System.out.println ( );
}
// ------------------------------------
//非遞歸遍歷
//中序 左 根 右
public void mid ( Node n )
{
Stack s = new Stack () ;
while ( n != null || s.isEmpty () != true )
{
while ( n != null )
{
s.push ( n );
n = n.left ;
}
//跳出循環代表遍歷左子節點完畢,左子節點都入棧了
if ( s.isEmpty () != true )
{
n = s.pop () ;
System.out.print ( n.data + " ");
n = n.right ;
}
}
}
//先序
public void front ( Node n )
{
Stack s = new Stack () ;
while ( n != null || s.isEmpty () != true )
{
while ( n != null )
{
System.out.print ( n.data + " ");
s.push ( n );
n = n.left ;
}
if ( s.isEmpty () != true )
{
n = s.pop () ;
n = n.right ;
}
}
}
//後序(沒寫出來)
public void last ( Node n )
{
}
}
stack
public class Stack
{
/*
* 棧:先進後出
*/
public Node top ;
public void push ( Node n )
{
if ( top == null )
{
top = n ;
return ;
}
n.next = top ;
top = n ;
}
public Node pop ()
{
Node temp = top ;
if ( top == null )
{
System.out.println ( "stack is empty" );
return null ;
}
top = top.next ;
return temp ;
}
public boolean isEmpty ()
{
return top == null ;
}
}
queue
**
public class queue
{
public Node front ;
public Node rear ;
public queue ()
{
front = null ;
rear = null ;
}
public void add ( Node n )
{
if ( isEmpty () )
{
front = n ;
rear = n ;
return ;
}
rear.next = n ;
rear = n;
}
public Node delete ()
{
if ( isEmpty () )
{
return null ;
}
Node temp = front ;
front = front.next ;
return temp ;
}
public boolean isEmpty ()
{
return front == null ;
}
}