資料結構之查詢
查詢就是根據給定的某個值,在查詢表中確定一個其關鍵字等於給定值的資料元素。
順序表查詢
順序表查詢(sequential search)又叫線性查詢,是最基本的查詢技術,它的查詢過程是:從表中第一個記錄開始,逐個進行記錄的關鍵字和給定值比較,若某個記錄的關鍵字和給定值相等,則查詢成功
,找到所查的記錄;如果查到最後一個記錄,其關鍵字和給定值比較都不等時,則表中沒有所查的記錄,查詢不成功
。
- 順序表查詢
a為陣列,key為要查詢的關鍵字,a[i]==key則查詢成功。 - 順序表查詢優化
以上演算法每次迴圈時都要對i是否越界,即是否小於等於n作判斷。事實上,還可以有更好一點的辦法,設定一個哨兵
O(n)
。
有序表查詢
折半查詢
折半查詢又稱為二分查詢,它的前提是線性表中的記錄必須是關鍵碼有序,線性表必須採用順序儲存,時間複雜度O(logn)
。
插值查詢
現在我們的問題是,為什麼一定要折半,而不是折1/4或者折更多呢?插值查詢時根據要查詢的關鍵字key與查詢表中最大最小記錄的關鍵字比較後的查詢方法,其核心就在於插值的計算公式:
mid = low + (high-low)*(key-a[low])/(a[high]-a[low]);
時間複雜度為O(logn),但對於表長較長,而關鍵字分佈又比較均勻
的查詢表來說,插值查詢演算法的平均效能比折半查詢好得多。
斐波那契查詢
除了插值查詢,還可以利用黃金分割原理來實現查詢,即斐波那契查詢。
- 陣列不夠斐波那契對應數字長度,得補全a[i]=a[n]
- mid=low+F[k-1]-1
- 如果key<a[mid],則high=mid-1;k=k-1
- 如果key>a[mid],則low=mid+1;k=k-2
- 直到key=a[mid],程式結束
如果mid>n,則說明是補全數值,返回n
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */
typedef int Status; /* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */
int F[100]; /* 斐波那契數列 */
/* 無哨兵順序查詢,a為陣列,n為要查詢的陣列個數,key為要查詢的關鍵字 ,i返回角標*/
int Sequential_Search(int *a, int n, int key)
{
int i;
for (i = 1; i <= n; i++)
{
if (a[i] == key)
return i;
}
return 0;
}
/* 有哨兵順序查詢 */
//a為表,n為表長,key為要查詢的數字,i返回數字角標
int Sequential_Search2(int *a, int n, int key)
{
int i;
a[0] = key;
i = n;
while (a[i] != key)
{
i--;
}
return i;
}
/* 折半查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字角標
int Binary_Search(int *a, int n, int key)
{
int low, high, mid;
low = 1; /* 定義最低下標為記錄首位 */
high = n; /* 定義最高下標為記錄末位 */
while (low <= high)
{
mid = (low + high) / 2; /* 折半 */
if (key<a[mid]) /* 若查詢值比中值小 */
high = mid - 1; /* 最高下標調整到中位下標小一位 */
else if (key>a[mid])/* 若查詢值比中值大 */
low = mid + 1; /* 最低下標調整到中位下標大一位 */
else
{
return mid; /* 若相等則說明mid即為查詢到的位置 */
}
}
return 0;
}
/* 插值查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字角標
int Interpolation_Search(int *a, int n, int key)
{
int low, high, mid;
low = 1; /* 定義最低下標為記錄首位 */
high = n; /* 定義最高下標為記錄末位 */
while (low <= high)
{
mid = low + (high - low)*(key - a[low]) / (a[high] - a[low]); /* 插值 */
if (key<a[mid]) /* 若查詢值比插值小 */
high = mid - 1; /* 最高下標調整到插值下標小一位 */
else if (key>a[mid])/* 若查詢值比插值大 */
low = mid + 1; /* 最低下標調整到插值下標大一位 */
else
return mid; /* 若相等則說明mid即為查詢到的位置 */
}
return 0;
}
/* 斐波那契查詢 */
//a為表,n為表長,key為要查詢的數字,mid返回數字的角標
int Fibonacci_Search(int *a, int n, int key)
{
int low, high, mid, i, k = 0;
low = 1; /* 定義最低下標為記錄首位 */
high = n; /* 定義最高下標為記錄末位 */
while (n>F[k] - 1)
k++;
for (i = n; i<F[k] - 1; i++)
a[i] = a[n];
while (low <= high)
{
mid = low + F[k - 1] - 1;
if (key<a[mid])
{
high = mid - 1;
k = k - 1;
}
else if (key>a[mid])
{
low = mid + 1;
k = k - 2;
}
else
{
if (mid <= n)
return mid; /* 若相等則說明mid即為查詢到的位置 */
else
return n;
}
}
return 0;
}
int main(void)
{
int a[MAXSIZE + 1], i, result;
int arr[MAXSIZE] = { 0,1,16,24,35,47,59,62,73,88,99 };
for (i = 0; i <= MAXSIZE; i++)
{
a[i] = i;
}
result = Sequential_Search(a, MAXSIZE, MAXSIZE);
printf("Sequential_Search:%d \n", result);
result = Sequential_Search2(a, MAXSIZE, 1);
printf("Sequential_Search2:%d \n", result);
result = Binary_Search(arr, 10, 62);
printf("Binary_Search:%d \n", result);
result = Interpolation_Search(arr, 10, 62);
printf("Interpolation_Search:%d \n", result);
F[0] = 0;//斐波那契數列,全域性變數
F[1] = 1;
for (i = 2; i < 100; i++)
{
F[i] = F[i - 1] + F[i - 2];
}
result = Fibonacci_Search(arr, 10, 62);
printf("Fibonacci_Search:%d \n", result);
system("pause");
return 0;
}
執行結果:
Sequential_Search:100
Sequential_Search2:1
Binary_Search:7
Interpolation_Search:7
Fibonacci_Search:7
請按任意鍵繼續. . .
線性索引查詢
稠密索引
稠密索引是指線上性索引中,將資料集中的每個記錄對應一個索引項,索引項一定是按照關鍵碼有序排列
。
- 關鍵字有序,可以使用折半、插值、斐波那契等有序查詢法
- 但對於
記憶體有限
的計算機來說,可能需要反覆的訪問磁碟,查詢效能下降。
分塊索引
分塊索引好比圖書館的書架,塊間有序,塊內無序,需要指定塊的的最大關鍵碼,使得下一個塊的最小關鍵字比上一個最大關鍵字大。
- 塊間使用折半、插值等演算法
- 塊內使用順序查詢演算法
- 效率比順序查詢O(n)高,比折半O(logn)低。
二叉排序樹
1. 順序儲存插、刪快,查詢慢
2. 對於有序表,折半、插值、斐波那契查詢快,增刪慢
二叉排序樹是以連結的方式儲存,保持了連結儲存結構在執行插入或刪除操作時不用移動元素的優點,插入刪除的時間效能好
。二叉樹的查詢效能取決於二叉樹的形狀
,不平衡的極端情況下可能需要查詢n次。
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */
typedef int Status; /* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */
/* 二叉樹的二叉連結串列結點結構定義 */
typedef struct BiTNode /* 結點結構 */
{
int data; /* 結點資料 */
struct BiTNode *lchild, *rchild; /* 左右孩子指標 */
} BiTNode, *BiTree;
/* 遞迴查詢二叉排序樹T中是否存在key, */
/* 指標f指向T的雙親,其初始呼叫值為NULL */
/* 若查詢成功,則指標p指向該資料元素結點,並返回TRUE */
/* 否則指標p指向查詢路徑上訪問的最後一個結點並返回FALSE */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
if (!T) /* 查詢不成功 */
{
*p = f;
return FALSE;
}
else if (key == T->data) /* 查詢成功 */
{
*p = T;
return TRUE;
}
else if (key<T->data)
return SearchBST(T->lchild, key, T, p); /* 在左子樹中繼續查詢 */
else
return SearchBST(T->rchild, key, T, p); /* 在右子樹中繼續查詢 */
}
/* 當二叉排序樹T中不存在關鍵字等於key的資料元素時, */
/* 插入key並返回TRUE,否則返回FALSE */
Status InsertBST(BiTree *T, int key)
{
BiTree p, s;
if (!SearchBST(*T, key, NULL, &p)) /* 查詢不成功 */
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
*T = s; /* 插入s為新的根結點 */
else if (key<p->data)
p->lchild = s; /* 插入s為左孩子 */
else
p->rchild = s; /* 插入s為右孩子 */
return TRUE;
}
else
return FALSE; /* 樹中已有關鍵字相同的結點,不再插入 */
}
/* 從二叉排序樹中刪除結點p,並重接它的左或右子樹。 */
Status Delete(BiTree *p)
{
BiTree q, s;
if ((*p)->rchild == NULL) /* 右子樹空則只需重接它的左子樹(待刪結點是葉子也走此分支) */
{
q = *p; *p = (*p)->lchild; free(q);
}
else if ((*p)->lchild == NULL) /* 只需重接它的右子樹 */
{
q = *p; *p = (*p)->rchild; free(q);
}
else /* 左右子樹均不空 */
{
q = *p; s = (*p)->lchild;
while (s->rchild) /* 轉左,然後向右到盡頭(找待刪結點的前驅) */
{
q = s;
s = s->rchild;
}
(*p)->data = s->data; /* s指向被刪結點的直接前驅(將被刪結點前驅的值取代被刪結點的值) */
if (q != *p)
q->rchild = s->lchild; /* 重接q的右子樹 */
else
q->lchild = s->lchild; /* 重接q的左子樹 */
free(s);
}
return TRUE;
}
/* 若二叉排序樹T中存在關鍵字等於key的資料元素時,則刪除該資料元素結點, */
/* 並返回TRUE;否則返回FALSE。 */
Status DeleteBST(BiTree *T, int key)
{
if (!*T) /* 不存在關鍵字等於key的資料元素 */
return FALSE;
else
{
if (key == (*T)->data) /* 找到關鍵字等於key的資料元素 */
return Delete(T);
else if (key<(*T)->data)
return DeleteBST(&(*T)->lchild, key);
else
return DeleteBST(&(*T)->rchild, key);
}
}
int main(void)
{
int i;
int a[10] = { 62,88,58,47,35,73,51,99,37,93 };
BiTree T = NULL;
for (i = 0; i<10; i++)
{
InsertBST(&T, a[i]);
}
BiTree p = NULL;
int result1 = SearchBST(T, 93, NULL, &p);
printf("查詢'93'的結果為(1:存在,0:不存在):%d\n",result1);
int result2 = DeleteBST(&T, 93);
printf("刪除'93'的結果為(1:成功,0:不成功):%d\n", result2);
int result3 = SearchBST(T, 93, NULL, &p);
printf("查詢'93'的結果為(1:存在,0:不存在):%d\n", result3);
//printf("本樣例建議斷點跟蹤檢視二叉排序樹結構\n");
system("pause");
return 0;
}
執行結果為:
查詢'93'的結果為(1:存在,0:不存在):1
刪除'93'的結果為(1:成功,0:不成功):1
查詢'93'的結果為(1:存在,0:不存在):0
請按任意鍵繼續. . .
平衡二叉樹(AVL樹)
平衡二叉樹是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差至多等於1。
- 圖2中58<59不滿足二叉排序樹定義
- 圖3中58左子樹高度2,右子樹為空,相差大於1,故不滿足
距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹,我們稱為最小不平衡子樹。當插入結點37時,距離它最近的平衡因子絕對值超過1的結點是58(左子樹高度2減去右子樹高度0),所以從58開始以下的子樹為最小不平衡子樹
。
平衡二叉樹實現原理:
每當插入一個結點時,先檢查是否因為插入結點而破壞了樹的平衡性,若是,則找出最小不平衡子樹。在保持二叉樹特性的前提下,調整最小不平衡子樹中結點的連結關係,進行相應的旋轉
,使之成為新的平衡二叉樹。
(1)插入結點5,結點3的BF值為-2說明要旋轉了,對這棵樹的最小平衡子樹進行左旋
,以此達到平衡。
(2)插入結點6,結點2的BF值為-2,把樹的最小平衡子樹左旋,旋轉後為滿足二叉排序樹的特性,結點3需要作出改變
。
(3)新增結點7,結點5的BF值為-2,左旋。
(4)新增結點10,結構不變化。新增結點9,結點7的BF值為-2,而結點10的BF為1,一正一負,符號不統一
。所以先把結點9、10右旋
,符號統一,再左旋
。新增結點8同理可得。
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 儲存空間初始分配量 */
typedef int Status; /* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */
/* 二叉樹的二叉連結串列結點結構定義 */
typedef struct BiTNode /* 結點結構 */
{
int data; /* 結點資料 */
int bf; /* 結點的平衡因子 */
struct BiTNode *lchild, *rchild; /* 左右孩子指標 */
} BiTNode, *BiTree;
/* 對以p為根的二叉排序樹作右旋處理, */
/* 處理之後p指向新的樹根結點,即旋轉處理之前的左子樹的根結點 */
void R_Rotate(BiTree *P)
{
BiTree L;
L = (*P)->lchild; /* L指向P的左子樹根結點 */
(*P)->lchild = L->rchild; /* L的右子樹掛接為P的左子樹 */
L->rchild = (*P);
*P = L; /* P指向新的根結點 */
}
/* 對以P為根的二叉排序樹作左旋處理, */
/* 處理之後P指向新的樹根結點,即旋轉處理之前的右子樹的根結點0 */
void L_Rotate(BiTree *P)
{
BiTree R;
R = (*P)->rchild; /* R指向P的右子樹根結點 */
(*P)->rchild = R->lchild; /* R的左子樹掛接為P的右子樹 */
R->lchild = (*P);
*P = R; /* P指向新的根結點 */
}
#define LH +1 /* 左高 */
#define EH 0 /* 等高 */
#define RH -1 /* 右高 */
/* 對以指標T所指結點為根的二叉樹作左平衡旋轉處理 */
/* 本演算法結束時,指標T指向新的根結點 */
void LeftBalance