查找算法總結及其算法實現(Python/Java)

在這裏插入圖片描述

前言

本文總結了經常使用的查找算法,內容包括:html

  • 查找算法的定義和思路,動畫演示
  • 查找算法的代碼實現:Python和Java
  • 查找算法性能分析:時間空間複雜度分析
  • 不一樣排序算法最佳使用場景

面試知識點複習手冊

此文屬於知識點複習手冊專欄內容,你還能夠經過如下兩種途徑查看全複習手冊文章導航java

-----正文開始-----

預備知識

查找算法分類

1)靜態查找和動態查找;node

注:靜態或者動態都是針對查找表而言的。動態表指查找表中有刪除和插入操做的表。git

2)無序查找和有序查找。程序員

  • 無序查找:被查找數列有序無序都可;
  • 有序查找:被查找數列必須爲有序數列。

平均查找長度(Average Search Length,ASL)

需和指定key進行比較的關鍵字的個數的指望值,稱爲查找算法在查找成功時的平均查找長度。github

對於含有n個數據元素的查找表,查找成功的平均查找長度爲:ASL = Pi*Ci的和。面試

Pi:查找表中第i個數據元素的機率。算法

Ci:找到第i個數據元素時已經比較過的次數。數據庫

查找性能

從快到慢:編程

  • 順序查找,時間複雜度O(N),
  • 分塊查找,時間複雜度O(logN+N/m);
  • 二分查找,時間複雜度O(logN)
  • Fibonacci查找,時間複雜度O(logN)
  • 差值查找,時間複雜度O(log(logN))
  • 哈希查找,時間複雜度O(1)

查找算法

1. 順序查找

說明:屬於有序查找,順序查找適合於存儲結構爲順序存儲或連接存儲的線性表。

複雜度分析:

查找成功時的平均查找長度爲:

