1. 程式人生 > >折半查詢與二叉查詢樹

折半查詢與二叉查詢樹

在生活當中,我們可能每天都要進行查詢工作,字典中查詢,搜尋引擎中查詢,資料庫中進行查詢。在這個資訊的時代下,我們每天都要從網際網路上接觸到很多資訊。這些資訊從哪裡來,當然是儲存在資料庫中。提到資料庫,大家首先想到的肯定是索引,是的資料庫的優劣很大是與索引相關。而索引是為了什麼,就是為了方便查詢,快速從資料庫中提取我們想要的資料,對於索引的優化也是一個難點。下面筆者就來介紹一下折半查詢和建立二叉查詢樹進行查詢。

折半查詢:

大家知道,順序查詢(線性查詢)是從線性表中的一端到另一端逐個進行比較,當資料非常大時,查詢效率非常低,時間複雜度為O(n),但是它對記錄的儲存沒有任何要求(記錄不必有序)。

折半查詢

:必須要求記錄有序,採用順序儲存,利用這個特點,所以折半查詢的效率也比順序查詢高,對於數量非常大時,非常快,時間複雜度為O(logN)。

基本思想:(1)在有序資料中,去中間的記錄作為比較物件,若給定的值與中間記錄相等,則查詢成功。  

    (2) 若給定的值小於中間記錄,則在中間記錄的左半區進行查詢。

    (3) 若給定的值大於中間記錄,則在中間記錄的右半區進行查詢。

    (4) 重複以上步驟,直到查詢成功,若查詢的區域無記錄,則查詢失敗。

下面筆者定義了一個折半查詢的類: 使用數字進行儲存資料。

	private Object[] array;
	private int size;  // 陣列的大小

	private BinarySearch(int max) {  //通過建構函式初始化陣列的大小
		array = new Object[max];
		size = 0;
	}
插入資料: 由於折半查詢要求資料必須有序,並且插入操作往往比查詢操作頻率更加小。所以在插入時就對資料進行排序。
	/**  插入元素
	 */
	@SuppressWarnings("unchecked")
	public void insert(T value) {
		int pos = 0;
		while(pos < size){
			if (((Comparable<? super T>) array[pos]).compareTo(value) > 0)
				break;
			pos++;
		}
		
		for (int k = size; k > pos; k--) {  //將比插入值大的元素後移
			array[k] = array[k - 1];
		}
		array[pos] = value;
		size++;
	}

折半查詢: 這裡使用的是遞迴進行查詢,時間複雜度為O(logN)。
	public int find(T searchKey) {
		return reFind(searchKey, 0, size - 1);
	}

	/**
	 * 通過將關鍵字與查詢部分的中間元素比較,並呼叫自身實現遞迴呼叫,直到找到關鍵字
	 * 
	 * @param searchKey 需要尋找的關鍵字
	 * @param lower 查詢部分的開始
	 * @param upper 查詢部分的結尾
	 * @return  成功找到返回關鍵字位置,失敗則返回 -1
	 */
	@SuppressWarnings("unchecked")
	public int reFind(T searchKey, int lower, int upper) {
		int current;
		current = (lower + upper) / 2;
		if (array[current] == searchKey) { // 找到關鍵字直接返回關鍵字位置
			return current;
		} else if (lower > upper) { // lower、upper交錯,不能找到,直接返回陣列大小
			return -1;
		} else {
			if (((Comparable<? super T>) array[current]).compareTo(searchKey) < 0) { // 關鍵字在小於 array[current] 的一半查詢
				return reFind(searchKey, current + 1, upper);
			} else {
				return reFind(searchKey, lower, current - 1); // 關鍵字在大於array[current]的一半查詢
			}
		}
	}

測試:
package org.TT.Recursion;

import java.util.Scanner;

/** 使用遞迴進行二分查詢。
 *    遞迴的二分查詢程式碼簡潔,時間複雜度為O(logN)
 */
public class BinarySearch<T extends Comparable<? super T>> {

	// 這裡的陣列需要已經排好序,在插入時就直接排序
	private Object[] array;
	private int size;  // 陣列的大小

	private BinarySearch(int max) {  //通過建構函式初始化陣列的大小
		array = new Object[max];
		size = 0;
	}

	/** 取得查詢陣列的大小
	 */
	public int size() {
		return size;
	}

	public int find(T searchKey) {
		return reFind(searchKey, 0, size - 1);
	}

