1. 程式人生 > >淺談演算法和資料結構: 十一 雜湊表

淺談演算法和資料結構: 十一 雜湊表

在前面的系列文章中,依次介紹了基於無序列表的順序查詢基於有序陣列的二分查詢平衡查詢樹,以及紅黑樹,下圖是他們在平均以及最差情況下的時間複雜度:

Different structure with different efficient

可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。

那麼有沒有查詢效率更高的資料結構呢,答案就是本文接下來要介紹了散列表,也叫雜湊表(Hash Table)

什麼是雜湊表

雜湊表就是一種以 鍵-值(key-indexed) 儲存資料的結構,我們只要輸入待查詢的值即key,即可查詢到其對應的值。

雜湊的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序陣列來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴充套件到可以處理更加複雜的型別的鍵。

使用雜湊查詢有兩個步驟:

  1. 使用雜湊函式將被查詢的鍵轉換為陣列的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被雜湊到同一個索引值的情況。所以雜湊查詢的第二個步驟就是處理衝突
  2. 處理雜湊碰撞衝突。有很多處理雜湊碰撞衝突的方法,本文後面會介紹拉鍊法和線性探測法。

雜湊表是一個在時間和空間上做出權衡的經典例子。如果沒有記憶體限制,那麼可以直接將鍵作為陣列的索引。那麼所有的查詢時間複雜度為O(1);如果沒有時間限制,那麼我們可以使用無序陣列並進行順序查詢,這樣只需要很少的記憶體。雜湊表使用了適度的時間和空間來在這兩個極端之間找到了平衡。只需要調整雜湊函式演算法即可在時間和空間上做出取捨。

雜湊函式

雜湊查詢第一步就是使用雜湊函式將鍵對映成索引。這種對映函式就是雜湊函式。如果我們有一個儲存0-M陣列,那麼我們就需要一個能夠將任意鍵轉換為該陣列範圍內的索引(0~M-1)的雜湊函式。雜湊函式需要易於計算並且能夠均勻分佈所有鍵。比如舉個簡單的例子,使用手機號碼後三位就比前三位作為key更好,因為前三位手機號碼的重複率很高。再比如使用身份證號碼出生年月位數要比使用前幾位數要更好。

在實際中,我們的鍵並不都是數字,有可能是字串,還有可能是幾個值的組合等,所以我們需要實現自己的雜湊函式。

1. 正整數

獲取正整數雜湊值最常用的方法是使用除留餘數法。即對於大小為素數M的陣列,對於任意正整數k,計算k除以M的餘數。M一般取素數。

2. 字串

將字串作為鍵的時候,我們也可以將他作為一個大的整數,採用保留除餘法。我們可以將組成字串的每一個字元取值然後進行雜湊,比如

public int GetHashCode(string str)
{
    char[] s = str.ToCharArray();
    int hash = 0;
    for (int i = 0; i < s.Length; i++)
    {
        hash = s[i] + (31 * hash); 
    }
    return hash;
}

上面的雜湊值是Horner計算字串雜湊值的方法,公式為:

h = s[0] · 31L–1 + … + s[L – 3] · 312 + s[L – 2] · 311 + s[L – 1] · 310

舉個例子,比如要獲取”call”的雜湊值,字串c對應的unicode為99,a對應的unicode為97,L對應的unicode為108,所以字串”call”的雜湊值為 3045982 = 99·313 + 97·312 + 108·311 + 108·310 = 108 + 31· (108 + 31 · (97 + 31 · (99)))

如果對每個字元去雜湊值可能會比較耗時,所以可以通過間隔取N個字元來獲取哈西值來節省時間,比如,可以 獲取每8-9個字元來獲取雜湊值:

public int GetHashCode(string str)
{
    char[] s = str.ToCharArray();
    int hash = 0;
    int skip = Math.Max(1, s.Length / 8);
    for (int i = 0; i < s.Length; i+=skip)
    {
        hash = s[i] + (31 * hash);
    }
    return hash;
}

但是,對於某些情況,不同的字串會產生相同的雜湊值,這就是前面說到的雜湊衝突(Hash Collisions),比如下面的四個字串:

