演算法之搜尋(Java版)-持續更新補充

kissjz發表於2018-08-06

一、順序查詢

順序查詢對序列本身沒有要求(比如不需要是已經排序好的),也不僅限於數字、字元,也可以用於字首,物件資訊的關鍵資訊的匹配(比如查詢指定id的相應資訊)。
衡量查詢效能的一個指標是————ASL(Average Search Length),ASL=Pi乘Ci,Pi是查詢第i個元素的概率,Ci是找到第i個已經比較過次數。
哨兵方式的順序查詢相比較基礎的順序查詢在迴圈的比較部分減少了一般。

//1. 順序查詢
public class SequentialSearch {
    private int[] array;
    
    public SequentialSearch(int[] array) {
        this.array = array;
    }
    
    public int search(int key) {
        for(int i = 0; i < array.length; i++) {
            if(array[i] == key) {
                return i;
            }
        }
        return -1;
    }
}
//2. 哨兵方式順序查詢
public class Search2 {
    private int[] array;
    
    public Search2(int[] array) {
        this.array = array;
    }
    
    public int search(int key) {
        if(key == array[0]) {
            return 0;
        }
        int temp = array[0];
        array[0] = key;
        int index = array.length-1;
        while(array[index] != key) {
            index--;
        }
        array[0] = temp;
        if(index == 0) {
            return -1;
        }else {
            return index;
        }
    }
}

二、二分查詢

如果是順序查詢,7個數最多可能會比較7次,但用二分查詢,最多隻要3次就能OK。時間複雜度是O(logn)(底數為2)。

二分查詢的優化————插值查詢

如果資料範圍是1~100000,讓你找10,那麼就不一定要從中間找起了。可以三分之一,四分之一處查詢,比如1~10,待查為3,那可以從前面三分之一為劃分點。對於要查詢的位置有個精確的計算公式P=low+(key-a[low])/(a[high]-a[low])*(high-low)

//1. 二分查詢遞迴與非遞迴的實現
public class BinarySearch {
    private int[] array;
    
    public BinarySearch(int[] array) {
        this.array = array;
    }
    
    public int searchRecursion(int target) {
        if(array == null) {
            return -1;
        }
        return  searchRecursion(target, 0, array.length - 1);
    }
    
