【Java】 大話數據結構(11) 查找算法(2)(二叉排序樹/二叉搜索樹)

 

本文根據《大話數據結構》一書,實現了Java版的二叉排序樹/二叉搜索樹html

二叉排序樹介紹

上篇博客中,順序表的插入和刪除效率還能夠,但查找效率很低;而有序線性表中,可使用折半、插值、斐波那契等查找方法來實現,但由於要保持有序,其插入和刪除操做很耗費時間。java

二叉排序樹(Binary Sort Tree),又稱爲二叉搜索樹,則能夠在高效率的查找下,同時保持插入和刪除操做也又較高的效率。下圖爲典型的二叉排序樹。node

二叉查找樹具備如下性質:算法

  (1) 若任意節點的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;數據結構

  (2) 任意節點的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;ide

  (3) 任意節點的左、右子樹也分別爲二叉查找樹。post

 

查找操做

思路:查找值與結點數據對比,根據大小肯定往左子樹仍是右子樹進行下一步比較。測試

採用遞歸的查找算法this

	/*
	 * 查找
	 */
	public boolean SearchBST(int key) {
		return SearchBST(key, root);
	}

	private boolean SearchBST(int key, Node node) {
		if (node == null)
			return false;
		if (node.data == key) {
			return true;
		} else if (node.data < key) {
			return SearchBST(key, node.rChild);
		} else {
			return SearchBST(key, node.lChild);
		}
	}

 

採用非遞歸的查找算法url

	/*
	 * 查找,非遞歸
	 */
	public boolean SearchBST2(int key) {
		Node p = root;
		while (p != null) {
			if (p.data > key) {
				p = p.lChild;
			} else if (p.data < key) {
				p = p.rChild;
			} else {
				return true;
			}
		}
		return false;
	}

  

插入操做

思路:與查找相似,但須要一個父節點來進行賦值。

採用非遞歸的插入算法:

	/*
	 * 插入,本身想的,非遞歸
	 */
	public boolean InsertBST(int key) {
		Node newNode = new Node(key);
		if (root == null) {
			root = newNode;
			return true;
		}
		Node f = null; // 指向父結點
		Node p = root; // 當前結點的指針
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				System.out.println("樹中已有相同數據,再也不插入!");
				return false;
			}
		}
		if (f.data > key) {
			f.lChild = newNode;
		} else if (f.data < key) {
			f.rChild = newNode;
		}
		return true;
	}

  

採用遞歸的插入算法:

	/*
	 * 插入,參考別人博客,遞歸
	 * 思路:把null狀況排除後用遞歸,不然沒法賦值
	 */
	public boolean InsertBST2(int key) {
		if (root == null) {
			root = new Node(key);
			return true;
		}
		return InsertBST2(key, root);
	}

	private boolean InsertBST2(int key, Node node) {
		if (node.data > key) {
			if (node.lChild == null) {
				node.lChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.lChild);
			}
		} else if (node.data < key) {
			if (node.rChild == null) {
				node.rChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.rChild);
			}
		} else {
			System.out.println("樹中已有相同數據,再也不插入!");
			return false;
		}
	}

 

新補充:在寫【Java】 大話數據結構(12) 查找算法(3) (平衡二叉樹(AVL樹))這篇博客時,發現如下的插入方法比較好(若是沒有要求返回值必須爲boolean格式的話):(推薦使用此類方法)

	/*
	 * 插入操做
	 */
	public void insert(int key) {
		root = insert(root, key);
	}

	private Node insert(Node node, int key) {
		if (node == null) {
			// System.out.println("插入成功!");
			// 也能夠定義一個布爾變量來保存插入成功與否
			return new Node(key);
		}
		if (key == node.data) {
			System.out.println("數據重複,沒法插入!");
		} else if (key < node.data) {
			node.lChild=insert(node.lChild, key);
		} else {
			node.rChild=insert(node.rChild, key);
		}
		return node;
	}

  

 

刪除操做

思路:

(1)刪除葉子結點

  直接刪除;

(2)刪除僅有左或右子樹的結點

  子樹移動到刪除結點的位置便可;

(3)刪除左右子樹都有的結點

   找到刪除結點p的直接前驅(或直接後驅)s,用s來替換結點p,而後刪除結點s,以下圖所示。

