查找算法

1. 基本概念

查找結構一般有四種操做:查詢某個特定元素是否在表中,檢索知足條件的某個特定元素的各類屬性,在查找表中插入某一數據元素,從查找表中刪除某個元素html

  • 只涉及前兩種操做的稱爲靜態查找,包括順序查找,二分(折半)查找,散列查找等,涉及到後面兩種操做的稱爲動態查找,包括二叉排序樹查找,散列查找等
  • 平均查找長度:全部查找過程當中關鍵碼比較次數的平均值,衡量查找算法效率的主要指標

2. 折半查找(二分查找)

  • 二分查找僅適用於事先已經排序過的線性表順序存儲結構(須要方便定位查找區域,存儲結構具備隨機存儲的特色)
  • 二分查找的時間複雜度爲O(log2N),查找成功或者查找不成功,最壞狀況下須要log2N + 1次檢索

3. 鍵樹

鍵樹稱爲數字查找樹,是度大於等於2的書,書的每一個結點包含的是組成關鍵字的符號,若是關鍵字是數值,則結點包含一個數位,若是關鍵字是單詞,則結點包含一個字母字符,以下圖所示。node

鍵樹.png

3.1 雙鏈樹(樹的孩子-兄弟鏈來表示鍵樹)

每一個Node有三個域:ios

  • symbol域:存儲關鍵字的一個字符
  • son域:存儲指向第一個子樹的根針
  • brother域:指向右兄弟指針
    查找過程是,從根結點出發,順着son查找,若是相等,繼續下一個son。不然沿着brother查找。直到到了空指針爲止。此時若仍未完成key的匹配,查找不成功。

雙鏈表.png

3.2 字典樹

字典樹,又叫作Trie樹,單詞查找樹,或者前綴樹,是一種用於快速檢索的多叉樹結構,樹的每一個結點包含d個指針域,d是關鍵字符的基,好比英文字母的字典樹是26叉樹,數字字典樹是10叉樹算法

字典樹1.png

字典樹2.jpg

  • Trie樹基本性質:
    1. 根結點不包含字符,除了根結點以外每一個結點包含一個字符
    2. 從根結點到某一個葉子節點,路徑上通過的字符鏈接起來,爲一個字符串
    3. 每一個結點所包含的子結點包含的字符串不一樣
  • Trie樹經過字符串的公共前綴來下降開銷,它的優勢是最大限度減小無謂的字符串比較,其典型應用是用於統計和排序大量字符串
  • Trie的缺點是:若是存在大量字符串,而這些字符串基本沒有公共前綴,那麼Trie樹將很是消耗內存。編程

  • Trie樹的實現:數組

#include<iostream>
#include<cstdlib>
using namespace std;

const int branchNum = 26;
struct Trie_node{
    bool isStr; // 記錄此處是否構成一個串
    Trie_node * next[branchNum]; //指向各個子樹的指針
    
    // 初始化
    Trie_node() :isStr(false){ memset(next, NULL, sizeof(next)); }
};

class Trie{
private:
    Trie_node *root;
public:
    Trie(){ root = new Trie_node(); }
    void insert(const char * str);
    bool search(char * str);
    void deleteTrie(Trie_node * root);
    Trie_node * getTrie(){ return root; }
};

void Trie::insert(const char * str){
    Trie_node * location = root;
    while (*str){
        // 若是不存在則創建結點
        if (location->next[*str - 'a'] == NULL){
            Trie_node *temp = new Trie_node();
            location->next[*str - 'a'] = temp;
        }
        location = location->next[*str - 'a'];
        // 每插入一步,至關於新串路過,指針移動
        str++;
    }
    location->isStr = true;//標記一個串

    // Trie *temp = (Trie *) malloc(sizeof(Trie));
    // for(int i =0;i<26,i++)
    // temp->next[i] = NULL:
}


bool Trie::search(char * str){
    Trie_node * location = root;
    while (*str && location){  // *str!='\0'
        location = location->next[*str - 'a'];
        str++;
    }
    return (location != NULL && location->isStr);
}

void Trie::deleteTrie(Trie_node *root){
    for (int i = 0; i < branchNum; i++){
        if (root->next[i] != NULL)
            deleteTrie(root->next[i]);
    }
    delete(root);
}

int main(){
    char *str = "abcdefg";
    Trie trie;
    trie.insert(str);
    if (trie.search(str)) cout << "true";
    system("pause");
    return 0;
}
  • Trie樹的應用:
    1. 給定一個單詞a,若是經過交換字幕的順序能夠獲得另外的單詞b,那麼稱a和b是是兄弟單詞,如今要求給一個字典,用戶輸入一個單詞,能夠根據字典找到該單詞的兄弟單詞,要求時間和空間效率儘量高。
      答:解法一:hash_map 和鏈表,定義一個ID,使得兄弟單詞有相同的id,不是兄弟單詞有不一樣的id,這個id能夠是將單詞從小到大排序後做爲其ID,也能夠是將單詞各個字母對應一個質數,將質數相乘當作hash id。建立一個hash_map,它的key爲單詞的id,value爲兄弟單詞鏈表的起始地址。全部的兄弟單詞存放在一個鏈表中。當須要找到該兄弟單詞時,只須要計算單詞id,而後到map中找到對應的鏈表便可。
      解法二:利用Trie樹,單詞插入Trie樹前,先按照字母排序,將排序後的字母放入Trie樹,在樹的結點中增長一個vector,用於記錄全部的兄弟單詞安全

    2. 數據文件A:1000萬條關鍵詞,數據文件B:關鍵詞與ID的對應表,100萬條左右,如今將A中關鍵詞替換爲ID,可用內存爲1GB,硬盤不限
      答:使用文件B生成Trie樹,而後用Trie樹實現關鍵詞對ID 的快速查找,Trie_node結點中包含ID信息,主要是實際應用中關鍵詞之間可能有不少前綴相同現象,因此空間耗費不會很高
  • 參考:
    1. 海量數據處理之Tire樹(字典樹)
    2. 字典樹(Trie樹)實現與應用