(假設每一個數據元素的機率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;

當查找不成功時,須要n+1次比較,時間複雜度爲O(n);

因此,順序查找的時間複雜度爲O(n)

Java實現:

public static int SequenceSearch(int a[], int value, int n) {
		for(int i=1;i<n;i++) {
			if(a[i]==value) {
				return i;
			}
		}
		return -1;
	}
複製代碼

2.二分查找

二分查找經典理解:https://www.zhihu.com/question/36132386/answer/155438728

基本思想:

也稱爲是折半查找,屬於有序查找算法。用給定值k先與中間結點的關鍵字比較,中間結點把線形表分紅兩個子表,若相等則查找成功;若不相等,再根據k與該中間結點關鍵字的比較結果肯定下一步查找哪一個子表,這樣遞歸進行,直到查找到或查找結束髮現表中沒有這樣的結點。

複雜度分析:

最壞狀況下,關鍵詞比較次數爲log2(n+1),且指望時間複雜度爲O(log2n);對於一個有1024個元素的數組,在最壞的狀況下,二分查找法只須要比較log2n + 1= 11次,而在最壞的狀況下線性查找要比較1023次。

注:折半查找的前提條件是須要有序表順序存儲,對於靜態查找表,一次排序後再也不變化,折半查找能獲得不錯的效率。但對於須要頻繁執行插入或刪除操做的數據集來講,維護有序的排序會帶來不小的工做量,那就不建議使用。——《大話數據結構》    注意點:爲何(low +high) / 2會溢出啊?答:兩個很大的int相加的話超出 Integer.MAX_VALUE 了

Java實現:

// 迭代版
public static int BinarySearch(int a[], int value, int n) {
		int low = 0;
		int high = n-1;
		int mid = 0;
		while (low <= high) {
			mid = low + (high - low) * 1/2;
			if(a[mid] == value) {
				return mid;
			}
			if(a[mid] > value) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		return -1; //return low 應該插入的位置
	}
// 遞歸版
public int binarySearchRecur(int[] nums, int target, int low, int high) {
	if (low > high) { return low; } //base case
	int mid = low + (high - low) / 2;
	if (nums[mid] > target) {
		return binarySearchRecur(nums,target,low,mid-1);
	} else if (nums[mid] < target) {
		return binarySearchRecur(nums,target,mid+1,high);
	} else {
		return mid;
	}
}
//含有重複數字的狀況
//主要區別:等於mid也要將high-1;直到low>high結束,返回low(實際上是高位,重複數字的第一位)
public static int BinarySearchDuplicate(int a[], int value, int n) {
	int low = 0;
	int high = n-1;
	int mid = 0;
	while (low <= high) {
		mid = low + (high - low) * 1/2;
		if(a[mid] >= value) {
			high = mid - 1;
		}
		else {
			low = mid + 1;
		}
	}
	return low;
}
//含有重複數字:遞歸版
public int firstOccurrenceRecur(int[] nums, int target, int low, int high) {
	if (low > high) { return low; }
		int mid = low + (high - low) / 2;
	if (nums[mid] < target) {
		return firstOccurrenceRecur(nums,target,mid + 1,high);
	} else {
		return firstOccurrenceRecur(nums,target,low,mid-1);
	}
}
複製代碼

3.插值查找

經過類比,咱們能夠將二分查找的點改進爲以下:

mid=low+(high-low)*(key-a[low])/(a[high]-a[low])//(1/2)換爲(key-a[low])/(a[high]-a[low])
複製代碼

也就是將上述的比例參數1/2改進爲自適應的,根據關鍵字在整個有序表中所處的位置,讓mid值的變化更靠近關鍵字key,這樣也就間接地減小了比較次數。    基本思想:

基於二分查找算法,將查找點的選擇改進爲自適應選擇,能夠提升查找效率。固然,差值查找也屬於有序查找。

注:對於表長較大,而關鍵字分佈又比較均勻的查找表來講,插值查找算法的平均性能比折半查找要好的多。反之,數組中若是分佈很是不均勻,那麼插值查找未必是很合適的選擇。

複雜度分析:

查找成功或者失敗的時間複雜度均爲O(log2(log2n))

Java實現:

public static int InsertionSearch(int a[], int value, int n) {
		int low = 0;
		int high = n-1;
		int mid = 0;
		while (low <= high) {
			mid = low + (high-low) * (value-a[low]) / (a[high]-a[low]);
			if(a[mid] == value) {
				return mid;
			}
			if(a[mid] > value) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		return -1;
	}
複製代碼

4. 斐波那契查找

https://blog.csdn.net/zsw12013/article/details/50003505

這裏寫圖片描述

斐波那契查找與折半查找很類似,他是根據斐波那契序列的特色對有序表進行分割的。他要求開始表中記錄的個數爲某個斐波那契數小1,n=F(k)-1;

複雜度分析: 最壞狀況下,時間複雜度爲O(log2n),且其指望複雜度也爲O(log2n)

注意:生成的數組長度是f[k]-1而不是f[k]

Java:

public final static int MAXSIZE = 20; // fib length
    public static int[] fibonacci() {  
        int[] f = new int[MAXSIZE];  
        int i = 0;  
        f[0] = 1;  
        f[1] = 1;  
        for (i = 2; i < MAXSIZE; i++) {  
            f[i] = f[i - 1] + f[i - 2];  
        }  
        return f;  
    }  
  
    public static int fibonacciSearch(int[] a, int value, int n) {  
        int low = 0;  
        int high = n - 1;  
        int mid = 0;  
  
        // 斐波那契分割數值下標  
        int k = 0;  
        // 序列元素個數  
        int i = 0;  
        // 獲取斐波那契數列  
        int[] f = fibonacci();  
        // 獲取斐波那契分割數值下標  
        while (n > f[k] - 1) {  
            k++;  
        }  
  
        // 建立臨時數組  
        int[] temp = new int[f[k] - 1];  
        for (int j = 0; j < n;j++) { 
        	temp[j] = a[j];  
        }  
  
        // 序列補充至f[k]個元素  
        // 補充的元素值爲最後一個元素的值  
        for (i = n; i < f[k] - 1; i++) {  
            temp[i] = temp[high];  
        }  
  
//        for (int j : temp) {  
//            System.out.print(j + " ");  
//        }  
//        System.out.println();  
  
        while (low <= high) {  
            // low:起始位置  
            // 前半部分有f[k-1]個元素,因爲下標從0開始  
            // 則-1 獲取 黃金分割位置元素的下標  
            mid = low + f[k - 1] - 1;  
  
            if (temp[mid] > value) {  
                // 查找前半部分,高位指針移動  
                high = mid - 1;  
                // (所有元素) = (前半部分)+(後半部分)  
                // f[k] - 1 = f[k-1] -1 + f[k-2] -1  
                // 由於前半部分有f[k-1]個元素,因此 k = k-1  
                k = k - 1;  
            } else if (temp[mid] < value) {  
                // 查找後半部分,高位指針移動  
                low = mid + 1;  
                // (所有元素) = (前半部分)+(後半部分)  
                // f[k] -1 = f[k-1] -1 + f[k-2] - 1  
                // 由於後半部分有f[k-2]個元素,因此 k = k-2  
                k = k - 2;  
            } else {  
                // 若是爲真則找到相應的位置  
                if (mid <= high) {  
                    return mid;  
                } else {  
                    // 出現這種狀況是查找到補充的元素  
                    // 而補充的元素與high位置的元素同樣  
                    return high;  
                }  
            }  
        }  
        return -1;  
    }    
複製代碼

Python:

MAXSIZE = 20

def fibonacci():  # 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
    f = [0] * MAXSIZE
    f[0] = 1
    f[1] = 1
    for i in range(2, MAXSIZE):
        f[i] = f[i-1] + f[i-2]
    return f

def fibonacciSearch(array, value):
    low, mid, high = 0, 0, len(array)-1
    k = 0
    f = fibonacci()
    while len(array) > f[k]-1:
        k += 1
    temp = array + [array[-1] * (f[k]-1-len(array))]
    while low <= high:
        mid = low + f[k-1] - 1
        if temp[mid] > value:
            high = mid - 1
            k = k - 1
        elif temp[mid] < value:
            low = mid + 1
            k = k - 2
        else:
            if mid <= high: # 若是在high位前,則返回mid位置,不然返回high位置
                return mid
            else:
                return high
    return -1

if __name__ == '__main__':
    a = [1, 3, 5, 6, 7, 88]
    print(fibonacciSearch(a, 2))

複製代碼

5.樹表查找

5.1 最簡單的樹表查找算法——二叉樹查找算法

基本思想:

這個算法的查找效率很高,可是若是使用這種查找方法要首先建立樹。

二叉查找樹(BinarySearch Tree,也叫二叉搜索樹,或稱二叉排序樹Binary Sort Tree)或者是一棵空樹,或者是具備下列性質的二叉樹:

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

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

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

二叉查找樹性質:

對二叉查找樹進行中序遍歷,便可獲得有序的數列。

有關二叉查找樹的查找、插入、刪除等操做的詳細講解,請移步淺談算法和數據結構: 七 二叉查找樹

複雜度分析:

它和二分查找同樣,插入和查找的時間複雜度均爲O(logn),可是在最壞的狀況下仍然會有O(n)的時間複雜度。緣由在於插入和刪除元素的時候,樹沒有保持平衡(好比,咱們查找上圖(b)中的「93」,咱們須要進行n次查找操做)。咱們追求的是在最壞的狀況下仍然有較好的時間複雜度,這就是平衡查找樹設計的初衷。

基於二叉查找樹進行優化,進而能夠獲得其餘的樹表查找算法,如平衡樹、紅黑樹等高效算法。

5.2 平衡查找樹之2-3查找樹(2-3 Tree)

https://riteme.github.io/blog/2016-3-12/2-3-tree-and-red-black-tree.html

2-3查找樹定義:和二叉樹不同,2-3樹運行每一個節點保存1個或者兩個的值。對於普通的2節點(2-node),他保存1個key和左右兩個本身點。對應3節點(3-node),保存兩個Key,2-3查找樹的定義以下:

1)要麼爲空,要麼:

2)對於2節點,該節點保存一個key及對應value,以及兩個指向左右節點的節點,左節點也是一個2-3節點,全部的值都比key要小,右節點也是一個2-3節點,全部的值比key要大。

3)對於3節點,該節點保存兩個key及對應value,以及三個指向左中右的節點。左節點也是一個2-3節點,全部的值均比兩個key中的最小的key還要小;中間節點也是一個2-3節點,中間節點的key值在兩個跟節點key值之間;右節點也是一個2-3節點,節點的全部key值比兩個key中的最大的key還要大。