	/**
	 * 通過將關鍵字與查詢部分的中間元素比較,並呼叫自身實現遞迴呼叫,直到找到關鍵字
	 * 
	 * @param searchKey 需要尋找的關鍵字
	 * @param lower 查詢部分的開始
	 * @param upper 查詢部分的結尾
	 * @return  成功找到返回關鍵字位置,失敗則返回 -1
	 */
	@SuppressWarnings("unchecked")
	public int reFind(T searchKey, int lower, int upper) {
		int current;
		current = (lower + upper) / 2;
		if (array[current] == searchKey) { // 找到關鍵字直接返回關鍵字位置
			return current;
		} else if (lower > upper) { // lower、upper交錯,不能找到,直接返回陣列大小
			return -1;
		} else {
			if (((Comparable<? super T>) array[current]).compareTo(searchKey) < 0) { // 關鍵字在小於 array[current] 的一半查詢
				return reFind(searchKey, current + 1, upper);
			} else {
				return reFind(searchKey, lower, current - 1); // 關鍵字在大於array[current]的一半查詢
			}
		}
	}

	/**  插入元素
	 */
	@SuppressWarnings("unchecked")
	public void insert(T value) {
		int pos = 0;
		while(pos < size){
			if (((Comparable<? super T>) array[pos]).compareTo(value) > 0)
				break;
			pos++;
		}
		
		for (int k = size; k > pos; k--) {  //將比插入值大的元素後移
			array[k] = array[k - 1];
		}
		array[pos] = value;
		size++;
	}

	/**
	 *  展示需要查詢的所有陣列值
	 */
	public void dispaly() {
		for (int i = 0; i < size; i++) {
			System.out.print(array[i] + " ");
		}
	}

	@SuppressWarnings("resource")
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);

		BinarySearch<Integer> search = new BinarySearch<>(10);
		search.insert(10);
		search.insert(90);
		search.insert(80);
		search.insert(50);
		search.insert(30);
		search.insert(20);
		search.insert(40);
		search.insert(700);
		
		System.out.print("陣列元素為: ");
		search.dispaly();

		System.out.println();
		System.out.print("請輸入需要查詢的元素:");
		int searchNum = in.nextInt();
		if (search.find(searchNum) != -1) {

			System.out.println("Found: " + searchNum);
		} else {
			System.out.println("Can't find " + searchNum);
		}
	}

}
測試


建立二叉查詢樹進行查詢

二叉查詢樹:
性質: 若它的左子樹不為空,則左子樹上所有節點的值均小於根節點;   若它的子樹不為空,則子樹上所有節點的值均小於根節點; 它的左右子樹都是二叉查詢樹。

首先構造一個結點類,儲存資料,並且有左孩子,右孩子的引用。在這裡屬性不設定成私有主要是為了便於訪問,若想設為私有屬性,可以設定get 、set方法訪問。

package org.TT.BinarySeachTree;


/**結點資料結構*/  
public class BinaryNode<T>{  
       T data;  
       BinaryNode<T> left;  
       BinaryNode<T> right;  
       public BinaryNode(T data) {  
           this(data,null,null);  
       }  
       public BinaryNode( T data, BinaryNode<T> left, BinaryNode<T> right) {  
           this.data =data;  
           this.left = left;  
           this.right =right;  
       }  
 }

查詢樹: 只已知一個根節點,建構函式初始化一棵空樹。

判斷是否為空:直接判斷根節點是否為空。

 清除樹,直接將根節點的應用置為空即可,虛擬機器可進行垃圾回收是發現引用為空,將進行垃圾回收。

<span style="white-space:pre">	</span>private BinaryNode<T> rootTree;


<span style="white-space:pre">	</span>/** 構造一顆空的二叉查詢樹 */
<span style="white-space:pre">	</span>public BinarySearchTree() {
<span style="white-space:pre">		</span>rootTree = null;
<span style="white-space:pre">	</span>}


<span style="white-space:pre">	</span>/** 清空二叉查詢樹 */
<span style="white-space:pre">	</span>public void clear() {
<span style="white-space:pre">		</span>rootTree = null;
<span style="white-space:pre">	</span>}


<span style="white-space:pre">	</span>/** 判斷是否為空 */
<span style="white-space:pre">	</span>public boolean isEmpty() {
<span style="white-space:pre">		</span>return rootTree == null;
<span style="white-space:pre">	</span>}

插入: 每次插入尋找正確的位置並插入,保證插入後還是一棵二叉查詢樹。

	/** 插入元素 */
	public void insert(T t) {
		rootTree = insert(t, rootTree); // 第一次插入時,直接建立新節點並賦值給根節點
	}

	/** 在某個位置開始判斷插入元素,採用遞迴實現不斷尋找插入位置,程式碼簡潔*/
	public BinaryNode<T> insert(T t, BinaryNode<T> parent) {
		if (parent == null) {
			return new BinaryNode<T>(t, null, null);  // 建立一個新節點
		}
		int result = t.compareTo(parent.data);  //若 t 小於 parent 返回負數, 大於返回正數,等於返回0
		if (result < 0)   
			parent.left = insert(t, parent.left);
		else if (result > 0)
			parent.right = insert(t, parent.right);
		else
			;// 若兩個元素相等,什麼也做,如果有需要可以在節點記錄裡附加域來指示重複元素插入的資訊
		return parent;
	}