4.後綴樹和後綴數組(suffix tree)

5. 哈希表

  • 哈希表的設計目的:空間換取時間,基於快速存取的角度設計,根據關鍵字直接訪問的數據結構,經過某種規則將關鍵字映射到數組某個位置,這個映射規則稱爲哈希函數/散列函數
  • 哈希衝突:不一樣的關鍵字經過哈希函數計算獲得了相同的數組下標,在設計hash函數應該儘可能避免這樣的衝突,同時還要設計處理好可能產生的衝突。
  • 哈希函數:若是兩個hash值不一樣,那麼對應的這兩個hash值的原始輸入是不相同的,可是兩個hash值相同,原始輸入的兩個key值不必定是相同的。
    1. 經常使用hash函數:直接定址法,數字分析法,平方取中法,除留餘數法,摺疊法
    2. MD四、MD5(更安全)、SHA-1;(用於文件檢驗,數字簽名,鑑權協議)
  • 處理衝突的方法:鏈地址法(同義詞存儲在同一個線性鏈表中),開放定址法,再散列法(發生衝突時利用另外一個哈希函數從新計算),創建一個公共溢出區(填入溢出表)

6. 一致性哈希

  • 集羣問題(待續)

7. 海量數據處理

7.1 hash映射-分治處理

對大文件處理時,若文件過大,沒法一次性讀入內存,將hash映射將文件元素映射到不一樣小文件中,在依次處理各個小文件,最後合併處理結果。
例子:a、b文件,各存放50個url,請找出a、b共同的URL?
答:遍歷a,hash(url)%1024,將a分別存放在1024個文件中,對b進行一樣操做,處理後,**全部可能相同的url都在對應的小文件中,如a0對應b0,而後分別對小文件進行遍歷搜索處理等便可數據結構

7.2 Top K問題

  • 常見問題:最大的k個數或者最小的k個數
  • 若是數據可以一次性讀入內存,快排的一次排序,時間複雜度爲O(n)
  • 若是海量數據,咱們一般使用,(最大k個數爲小根堆,最小K個數使用大根堆)

【編程之美】讀書筆記:尋找最大的K個數
《編程之美》——尋找最大的K個數函數

// 快排 咱們基於數組的第K個數字來調整時,最小的k個數
void getleastNumber(int *input, int n, int *output, int k){
    if (input == NULL || output == NULL || k>n || n <= 0 || k <= 0)
        return;

    int start = 0;
    int end = n - 1;
    int index = Partition(input,  start, end);
    while (index != k - 1){
        if (index > k - 1){
            end = index - 1;
            // 一趟快排
            index = Partition(input, start, end);
        }
        else{
            start = index + 1;
            index = Partition(input,  start, end);
        }
    }

    for (int i = 0; i < k; ++i)
        output[i] = input[i];
}

int Partition(int *a, int low, int high){
    int pivot = a[low];
    while (low < high){
        while (low < high && a[high] >= pivot) --high;
        a[low] = a[high];
        while (low < high && a[low] <= pivot) ++low;
        a[high] = a[low];
    }
    a[low] = pivot;
    return low;
}
// 基於multiset的實現
void getLeastNumbers(const vector<int> &data, multiset<int, int> & leastNumbers, int k){
    leastNumbers.clear();

    if (k < 1 || data.size() < k)
        return;

    vector<int>::const iterator = data.begin();
    for (; iter != data.end(); ++iter){
        if (leastNumbers.size() < k)
            leastNumbers.insert(*iter);
        else{
            mutiset<int, int>::iterator setiter = leastNumbers.begin();
            if (*iter < *(leastNumbers.begin())){
                leastNumbers.erase(setiter);
                leastNumbers.insert(iter);
            }
        }
    }
}

7.3 Bit-map

  • 使用位數組來表示某些元素是否存在,採用bit做爲單位存儲數據
  • 時間複雜度爲O(n),以空間換時間,根據具體狀況須要n位的串url

  • 例1: 40億個不重複的unsigned int的值,沒排過序,再給一個數,如何判斷這個數是否在40個億數中?
    答:unsigned int 最多2^32個數,須要申請512M的內存,一個bit位表明一個unsigned int的值,讀入40億個數,設置對應bit位,讀入數,查詢相應的位

  • 例2:4,7,2,5,3排序 答:申請一個8位byte位,讀入第一個值4,則將byte第5位置1,而後依次置位,最後遍歷bit區域,將該位是1的編號輸出

相關文章
相關標籤/搜索