2-3查找樹的性質:

1)若是中序遍歷2-3查找樹,就能夠獲得排好序的序列;

2)在一個徹底平衡的2-3查找樹中,根節點到每個爲空節點的距離都相同。(這也是平衡樹中「平衡」一詞的概念,根節點到葉節點的最長距離對應於查找算法的最壞狀況,而平衡樹中根節點到葉節點的距離都同樣,最壞狀況也具備對數複雜度。) 複雜度分析:

2-3樹的查找效率與樹的高度是息息相關的。

距離來講,對於1百萬個節點的2-3樹,樹的高度爲12-20之間,對於10億個節點的2-3樹,樹的高度爲18-30之間。

對於插入來講,只須要常數次操做便可完成,由於他只須要修改與該節點關聯的節點便可,不須要檢查其餘節點,因此效率和查找相似。

這裏寫圖片描述

5.3 平衡查找樹之紅黑樹(Red-Black Tree)

可是2-3樹實現起來比較複雜,因而就有了一種簡單實現2-3樹的數據結構,即紅黑樹(Red-Black Tree)。

紅黑樹的定義:

紅黑樹是一種具備紅色和黑色連接的平衡查找樹,同時知足:

  • 紅色節點向左傾斜

  • 一個節點不可能有兩個紅色連接

  • 整個樹徹底黑色平衡,即從根節點到因此葉子結點的路徑上,黑色連接的個數都相同。

