查詢算法(一)二分查找

0. 簡介
  • 二分查找(或折半查找)應用於有序數據。(二分查找的重點在於,1. 確認當前二分操作中間點的元素下標,使用 double 類型,試圖提高精準度, 2. 下一次二分操作的元素空間長度,其等於當前二分空間元素長度的二分之一)
  • 還要注意一點,大量的遞歸操作會顯著影響性能,所以使用例如 for 循環來替代遞歸。
  • 每執行一次二分操作,需要進行比較操作的元素數量就減少一半,隨着繼續二分操作,需要進行比較操作的元素空間會持續縮小,直到某個範圍,然後對這個小範圍中的元素執行線性查找(注意:線性查找操作不能逃逸出最後確定的這個元素範圍,而且還要注意避免數組下標越界)。
    二分操作
1. 二分查找程序示例
/**
 * 二分查找
 * @author jokee
 *
 */
public class Structure_Query_1 {
	//  簡單測試/對比
	public static void main(String[] args) {
		int number_num = 100000000;
		int[] sortedArray = new int[number_num];
		for(int i = 0;i < number_num;i++) {
			sortedArray[i] = i + 1;
		}
		long startTime = System.currentTimeMillis();
		System.out.println("二分查找,結果:" + _doBinaryQuery(sortedArray, 1, 20));
		System.out.println("二分查找,耗時:" + (System.currentTimeMillis() - startTime) + "ms");
		System.out.println("--------------------"); // 打印分割線
		startTime = System.currentTimeMillis();
		System.out.println("線性查找,結果:" + doSequenceQuery(sortedArray, 99999999));
		System.out.println("線性查找,耗時:" + (System.currentTimeMillis() - startTime) + "ms");		
	}
	
	/**
	 * 線性查找
	 */
	public static int doSequenceQuery(int[] sortedArray, int e) {
		int count = 0; // 用於統計 for 循環體執行次數
		int len = 0;
		if (sortedArray != null && (len = sortedArray.length) > 0) {
			for(int i = 0;i < len;i++) {
				count++;
				if (e == sortedArray[i]) {
					System.out.println("線性查找:\n循環次數:" + count);
					return i;
				}
			}
		}
		System.out.println("線性查找:\n循環次數:" + count);
		return -1;
	}
	
	/** 二分查找 */
	public static int doBinaryQuery(int[] sortedArray, int e) {
		int minSpaceLengthToSplit = 20;
		return _doBinaryQuery(sortedArray, e, minSpaceLengthToSplit);
	}
	
	/**
	 * 執行二分查找,判斷元素 e 是否存在於有序數組 sortedArray 中。
	 * @param sortedArray 有序數組
	 * @param e 要查找的元素
	 * @param minSpaceLengthToSplit 指定可進行二分操作的元素空間的最小長度,minSpaceLengthToSplit >= 2
	 * @return -1 表示有序數組 sortArray 中不存在指定元素 e,否則返回元素 e 在數組中的下標
	 */
	public static int _doBinaryQuery(int[] sortedArray, int e, int minSpaceLengthToSplit) {
		// 下列變量用於統計信息
		int forCount = 0; // 單獨記錄 for 循環體執行的次數
		int whileCount = 0; // 單獨記錄 while 循環體執行的次數
		int splitCount = 0; // 記錄二分操作執行的次數
		//
		int len = 0;
		int result = -1;
		if (sortedArray != null && (len = sortedArray.length) > 1) {
			// 執行二分查找之前,先做簡單過濾
			int maxValueOfSortedArray = Math.max(sortedArray[0], sortedArray[len - 1]);
			int minValueOfSortedArray = Math.min(sortedArray[0], sortedArray[len - 1]);
			if (e < minValueOfSortedArray || e > maxValueOfSortedArray) {
				// 元素 e 不在有序數組 sortArray 的元素值範圍內
				// 輸出統計信息
				System.out.println("二分查找:\nwhile 循環次數=" + whileCount 
						+ "\nfor 循環次數=" + forCount + "\n二分操作 次數=" + splitCount);
				return result;
			}
			
			minSpaceLengthToSplit = (minSpaceLengthToSplit < 2 ? 2 : minSpaceLengthToSplit);
			// 下一次可二分操作的元素空間長度,等於當前已二分空間的二分之一
			double nextSpaceLengthToSplit = len / 2.0;
			// 當前二分空間的中間點的索引值,使用 double 提高精確度
			double middleIndex = (len - 1) / 2.0;
			boolean doQueryHeadAndTail = true;
			while(true) {
				whileCount++; //
				if(doQueryHeadAndTail 
						&& e == sortedArray[0]) {
					// 查詢頭節點
					result = 0;
					break;
				}else if(doQueryHeadAndTail 
						&& (doQueryHeadAndTail = false) == false // 控制對頭/尾節點僅僅比較一次
						&& e == sortedArray[len - 1]) {
					// 查詢尾節點
					result = len - 1;
					break;
				}
				
				splitCount++; //
				int validMiddleIndex = Math.round((float)middleIndex);
				int middleValue = sortedArray[validMiddleIndex];
				if (e == middleValue) {
					result = validMiddleIndex;
					break;
				} else if (e < middleValue) {
					// 嘗試繼續二分操作
					if (nextSpaceLengthToSplit >= minSpaceLengthToSplit) {
						splitCount++; //
						// 繼續向左邊拆分
						middleIndex = middleIndex - nextSpaceLengthToSplit / 2.0;
						nextSpaceLengthToSplit /= 2.0;
						continue;
					}
					// to left for query
					// 必須小心數組下標向左邊越界,以及控制 for 循環的最大次數
					for(--validMiddleIndex;validMiddleIndex > 0
							&& validMiddleIndex >= middleIndex - nextSpaceLengthToSplit
							;validMiddleIndex--) {
						forCount++; //
						if (e == sortedArray[validMiddleIndex]) {
							result = validMiddleIndex;
							break;
						}
					}
					break;
				} else {
					// 嘗試繼續二分操作
					if (nextSpaceLengthToSplit >= minSpaceLengthToSplit) {
						splitCount++; //
						// 繼續向右邊拆分
						middleIndex = middleIndex + nextSpaceLengthToSplit / 2.0;
						nextSpaceLengthToSplit /= 2.0;
						continue;
					}
					// to right for query
					// 必須小心數組下標向右邊越界,以及控制 for 循環的最大次數
					for(++validMiddleIndex;validMiddleIndex < len - 1 
							&& validMiddleIndex <= middleIndex + nextSpaceLengthToSplit;
							validMiddleIndex++) {
						forCount++; //
						if (e == sortedArray[validMiddleIndex]) {
							result = validMiddleIndex;
							break;
						}
					}
					break;
				}
			}
		}
		// 輸出統計信息
		System.out.println("二分查找:\nwhile 循環次數=" + whileCount 
				+ "\nfor 循環次數=" + forCount + "\n二分操作 次數=" + splitCount);
		return result;
	}
}

執行結果如下:

二分查找:
while 循環次數=1
for 循環次數=0
二分操作 次數=0
二分查找,結果:0
二分查找,耗時:1ms
--------------------
線性查找:
循環次數:99999999
線性查找,結果:99999998
線性查找,耗時:38ms
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章