查找

在面試的時候二分查找是用的比較多一種查找算法,如何在面試官面前快速準確得的寫出代碼決定你是否可以被錄取。之前一直覺得二分查找很簡單,因此就沒怎麼重視,可是真要在面試官面前對着黑板手寫出來,仍是漏洞百出。今天本身在電腦面前敲出了二分查找的代碼,也花了將近半個小時。對於這種基礎排序查找算法,仍是得好好重視。 html

  1. 二分查找的時間複雜度是O(log(n)),最壞狀況下的時間複雜度是O(n)。
  2. 二分查找的一個條件是待查詢的數組是有序的,咱們假設這裏的數組是升序的。
  3. 二分查找的主要思路就是設定兩個指針start和end分別指向數組元素的收尾兩端,而後比較數組中間結點arry[mid]和待查找元素。若是待查找元素小於中間元素,那麼代表帶查找元素在數組的前半段,那麼將end=mid-1,若是待查找元素大於中間元素,那麼代表該元素在數組的後半段,將start=mid+1;若是中間元素等於待查找元素,那麼返回mid的值。

二分查找可使用遞歸非遞歸的方法來解決,下面給出代碼實例。ios

#include<iostream>
#include<stdlib.h>
using namespace std;


//不適用遞歸,若是存在返回數組位置,不存在則返回-1
int BinarySearch(int arry[],int len,int value)
{
    //若是傳入的數組爲空或者數組長度<=0那麼就返回-1。防護性編程
    if(arry==NULL||len<=0)
        return -1;

    int start=0;
    int end=len-1;
    
    while(start<=end)//判斷清是否有=
    {
        int mid=start+(end-start)/2;
        if(arry[mid]==value)
            return mid;
        else if(value<arry[mid])
            end=mid-1;
        else
            start=mid+1;
    }
    return -1;

}

//改進思路:1.不要傳參,而是傳引用調用,減小垃圾
//        2.使用模板
int BinarySearchRecursion(int arry[],int value,int start,int end)
{
    if(start>end)
        return -1;

    int mid=start+(end-start)/2;
    if(arry[mid]==value)
        return mid;

    else if(value<arry[mid])
        return    BinarySearchRecursion(arry,value,start,mid-1);
    else
        return    BinarySearchRecursion(arry,value,mid+1,end);

}

int BinarySearchRecursion(int arry[],int len,int value)
{
    //若是傳入的數組爲空或者數組長度<=0那麼就返回-1。防護性編程
    if(arry==NULL||len<=0)
        return -1;
    return BinarySearchRecursion(arry,value,0,len-1);
}

void main()
{
    int arry[]={1,2,3,4,5,6,7,8};
    int len=sizeof(arry)/sizeof(int);

    int index=BinarySearch(arry,len,4);
    cout<<"index:"<<index<<endl;

    int index2=BinarySearchRecursion(arry,len,9);
    cout<<"index2:"<<index2<<endl;

    system("pause");
}

在上述遞歸的二分查找方法中:程序員

int BinarySearchRecursion(int arry[],int value,int start,int end)

咱們能夠發現這個方法中的後三個參數value,start,end採用的是傳值調用,只有第一個參數arry是傳址調用。咱們知道在效率方面,傳值調用要比傳址調用來的低,由於傳值調用要進行一次變量的拷貝,而傳址調用則是直接對這個變量進行操做。所以這裏咱們能夠將後面的三個參數改成傳址調用 面試

改進後的代碼實例以下:算法

int BinarySearchRecursion(int arry[],int &value,int &start,int &end)
{
    if(start>end)
        return -1;

    int mid=start+(end-start)/2;
    if(arry[mid]==value)
        return mid;

    else if(value<arry[mid])
    {
        end=mid-1;
        return BinarySearchRecursion(arry,value,start,end);
    }
    else
    {
        start=mid+1;
        return BinarySearchRecursion(arry,value,start,end);
    }
}

int BinarySearchRecursion(int arry[],int &len,int &value)
{
    //若是傳入的數組爲空或者數組長度<=0那麼就返回-1。防護性編程
    if(arry==NULL||len<=0)
        return -1;
    int start=0;
    int end=len-1;
    return BinarySearchRecursion(arry,value,start,end);
}