紅黑樹的性質:整個樹徹底黑色平衡,即從根節點到因此葉子結點的路徑上,黑色連接的個數都相同(2-3樹的第2)性質,從根節點到葉子節點的距離都相等)。

這裏寫圖片描述

複雜度分析:

最壞的狀況就是,紅黑樹中除了最左側路徑所有是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2倍。

下圖是一個典型的紅黑樹,從中能夠看到最長的路徑(紅黑相間的路徑)是最短路徑的2倍:   

這裏寫圖片描述

紅黑樹這種數據結構應用十分普遍,在多種編程語言中被用做符號表的實現,如:

  • Java中的java.util.TreeMap,java.util.TreeSet;

  • C++ STL中的:map,multimap,multiset;

  • .NET中的:SortedDictionary,SortedSet 等。

5.4 B樹和B+樹(B Tree/B+ Tree)

廣泛運用在數據庫和文件系統。

B樹能夠看做是對2-3查找樹的一種擴展,即他容許每一個節點有M-1個子節點。

  • 根節點至少有兩個子節點

  • 每一個節點有M-1個key,而且以升序排列

  • 位於M-1和M key的子節點的值位於M-1 和M key對應的Value之間

  • 其它節點至少有M/2個子節點

能夠看到B樹是2-3樹的一種擴展,他容許一個節點有多於2個的元素。B樹的插入及平衡化操做和2-3樹很類似,這裏就不介紹了。

下面是往B樹中依次插入6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

這裏寫圖片描述

B+樹是對B樹的一種變形樹,它與B樹的差別在於:

  • 有k個子結點的結點必然有k個關鍵碼;

  • 非葉結點僅具備索引做用,跟記錄有關的信息均存放在葉結點中。

  • 樹的全部葉結點構成一個有序鏈表,能夠按照關鍵碼排序的次序遍歷所有記錄。

B和B+樹的區別在於,B+樹的非葉子結點只包含導航信息,不包含實際的值,全部的葉子結點和相連的節點使用鏈表相連,便於區間查找和遍歷。

這裏寫圖片描述