插入過程: 插入過程

查詢: 每次查詢與根結點比較,(1)相等則返回   (2)小於,在左子樹中查詢   (3)大於,在右子樹中查詢

	/** 查詢指定的元素,預設從根結點出開始查詢 */
	public boolean find(T t) {
		return find(t, rootTree);

	}

	/** 從某個結點出開始查詢元素,
	 * 用遞迴實現並不斷將關鍵字與父節點比較,大於往右子樹走,小於往左子樹走等於返回 */
	public boolean find(T t, BinaryNode<T> node) {
		if (node == null)
			return false;
		int result = t.compareTo(node.data);  
		if (result > 0)
			return find(t, node.right);
		else if (result < 0)
			return find(t, node.left);
		else
			return true;
	}
查詢最大值、最小值:  若查詢最小值,直接在左子樹上查詢,一直往左查詢。 若查詢最大值,直接在右子樹上查詢,一直往右查詢。 
	/** 查詢二叉查詢樹中的最小值 */
	public T findMin() {
		if (isEmpty()) {
			System.out.println("二叉樹為空");
			return null;
		} else
			return findMin(rootTree).data;

	}

	/** 查找出最小元素所在的結點 */
	public BinaryNode<T> findMin(BinaryNode<T> node) {
		if (node == null)
			return null;
		else if (node.left == null)
			return node;
		return findMin(node.left);// 尾遞迴查詢
	}

	/** 查詢二叉查詢樹中的最大值 */
	public T findMax() {
		if (isEmpty()) {
			System.out.println("二叉樹為空");
			return null;
		} else
			return findMax(rootTree).data;
	}

	/** 查找出最大元素所在的結點 */
	public BinaryNode<T> findMax(BinaryNode<T> node) {
		if (node != null) {
			while (node.right != null)
				node = node.right;
		}
		return node;
	}
刪除: 刪除結點才用遞迴實現。

(1) 刪除結點是葉子節點,直接刪除 。

(2)只有左子樹、右子樹,只需重新連線。

(3)刪除有兩個孩子的節點:用右子樹最小節點代替刪除節點,並刪除右子樹最小節點

	/** 刪除元素 */
	public void remove(T t) {
		rootTree = remove(t, rootTree);
	}

	/** 在某個位置開始判斷刪除某個結點,同樣用遞迴實現 */
	public BinaryNode<T> remove(T t, BinaryNode<T> node) {
		if (node == null)
			return node;
		int result = t.compareTo(node.data);
		if (result > 0)
			node.right = remove(t, node.right);
		else if (result < 0)
			node.left = remove(t, node.left);
		else if (node.left != null && node.right != null) { 
			// 刪除有兩個孩子的節點:用右子樹最小節點代替刪除節點,並刪除右子樹最小節點
			node.data = findMin(node.right).data;   // 找到刪除節點右子樹的最小節點
			node.right = remove(node.data, node.right); // 刪除右子樹最小節點
		} else  
			node = (node.left != null) ? node.left : node.right;
		//刪除沒有子節點的節點(葉子)和只有一個節點的節點
		return node;
	}

遍歷及測試:
package org.TT.BinarySeachTree;

/**
 * 二叉查詢樹的性質:
 *  1.對於樹中的每個節點X,它的左子樹中所有項的值小於X,而它的右子樹中所有項的值大於X, 
 *  		Java實現二叉查詢樹(遞迴的編寫二叉查詢樹的各種操作) 
 *    2.樹的所有節點的平均深度為 O(logN) 
 *    3.所有操作的平均執行時間也是 O(logN)
 *    4.此處的操作:插入結點,構造二叉查詢樹、清空二叉樹、判斷樹是否為空、查詢指定結點、刪除結點、查詢最大值、查詢最小值
 */
public class BinarySearchTree<T extends Comparable<? super T>> {

	private BinaryNode<T> rootTree;

	/** 構造一顆空的二叉查詢樹 */
	public BinarySearchTree() {
		rootTree = null;
	}

	/** 清空二叉查詢樹 */
	public void clear() {
		rootTree = null;
	}

	/** 判斷是否為空 */
	public boolean isEmpty() {
		return rootTree == null;
	}
	
	/** 插入元素 */
	public void insert(T t) {
		rootTree = insert(t, rootTree); // 第一次插入時,直接建立新節點並賦值給根節點
	}

