資料結構基礎溫故-6.查詢(下):雜湊表
雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示表示出來,而雜湊技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。因此,雜湊主要是面向查詢的儲存結構。雜湊技術最適合的求解問題是查詢與給定值相等的記錄。
一、基本概念及原理
1.1 雜湊定義的引入
這裡首先看一個場景:在大多數情況下,陣列中的索引並不具有實際的意義,它僅僅表示一個元素在陣列中的位置而已,當需要查詢某個元素時,往往會使用有實際意義的欄位。例如下面一段程式碼,它使用學生的學號來查詢學生的地址。
(1)學生實體類定義
public class StudentInfo { public string Number { get; set; } public string Address { get; set; } public StudentInfo(string number, string address) { Number = number; Address = address; } }
(2)通過索引遍歷查詢
staticStudentInfo[] InitialStudents() { StudentInfo[] arrStudent = { new StudentInfo("200807001","四川達州"), new StudentInfo("200807002","四川成都"), new StudentInfo("200807003","山東青島"), new StudentInfo("200807004","河南鄭州"), newStudentInfo("200807005","江蘇徐州") }; return arrStudent; } static void NormalSearch(StudentInfo[] arrStudent, string searchNumber) { bool isFind = false; foreach (var student in arrStudent) { if (student.Number == searchNumber) { isFind = true; Console.WriteLine("Search successfully!{0} address:{1}", searchNumber, student.Address); } } if (!isFind) { Console.WriteLine("Search {0} failed!", searchNumber); } } static void Main(string[] args) { StudentInfo[] arrStudent = InitialStudents(); // 01.普通陣列遍歷查詢 NormalSearch(arrStudent, "200807005"); Console.ReadKey(); }
執行結果如下圖所示,可以看到圓滿完成了查詢任務。
但是,如果查詢的記錄位於陣列的最後或者根本就不存在,仍然需要遍歷整個陣列。當陣列非常巨大時,還以這樣的方式查詢將會消耗較多的時間。是否有一種方法可以通過學號關鍵字就能直接地定位到相應的記錄?
(3)改寫查詢方式為雜湊查詢
通過觀察學號記錄與索引的對應關係,學號的後三位陣列恰好是一組有序數列,如果把每個學生的學號後三位陣列抽取出來並減去1,結果剛好可以與陣列的索引號一一對應。於是,我們可以將上例改寫為如下方式:
static int GetHashCode(string number) { string index = number.Substring(6); return Convert.ToInt32(index) - 1; } static void HashSearch(StudentInfo[] arrStudent, string searchNumber) { Console.WriteLine("{0} address:{1}", searchNumber, arrStudent[GetHashCode(searchNumber)].Address); } static void Main(string[] args) { StudentInfo[] arrStudent = InitialStudents(); HashSearch(arrStudent, "200807005"); HashSearch(arrStudent, "200807001"); Console.ReadKey(); }
可以看出,通過封裝GetHashCode()方法,實現了學號與陣列索引的一一對應關係,在查詢中直接定位到了索引號,避免了遍歷操作,從而提高了查詢效率,從原來的O(n)提高到了O(1),執行結果如下圖所示:
上例中的學號是不重複的,它可以唯一標識學生集合中的每一條記錄,這樣的欄位就被稱為key(關鍵字)。而在記錄儲存地址和它的關鍵字之間建立一個確定的對應關係h,使得每個關鍵字和一個唯一的儲存位置相對應。在查詢時,只需要根據這個對應關係h,就可以找到所需關鍵字及其對應的記錄,這種查詢方式就被稱為雜湊查詢,關鍵字和儲存位置的對應關係可以用函式表示為:
h(key)=儲存地址
1.2 構造雜湊函式的方法
構造雜湊函式的目標在於使雜湊地址儘可能均勻地分佈在連續的記憶體單元地址上,以減少發生衝突的可能性,同時使計算儘可能簡單以達到儘可能高的時間效率,這裡主要看看兩個構造雜湊函式的方法。
(1)直接地址法
直接地址法取關鍵字的某個線性函式值為雜湊地址,即h(key)=key 或 h(key)=a*key+b
其中,a、b均為常數,這樣的雜湊函式優點就是簡單、均勻,也不會產生衝突,但問題是這需要事先知道關鍵字的分佈情況,適合查詢表較小且連續的情況。由於這樣的限制,在現實應用中,此方法雖然簡單,但卻並不常用。
(2)除留餘數法
除留餘數法採用取模運算(%)把關鍵字除以某個不大於雜湊表表長的整數得到的餘數作為雜湊地址,它也是最常用的構造雜湊函式的方法,其形式為:h(key)=key%p
本方法的關鍵就在於選擇合適的p,p如果選得不好,就可能會容易產生同義詞。
PS:根據前輩們的經驗,若雜湊表表長為m,通常p為小於或等於表長(最好接近m)的最小質數或不包含小於20質因子的合數。
1.3 解決雜湊衝突的方法
(1)閉雜湊法
閉雜湊法時把所有的元素都儲存在雜湊表陣列中,當發生衝突時,在衝突位置的附近尋找可存放記錄的空單元。尋找“下一個”空位的過程則稱為探測。上述方法可用如下公式表示為:
其中,h(key)為雜湊函式,m為雜湊表長度,di為遞增的序列。根據di的不同,又可以分為幾種探測方法:線性探測法、二次探測法以及雙重雜湊法。
(2)開雜湊法
開雜湊法的常見形式是將所有關鍵字為同義詞的記錄儲存在一個單鏈表中。我們稱這種表為同義詞子表,在散列表中只儲存所有同義詞子表的頭指標。對於關鍵字集合{12,67,56,16,25,37,22,29,15,47,48,34},我們用前面同樣的12為除數,進行除留餘數法,可得到如下圖所示的結構,此時,已經不存在什麼衝突換址的問題,無論有多少個衝突,都只是在當前位置給單鏈表增加結點的問題。
該方法對於可能會造成很多衝突的雜湊函式來說,提供了絕不會出現找不到地址的保障。當然,這也就帶來了查詢時需要遍歷單鏈表的效能損耗。在.NET中,連結串列的各個元素分散於託管堆各處,也會給GC垃圾回收帶來壓力,影響程式的效能。
二、.NET中的Hashtable
2.1 Hashtable的用法
在.NET中,實現了雜湊表資料結構的集合類有兩個,其中一個就是Hashtable,另一個是泛型版本的Dictionary<TKey,TValue>。這裡我們首先看看Hashtable的用法,由於Hashtable中key/value鍵值對均為object型別,所以Hashtable可以支援任何型別的key/value鍵值對。
static void HashtableTest() { // 建立一個Hashtable例項 Hashtable ht = new Hashtable(); // 新增key/value鍵值對 ht.Add("北京", "帝都"); ht.Add("上海", "魔都"); ht.Add("廣州", "省會"); ht.Add("深圳", "特區"); // 根據key獲取value string capital = (string)ht["北京"]; Console.WriteLine("北京:{0}", capital); Console.WriteLine("--------------------"); // 判斷雜湊表是否包含特定鍵,其返回值為true或false Console.WriteLine("包含上海嗎?{0}",ht.Contains("上海")); Console.WriteLine("--------------------"); // 移除一個key/value鍵值對 ht.Remove("深圳"); // 遍歷雜湊表 foreach (DictionaryEntry de in ht) { Console.WriteLine("{0}:{1}", de.Key, de.Value); } Console.WriteLine("--------------------"); // 移除所有元素 ht.Clear(); // 遍歷雜湊表 foreach (DictionaryEntry de in ht) { Console.WriteLine("{0}:{1}", de.Key, de.Value); } }
執行結果如下圖所示:
2.2 剖析Hashtable
(1)閉雜湊法
Hashtable內部使用了閉雜湊法來解決衝突,它通過一個結構體bucket來表示雜湊表中的單個元素,這個結構體有三個成員:
private struct bucket { public object key; public object val; public int hash_coll; }
兩個object型別(那麼必然會涉及到裝箱和拆箱操作)的變數,其中key表示鍵,val表示值,而hash_coll則是一個int型別,它用於表示鍵所對應的雜湊碼。眾所周知,一個int型別佔4個位元組(這裡主要探討32位系統中),一個位元組又是8位,那麼4*8=32位。它的最高位是符號位,當最高位為“0”時,表示是一個正整數,而為“1”時則表示是一個負整數。hash_coll使用最高位表示當前位置是否發生衝突,為“0”時也就是正數時,表示未發生衝突;為“1”時,則表示當前位置存在衝突。之所以專門使用一個標誌位用於標註是否發生衝突,主要是為了提高雜湊表的執行效率。
(2)雙重雜湊法
Hashtable解決衝突使用了雙重雜湊法,但又與普通的雙重雜湊法不同。它探測地址的方法如下:
其中,雜湊函式h1和h2的公式有如下所示:
由於使用了二度雜湊,最終的h(key,i)的值有可能會大於hashsize,所以需要對h(key,i)進行取模運算,最終計算的雜湊地址為:
這裡需要注意的是:在bucket結構體中,hash_coll變數儲存的是h(key,i)的值而不是最終的雜湊地址。
Hashtable通過關鍵字查詢元素時,首先會計算出鍵的雜湊地址,然後通過這個雜湊地址直接訪問陣列的相應位置並對比兩個鍵值,如果相同,則查詢成功並返回;如果不同,則根據hash_coll的值來決定下一步操作。
①當hash_coll為0或整數時,表明沒有衝突,此時表明查詢失敗;
②當hash_coll為負數時,表明存在衝突,此時需要通過二度雜湊繼續計算雜湊地址進行查詢,如此反覆直到找到相應的鍵值表明查詢成功,如果在查詢過程中遇到hash_coll為正數或計算二度雜湊的次數等於雜湊表長度則查詢失敗。
由此可知,將hash_coll的高位設為衝突檢測位主要是為了提高查詢速度,避免無意義地多次計算二度雜湊的情況。
(3)使用素數實現hashsize
通過檢視Hashtable的建構函式,我們可以發現呼叫了HashHelpers的GetPrime方法生成了一個素數來為hashsize賦值:
public Hashtable(int capacity, float loadFactor) { ....... int num2 = (num > 3.0) ? HashHelpers.GetPrime((int) num) : 3; this.buckets = new bucket[num2]; this.loadsize = (int) (this.loadFactor * num2); ....... }
Hashtable是可以自動擴容的,當以指定長度初始化雜湊表或給雜湊表擴容時都需要保證雜湊表的長度為素數,GetPrime(int min)方法正是用於獲取這個素數,引數min表示初步確定的雜湊表長度,它返回一個比min大的最合適的素數。
三、.NET中的Dictionary
3.1 Dictionary的用法
Dictionary是泛型版本的雜湊表,但和Hashtable之間並非只是簡單的泛型和非泛型的區別,兩者使用了完全不同的雜湊衝突解決辦法,我們先來看看Dictionary如何使用。
static void DictionaryTest() { Dictionary<string, StudentInfo> dict = new Dictionary<string, StudentInfo>(); for (int i = 0; i < 10; i++) { StudentInfo stu = new StudentInfo() { Number = "200807" + i.ToString().PadLeft(3, '0'), Name = "Student" + i.ToString() }; dict.Add(i.ToString(), stu); } // 判斷是否包含某個key if (dict.ContainsKey("1")) { Console.WriteLine("已經存在key為{0}的鍵值對了,它是{1}", 1, dict["1"].Name); } Console.WriteLine("--------------------------------"); // 遍歷鍵值對 foreach (var de in dict) { Console.WriteLine("Key:{0},Value:[Number:{1},Name:{2}]", de.Key, de.Value.Number, de.Value.Name); } // 移除一個鍵值對 if(dict.ContainsKey("5")) { dict.Remove("5"); } Console.WriteLine("--------------------------------"); // 遍歷鍵值對 foreach (var de in dict) { Console.WriteLine("Key:{0},Value:[Number:{1},Name:{2}]", de.Key, de.Value.Number, de.Value.Name); } // 清空鍵值對 dict.Clear(); Console.WriteLine("--------------------------------"); // 遍歷鍵值對 foreach (var de in dict) { Console.WriteLine("Key:{0},Value:[Number:{1},Name:{2}]", de.Key, de.Value.Number, de.Value.Name); } }
執行結果如下圖所示:
3.2 剖析Dictionary
(1)較Hashtable簡單的雜湊地址公式
Dictionary的雜湊地址求解方式較Hashtable簡單了許多,其計算公式如下所示:
通過檢視其原始碼,在Add方法中驗證是否是上面的地址計算方式:
int num = this.comparer.GetHashCode(key) & 0x7fffffff; int index = num % this.buckets.Length;
(2)內部兩個陣列結合的儲存結構
Dictionary內部有兩個陣列,一個數組名為buckets,用於存放由多個同義詞組成的靜態連結串列頭指標(連結串列的第一個元素在陣列中的索引號,當它的值為-1時表示此雜湊地址不存在元素);另一個數組為entries,它用於存放雜湊表中的實際資料,同時這些資料通過next指標構成多個單鏈表。entries中所存放的是Entry結構體,Entry結構體由4個部分組成,如下所示:
private struct Entry { public int hashCode; public int next; public TKey key; public TValue value; }
Dictionary將多個連結串列繼承於一個順序表之中進行統一管理:
Hashtable 與 Dictionary 的區別:
①Hashtable使用閉雜湊法來解決衝突,而Dictionary使用開雜湊法解決衝突;
②Dictionary相對Hashtable來說需要更多的儲存空間,但它不會發生二次聚集的情況,並且使用了泛型,相對非泛型可能需要的裝箱和拆箱操作,Dictionary的速度更快;
③Hashtable使用了填充因子的概念,而Dictionary則不存在填充因子的概念;
④Hashtable在擴容時由於重新計算雜湊地址,會消耗大量時間計算,而Dictionary的擴容相對Hashtable來說更快;
⑤單執行緒程式中推薦使用Dictionary,而多執行緒程式中則推薦使用Hashtable。預設的Hashtable允許單執行緒寫入,多執行緒讀取,對Hashtable進一步呼叫Synchronized()方法可以獲得完全執行緒安全的型別。相反,Dictionary不是執行緒安全的,必須人為使用lock語句進行保護,效率有所降低。
四、.NET中幾種查詢表的對比
4.1 測試對比介紹
在.NET中有三種主要的查詢表的資料結構,分別是SortedDictionary(前面已經介紹過了,其內部是紅黑樹資料結構實現)、Hashtable與Dictionary。本次測試會首先建立一個100萬個隨機排列整數的陣列,然後將陣列中的數字依次插入三種資料結構中,最後從三種資料結構中刪除所有資料,每個操作分別計算耗費時間(這裡計算操作使用了老趙的CodeTimer類實現效能計數)。
(1)準備工作:初始化一個100萬個隨機數的陣列
int length = 1000000; int[] arrNumber = new int[length]; // 首先生成有序陣列進行初始化 for (int i = 0; i < length; i++) { arrNumber[i] = i; } Random rand = new Random(); // 隨機將陣列中的數字打亂順序 for (int i = 0; i < length; i++) { int randIndex = rand.Next(i,length); // 交換兩個數字 int temp = arrNumber[i]; arrNumber[i] = arrNumber[randIndex]; arrNumber[randIndex] = temp; }
(2)測試SortedDictionary
// Test1:SortedDictionary型別測試 SortedDictionary<int, int> sd = new SortedDictionary<int, int>(); Console.WriteLine("SortedDictionary插入測試開始:"); CodeTimer.Time("SortedDictionary_Insert_Test", 1, () => { for (int i = 0; i < length; i++) { sd.Add(arrNumber[i], arrNumber[i]); } }); Console.WriteLine("SortedDictionary插入測試結束;"); Console.WriteLine("-----------------------------"); Console.WriteLine("SortedDictionary刪除測試開始:"); CodeTimer.Time("SortedDictionary_Delete_Test", 1, () => { for (int i = 0; i < length; i++) { sd.Remove(arrNumber[i]); } }); Console.WriteLine("SortedDictionary刪除測試結束;"); Console.WriteLine("-----------------------------");
(3)測試Hashtable
// Test2:Hashtable型別測試 Hashtable ht = new Hashtable(); Console.WriteLine("Hashtable插入測試開始:"); CodeTimer.Time("Hashtable_Insert_Test", 1, () => { for (int i = 0; i < length; i++) { ht.Add(arrNumber[i], arrNumber[i]); } }); Console.WriteLine("Hashtable插入測試結束;"); Console.WriteLine("-----------------------------"); Console.WriteLine("Hashtable刪除測試開始:"); CodeTimer.Time("Hashtable_Delete_Test", 1, () => { for (int i = 0; i < length; i++) { ht.Remove(arrNumber[i]); } }); Console.WriteLine("Hashtable刪除測試結束;"); Console.WriteLine("-----------------------------");
(4)測試Dictionary
// Test3:Dictionary型別測試 Dictionary<int, int> dict = new Dictionary<int, int>(); Console.WriteLine("Dictionary插入測試開始:"); CodeTimer.Time("Dictionary_Insert_Test", 1, () => { for (int i = 0; i < length; i++) { dict.Add(arrNumber[i], arrNumber[i]); } }); Console.WriteLine("Dictionary插入測試結束;"); Console.WriteLine("-----------------------------"); Console.WriteLine("Dictionary刪除測試開始:"); CodeTimer.Time("Dictionary_Delete_Test", 1, () => { for (int i = 0; i < length; i++) { dict.Remove(arrNumber[i]); } }); Console.WriteLine("Dictionary刪除測試結束;"); Console.WriteLine("-----------------------------");
4.2 測試對比結果
(1)SortedDictionary測試結果:
SortedDictionary內部是紅黑樹結構,在插入和刪除操作時需要經過大量的旋轉操作來維持平衡,因此耗時是三種類型中最多的。此外,在插入過程中,引起了GC大量的垃圾回收操作。
(2)Hashtable測試結果:
Hashtable插入操作的耗時和SortedDictionary相近,但刪除操作卻比SortedDictionary快了好幾倍。
(3)Dictionary測試結果:
Dictionary在插入和刪除操作上是三種類型中最快的,且對GC的友好程度上也較前兩種型別好很多。
參考資料
(1)陳廣,《資料結構(C#語言描述)》
(2)程傑,《大話資料結構》
(3)段恩澤,《資料結構(C#語言版)》
作者:周旭龍
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。
相關推薦
資料結構基礎溫故-6.查詢(下):雜湊表
雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示表示出來,而雜湊技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。因此,雜湊主要是面向查詢的儲存結構。雜湊技術最適合的求解問題是查詢與給定值相等的記錄。
資料結構基礎溫故-6.查詢(上):基本查詢與樹表查詢
只要你開啟電腦,就會涉及到查詢技術。如炒股軟體中查股票資訊、硬碟檔案中找照片、在光碟中搜DVD,甚至玩遊戲時在記憶體中查詢攻擊力、魅力值等資料修改用來作弊等,都要涉及到查詢。當然,在網際網路上查詢資訊就更加是家常便飯。查詢是計算機應用中最常用的操作之一,也是許多程式中最耗時的一部分,查詢方法的優劣對於系統的執
資料結構基礎溫故-5.圖(下):最短路徑
圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪一條路徑是最短的等等。這就是帶權圖中求最短路徑的問題,此時路徑的長度不再是路徑上邊的數目總和,而是路徑上的邊所帶權值的和。帶權圖分為無向帶權圖和有向帶權圖,但如
資料結構基礎溫故-5.圖(中):圖的遍歷演算法
上一篇我們瞭解了圖的基本概念、術語以及儲存結構,還對鄰接表結構進行了模擬實現。本篇我們來了解一下圖的遍歷,和樹的遍歷類似,從圖的某一頂點出發訪問圖中其餘頂點,並且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。如果只訪問圖的頂點而不關注邊的資訊,那麼圖的遍歷十分簡單,使用
資料結構基礎溫故-5.圖(中):最小生成樹演算法
圖的“多對多”特性使得圖在結構設計和演算法實現上較為困難,這時就需要根據具體應用將圖轉換為不同的樹來簡化問題的求解。 一、生成樹與最小生成樹 1.1 生成樹 對於一個無向圖,含有連通圖全部頂點的一個極小連通子圖成為生成樹(Spanning Tree)。其本質就是從連通圖任一頂點出發進行遍歷操作所經過
資料結構基礎溫故-5.圖(上):圖的基本概念
前面幾篇已經介紹了線性表和樹兩類資料結構,線性表中的元素是“一對一”的關係,樹中的元素是“一對多”的關係,本章所述的圖結構中的元素則是“多對多”的關係。圖(Graph)是一種複雜的非線性結構,在圖結構中,每個元素都可以有零個或多個前驅,也可以有零個或多個後繼,也就是說,元素之間的關係是任意的。現實生活中的很多
資料結構基礎之查詢(下):雜湊表
轉自:http://www.cnblogs.com/edisonchou/p/4706253.html 查詢(下):雜湊表 雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示
資料結構之查詢(下):雜湊表
雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示表示出來,而雜湊技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。因此,雜湊主要是面向查詢的儲存結構。雜湊技術
淺談演算法和資料結構(11):雜湊表
在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼
data_structure_and_algorithm -- 雜湊演算法(下):雜湊演算法在分散式系統中有哪些應用?
今天主要看一下雜湊演算法的應用(二),主要參考:前谷歌工程師王爭的課程,感興趣可以通過下面方式微信掃碼購買: 你可能已經發現,這三個應用都跟分散式系統有關。沒錯,今天我就帶你看下,雜湊演算法是如何解決這些分散式問題的。 應用五:負載均衡 我們知道,負載均衡演算法
資料結構基礎溫故-1.線性表(中)
在上一篇中,我們學習了線性表最基礎的表現形式-順序表,但是其存在一定缺點:必須佔用一整塊事先分配好的儲存空間,在插入和刪除操作上需要移動大量元素(即操作不方便),於是不受固定儲存空間限制並且可以進行比較快捷地插入和刪除操作的連結串列橫空出世,所以我們就來複習一下連結串列。 一、單鏈表基礎 1.1 單鏈表的
資料結構基礎溫故-4.樹與二叉樹(下)
上面兩篇我們瞭解了樹的基本概念以及二叉樹的遍歷演算法,還對二叉查詢樹進行了模擬實現。數學表示式求值是程式設計語言編譯中的一個基本問題,表示式求值是棧應用的一個典型案例,表示式分為字首、中綴和字尾三種形式。這裡,我們通過一個四則運算的應用場景,藉助二叉樹來幫助求解表示式的值。首先,將表示式轉換為二叉樹,然後通過
資料結構基礎溫故-4.樹與二叉樹(中)
在上一篇中,我們瞭解了樹的基本概念以及二叉樹的基本特點和程式碼實現,還用遞迴的方式對二叉樹的三種遍歷演算法進行了程式碼實現。但是,由於遞迴需要系統堆疊,所以空間消耗要比非遞迴程式碼要大很多。而且,如果遞迴深度太大,可能系統撐不住。因此,我們使用非遞迴(這裡主要是迴圈,迴圈方法比遞迴方法快, 因為迴圈避免了一系
資料結構基礎溫故-4.樹與二叉樹(上)
前面所討論的線性表元素之間都是一對一的關係,今天我們所看到的結構各元素之間卻是一對多的關係。樹在計算機中有著廣泛的應用,甚至在計算機的日常使用中,也可以看到樹形結構的身影,如下圖所示的Windows資源管理器和應用程式的選單都屬於樹形結構。樹形結構是一種典型的非線性結構,除了用於表示相鄰關係外,還可以表示層次
資料結構基礎溫故-1.線性表(下)
在上一篇中,我們瞭解了單鏈表與雙鏈表,本次將單鏈表中終端結點的指標端由空指標改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱為單迴圈連結串列,簡稱迴圈連結串列(circular linked list)。 一、迴圈連結串列基礎 1.1 迴圈連結串列節點結構 迴圈連結串列和單鏈表的
資料結構基礎溫故-2.棧
現實生活中的事情往往都能總結歸納成一定的資料結構,例如餐館中餐盤的堆疊和使用,羽毛球筒裡裝的羽毛球等都是典型的棧結構。而在.NET中,值型別線上程棧上進行分配,引用型別在託管堆上進行分配,本文所說的“棧”正是這種資料結構。棧和佇列都是常用的資料結構,它們的邏輯結構與線性表相通,不同之處則在於操作受某種特殊限制
資料結構基礎溫故-3.佇列
在日常生活中,佇列的例子比比皆是,例如在車展排隊買票,排在隊頭的處理完離開,後來的必須在隊尾排隊等候。在程式設計中,佇列也有著廣泛的應用,例如計算機的任務排程系統、為了削減高峰時期訂單請求的訊息佇列等等。與棧類似,佇列也是屬於操作受限的線性表,不過佇列是隻允許在一端進行插入,在另一端進行刪除。在其他資料結構如
資料結構基礎溫故-7.排序
排序(Sorting)是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為按關鍵字“有序”的記錄序列。如何進行排序,特別是高效率地進行排序時計算機工作者學習和研究的重要課題之一。排序有內部排序和外部排序之分,若整個排序過程不需要訪問外存便能完成,則稱此類排序為內部排序,反之則為外部排序。本篇主
資料結構-棧和佇列面試題(下)
面試題四:元素出棧、入棧順序的合法性。如入棧的序列(1,2,3,4,5),出棧序列為(4,5,3,2,1)。 思路: ①首先判斷出棧入棧序列長度是否一致,不一致直接返回false; ②借用一個臨時的棧,依次遍歷入棧序列的每一個元素,每次
資料結構入門---初始二叉樹(下)
這篇文章我們準備將二叉樹實現為具體的程式碼 首先我們要從二叉樹的遍歷說起。二叉樹的遍歷主要有四種形式 1. 前序遍歷 方法:如果二叉樹為空,則直接返回。如果二叉樹非空,則訪問根結點,再前序遍歷左子樹,然後前序遍歷右子樹 我們可以知道這樣的遍歷方式是以遞迴