可是B樹也有優勢,其優勢在於,因爲B樹的每個節點都包含key和value,所以常常訪問的元素可能離根節點更近,所以訪問也更迅速。

  • Windows:HPFS文件系統;

  • Mac:HFS,HFS+文件系統;

  • Linux:ResiserFS,XFS,Ext3FS,JFS文件系統;

  • 數據庫:ORACLE,MYSQL,SQLSERVER等中。

樹表查找總結:

二叉查找樹平均查找性能不錯,爲O(logn),可是最壞狀況會退化爲O(n)。在二叉查找樹的基礎上進行優化,咱們可使用平衡查找樹。平衡查找樹中的2-3查找樹,這種數據結構在插入以後可以進行自平衡操做,從而保證了樹的高度在必定的範圍內進而可以保證最壞狀況下的時間複雜度。可是2-3查找樹實現起來比較困難,紅黑樹是2-3樹的一種簡單高效的實現,他巧妙地使用顏色標記來替代2-3樹中比較難處理的3-node節點問題。紅黑樹是一種比較高效的平衡查找樹,應用很是普遍,不少編程語言的內部實現都或多或少的採用了紅黑樹。

除此以外,2-3查找樹的另外一個擴展——B/B+平衡樹,在文件系統和數據庫系統中有着普遍的應用。

6. 分塊查找

解釋:https://blog.csdn.net/u013036274/article/details/49176027

屬於順序查找,分塊查找又稱索引順序查找,它是順序查找的一種改進方法。

這裏寫圖片描述

算法思想

將n個數據元素"按塊有序"劃分爲m塊(m ≤ n)。每一塊中的結點沒必要有序,但塊與塊之間必須"按塊有序";即第1塊中任一元素的關鍵字都必須小於第2塊中任一元素的關鍵字;而第2塊中任一元素又都必須小於第3塊中的任一元素,……

算法流程:

step1 先選取各塊中的最大關鍵字構成一個索引表;

step2 查找分兩個部分:先對索引表進行二分查找或順序查找,以肯定待查記錄在哪一塊中;而後,在已肯定的塊中用順序法進行查找。

7.哈希查找

單純論查找複雜度:對於無衝突的Hash表而言,查找複雜度爲O(1)(注意,在查找以前咱們須要構建相應的Hash表)。

常見的解決衝突的方法:拉鍊法和線性探測法。

詳細的介紹能夠參見:淺談算法和數據結構: 十一 哈希表。

附錄:

Java完整代碼,帶有測試用例:

public class test {
	public static int SequenceSearch(int a[], int value, int n) {
		for(int i=1;i<n;i++) {
			if(a[i]==value) {
				return i;
			}
		}
		return -1;
	}
	