    public int search(int target) {
        if(array == null) {
            return -1;
        }
        int start = 0;
        int end = array.length - 1;
        while(start <= end) {
            int mid = start + (end - start) / 2;
            if(array[mid] == target) {
                return mid;
            }else if(target < array[mid]) {
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
        return -1;
    }
    
    private int searchRecursion(int target, int start, int end) {
        if(start > end) {
            return -1;
        }
        int mid = start + (end - start) / 2;
        if(array[mid] == target) {
            return mid;
        }else if(array[mid] < target) {
            return searchRecursion(target, mid + 1, end);
        }else {
            return searchRecursion(target, start, mid -1);
        }
    }
}
//2. 二分插入排序
public class BinaryInsertSort {
    private int[] array;
    
    public BinaryInsertSort(int[] array) {
        this.array = array;
    }
    
    public void sort() {
        int length = array.length;
        for(int i = 1; i < length; i++) {
            int temp = array[i];
            int insertIndex = binarySearch(i - 1, temp);
            if(insertIndex != i) {
                for(int j = i; j > insertIndex; j--) {
                    array[j] = array[j - 1];
                }
                array[insertIndex] = temp;
            }
        }
    }
    public void print() {
        for(int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
    private int binarySearch(int end, int target) {
        int start = 0;
        int mid = -1;
        while(start <= end) {
            mid = start + (end - start) / 2;
            if(array[mid] > target) {
                end = mid - 1;
            }else {
                //如果相等,也插入到後面
                start = mid + 1;
            }
        }
        return start;
    }
}

三、楊氏矩陣的的查詢

楊氏矩陣就是行列遞增的矩陣。

楊氏矩陣的操作

  1. 插入。插入一個數,需要移動其他元素
  2. 刪除。給定x,y座標,刪除那個數,伴隨其他元素移動,怎樣移動操作最少?
  3. 查詢t是否存在於矩陣中。這也是這篇部落格裡所要關注的。
  4. 返回第k大的數。涉及到堆查詢,後續部落格再細說。

關於查詢t是否存在於矩陣,書中給了幾種實現的方法:

  1. 遞迴實現和非遞迴實現
    優化:
  2. 每次不都從每行的第一個數開始查詢,左右上下進行比較然後查詢。
  3. 分治法。楊氏矩陣行列是遞增的,那麼對角線也是遞增的,可以利用對角線劃分的區域來縮小要查詢數的範圍。(實現略)
  4. 定位查詢法。先定位到第一行最右的數,然後只需要往下走,往左走兩種操作即可,相比方法2省掉了往右走。
public class YoungSearch {
    private int[][] array;
    
    public YoungSearch(int[][] array) {
        this.array = array;
    }
    //1.遞迴實現
    public boolean recursionSearch(int x, int y, int target) {
        if(x == array.length || y == array[0].length) {
            return false;
        }
        if(target < array[x][y]) {
            return false;
        }
        if(target == array[x][y]) {
            System.out.println(String.format("x: %d, y: %d", x, y));
            return true;
        }
        return recursionSearch(x + 1, y, target) || recursionSearch(x, y + 1, target);
    }
    //非遞迴實現
    public boolean search(int target) {
        for(int i = 0; i < array.length; i++) {
            for(int j = 0; j < array[0].length && target >= array[i][j]; j++) {
                if(target == array[i][j]) {
                    System.out.println(String.format("x: %d y: %d", i, j));
                    return true;
                }
            }
        }
        return false;
    }
    //2.簡單優化(向左/右/下走)
    public boolean search2(int target) {
        int width = array[0].length;
        int height = array.length;
        if(target >= array[0][0]) {
        int i = 0;
        for(; i < width && target >= array[0][i]; i++) {
            if(target == array[0][i]) {
                System.out.println(String.format("x: %d, y: %d", 0, i));
                return true;
            }
        }
        if(i > width - 1) {
            i--;
        }
        //迴圈向下查詢
        for(int j = 1; j < height; j++) {
            if(target == array[j][i]) {
                System.out.println(String.format("x: %d, y: %d", j, i));
                return true;        
            }else if(target < array[j][i]) {
                for(; i >= 0; i--) {
                    if(target == array[j][i]) {
                        System.out.println(String.format("x: %d, y: %d", j, i));
                        return true;
                    }else if(target > array[j][i]) {
                        break;
                    }
                }
                if(i < 0) {
                    i++;
                }
            }else if(target > array[j][i]) {
                for(; i < width; i++) {
                    if(target == array[j][i]){
                        System.out.println(String.format("x: %d, y: %d", j, i));
                        return true; 
                    }else if(target < array[j][i]) {
                        break;
                    }
                }
                if(i > width - 1) {
                    i--;
                }
            }
        }
        }
        return false;
    }
    //3.進一步優化(從第一行最右邊的數開始,只需要向下和向左兩個操作)
    public boolean search3(int target) {
        int i = 0;
        int j = array[0].length - 1;
        int temp = array[i][j];
        while(true) {
            if(target == temp) {
                System.out.println(String.format("x: %d, y: %d", i, j));
                return true;
            }else if(j > 0 && target < temp){
                temp = array[i][--j];
            }else if(i < array.length - 1 && target > temp) {
                temp = array[++i][j];
            }else {
                return false;
            }
        }
    }
}

四、分塊查詢

對於待查詢的資料列表來說,如果元素變動很少,那麼可以先進行排序再查詢。但如果這個資料經常需要新增元素,那麼每次查詢前都需要排序,這並不是一個好的選擇。
就有了分塊查詢,這個概念再學資料庫的時候聽過。分塊查詢裡有索引表和分塊這兩個概念。索引表就是幫助分塊查詢的一個分塊依據,就是一個陣列,用來儲存每塊最大的儲存值(範圍上限);分塊就是通過索引表把資料分為幾塊。
原理:當需要增加一個元素的時候,先根據索引表,獲取這個元素應該在那一塊,然後直接把元素加入到相應的塊裡,而塊內的元素直接不需要有序
從上面可知,分塊查詢只需要索引表有序,每一個塊裡的元素可以是無序的,但第i塊的每個元素一定比第i-1塊的每一個元素大(小)。當索引表很大的時候,可以對索引表進行二分查詢,鎖定塊的位置,然後對塊內的元素進行順序查詢。總效能不如二分查詢,但強過順序查詢,更好的是不需要數列完全有序。
舉個例子,比如索引表為【10,20,30】,分塊一【2,1,4,2】分塊二【19,15,18,】分塊三【22,27,23】,現在要增加22這個數,直接根據索引表把22放到分塊三最後就行了【22,27,23,22】。

可以看出,分塊查詢同時有順序查詢和二分查詢的有點————不需要有序、速度快。

應用場景

視訊網站對使用者觀看行為記錄,每個使用者分別觀看了一個視訊多久,如果對每條這樣的記錄都放到一個表裡,那太多了,可以根據具體業務做分表,一天一個表,表名如t_user_watch_xxx_20180806,儲存查詢的時候就可以根據時間去做一個表的分塊,在查詢詳細的記錄。

//分塊查詢
import java.util.ArrayList;

public class BlockSearch {
    private int[] index;
    private ArrayList<ArrayList<Integer>> list;
    
    public BlockSearch(int[] index) {
        this.index = index;
        list = new ArrayList<ArrayList<Integer>>();
        for(int i = 0; i < index.length; i++) {
            list.add(new ArrayList<Integer>());
        }
    }
    
    public void insert(Integer value) {
        int i = binarySearch(value);
        list.get(i).add(value);
        
    }
    
    public boolean search(int data) {
        int i = binarySearch(data);
        for(int j = 0; j < list.get(i).size(); j++) {
            if(data == list.get(i).get(j)) {
                return true;
            }
        }
        return false;
    }
    public void printAll() {
        for(int i = 0; i < list.size(); i++) {
            ArrayList<Integer> l = list.get(i);
            System.out.println("ArrayList: " + i +  ":");
            for(int j = 0; j < l.size(); j++) {
                System.out.println(l.get(j));
            }
        }
    }
    
    private int binarySearch(int target) {
        int start = 0;
        int end = index.length - 1 ;
        int mid = -1;
        while(start <= end) {
            mid = (start + end) / 2;
            if(target == index[mid]) {
                return mid;
            }else if(target < index[mid]) {
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
        return start;
    }
}


相關文章