hash code collision

如果我們按照每8個字元取雜湊的話,就會得到一樣的雜湊值。所以下面來講解如何解決雜湊碰撞:

避免雜湊衝突

通過雜湊函式,我們可以將鍵轉換為陣列的索引(0-M-1),但是對於兩個或者多個鍵具有相同索引值的情況,我們需要有一種方法來處理這種衝突。

一種比較直接的辦法就是,將大小為M 的陣列的每一個元素指向一個條連結串列,連結串列中的每一個節點都儲存雜湊值為該索引的鍵值對,這就是拉鍊法。下圖很清楚的描述了什麼是拉鍊法

seperate chaining with link list

圖中,”John Smith”和”Sandra Dee” 通過雜湊函式都指向了152 這個索引,該索引又指向了一個連結串列, 在連結串列中依次儲存了這兩個字串。

該方法的基本思想就是選擇足夠大的M,使得所有的連結串列都儘可能的短小,以保證查詢的效率。對採用拉鍊法的雜湊實現的查詢分為兩步,首先是根據雜湊值找到等一應的連結串列,然後沿著連結串列順序找到相應的鍵。 我們現在使用我們之前介紹符號表中的使用無序連結串列實現的查詢表SequentSearchSymbolTable 來實現我們這裡的雜湊表。當然,您也可以使用.NET裡面內建的LinkList。

首先我們需要定義一個連結串列的總數,在內部我們定義一個SequentSearchSymbolTable的陣列。然後每一個對映到索引的地方儲存一個這樣的陣列。

public class SeperateChainingHashSet<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TKey>
{
    private int M;//散列表大小
    private SequentSearchSymbolTable<TKey, TValue>[] st;//

    public SeperateChainingHashSet()
        : this(997)
    {

    }

    public SeperateChainingHashSet(int m)
    {
        this.M = m;
        st = new SequentSearchSymbolTable<TKey, TValue>[m];
        for (int i = 0; i < m; i++)
        {
            st[i] = new SequentSearchSymbolTable<TKey, TValue>();
        }
    }

    private int hash(TKey key)
    {
        return (key.GetHashCode() & 0x7fffffff) % M;
    }

    public override TValue Get(TKey key)
    {
        return st[hash(key)].Get(key);
    }

    public override void Put(TKey key, TValue value)
    {
        st[hash(key)].Put(key, value);
    }

}

可以看到,該實現中使用

  • Get方法來獲取指定key的Value值,我們首先通過hash方法來找到key對應的索引值,即找到SequentSearchSymbolTable陣列中儲存該元素的查詢表,然後呼叫查詢表的Get方法,根據key找到對應的Value。
  • Put方法用來儲存鍵值對,首先通過hash方法找到改key對應的雜湊值,然後找到SequentSearchSymbolTable陣列中儲存該元素的查詢表,然後呼叫查詢表的Put方法,將鍵值對儲存起來。
  • hash方法來計算key的雜湊值, 這裡首先通過取與&操作,將符號位去除,然後採用除留餘數法將key應到到0-M-1的範圍,這也是我們的查詢表陣列索引的範圍。

實現基於拉鍊表的散列表,目標是選擇適當的陣列大小M,使得既不會因為空連結串列而浪費記憶體空間,也不會因為連結串列太而在查詢上浪費太多時間。拉鍊表的優點在於,這種陣列大小M的選擇不是關鍵性的,如果存入的鍵多於預期,那麼查詢的時間只會比選擇更大的陣列稍長,另外,我們也可以使用更高效的結構來代替連結串列儲存。如果存入的鍵少於預期,索然有些浪費空間,但是查詢速度就會很快。所以當記憶體不緊張時,我們可以選擇足夠大的M,可以使得查詢時間變為常數,如果記憶體緊張時,選擇儘量大的M仍能夠將效能提高M倍。

線性探測法

線性探測法是開放定址法解決雜湊衝突的一種方法,基本原理為,使用大小為M的陣列來儲存N個鍵值對,其中M>N,我們需要使用陣列中的空位解決碰撞衝突。如下圖所示:

open address

對照前面的拉鍊法,在該圖中,”Ted Baker” 是有唯一的雜湊值153的,但是由於153被”Sandra Dee”佔用了。而原先”Snadra Dee”和”John Smith”的雜湊值都是152的,但是在對”Sandra Dee”進行雜湊的時候發現152已經被佔用了,所以往下找發現153沒有被佔用,所以存放在153上,然後”Ted Baker”雜湊到153上,發現已經被佔用了,所以往下找,發現154沒有被佔用,所以值存到了154上。

開放定址法中最簡單的是線性探測法:當碰撞發生時即一個鍵的雜湊值被另外一個鍵佔用時,直接檢查散列表中的下一個位置即將索引值加1,這樣的線性探測會出現三種結果:

  1. 命中,該位置的鍵和被查詢的鍵相同
  2. 未命中,鍵為空
  3. 繼續查詢,該位置和鍵被查詢的鍵不同。

實現線性探測法也很簡單,我們只需要兩個大小相同的陣列分別記錄key和value。

public class LinearProbingHashSet<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TKey>
{
    private int N;//符號表中鍵值對的總數
    private int M = 16;//線性探測表的大小
    private TKey[] keys;
    private TValue[] values;

    public LinearProbingHashSet()
    {
        keys = new TKey[M];
        values = new TValue[M];
    }

    private int hash(TKey key)
    {
        return (key.GetHashCode() & 0xFFFFFFF) % M;
    }

    public override TValue Get(TKey key)
    {
        for (int i = hash(key); keys[i] != null; i = (i + 1) % M)
        {
            if (key.Equals(keys[i])) { return values[i]; }
        }
        return default(TValue);
    }

    public override void Put(TKey key, TValue value)
    {
        int hashCode = hash(key);
        for (int i = hashCode; keys[i] != null; i = (i + 1) % M)
        {
            if (keys[i].Equals(key))//如果和已有的key相等,則用新值覆蓋
            {
                values[i] = value;
                return;
            }
            //插入
            keys[i] = key;
            values[i] = value;
        }
    }
}

線性探查(Linear Probing)方式雖然簡單,但是有一些問題,它會導致同類雜湊的聚集。在存入的時候存在衝突,在查詢的時候衝突依然存在。

效能分析

我們可以看到,雜湊表儲存和查詢資料的時候分為兩步,第一步為將鍵通過雜湊函式對映為陣列中的索引, 這個過程可以認為是隻需要常數時間的。第二步是,如果出現雜湊值衝突,如何解決,前面介紹了拉鍊法和線性探測法下面就這兩種方法進行討論:

對於拉鍊法,查詢的效率在於連結串列的長度,一般的我們應該保證長度在M/8~M/2之間,如果連結串列的長度大於M/2,我們可以擴充連結串列長度。如果長度在0~M/8時,我們可以縮小連結串列。

對於線性探測法,也是如此,但是動態調整陣列的大小需要對所有的值從新進行重新雜湊並插入新的表中。

不管是拉鍊法還是雜湊法,這種動態調整連結串列或者陣列的大小以提高查詢效率的同時,還應該考慮動態改變連結串列或者陣列大小的成本。散列表長度加倍的插入需要進行大量的探測, 這種均攤成本在很多時候需要考慮。

雜湊碰撞攻擊

我們知道如果雜湊函式選擇不當會使得大量的鍵都會對映到相同的索引上,不管是採用拉鍊法還是開放定址法解決衝突,在後面查詢的時候都需要進行多次探測或者查詢, 在很多時候會使得雜湊表的查詢效率退化,而不再是常數時間。下圖清楚的描述了退化後的雜湊表:

hashCollision 

雜湊表攻擊就是通過精心構造雜湊函式,使得所有的鍵經過雜湊函式後都對映到同一個或者幾個索引上,將雜湊表退化為了一個單鏈表,這樣雜湊表的各種操作,比如插入,查詢都從O(1)退化到了連結串列的查詢操作,這樣就會消耗大量的CPU資源,導致系統無法響應,從而達到拒絕服務供給(Denial of Service, Dos)的目的。之前由於多種程式語言的雜湊演算法的“非隨機”而出現了Hash碰撞的DoS安全漏洞,在ASP.NET中也曾出現過這一問題