void main()
{
    int arry[]={1,2,3,4,5,6,7,8};
    int len=sizeof(arry)/sizeof(int);
    int especteNum1=4;
    int especteNum2=9;
    int index=BinarySearch(arry,len,especteNum1);
    cout<<"index:"<<index<<endl;

    int index2=BinarySearchRecursion(arry,len,especteNum2);
    cout<<"index2:"<<index2<<endl;

    system("pause");
}

http://www.cnblogs.com/xwdreamer/archive/2012/05/07/2487246.html編程

暴雪公司有個經典的字符串的hash公式 數組

先提一個簡單的問題,假若有一個龐大的字符串數組,而後給你一個單獨的字符串,讓你從這個數組中查找是否有這個字符串並找到它,你會怎麼作?  安全

有一個方法最簡單,老老實實從頭查到尾,一個一個比較,直到找到爲止,我想只要學過程序設計的人都能把這樣一個程序做出來,但要是有程序員把這樣的程序交給用戶,我只能用無語來評價,或許它真的能工做,但也只能如此了。 
最合適的算法天然是使用HashTable(哈希表),先介紹介紹其中的基本知識,所謂Hash,通常是一個整數,經過某種算法,能夠把一個字符串"壓縮" 成一個整數,這個數稱爲Hash,固然,不管如何,一個32位整數是沒法對應回一個字符串的,但在程序中,兩個字符串計算出的Hash值相等的可能很是小,下面看看在MPQ中的Hash算法 
數據結構

unsigned long HashString(char *lpszFileName, unsigned long dwHashType)  
{  
unsigned char *key = (unsigned char *)lpszFileName;  
unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;  
int ch;  
while(*key != 0)  
{  
ch = toupper(*key );  
seed1 = cryptTable[(dwHashType < < 8) ch] ^ (seed1 seed2);  
seed2 = ch seed1 seed2 (seed2 < < 5) 3;  
}  
return seed1;  
}  


Blizzard的這個算法是很是高效的,被稱爲"One-Way Hash",舉個例子,字符串"unitneutralacritter.grp"經過這個算法獲得的結果是0xA26067F3。 
是否是把第一個算法改進一下,改爲逐個比較字符串的Hash值就能夠了呢,答案是,遠遠不夠,要想獲得最快的算法,就不能進行逐個的比較,一般是構造一個哈希表(Hash Table)來解決問題,哈希表是一個大數組,這個數組的容量根據程序的要求來定義,例如1024,每個Hash值經過取模運算 (mod)對應到數組中的一個位置,這樣,只要比較這個字符串的哈希值對應的位置又沒有被佔用,就能夠獲得最後的結果了,想一想這是什麼速度?是的,是最快的O(1),如今仔細看看這個算法吧 
ide

int GetHashTablePos(char *lpszString, SOMESTRUCTURE *lpTable, int nTableSize)  
{  
int nHash = HashString(lpszString), nHashPos = nHash % nTableSize;  
if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString))  
return nHashPos;  
else  
return -1; //Error value  
}  


看到此,我想你們都在想一個很嚴重的問題:"假如兩個字符串在哈希表中對應的位置相同怎麼辦?",究竟一個數組容量是有限的,這種可能性很大。解決該問題的方法不少,我首先想到的就是用"鏈表",感謝大學裏學的數據結構教會了這個百試百靈的法寶,我碰到的不少算法均可以轉化成鏈表來解決,只要在哈希表的每一個入口掛一個鏈表,保存全部對應的字符串就OK了。 
事情到此彷佛有了完美的結局,假如是把問題獨自交給我解決,此時我可能就要開始定義數據結構而後寫代碼了。然而Blizzard的程序員使用的方法則是更精妙的方法。基本原理就是:他們在哈希表中不是用一個哈希值而是用三個哈希值來校驗字符串。 
中國有句古話"再一再二不能再三再四",看來Blizzard也深得此話的精髓,假如說兩個不一樣的字符串通過一個哈希算法獲得的入口點一致有可能,但用三個不一樣的哈希算法算出的入口點都一致,那幾乎能夠確定是不可能的事了,這個概率是1:18889465931478580854784,大概是10的 22.3次方分之一,對一個遊戲程序來講足夠安全了。 
如今再回到數據結構上,Blizzard使用的哈希表沒有使用鏈表,而採用"順延"的方式來解決問題,看看這個算法: 