	/** 在某個位置開始判斷插入元素,採用遞迴實現不斷尋找插入位置,程式碼簡潔*/
	public BinaryNode<T> insert(T t, BinaryNode<T> parent) {
		if (parent == null) {
			return new BinaryNode<T>(t, null, null);  // 建立一個新節點
		}
		int result = t.compareTo(parent.data);  //若 t 小於 parent 返回負數, 大於返回正數,等於返回0
		if (result < 0)   
			parent.left = insert(t, parent.left);
		else if (result > 0)
			parent.right = insert(t, parent.right);
		else
			;// 若兩個元素相等,什麼也做,如果有需要可以在節點記錄裡附加域來指示重複元素插入的資訊
		return parent;
	}
	
	/** 查詢指定的元素,預設從根結點出開始查詢 */
	public boolean find(T t) {
		return find(t, rootTree);

	}

	/** 從某個結點出開始查詢元素,
	 * 用遞迴實現並不斷將關鍵字與父節點比較,大於往右子樹走,小於往左子樹走等於返回 */
	public boolean find(T t, BinaryNode<T> node) {
		if (node == null)
			return false;
		int result = t.compareTo(node.data);  
		if (result > 0)
			return find(t, node.right);
		else if (result < 0)
			return find(t, node.left);
		else
			return true;
	}

	/** 查詢二叉查詢樹中的最小值 */
	public T findMin() {
		if (isEmpty()) {
			System.out.println("二叉樹為空");
			return null;
		} else
			return findMin(rootTree).data;

	}

	/** 查找出最小元素所在的結點 */
	public BinaryNode<T> findMin(BinaryNode<T> node) {
		if (node == null)
			return null;
		else if (node.left == null)
			return node;
		return findMin(node.left);// 尾遞迴查詢
	}

	/** 查詢二叉查詢樹中的最大值 */
	public T findMax() {
		if (isEmpty()) {
			System.out.println("二叉樹為空");
			return null;
		} else
			return findMax(rootTree).data;
	}

	/** 查找出最大元素所在的結點 */
	public BinaryNode<T> findMax(BinaryNode<T> node) {
		if (node != null) {
			while (node.right != null)
				node = node.right;
		}
		return node;
	}

	/** 刪除元素 */
	public void remove(T t) {
		rootTree = remove(t, rootTree);
	}

	/** 在某個位置開始判斷刪除某個結點,同樣用遞迴實現 */
	public BinaryNode<T> remove(T t, BinaryNode<T> node) {
		if (node == null)
			return node;
		int result = t.compareTo(node.data);
		if (result > 0)
			node.right = remove(t, node.right);
		else if (result < 0)
			node.left = remove(t, node.left);
		else if (node.left != null && node.right != null) { 
			// 刪除有兩個孩子的節點:用右子樹最小節點代替刪除節點,並刪除右子樹最小節點
			node.data = findMin(node.right).data;   // 找到刪除節點右子樹的最小節點
			node.right = remove(node.data, node.right); // 刪除右子樹最小節點
		} else  
			node = (node.left != null) ? node.left : node.right;
		//刪除沒有子節點的節點(葉子)和只有一個節點的節點
		return node;
	}

	/** 前序遍歷 */
	public void preOrder(BinaryNode<T> node) {
		if (node != null) {
			System.out.print(node.data + ",");
			preOrder(node.left);
			preOrder(node.right);
		}
	}

	/** 後序遍歷 */
	public void postOrder(BinaryNode<T> node) {
		if (node != null) {
			postOrder(node.left);
			postOrder(node.right);
			System.out.print(node.data + ",");
		}
	}

	/** 中序遍歷 */
	public void inOrder(BinaryNode<T> node) {
		if (node != null) {
			inOrder(node.left);
			System.out.print(node.data + ",");
			inOrder(node.right);
		}
	}

	public static void main(String[] args) {

		int[] value = { 6,8,3,7,10,1,9 };
		BinarySearchTree<Integer> tree = new BinarySearchTree<>();
		for (int v : value) {
			tree.insert(v);
		}
		System.out.println("前序遍歷");
		tree.preOrder(tree.rootTree);
		System.out.println();
		System.out.println("後序遍歷");
		tree.postOrder(tree.rootTree);
		System.out.println();
		System.out.println("中序遍歷");
		tree.inOrder(tree.rootTree);
		System.out.println();
		System.out.println("最小值==" + tree.findMin());
		System.out.println("最大值==" + tree.findMax());
		System.out.println("查詢8: " + tree.find(8));
		System.out.println("是否為空: " + tree.isEmpty());
		System.out.println();

		tree.remove(8);
		System.out.println("刪除節點值為8的節點,再中序遍歷");
		System.out.println("查詢8: " + tree.find(8));
		tree.inOrder(tree.rootTree);
	}

}
測試