資料結構和演算法學習筆記十二:查詢
一.概述
在工程中對資料庫的操作主要有增刪查改幾類,其中增刪改的操作都依賴查詢,畢竟得先找到資料才能進行其他操作.
在大話資料結構一書中對查詢(Searching)的定義是:根據給定的某個值,在查詢表中確定一個其關鍵字等於給定值的資料元素(或記錄).相對來說概念很好理解.
查詢表分為靜態查詢表和動態查詢表兩類:
1.靜態查詢表(Static Search Table):只作查詢工作的查詢表,主要有根據給定鍵查詢資料元素是否存在和查詢資料元素的各種屬性兩種.
2.動態查詢表(Dynamic Search Table):查詢的同時進行其他操作,可以進行增刪改等操作.
針對主要進行靜態查詢的查詢表,使用線性表結構組織資料更方便,但是針對主要進行動態查詢的查詢表,則需要使用二叉樹等更為複雜的結構組織資料.
注:本文中實現使用的程式碼為C#程式碼.查詢實現均為從一堆使用特定資料結構組織的整型數字中查詢特定的數字.
二.順序表查詢
1.簡介:順序表查詢(Sequential Search)又叫線性查詢,資料使用線性結構組織,查詢時從表的第一個資料依次遍歷.
2.程式碼實現:
/// <summary> /// 線性表查詢 /// </summary> /// <param name="table">線性表</param> /// <param name="key">鍵</param>/// <param name="index">查詢到資料所在的下標,沒有查詢到時返回-1</param> /// <returns>是否查詢到資料</returns> public static bool SequentialSearch(List<int> table, int key, out int index) { index = -1; int length = table.Count; for (inti = 0; i < length; i++) { if(table[i] == key) { index = i; return true; } } return false; }
3.線性表查詢優化:在for迴圈中,每次都要檢查是否i越界了,實際上可以將index當作哨兵,這樣不需要每次比較i和length了.當然,陣列中下標為0的位置不能用於儲存.
4.線性表查詢優化程式碼實現:
/// <summary> /// 線性表查詢優化 /// </summary> /// <param name="table">線性表</param> /// <param name="key">鍵</param> /// <param name="index">查詢到資料所在的下標,沒有查詢到時返回-1</param> /// <returns>是否查詢到資料</returns> public static bool SequentialSearch2(List<int> table, int key, out int index) { int count = table.Count; table.Add(key); index = 0; while(table[index] != key) { index++; } if(index == count) { index = -1; return false; } return true; }
5.線性查詢的時間複雜度:O(n)
三.有序表查詢:對於表中資料的儲存,如果我們使用一定的方式優化就可以使資料變得有序,這樣可以有效縮小查詢範圍.
1.折半查詢:折半查詢又稱二分查詢.我們在存放資料時,將資料的鍵值按照從小到大排列,這樣每次查詢後將剩餘資料平均分為兩部分,判斷當前資料在哪部分,接著繼續將這部分資料平均分為兩部分,判斷要查詢的資料在哪部分...折半查詢的時間複雜度是O(logn).
2.折半查詢程式碼實現:
/// <summary> /// 折半查詢 /// </summary> /// <param name="table">線性表</param> /// <param name="key">鍵</param> /// <param name="index">查詢到資料所在的下標,沒有查詢到時返回-1</param> /// <returns>是否查詢到資料</returns> public static bool BinarySearch(List<int> table, int key, out int index) { int low = 0; int high = table.Count - 1; int mid; while(low <= high) { mid = (low + high) / 2; if(key < table[mid]) { high = mid - 1; } else if(key > table[mid]) { low = mid + 1; } else { index = mid; return true; } } index = -1; return false; }
3.插值查詢:我們針對折半查詢進行改進,原來每次沒有查詢到資料時mid會置為low和high的中間位置,如果我們充分考慮到要查詢的key值和當前的low\high指標指向的值的差距,那麼我們可以採取這樣的方案:當key值和low指標指向的值更接近時,讓mid指標離low指標更近一些,key值和low指標指向的值越接近,就讓mid和low離得越近,同理,當key值和high指標指向的值更接近時我們讓mid指標離high指標更近一些.理論上這樣要比每次都將mid指標置為low和high的中間位置運算次數更少,實際上大多數情況下也是如此,雖然它的時間複雜度仍然為O(logn).至於使用這種方式時得到mid指標的位置的公式,見下方的實現程式碼:
4.插值查詢程式碼實現:
/// <summary> /// 插值查詢 /// </summary> /// <param name="table">線性表</param> /// <param name="key">鍵</param> /// <param name="index">查詢到資料所在的下標,沒有查詢到時返回-1</param> /// <returns>是否查詢到資料</returns> public static bool InterpolationSearch(List<int> table, int key, out int index) { int low = 0; int high = table.Count - 1; int mid; while (low <= high) { //公式一 mid = low + (key - table[low]) * (high - low) / (table[high] - table[low]); //公式二 //mid = high - (table[high] - key) * (high - low) / (table[high] - table[low]); if (key < table[mid]) { high = mid - 1; } else if (key > table[mid]) { low = mid + 1; } else { index = mid; return true; } } index = -1; return false; }
5.斐波那契查詢:對於折半查詢優化,除了使用插值查詢的方案外,還可以使用斐波那契數列優化.斐波那契查詢的mid指標位置取法使用了斐波那契數列,斐波那契數列的特點是除了前兩個數字外,任意數字都等於前兩個數字的和,因此首先找到當前資料的長度在哪兩個斐波那契數字之間,然後取其中較大的那一個作為當前資料長度,每次比較完後將資料分為數量分別等於當前斐波那契數字前兩個數字的兩部分,再確定key在哪部分數字中...
6.斐波那契查詢程式碼實現:
/// <summary> /// 斐波那契查詢 /// </summary> /// <param name="table">線性表</param> /// <param name="key">鍵</param> /// <param name="index">查詢到資料所在的下標,沒有查詢到時返回-1</param> /// <returns>是否查詢到資料</returns> public static bool FibonacciSearch(List<int> table, int key, out int index) { int count = table.Count; //取出當前table的長度 int low = 0; int high = count - 1; int mid; //構造斐波那契數列,數列長度足夠覆蓋所有資料即可,數列前兩個數初始化為0和1 List<int> fibonacci = new List<int>(); fibonacci.Add(0); fibonacci.Add(1); int fibonacciIndex = 1; //當前斐波那契數列下標指向1 while(count > fibonacci[fibonacciIndex]) { fibonacci.Add(fibonacci[fibonacciIndex] + fibonacci[fibonacciIndex - 1]); fibonacciIndex++; } for(int i = count;i < fibonacci[fibonacciIndex]; i++) { table.Add(table[count - 1]); } while (low <= high) { mid = low + fibonacci[fibonacciIndex - 1]; if (key < table[mid]) { high = mid - 1; fibonacciIndex--; } else if (key > table[mid]) { low = mid + 1; fibonacciIndex = fibonacciIndex - 2; } else { index = mid < count ? mid : count - 1; return true; } } index = -1; return false; }
四.線性索引查詢:
對於一些增長非常迅速的資料,如果想要直接使用有序查詢不太現實,因為資料量大時將資料按照某個關鍵字有序排列的時間代價很高,所以我們需要採取其他的方式查詢資料:
1.稠密索引:使用一種資料結構將所有資料的key集中存放,這種資料結構包含key值和地址指標兩部分,地址指標指向實際的資料位置,這樣在使key有序的過程中就不用將整條資料移動位置,而只需要讓指標和key一起移動即可.這個集中存放資料key值和資料地址指標的表稱為索引表.這種方式有效地減少了排列資料的時間成本.與稠密索引相對的還有稀疏索引,稀疏索引將經常被查詢的資料存放在索引表中,當在索引表中找不到key時再順序查詢一次整個表.稀疏索引降低了索引表的資料量,但是在索引表中找不到資料時卻只能順序遍歷所有資料.
2.分塊索引:我們很容易想到對於一個非常龐大的索引表,可以採用類似圖書館存放圖書的方式存放所有索引,也就是將索引分類存放,這種索引存放方式稱為分塊索引.分塊的資料塊內可以是無序的也可以是有序的.
3.倒排索引:通常情況下,資料的key都是id,每條資料的id都不相同,我們可以將id稱為資料的主關鍵碼.但是在實際的查詢應用中我們往往更多的需求是查詢某個欄位含有特定值的所有資料記錄,如name是"張三"或性別是男.這種情況下,這些查詢的關鍵字("張三"或男)我們可以稱為次關鍵字,並且我們可以建立一個索引表,這個索引表以次關鍵字為鍵,值是指向所有含有這個次關鍵字的資料指標或者是所有含有這個次關鍵字的資料的主關鍵字,這樣的索引方法就是倒排索引.