淺談演算法和資料結構: 六 符號表及其基本實現
前面幾篇文章介紹了基本的排序演算法,排序通常是查詢的前奏操作。從本文開始介紹基本的查詢演算法。
在介紹查詢演算法,首先需要了解符號表這一抽象資料結構,本文首先介紹了什麼是符號表,以及這一抽象資料結構的的API,然後介紹了兩種簡單的符號表的實現方式。
一符號表
在開始介紹查詢演算法之前,我們需要定義一個名為符號表(Symbol Table)的抽象資料結構,該資料結構類似我們再C#中使用的Dictionary,他是對具有鍵值對元素的一種抽象,每一個元素都有一個key和value,我們可以往裡面新增key,value鍵值對,也可以根據key來查詢value。在現實的生活中,我們經常會遇到各種需要根據key來查詢value的情況,比如DNS根據域名查詢IP地址,圖書館根據索引號查詢圖書等等:
為了實現這一功能,我們定義一個抽象資料結構,然後選用合適的資料結構來實現:
public class ST<Key, Value>
ST() |
建立一個查詢表物件 |
void Put(Key key, Value val) |
往集合中插入一條鍵值對記錄,如果value為空,不新增 |
Value Get(Key key) |
根據key查詢value,如果沒找到返回null |
void Delete(Key key) |
刪除鍵為key的記錄 |
boolean Contains(Key key) |
判斷集合中是否存在鍵為key的記錄 |
boolean IsEmpty() |
判斷查詢表是否為空 |
int Size() |
返回集合中鍵值對的個數 |
Iterable<Key> Keys() |
返回集合中所有的鍵 |
二實現
1 使用無序連結串列實現查詢表
查詢表的實現關鍵在於資料結構的選擇,最簡單的一種實現是使用無序連結串列來實現,每一個節點記錄key值,value值以及指向下一個記錄的物件。
如圖,當我們往連結串列中插入元素的時候,從表頭開始查詢,如果找到,則更新value,否則,在表頭插入新的節點元素。
實現起來也很簡單:
public class SequentSearchSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TKey> { private int length = 0; Node first; private class Node { public TKey key { get; set; } public TValue value { get; set; } public Node next { get; set; } public Node(TKey key, TValue value, Node next) { this.key = key; this.value = value; this.next = next; } } public override TValue Get(TKey key) { TValue result = default(TValue); Node temp = first; while (temp != null) { if (temp.key.Equals(key)) { result = temp.value; break; } temp = temp.next; } return result; } public override void Put(TKey key, TValue value) { Node temp = first; while (temp != null) { if (temp.key.Equals(key)) { temp.value = value; return; } temp = temp.next; } first = new Node(key, value, first); length++; } .... }
分析:
從圖或者程式碼中分析可知,插入的時候先要查詢,如果存在則更新value,查詢的時候需要從連結串列頭進行查詢,所以插入和查詢的平均時間複雜度均為O(n)。那麼有沒有效率更好的方法呢,下面就介紹二分查詢。
2 使用二分查詢實現查詢表
和採用無序連結串列實現不同,二分查詢的思想是在內部維護一個按照key排好序的二維陣列,每一次查詢的時候,跟中間元素進行比較,如果該元素小,則繼續左半部分遞迴查詢,否則繼續右半部分遞迴查詢。整個實現程式碼如下:
class BinarySearchSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TKey> { private TKey[] keys; private TValue[] values; private int length; private static readonly int INIT_CAPACITY = 2; public BinarySearchSymbolTable(int capacity) { keys = new TKey[capacity]; values = new TValue[capacity]; length = capacity; } public BinarySearchSymbolTable() : this(INIT_CAPACITY) { } /// <summary> /// 根據key查詢value。 /// 首先查詢key在keys中所處的位置,如果在length範圍內,且存在該位置的值等於key,則返回值 /// 否則,不存在 /// </summary> /// <param name="key"></param> /// <returns></returns> public override TValue Get(TKey key) { int i = Rank(key); if (i < length && keys[i].Equals(key)) return values[i]; else return default(TValue); } /// <summary> /// 向符號表中插入key,value鍵值對。 /// 如果存在相等的key,則直接更新value,否則將該key,value插入到合適的位置 /// 1.首先將該位置往後的元素都往後移以為 /// 2.然後再講該元素放到為i的位置上 /// </summary> /// <param name="key"></param> /// <param name="value"></param> public override void Put(TKey key, TValue value) { int i = Rank(key); if (i < length && keys[i].Equals(key)) { values[i] = value; return; } //如果長度相等,則擴容 if (length == keys.Length) Resize(2 * keys.Length); for (int j = length; j > i; j--) { keys[j] = keys[j - 1]; values[j] = values[j - 1]; } keys[i] = key; values[i] = value; length++; } /// <summary> /// 返回key在陣列中的位置 /// </summary> /// <param name="key"></param> /// <returns></returns> private int Rank(TKey key) { int lo = 0; int hi = length - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (key.CompareTo(keys[mid]) > 0) lo = mid + 1; else if (key.CompareTo(keys[mid]) < 0) hi = mid - 1; else return mid; } return lo; } 。。。 }
這裡面重點是Rank方法,我們可以看到首先獲取mid位置,然後將當前元素和mid位置元素比較,然後更新lo或者hi的位置用mid來替換,如果找到相等的,則直接返回mid,否則返回該元素在集合中應該插入的合適位置。上面是使用迭代的方式來實現的,也可以改寫為遞迴:
private int Rank(TKey key, int lo, int hi) { if (lo >= hi) return lo; int mid = lo + (hi - lo) / 2; if (key.CompareTo(keys[mid]) > 0) return Rank(key, mid + 1, hi); else if (key.CompareTo(keys[mid]) < 0) return Rank(key, lo, hi - 1); else return mid; }
二分查詢的示意圖如下:
分析:
使用有序的二維陣列來實現查詢表可以看出,採用二分查詢只需要最多lgN+1次的比較即可找到對應元素,所以查詢效率比較高。
但是對於插入元素來說,每一次插入不存在的元素,需要將該元素放到指定的位置,然後,將他後面的元素依次後移,所以平均時間複雜度O(n),對於插入來說效率仍然比較低。
三 總結
本文介紹了符號表這一抽象資料結構,然後介紹了兩種基本實現:基於無序連結串列的實現和基於有序陣列的實現,兩種實現的時間複雜度如下:
可以看到,使用有序陣列的二分查詢法提高了符號表的查詢速度,但是插入效率仍舊沒有得到提高,而且在要維護陣列有序,還需要進行排序操作。這兩種實現方式簡單直觀,但是無法同時達到較高查詢和插入效率。那麼有沒有一種資料結構既能夠在查詢的時候有較高的效率,在插入的時候也有較好的效率呢,本文只是一個引子,後面的系列文章將會介紹二叉查詢樹,平衡查詢樹以及雜湊表。
希望本文對您瞭解查詢表的基本概念以及兩種基本實現有所幫助。
相關推薦
淺談演算法和資料結構: 六 符號表及其基本實現
前面幾篇文章介紹了基本的排序演算法,排序通常是查詢的前奏操作。從本文開始介紹基本的查詢演算法。 在介紹查詢演算法,首先需要了解符號表這一抽象資料結構,本文首先介紹了什麼是符號表,以及這一抽象資料結構的的API,然後介紹了兩種簡單的符號表的實現方式。 一符號表 在開始介紹查詢演算法之前,我們需要定義一個名
查詢演算法 淺談演算法和資料結構: 七 二叉查詢樹 淺談演算法和資料結構: 十一 雜湊表
閱讀目錄 1. 順序查詢 2. 二分查詢 3. 插值查詢 4. 斐波那契查詢 5. 樹表查詢 6. 分塊查詢 7. 雜湊查詢 查詢是在大量的資訊中尋找一個特定的資訊元素,在計算機應用中,查詢是常用的基本運算,例如編譯程式中符號表的查詢。本文
淺談演算法和資料結構: 五 優先順序佇列與堆排序
在很多應用中,我們通常需要按照優先順序情況對待處理物件進行處理,比如首先處理優先順序最高的物件,然後處理次高的物件。最簡單的一個例子就是,在手機上玩遊戲的時候,如果有來電,那麼系統應該優先處理打進來的電話。 在這種情況下,我們的資料結構應該提供兩個最基本的操作,一個是返回最高優先
淺談演算法和資料結構: 八 平衡查詢樹之2-3樹
前面介紹了二叉查詢樹(Binary Search Tree),他對於大多數情況下的查詢和插入在效率上來說是沒有問題的,但是他在最差的情況下效率比較低。本文及後面文章介紹的平衡查詢樹的資料結構能夠保證在最差的情況下也能達到lgN的效率,要實現這一目標我們需要保證樹在插入完成之後
淺談演算法和資料結構: 九 平衡查詢樹之紅黑樹
前面一篇文章介紹了2-3查詢樹,可以看到,2-3查詢樹能保證在插入元素之後能保持樹的平衡狀態,最壞情況下即所有的子節點都是2-node,樹的高度為lgN,從而保證了最壞情況下的時間複雜度。但是2-3樹實現起來比較複雜,本文介紹一種簡單實現2-3樹的資料結構,即紅黑樹(
淺談演算法和資料結構: 十 平衡查詢樹之B樹
前面講解了平衡查詢樹中的2-3樹以及其實現紅黑樹。2-3樹種,一個節點最多有2個key,而紅黑樹則使用染色的方式來標識這兩個key。 維基百科對B樹的定義為“在電腦科學中,B樹(B-tree)是一種樹狀資料結構,它能夠儲存資料、對其進行排序並允許以O(log n)的時間複雜度執行進行查詢、順序讀取、插入和刪
淺談演算法和資料結構: 十一 雜湊表
在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是他們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒有查詢效率更高的資料結構呢,答案就是本文接下來要介紹了
淺談演算法和資料結構: 四 快速排序
上篇文章介紹了時間複雜度為O(nlgn)的合併排序,本篇文章介紹時間複雜度同樣為O(nlgn)但是排序速度比合並排序更快的快速排序(Quick Sort)。 快速排序也是一種採用分治法解決問題的一個典型應用。在很多程式語言中,對陣列,列表進行的非穩定排序在內部實現中都使用的是快速排序。而且快速排序在
淺談演算法和資料結構----無向圖相關演算法基礎
最近幾個專案用到了求所有最小哈密爾頓迴路,貪婪遍歷查詢等演算法,都是自己想或者查論文,雖然都是資料結構的基礎內容,但感覺比較零散,很糾結。 前幾天突然聽到“圖計算”這個名詞,覺得應該是找到組織了,因此轉載如下,後續會不斷轉載其他有用的文章。 以下內容轉載自:http:/
淺談演算法和資料結構: 三 合併排序
合併排序,顧名思義,就是通過將兩個有序的序列合併為一個大的有序的序列的方式來實現排序。合併排序是一種典型的分治演算法:首先將序列分為兩部分,然後對每一部分進行迴圈遞迴的排序,然後逐個將結果進行合併。 合併排序最大的優點是它的時間複雜度為O(nlgn),這個是我們之前的選擇排序和插入排序所達不到的。他還
淺談演算法和資料結構: 一 棧和佇列
最近晚上在家裡看Algorithems,4th Edition,我買的英文版,覺得這本書寫的比較淺顯易懂,而且“圖碼並茂”,趁著這次機會打算好好學習做做筆記,這樣也會印象深刻,這也是寫這一系列文章的原因。另外普林斯頓大學在Coursera 上也有這本書同步的公開課,還有另外一門演算法分析課,這門課程的作者也是
淺談演算法和資料結構: 二 基本排序演算法
本篇開始學習排序演算法。排序與我們日常生活中息息相關,比如,我們要從電話簿中找到某個聯絡人首先會按照姓氏排序、買火車票會按照出發時間或者時長排序、買東西會按照銷量或者好評度排序、查詢檔案會按照修改時間排序等等。在計算機程式設計中,排序和查詢也是最基本的演算法,很多其他的演算法都是以排序演算法為基礎,在一般的資
淺談演算法和資料結構(11):雜湊表
在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼
淺談演算法和資料結構(7):二叉查詢樹
前文介紹了符號表的兩種實現,無序連結串列和有序陣列,無序連結串列在插入的時候具有較高的靈活性,而有序陣列在查詢時具有較高的效率,本文介紹的二叉查詢樹(Binary Search Tree,BST)這一資料結構綜合了以上兩種資料結構的優點。 二叉查詢樹具有很高的靈活性
淺談演算法和資料結構:雜湊表
在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒
《 常見演算法與資料結構》符號表ST(2)——初等實現分析和有序符號表
符號表(Symbol Table) (2) 本系列文章主要介紹常用的演算法和資料結構的知識,記錄的是《Algorithms I/II》課程的內容,採用的是“演算法(第4版)”這本紅寶書作為學習教材的,語言是java。這本書的名氣我不用多說吧?豆瓣評分9.
《 常見演算法與資料結構》符號表ST(4)——二叉查詢樹刪除 (附動畫)
符號表ST(4)——二叉查詢樹刪除 (附動畫) 本系列文章主要介紹常用的演算法和資料結構的知識,記錄的是《Algorithms I/II》課程的內容,採用的是“演算法(第4版)”這本紅寶書作為
《 常見演算法與資料結構》符號表ST(1)——基本介紹
符號表(Symbol Table) 本系列文章主要介紹常用的演算法和資料結構的知識,記錄的是《Algorithms I/II》課程的內容,採用的是“演算法(第4版)”這本紅寶書作為學習教材的,語言是java。這本書的名氣我不用多說吧?豆瓣評分9.4,我自
符號表及其基本實現
前面幾篇文章介紹了基本的排序演算法,排序通常是查詢的前奏操作。從本文開始介紹基本的查詢演算法。 在介紹查詢演算法,首先需要了解符號表這一抽象資料結構,本文首先介紹了什麼是符號表,以及這一抽象資料結構的的API,然後介紹了兩種簡單的符號表的實現方式。 一符號表 在開始
拒絕調包俠,不需要高階演算法和資料結構技巧
前言 大多數工科學生或者剛剛入門近年來比較火的“人工智慧”相關演算法的同學,在選擇語言的時候,都會選擇MATLAB、Python、R等等這些高階語言,對自己所學的演算法進行實現和除錯。這些高階語言中,包含了實現複雜演算法的基礎數學演算法、基本統計演算法、基礎資料結構的實現,比如均值(mean)、方差(std