int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)  
{  
const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;  
int nHash = HashString(lpszString, HASH_OFFSET);  
int nHashA = HashString(lpszString, HASH_A);  
int nHashB = HashString(lpszString, HASH_B);  
int nHashStart = nHash % nTableSize, nHashPos = nHashStart;  
while (lpTable[nHashPos].bExists)  
{  
if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)  
return nHashPos;  
else  
nHashPos = (nHashPos 1) % nTableSize;  
if (nHashPos == nHashStart)  
break;  
}  
return -1; //Error value  
}  


1. 計算出字符串的三個哈希值(一個用來肯定位置,另外兩個用來校驗) 
2. 察看哈希表中的這個位置 
3. 哈希表中這個位置爲空嗎?假如爲空,則確定該字符串不存在,返回 
4. 假如存在,則檢查其餘兩個哈希值是否也匹配,假如匹配,則表示找到了該字符串,返回 
5. 移到下一個位置,假如已經越界,則表示沒有找到,返回 
6. 看看是否是又回到了原來的位置,假如是,則返回沒找到 
7. 回到3 
怎麼樣,很簡單的算法吧,但確實是天才的idea, 其實最優秀的算法每每是簡單有效的算法。

舉例:

查找,也可稱檢索,是在大量的數據元素中找到某個特定的數據元素而進行的工做。查找是一種操做。

2、順序查找

針對無序序列的一種最簡單的查找方式。

時間複雜度爲O(n)。

3、折半查找

針對已排序序列的一種查找方式。而且只適用於順序存儲結構的序列。要求序列中的元素基本不變,在須要作刪除和插入操做的時候,會影響檢索效率。

時間複雜度爲O(logN)。

4、B樹

B樹又稱二叉排序樹(Binary Sort Tree)。

一、概念:

   它或者是一棵空樹;或者是具備下列性質的二叉樹:

  (1)若左子樹不空,則左子樹上全部結點的值均小於左子樹所在樹的根結點的值;

  (2)若右子樹不空,則右子樹上全部結點的值均大於右子樹所在樹的根結點的值;

  (3)左、右子樹也分別爲二叉排序樹;

二、B樹的查找:

時間複雜度與樹的深度的有關。

  步驟:若根結點的關鍵字值等於查找的關鍵字,成功。

  不然:若小於根結點的關鍵字值,遞歸查左子樹。

  若大於根結點的關鍵字值,遞歸查右子樹。

  若子樹爲空,查找不成功。

三、B樹的插入:

首先執行查找算法,找出被插結點的父親結點。

  判斷被插結點是其父親結點的左兒子仍是右兒子。將被插結點做爲葉子結點插入。

  若二叉樹爲空。則首先單獨生成根結點。

  注意:新插入的結點老是葉子結點,因此算法複雜度是O(h)。

四、B樹的刪除:

  若是刪除的結點沒有孩子,則刪除後算法結束;

  若是刪除的結點只有一個孩子,則刪除後該孩子取代被刪除結點的位置;

  若是刪除的結點有兩個孩子,則選擇該結點的後繼結點(該結點右孩子爲根的樹中的左子樹中的值最小的點)做爲新的根,同時在該後繼結點開始,執行前兩種刪除算法,刪除算法結束。

五、B+樹

一棵m階的B+樹知足下列條件:

(1)每一個結點最多m個孩子。

(2)除根結點和葉子結點外,其它每一個結點至少有ém/2ù個孩子。

(3)根結點至少有兩個孩子。

(4)全部的葉子結點在同一層,且包含了全部關鍵字信息。

(5)有k個孩子的分支結點包含k個關鍵字。

例如:

5、散列(hash)表

關鍵字:哈希函數、裝填因子、衝突、同義詞;

關鍵字和和存儲的地址創建一個對應的關係:

Add = Hash(key);

解決衝突方法:

開放定址法 – 探測方式:線性探測、二次探測。

分離連接法 – 利用鏈表的方式。

查找找效率不依賴於數據長度n,查找效率很是快,不少能達到O(1),查找的效率是a(裝填因子)的函數,而不是n的函數。所以無論n多大均可以找到一個合適的裝填因子以便將平均查找長度限定在一個範圍內。

舉例: 

相關文章
相關標籤/搜索