在.NET中String的雜湊值內部實現中,通過使用雜湊值隨機化來對這種問題進行了限制,通過對碰撞次數設定閾值,超過該閾值就對雜湊函式進行隨機化,這也是防止雜湊表退化的一種做法。下面是BCL中string型別的GetHashCode方法的實現,可以看到,當碰撞超過一定次數的時候,就會開啟條件編譯,對雜湊函式進行隨機化。

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical, __DynamicallyInvokable]
public override unsafe int GetHashCode()
{
    if (HashHelpers.s_UseRandomizedStringHashing)
    {
        return InternalMarvin32HashString(this, this.Length, 0L);
    }
    fixed (char* str = ((char*) this))
    {
        char* chPtr = str;
        int num = 0x15051505;
        int num2 = num;
        int* numPtr = (int*) chPtr;
        int length = this.Length;
        while (length > 2)
        {
            num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
            num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
            numPtr += 2;
            length -= 4;
        }
        if (length > 0)
        {
            num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
        }
        return (num + (num2 * 0x5d588b65));
    }
}

.NET中雜湊的實現

我們可以通過線上原始碼檢視.NET 中Dictionary,型別的實現,我們知道任何作為key的值新增到Dictionary中時,首先會獲取key的hashcode,然後將其對映到不同的bucket中去:

public Dictionary(int capacity, IEqualityComparer<TKey> comparer) {
    if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
    if (capacity > 0) Initialize(capacity);
    this.comparer = comparer ?? EqualityComparer<TKey>.Default;
}

在Dictionary初始化的時候,會如果傳入了大小,會初始化bucket 就是呼叫Initialize方法:

private void Initialize(int capacity) {
    int size = HashHelpers.GetPrime(capacity);
    buckets = new int[size];
    for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
    entries = new Entry[size];
    freeList = -1;
}

我們可以看看Dictonary的Add方法,Add方法在內部呼叫了Insert方法:

private void Insert(TKey key, TValue value, bool add) 
{
        if( key == null ) {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
        }
 
        if (buckets == null) Initialize(0);
        int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
        int targetBucket = hashCode % buckets.Length;
 
#if FEATURE_RANDOMIZED_STRING_HASHING
        int collisionCount = 0;
#endif
 
        for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
            if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
                if (add) { 
                    ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
                }
                entries[i].value = value;
                version++;
                return;
            } 
 
#if FEATURE_RANDOMIZED_STRING_HASHING
            collisionCount++;
#endif
        }
        int index;
        if (freeCount > 0) {
            index = freeList;
            freeList = entries[index].next;
            freeCount--;
        }
        else {
            if (count == entries.Length)
            {
                Resize();
                targetBucket = hashCode % buckets.Length;
            }
            index = count;
            count++;
        }
 
        entries[index].hashCode = hashCode;
        entries[index].next = buckets[targetBucket];
        entries[index].key = key;
        entries[index].value = value;
        buckets[targetBucket] = index;
        version++;
 
#if FEATURE_RANDOMIZED_STRING_HASHING
        if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer)) 
        {
            comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer);
            Resize(entries.Length, true);
        }
#endif
 
    }

首先,根據key獲取其hashcode,然後將hashcode除以backet的大小取餘映射到目標backet中,然後遍歷該bucket儲存的連結串列,如果找到和key相同的值,如果不允許後新增的鍵與存在的鍵相同替換值(add),則丟擲異常,如果允許,則替換之前的值,然後返回。

如果沒有找到,則將新新增的值放到新的bucket中,當空餘空間不足的時候,會進行擴容操作(Resize),然後重新hash到目標bucket。這裡面需要注意的是Resize操作比較消耗資源。

總結

