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