首先找到刪除結點位置及其父結點

	/*
	 * 刪除操做,先找到刪除結點位置及其父結點
	 * 由於須要有父結點,因此暫時沒想到遞歸的方法(除了令Node對象帶個parent屬性)
	 */
	public boolean deleteBST(int key) {
		if (root == null) {
			System.out.println("空表,刪除失敗");
			return false;
		}
		Node f = null; // 指向父結點
		Node p = root; // 指向當前結點
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				delete(p, f);
				return true;
			}
		}
		System.out.println("該數據不存在");
		return false;
	}

  

再根據上述思路進行結點p的刪除:(需注意刪除結點爲根節點的狀況)

	/*
	 * 刪除結點P的操做
	 * 必需要有父結點,由於Java沒法直接取得變量p的地址(沒法使用*p=(*p)->lChild)
	 */
	private void delete(Node p, Node f) {// p爲刪除結點,f爲其父結點
		if (p.lChild == null) { // 左子樹爲空,重接右子樹
			if (p == root) { // 被刪除結點爲根結點時,沒法利用f,該狀況不能忽略
				root = root.rChild;
				p = null;
			} else {
				if (f.data > p.data) { // 被刪結點爲父結點的左結點,下同
					f.lChild = p.rChild;
					p = null; // 釋放結點別忘了
				} else {// 被刪結點爲父結點的右結點,下同
					f.rChild = p.rChild;
					p = null;
				}
			}
		} else if (p.rChild == null) { // 右子樹爲空,重接左子樹
			if (p == root) { // 被刪除結點爲根結點
				root = root.lChild;
				p = null;
			} else {
				if (f.data > p.data) {
					f.lChild = p.lChild;
					p = null;
				} else {
					f.rChild = p.lChild;
					p = null;
				}
			}
		} else { // 左右子樹都不爲空,刪除位置用前驅結點替代
			Node q, s;
			q = p;
			s = p.lChild;
			while (s.rChild != null) { // 找到待刪結點的最大前驅s
				q = s;
				s = s.rChild;
			}
			p.data = s.data; // 改變p的data就OK
			if (q != p) {
				q.rChild = s.lChild;
			} else {
				q.lChild = s.lChild;
			}
			s = null;
		}
	}

  

  

 

完整代碼(含測試代碼)

package BST;

/**
 * 二叉排序樹(二叉查找樹)
 * 如果泛型,則要求知足T extends Comparable<T> static問題
 * @author Yongh
 *
 */
class Node {
	int data;
	Node lChild, rChild;

	public Node(int data) {
		this.data = data;
		lChild = null;
		rChild = null;
	}
}

public class BSTree {
	private Node root;

	public BSTree() {
		root = null;
	}

	/*
	 * 查找
	 */
	public boolean SearchBST(int key) {
		return SearchBST(key, root);
	}

	private boolean SearchBST(int key, Node node) {
		if (node == null)
			return false;
		if (node.data == key) {
			return true;
		} else if (node.data < key) {
			return SearchBST(key, node.rChild);
		} else {
			return SearchBST(key, node.lChild);
		}
	}

	/*
	 * 查找,非遞歸
	 */
	public boolean SearchBST2(int key) {
		Node p = root;
		while (p != null) {
			if (p.data > key) {
				p = p.lChild;
			} else if (p.data < key) {
				p = p.rChild;
			} else {
				return true;
			}
		}
		return false;
	}