前面幾篇文章先後介紹了基於無序列表的順序查詢基於有序陣列的二分查詢平衡查詢樹,以及紅黑樹,本篇文章最後介紹了查詢演算法中的最後一類即符號表又稱雜湊表,並介紹了雜湊函式以及處理雜湊衝突的兩種方法:拉鍊法和線性探測法。各種查詢演算法的最壞和平均條件下各種操作的時間複雜度如下圖:

search method efficient conclusion

在實際編寫程式碼中,如何選擇合適的資料結構需要根據具體的資料規模,查詢效率要求,時間和空間侷限來做出合適的選擇。希望本文以及前面的幾篇文章對您有所幫助。

參考資料

相關推薦

查詢演算法 演算法資料結構: 七 二叉查詢樹 演算法資料結構:

閱讀目錄 1. 順序查詢 2. 二分查詢 3. 插值查詢 4. 斐波那契查詢 5. 樹表查詢 6. 分塊查詢 7. 雜湊查詢   查詢是在大量的資訊中尋找一個特定的資訊元素,在計算機應用中,查詢是常用的基本運算,例如編譯程式中符號表的查詢。本文

演算法資料結構:

在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是他們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒有查詢效率更高的資料結構呢,答案就是本文接下來要介紹了

演算法資料結構: 平衡查詢樹之B樹

前面講解了平衡查詢樹中的2-3樹以及其實現紅黑樹。2-3樹種,一個節點最多有2個key,而紅黑樹則使用染色的方式來標識這兩個key。 維基百科對B樹的定義為“在電腦科學中,B樹(B-tree)是一種樹狀資料結構,它能夠儲存資料、對其進行排序並允許以O(log n)的時間複雜度執行進行查詢、順序讀取、插入和刪

演算法資料結構: 佇列

最近晚上在家裡看Algorithems,4th Edition,我買的英文版,覺得這本書寫的比較淺顯易懂,而且“圖碼並茂”,趁著這次機會打算好好學習做做筆記,這樣也會印象深刻,這也是寫這一系列文章的原因。另外普林斯頓大學在Coursera 上也有這本書同步的公開課,還有另外一門演算法分析課,這門課程的作者也是

演算法資料結構: 五 優先順序佇列與堆排序

在很多應用中,我們通常需要按照優先順序情況對待處理物件進行處理,比如首先處理優先順序最高的物件,然後處理次高的物件。最簡單的一個例子就是,在手機上玩遊戲的時候,如果有來電,那麼系統應該優先處理打進來的電話。 在這種情況下,我們的資料結構應該提供兩個最基本的操作,一個是返回最高優先

演算法資料結構: 八 平衡查詢樹之2-3樹

前面介紹了二叉查詢樹(Binary Search Tree),他對於大多數情況下的查詢和插入在效率上來說是沒有問題的,但是他在最差的情況下效率比較低。本文及後面文章介紹的平衡查詢樹的資料結構能夠保證在最差的情況下也能達到lgN的效率,要實現這一目標我們需要保證樹在插入完成之後

演算法資料結構: 九 平衡查詢樹之紅黑樹

