2021-11-30 14:46:14 星期二
序言
查找,顧名思義,就是從某一集體中找出一個或一種元素。又稱檢索。
其中,在計算機語言學習中,怎么利用機器對數據進行簡便查找更是一項重要的工程。
根據對查找表操作不同,查找又分靜態查找和動態查找。
根據查找表的特點,我們可以利用不同的方法進行找到我們所需的那個唯一關鍵字。
對於靜態表的查找方法,這里我們主要介紹
一、順序查找(線性查找)
二、折半查找(二分或對分查找)
三、靜態樹表的查找
四、分塊查找(索引順序查找)
對於動態表,我們主要介紹:
典型的動態表—— 二叉排序樹
既然有方法了,那么怎么能判斷方法的優劣呢?
這樣就需要一個工具,來簡單的判斷該方法的優劣,它就是ASL(平均查找長度)
基本概念
查找表:——————由同一類型的數據元素(或記錄)構成的集合
查 找:——————查詢(Searching)特定元素是否在表中。
查找成功:——————若表中存在特定元素,稱查找成功,應輸出該記錄或位置;
查找不成功:————(與查找成功對立)否則,稱查找不成功(也應輸出失敗標志或失敗位置)
靜態查找表:————只查找,不改變集合內的數據元素。
動態查找表:——既查找,又改變(增減)集合內的數據元素。
關鍵字:——記錄中某個數據項的值,可用來識別一個記錄
主關鍵字:——可以唯一標識一個記錄的關鍵字
次關鍵字:——識別若干記錄的關鍵字
工具:ASL
如何評估查找方法的優劣?
明確:查找的過程就是將給定的K值與文件中各記錄的關鍵字項進行比較的過程。所以用比較次數的平均值來評估算法的優劣。稱為平均查找長度(ASL:average search length)。
其中:
n是記錄個數;
Pi是查找第i個記錄的查找概率(通常取等概率,即Pi =1/n);
Ci是找到第i個記錄時所經歷的比較次數。
物理意義:假設每一元素被查找的概率相同,則查找每一元素所需的比較次數之總和再取平均,即為ASL。
顯然,ASL值越小,時間效率越高。
例如:
給定一個集合:1 3 7 4 9 0,順序查找的方法,查找成功的ASL是多少?
解:對於每一個元素的查找成功的概率相同,都是1/6 既pi = 1/6,然后每個元素被查找成功的比較次數分別為(1,2,3,4,5,6)
ASL = (1+2+3+4+5+6)/6 = 21/6
靜態查找表
抽象數據類型的靜態查找表的定義為:
ADT StaticSearchTable
{數據對象D:D是具有相同特性的數據元素的集合。每個數據元素均含有類型相同、可唯一標識數據元素的關鍵字。
數據關系R:數據元素同屬一個集合。
基本操作P:
Create(ST,n); 操作結果:構造一個含n個數據元素的靜態查找表ST。
Destroy(ST); 操作結果:銷毀表ST。
Search(ST,key); 操作結果:若ST中存在其關鍵字等於key的數據元素,則函數值為該元素的值或者在表中的位置,否則為空。
Traverse(ST,Visit()); 操作結果:按照某種次序對ST的每個元素調用函數VIsit()一次且僅一次,一旦visit()失敗,則操作失敗
}
(1)順序表的機內存儲結構:
typedef ElemType{
keyType key;
anyType otherItems;
} ElemType
typedef struct {
ElemType *elem; //表基址,0號單元留空。表容量為全部元素
int length; //表長,即表中數據元素個數
}SSTable;
存儲圖例:
順序查找
利用上圖
如果我們要找出其中的元素四,
首先,要找到其關鍵字(這里是注關鍵字)------數組序號也就是長度
然后,選取方法。
這里我們采用第一種方法,也就是順序查找
Linear search,又稱線性查找,即用逐一比較的辦法順序查找關鍵字,這顯然是最直接的辦法。
(2)算法的實現:
技巧:把待查關鍵字key存入表頭或表尾(俗稱“哨兵”),
若將待查找的key存入順序表的首部(如0號單元),則順序查找的實現方案為:從后向前逐個比較!
int Search_Seq( SSTable ST , KeyType key ){
ST.elem[0].key =key; //設立哨兵
for( i=ST.length; ST.elem[ i ].key!=key; - - i );
//從后向前逐個比較
return i;
} // Search_Seq
設立哨兵:可免去查找過程中每一步都要檢測是否查找完畢。當n>1000時,查找時間將減少一半。
不要用for(i=n; i>0; - -i) 或 for(i=1; i<=n; i++) 了
到達0號單元(哨兵)結束循環,查找失敗,返回0(i=0)。查找成功,則返回該元素位置i。
圖例:
等概率下的平均比較查找長度:
折半查找(二分查找 對分查找)
先給數據排序(例如按升序排好),形成有序表,然后再將key與正中元素相比,若比key小,則縮小至右半部內查找;再取其中值比較,每次縮小1/2的范圍,直到查找成功或失敗為止。
存儲圖例:
② 運算步驟:
1)low =1,high =11 ,mid =6 ,待查范圍是 [1,11];
2)若 ST.elem[mid].key < key,說明 key[ mid+1,high] ,
則令:low =mid+1;重算 mid= (low+high)/2;.
3)若 ST.elem[mid].key > key,說明key[low ,mid-1],
則令:high =mid–1;重算 mid ;
4)若 ST.elem[ mid ].key = key,說明查找成功,元素序號=mid;
③ 結束條件: (1)查找成功 : ST.elem[mid].key = key
(2)查找不成功 : high≤low (意即區間長度小於0)
最后(low+high)/2下取整,得mid = 4 得出查找結果:查找成功,21所在位置是4
給出代碼
int bin_search(SSTable ST,int key)
{ int low,high,mid;
low=1; high=ST.length;
while(low<=high)
{
mid=(low+high)/2;
if EQ(key,ST.elem[mid].key) return mid; //等於
else if LT(key,ST.elem[mid].key) high=mid-1; //小於
else low=mid+1;
}
return 0;
}/*bin_search*/
算法效率:
分塊查找
又稱索引順序查找這是一種順序查找的另一種改進方法。
先讓數據分塊有序,即分成若干子表,要求每個子表中的數值(用關鍵字更准確)都比后一塊中數值小(但子表內部未必有序)。
然后將各子表中的最大關鍵字構成一個索引表,表中還要包含每個子表的起始地址(即頭指針)。
兩步走: 首先通過索引表查找待查記錄所在的塊(子表),然后在塊內進行順序查找
索引表的存儲結構定義如下,
#define MAX_SUBLIST_NUM 10
typedef struct{
KeyType maxKey; /*---子表中的最大關鍵字---*/
int index; /*---子表中第一個記錄在基本表中的位置---*/
}IndexItem; /*---索引項---*/
typedef IndexItem indexList[MAX_SUBLIST_NUM]; /*----索引表類型--*/
那么,可以得出分塊查找算法的平均查找長度
動態查找表
典型的動態查找表——————二叉排序樹
二叉排序樹的定義:
或是一棵空樹;或者是具有如下性質的非空二叉樹:
(1)左子樹的所有結點值均小於根的值;
(2)右子樹的所有結點值均大於根的值;
(3)它的左右子樹也分別為二叉排序樹。
從定義上可以看出,二叉排序樹是一個可以由遞歸創建的邏輯結構。
二叉排序的圖例:
二叉排序樹的存儲結構(以鏈表存儲為例):
/*---二叉排序樹的二叉鏈表存儲結構---*/
typedef struct BTNode{ /*---元素的結點結構---*/
Elemtype key; /*---記錄的關鍵字,忽略記錄的其他數據項---*/
struct BTNode *lchild,*rchild; /*---左右指針---*/
}BTNode,*BStree;
二叉排序樹的創建,插入,查找,刪除
創建與插入
根據定義創建二叉樹:
while(序列不為空){
- 順序拿出元素
- 元素比根結點大,放到根結點的右子樹。
- 元素比根結點小,放到根結點的左子樹。
- 重復步驟2-3,至成為葉子結點。
}
給出代碼:
創建:
BStree CreateBST()
{
BStree T,s;
T = NULL;
printf("輸入關鍵字key,輸入“-1”結束。\n");
while(1)
{
scanf("%d",&key); /*---這里假設key是int類型---*/
if(key) break;
s = (BTNode*)malloc(sizeof(BTNode));
s->key = key;
s->lchild = NULL;
s->rchild = NULL;
T = InsertBST(T,s); /*---插入二叉排序樹---*/
}
return T;
}
插入(1:遞歸版本)
BSTree InsertBST(BSTree T,BTNode *s)
{/*---在以T為根的二叉排序樹上插入一個指針s所指向的結點的遞歸算法---*/
if(T==NULL) T = s;
else
{
if(s->key > T->key) T->rchild = InsertBST(T->rchild,s); /*---遞歸插入到T的右子樹中---*/
else if(s->key < T->key) T->lchild = InsertBST(T->lchild,s); /*---遞歸插入到T的左子樹中---*/
}
return T;
}
插入(2:非遞歸版本)
點擊查看代碼
BSTree InsertBST(BSTree T,BTNode *s)
{
BSTree p = T;
BTNode *f = NULL;
if(T==NULL)
{
T = s;
return T;
}
while(p)
{
if(p->key == s->key) return T;
f = p; /*---記錄訪問的結點:便於連接s---*/
if(s->key < p->key)
p = p->lchild;
else p = p->rchild;
}//跳出循環:1.p = null;2.f就是s與之連接的結點
if(s->key > p->key) f->rchild = s;
if(s->key < p->key) f->lchild = s;
return T;
}
查找與刪除
查找的基本過程如下:
當二叉樹為空時,返回0;
當二叉樹不為空時,首先將給定的值key與根結點關鍵字進行比較,若相等,則查找成功,返回1;否則將依據給定值key與根結點關鍵字值的大小關系,分別在其左子樹或者右子樹中繼續查找。可見,二叉排序樹的查找可以是一個遞歸的過程。
刪除的基本過程:
給出代碼:
查找代碼:
int BSTsearch(BSTree T, int key)
{
BSTree p = T;
if(!p) return 0; /*---空樹,返回0---*/
else if(p->key == key) return 1;
else if(p->key > key) BSTsearch(p->lchild,key);
else BSTsearch(p->rchild,key);
}
查找非遞歸算法
點擊查看代碼
//二叉排序樹查找非遞歸算法
int BSTsearch2(BSTNode *T, int data)
{
BSTNode *p = T;
while(p)
{
if(p->key == data)
return 1;
p = (p->key > data)? p->lchild : p->rchild;
}
return 0;
}
刪除代碼:
//二叉排序樹刪除操作
void DelBST(BSTNode *T,int key)
{
BSTNode *p = T, *f, *q, *s;
while(p)
{
if(p->key == key) break; //找到關鍵字為key的結點
f=p;//記下關鍵字key節點的父節點
p=(key < p->key)? p->lchild : p->rchild;//分別在*p的左、右子樹中查找
}
if(!p) return;//二叉排序樹中無關鍵字為key的結點
if(p->lchild == NULL && p->rchild == NULL)//p無左子樹無右子樹
{
if(p == T) T = NULL;//刪除的是根節點
else
if(p == f->lchild)
f->lchild = NULL;
else
f->rchild = NULL;
}
else if(p->lchild == NULL && p->rchild != NULL)//p無左子樹有右子樹
{
if(f->lchild == p)
f->lchild = p->rchild;
else
f->rchild = p->rchild;
}
else if(p->rchild == NULL && p->lchild != NULL)//p有左子樹無右子樹
{
if (f->lchild == p)
f->lchild = p->lchild;
else
f->rchild = p->lchild;
}
else if(p->lchild != NULL && p->rchild != NULL)//p既有左子樹又有右子樹
{
q = p;
s = p->lchild;//轉左
while(s->rchild)
{//然后向右到盡頭
q = s;
s = s->rchild;//s指向被刪節點的“前驅”(中序前驅)
}
p->key = s->key;//以p的中序前趨結點s代替p(即把s的數據復制到p中)
if(q != p)
q->rchild = s->lchild;//重接q的右子樹
else
q->lchild = s->lchild;//重接q的左子樹。
}
}