	public static int BinarySearch(int a[], int value, int n) {
		int low = 0;
		int high = n-1;
		int mid = 0;
		while (low <= high) {
			mid = low + (high - low) * 1/2;
			if(a[mid] == value) {
				return mid;
			}
			if(a[mid] > value) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		return -1;
	}
	
	public static int BinarySearchDuplicate(int a[], int value, int n) {
		int low = 0;
		int high = n-1;
		int mid = 0;
		while (low <= high) {
			mid = low + (high - low) * 1/2;
			if(a[mid] >= value) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		return low;
	}
	
	public static int InsertionSearch(int a[], int value, int n) {
		int low = 0;
		int high = n-1;
		int mid = 0;
		while (low <= high) {
			mid = low + (high-low) * (value-a[low]) / (a[high]-a[low]);
			if(a[mid] == value) {
				return mid;
			}
			if(a[mid] > value) {
				high = mid - 1;
			}
			else {
				low = mid + 1;
			}
		}
		return -1;
	}
	
	public final static int MAXSIZE = 20; // fib length
    public static int[] fibonacci() {  
        int[] f = new int[MAXSIZE];  
        int i = 0;  
        f[0] = 1;  
        f[1] = 1;  
        for (i = 2; i < MAXSIZE; i++) {  
            f[i] = f[i - 1] + f[i - 2];  
        }  
        return f;  
    }  
  
    public static int fibonacciSearch(int[] a, int value, int n) {  
        int low = 0;  
        int high = n - 1;  
        int mid = 0;  
  
        // 斐波那契分割數值下標  
        int k = 0;  
        // 序列元素個數  
        int i = 0;  
        // 獲取斐波那契數列  
        int[] f = fibonacci();  
        // 獲取斐波那契分割數值下標  
        while (n > f[k] - 1) {  
            k++;  
        }  
  
        // 建立臨時數組  
        int[] temp = new int[f[k] - 1];  
        for (int j = 0; j < n;j++) { 
        	temp[j] = a[j];  
        }  
  
        // 序列補充至f[k]個元素  
        // 補充的元素值爲最後一個元素的值  
        for (i = n; i < f[k] - 1; i++) {  
            temp[i] = temp[high];  
        }  
  
//        for (int j : temp) {  
//            System.out.print(j + " ");  
//        }  
//        System.out.println();  
  
        while (low <= high) {  
            // low:起始位置  
            // 前半部分有f[k-1]個元素,因爲下標從0開始  
            // 則-1 獲取 黃金分割位置元素的下標  
            mid = low + f[k - 1] - 1;  
  
            if (temp[mid] > value) {  
                // 查找前半部分,高位指針移動  
                high = mid - 1;  
                // (所有元素) = (前半部分)+(後半部分)  
                // f[k] - 1 = f[k-1] -1 + f[k-2] -1  
                // 由於前半部分有f[k-1]個元素,因此 k = k-1  
                k = k - 1;  
            } else if (temp[mid] < value) {  
                // 查找後半部分,高位指針移動  
                low = mid + 1;  
                // (所有元素) = (前半部分)+(後半部分)  
                // f[k] -1 = f[k-1] -1 + f[k-2] - 1  
                // 由於後半部分有f[k-2]個元素,因此 k = k-2  
                k = k - 2;  
            } else {  
                // 若是爲真則找到相應的位置  
                if (mid <= high) {  
                    return mid;  
                } else {  
                    // 出現這種狀況是查找到補充的元素  
                    // 而補充的元素與high位置的元素同樣  
                    return high;  
                }  
            }  
        }  
        return -1;  
    }    

    public static void main(String[] args) {
    	int[] a = {1,5,2,7,3,9};
    	int[] b = {1,3,5,6,7,88};
    	int[] c = {1,3,5,7,7,88};//帶重複元素
    	int value = 5;
    	int n1 = a.length;
    	int n2 = b.length;
    	int n3 = c.length;
//    	int result = SequenceSearch(a, value, n1);
//    	int result = BinarySearch(b, value, n2);
//    	int result = BinarySearchDuplicate(c, value, n3);
//    	int result = InsertionSearch(b, value, n2);
    	int result = fibonacciSearch(b, value, n2);
    	System.out.println(result);
    }
}
複製代碼

主要參考網頁

http://www.cnblogs.com/maybe2030/p/4715035.html#_label6

-----正文結束-----

全複習手冊文章導航:經過如下兩種途徑查看

知識點複習手冊文章推薦

關注我

我是蠻三刀把刀,目前爲後臺開發工程師。主要關注後臺開發,網絡安全,Python爬蟲等技術。

來微信和我聊聊:yangzd1102

Github:github.com/qqxx6661

原創博客主要內容

  • 筆試面試複習知識點手冊
  • Leetcode算法題解析(前150題)
  • 劍指offer算法題解析
  • Python爬蟲相關技術分析和實戰
  • 後臺開發相關技術分析和實戰

同步更新如下博客

1. Csdn

blog.csdn.net/qqxx6661

擁有專欄:

  • Leetcode題解(Java/Python)
  • Python爬蟲實戰
  • Java程序員知識點複習手冊

2. 知乎

www.zhihu.com/people/yang…

擁有專欄:

  • Java程序員面試複習手冊
  • LeetCode算法題詳解與代碼實現
  • 後臺開發實戰

3. 掘金

juejin.im/user/5b4801…

4. 簡書

www.jianshu.com/u/b5f225ca2…

我的公衆號:Rude3Knife

我的公衆號:Rude3Knife

若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章
相關標籤/搜索