前面一篇文章介紹了2-3查詢樹,可以看到,2-3查詢樹能保證在插入元素之後能保持樹的平衡狀態,最壞情況下即所有的子節點都是2-node,樹的高度為lgN,從而保證了最壞情況下的時間複雜度。但是2-3樹實現起來比較複雜,本文介紹一種簡單實現2-3樹的資料結構,即紅黑樹(

演算法資料結構: 四 快速排序

上篇文章介紹了時間複雜度為O(nlgn)的合併排序,本篇文章介紹時間複雜度同樣為O(nlgn)但是排序速度比合並排序更快的快速排序(Quick Sort)。 快速排序也是一種採用分治法解決問題的一個典型應用。在很多程式語言中,對陣列,列表進行的非穩定排序在內部實現中都使用的是快速排序。而且快速排序在

演算法資料結構: 六 符號及其基本實現

前面幾篇文章介紹了基本的排序演算法,排序通常是查詢的前奏操作。從本文開始介紹基本的查詢演算法。 在介紹查詢演算法,首先需要了解符號表這一抽象資料結構,本文首先介紹了什麼是符號表,以及這一抽象資料結構的的API,然後介紹了兩種簡單的符號表的實現方式。 一符號表 在開始介紹查詢演算法之前,我們需要定義一個名

演算法資料結構----無向圖相關演算法基礎

最近幾個專案用到了求所有最小哈密爾頓迴路,貪婪遍歷查詢等演算法,都是自己想或者查論文,雖然都是資料結構的基礎內容,但感覺比較零散,很糾結。 前幾天突然聽到“圖計算”這個名詞,覺得應該是找到組織了,因此轉載如下,後續會不斷轉載其他有用的文章。 以下內容轉載自:http:/

演算法資料結構: 三 合併排序

合併排序,顧名思義,就是通過將兩個有序的序列合併為一個大的有序的序列的方式來實現排序。合併排序是一種典型的分治演算法:首先將序列分為兩部分,然後對每一部分進行迴圈遞迴的排序,然後逐個將結果進行合併。   合併排序最大的優點是它的時間複雜度為O(nlgn),這個是我們之前的選擇排序和插入排序所達不到的。他還

演算法資料結構: 二 基本排序演算法

本篇開始學習排序演算法。排序與我們日常生活中息息相關,比如,我們要從電話簿中找到某個聯絡人首先會按照姓氏排序、買火車票會按照出發時間或者時長排序、買東西會按照銷量或者好評度排序、查詢檔案會按照修改時間排序等等。在計算機程式設計中,排序和查詢也是最基本的演算法,很多其他的演算法都是以排序演算法為基礎,在一般的資

演算法資料結構(11):

在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼

演算法資料結構(7):二叉查詢樹

前文介紹了符號表的兩種實現,無序連結串列和有序陣列,無序連結串列在插入的時候具有較高的靈活性,而有序陣列在查詢時具有較高的效率,本文介紹的二叉查詢樹(Binary Search Tree,BST)這一資料結構綜合了以上兩種資料結構的優點。 二叉查詢樹具有很高的靈活性

演算法資料結構

在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒

檢視動畫學習演算法資料結構)()

轉載請註明原出處:http://blog.csdn.net/lrs123123/article/details/43114619 這是一個寫給自己複習溫習的博文,不喜勿噴 一、排序板塊 ①氣泡排序(BubbleSort) 動畫展示: java程式碼: public

併發資料結構-1.6

原文連結,譯文連結,譯者:iDestiny,校對:周可人 典型可擴充套件的雜湊表即一個可調整大小的桶陣列(buckets), 每一個桶存放預期數量的元素,因此雜湊表平均在常量時間內進行插入,刪除,查詢操作。雜湊表調整大小的主要成本—–在於新舊桶(buckets)之間進行重新分配操作,該操作被分

拒絕調包俠,不需要高階演算法資料結構技巧

前言 大多數工科學生或者剛剛入門近年來比較火的“人工智慧”相關演算法的同學,在選擇語言的時候,都會選擇MATLAB、Python、R等等這些高階語言,對自己所學的演算法進行實現和除錯。這些高階語言中,包含了實現複雜演算法的基礎數學演算法、基本統計演算法、基礎資料結構的實現,比如均值(mean)、方差(std

java中各種演算法資料結構的使用場景

一。通用資料結構:陣列,連結串列,樹,雜湊表 通用資料結構通過關鍵字的值來儲存並查詢資料,如報表,合同,記錄,業績等資料。通用資料結構可以用速度的快慢來分類,陣列和連結串列是最慢的,樹相對較快,雜湊表是最快的。請注意,並不是最快的就一定是最好的,因為最快的結構的

演算法資料結構

資料結構 堆 長度為n的陣列構建成最小堆的時間複雜度 B、B+樹、紅黑樹 說一下B+樹和二叉搜尋樹的區別? 說一下二叉搜尋樹和AVL樹、紅黑樹之間的差別 說下紅黑樹原理,紅黑樹你看虛擬碼的時候他有兩坨比較一樣的有沒有注意過 哪些情況下用棧 知道雜湊嗎?二叉樹比