	/*
	 * 插入,本身想的,非遞歸
	 */
	public boolean InsertBST(int key) {
		Node newNode = new Node(key);
		if (root == null) {
			root = newNode;
			return true;
		}
		Node f = null; // 指向父結點
		Node p = root; // 當前結點的指針
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				System.out.println("數據重複,沒法插入!");
				return false;
			}
		}
		if (f.data > key) {
			f.lChild = newNode;
		} else if (f.data < key) {
			f.rChild = newNode;
		}
		return true;
	}

	/*
	 * 插入,參考別人博客,遞歸
	 * 思路:相似查找,
	 *       但若方法中的node爲null的話,將沒法插入新數據,需排除null的狀況
	 */
	public boolean InsertBST2(int key) {
		if (root == null) {
			root = new Node(key);
			return true;
		}
		return InsertBST2(key, root);
	}

	private boolean InsertBST2(int key, Node node) {
		if (node.data > key) {
			if (node.lChild == null) { // 有null的狀況下,纔有父結點
				node.lChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.lChild);
			}
		} else if (node.data < key) {
			if (node.rChild == null) {
				node.rChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.rChild);
			}
		} else {
			System.out.println("數據重複,沒法插入!");
			return false;
		}
	}

	/*
	 * 這樣的插入是錯誤的(node沒法真正被賦值)
	 */
	/*
	private boolean InsertBST2(int key, Node node) {
		if(node!=null) {
			if (node.data > key) 
				return InsertBST2(key, node.lChild);
			else if (node.data < key) 
				return InsertBST2(key, node.rChild);
			else 
				return false;//重複
		}else {
			node=new Node(key);
			return true;
		}			
	}
	*/

	/*
	 * 刪除操做,先找到刪除結點位置及其父結點
	 * 由於須要有父結點,因此暫時沒想到遞歸的方法(除了令Node對象帶個parent屬性)
	 */
	public boolean deleteBST(int key) {
		if (root == null) {
			System.out.println("空表,刪除失敗");
			return false;
		}
		Node f = null; // 指向父結點
		Node p = root; // 指向當前結點
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				delete(p, f);
				System.out.println("刪除成功!");
				return true;
			}
		}
		System.out.println("該數據不存在");
		return false;
	}

	/*
	 * 刪除結點P的操做
	 * 必需要有父結點,由於Java沒法直接取得變量p的地址(沒法使用*p=(*p)->lChild)
	 */
	private void delete(Node p, Node f) {// p爲刪除結點,f爲其父結點
		if (p.lChild == null) { // 左子樹爲空,重接右子樹
			if (p == root) { // 被刪除結點爲根結點,該狀況不能忽略
				root = root.rChild;
				p = null;
			} else {
				if (f.data > p.data) { // 被刪結點爲父結點的左結點,下同
					f.lChild = p.rChild;
					p = null; // 釋放結點別忘了
				} else {// 被刪結點爲父結點的右結點,下同
					f.rChild = p.rChild;
					p = null;
				}
			}
		} else if (p.rChild == null) { // 右子樹爲空,重接左子樹
			if (p == root) { // 被刪除結點爲根結點
				root = root.lChild;
				p = null;
			} else {
				if (f.data > p.data) {
					f.lChild = p.lChild;
					p = null;
				} else {
					f.rChild = p.lChild;
					p = null;
				}
			}
		} else { // 左右子樹都不爲空,刪除位置用前驅結點替代
			Node q, s;
			q = p;
			s = p.lChild;
			while (s.rChild != null) { // 找到待刪結點的最大前驅s
				q = s;
				s = s.rChild;
			}
			p.data = s.data; // 改變p的data就OK
			if (q != p) {
				q.rChild = s.lChild;
			} else {
				q.lChild = s.lChild;
			}
			s = null;
		}
	}

	/*
	 * 中序遍歷
	 */
	public void inOrder() {
		inOrder(root);
		System.out.println();
	}

	public void inOrder(Node node) {
		if (node == null)
			return;
		inOrder(node.lChild);
		System.out.print(node.data + " ");
		inOrder(node.rChild);
	}

	/*
	 * 測試代碼
	 */
	public static void main(String[] args) {
		BSTree aTree = new BSTree();
		BSTree bTree = new BSTree();
		int[] arr = { 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 };
		for (int a : arr) {
			aTree.InsertBST(a);
			bTree.InsertBST2(a);
		}
		aTree.inOrder();
		bTree.inOrder();
		System.out.println(aTree.SearchBST(35));
		System.out.println(bTree.SearchBST2(99));
		aTree.deleteBST(47);
		aTree.inOrder();
	}
}

  

35 37 47 51 58 62 73 88 93 99 
35 37 47 51 58 62 73 88 93 99 
true
true
刪除成功!
35 37 51 58 62 73 88 93 99 
BSTree

 

 小結(本身編寫時的注意點):

  查找:操做簡單,注意遞歸的方法沒有循環while (p!=null),而是並列的幾個判斷;

  插入:非遞歸時,要有父結點;遞歸時,要注意排除null的狀況;

  刪除:記住要分兩步,第一步找結點位置時也要把父結點帶上;第二步刪除結點時,要令p=null,還要注意p==root的狀況以及q==p的狀況。

相關文章
相關標籤/搜索