一、查找的基本概念
1、查找表:
用於查找的數據元素集合。它由同一類型的數據元素構成。
2、關鍵字、主關鍵字、次關鍵字:
關健字:數據元素中的某個數據項;
主關鍵字:唯一能標識數據元素的關鍵字,即每個元素的關鍵字值互不相同;
次關健字:若查找表中某些元素的關健字值相同,比如學生信息表中的學號是主關鍵字,姓名是次關鍵字。
3、查找:
指在數據元素集合中查找滿足某種條件的數據元素的過程。
4、靜態查找:
若只對查找表在查找表中查看某個特定的數據元素是否在查找表中,或檢索某個特定元素的各種屬性。
5、動態查找:
在查找過程中可以將查找表不存在的數據元素插入,或從查找表中刪除某個數據元素。
6、內查找和外查找:
整個查找過程全部在內存中進行,則稱爲內查找;
若查找過程中還需要訪問外存,則稱爲外查找。
二、常見的幾種查找方法
1、靜態查找:
靜態查找一般以順序或線性表表示,線性表可以有不同的表示方法,在不同的表示方法中,實現查找操作的方法也不同。
(1)順序查找:從表的一端開始,順序掃描整個線性表,依次將掃描到的結點關鍵字與給定值k進行比較,若當前掃描到的結點關鍵字與k相等,則查找成功,返回該結點在表中的位置;若掃描結束後,仍然未找到關鍵字等於k的結點,則查找失敗,返回特定的值(0或NULL)
int Seq_search(int a[],int n,int key){
int i=n;
while(a[i]!=x){
i--;
}
return i;///返回查找到的元素的位置
}
(2)折半查找(二分查找):是一種效率較高的查找方法,折半查找要求查找表用順序存儲結構存放且各數據元素按關鍵字序列有序(升序或降序)排列,並且要求線性順序存儲。
比如:
int bin_search(int a[],int k){
low=1;hign=n;
while(low<=high){
mid=(low+high)/2;
if(k==a[mid]) break;
else if(k<a[mid]) high=mid-1;
else low=mid+1;
}
if(low<=high) return mid;///查找成功
else return 0;///查找失敗
}
(3)分塊查找:又稱爲索引順序查找,是對順序查找的一種改進。性能介於順序查找和二分查找之間。
基本思想:查找表可以分爲若干個子塊。塊內元素可以無序,但塊之間是有序的,即第一塊的最大關健字小於第二塊中的所有記錄的關鍵字。其中索引表是按索引值遞增(或遞減)排序的。
算法流程:
1)先選取各塊中的最大關健字構成一個索引表;
2)查找分兩部分:先對索引表進行二分查找或順序查找,以確定待查記錄在哪一塊;然後在已確定的塊中用順序法進行查找
struct table{
int key;///塊的關鍵字
int strart;///塊的起始值
int end;///塊的結束值
}t[4];///定義結構數組
int serch(int key,int a[]){
int i,j;
i=1;
while(i<=3&&key>t[i].key) i++;///確定在哪一塊
if(i>3) return 0;
j=t[i].start;///塊範圍的起始值
while(j<=t[i].end&&a[i]!=key) j++;///在確定的塊內進行順序查找
if(j>t[i].end) j=0;
return j;
}
int main(){
int j=0;
for(int i=1;i<16;i++) scanf(&a[i]);///輸入15個從小到大的15個數。
for(int i=1;i<=3;i++){
t[i].start=j+1;
j++;\\\j=1
t[i].end=j+4;///j=5,塊範圍的結束值
j+=4;///j=5;
t[i].key=a[j];///塊範圍中元素的最大值
}
scanf(&key)///輸入要查詢的元素key;
k=search(key,a);///調用函數查找
//// 如果k不等0,就找到,否則就找不到
}
2、動態查找:
(1)二叉排序樹(二叉查找樹)
它是具有以下性質的二叉樹:
1)若它的左子樹非空,則左子樹上所有結點的值均小於它的根結點的值
2)若它的右子樹非空,則右子樹上所有結點的值均大於(若允許具有相同關鍵字的結點存在,則大於等於)它的根結點的值。
3)它的左、右子樹本身又是一棵二叉排序樹。
///結點結構
typedef int DataType;
typedef struct Node
{
DataType data;///結點
struct Node *lchild, *rchild;///左右指針域
}BiTree;
二叉排序樹查找關鍵字(遞歸版、非遞歸版):
BiTree SearchBST(BiTree T,int x){///二叉樹查找關健字遞歸版:
//如果遞歸過程中 T 爲空,則查找結果,返回NULL;或者查找成功,返回指向該關鍵字的指針
if (!T || x==T->data) {
return T;
}else if(x<T->data){
//遞歸遍歷其左孩子
return SearchBST(T->lchild, x);
}else{
//遞歸遍歷其右孩子
return SearchBST(T->rchild, x);
}
}
BiTree Search_BST(BitTree T,DataType x)///二叉樹查找關健字非遞歸版:
{
BiTree *p = T;
while (p)
{
if (p->data == x) return p;
p = (x < p->data) ? p->lchild : p->rchild;
}
return NULL;
}
二叉排序樹插入關鍵字:
///插入關健字
BOOL SearchBST(BiTree T,KeyType key,BiTree f,BiTree *p){
//如果 T 指針爲空,說明查找失敗,令 p 指針指向查找過程中最後一個葉子結點,並返回查找失敗的信息
if (!T){
*p=f;
return false;
}
//如果相等,令 p 指針指向該關鍵字,並返回查找成功信息
else if(key==T->data){
*p=T;
return true;
}
//如果 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹
else if(key<T->data){
return SearchBST(T->lchild,key,T,p);
}else{
return SearchBST(T->rchild,key,T,p);
}
}
//插入函數
BOOL InsertBST(BiTree T,ElemType e){
BiTree p=NULL;
//如果查找不成功,需做插入操作
if (!SearchBST(T, e,NULL,&p)) {
//初始化插入結點
BiTree s=(BiTree)malloc(sizeof(BiTree));
s->data=e;
s->lchild=s->rchild=NULL;
//如果 p 爲NULL,說明該二叉排序樹爲空樹,此時插入的結點爲整棵樹的根結點
if (!p) {
T=s;
}
//如果 p 不爲 NULL,則 p 指向的爲查找失敗的最後一個葉子結點,只需要通過比較 p 和 e 的值確定 s 到底是 p 的左孩子還是右孩子
else if(e<p->data){
p->lchild=s;
}else{
p->rchild=s;
}
return true;
}
//如果查找成功,不需要做插入操作,插入失敗
return false;
}
二叉排序樹刪除關鍵字:
///刪除關健字
int Delete(BiTree *p)
{
BiTree q, s;
//情況 1,結點 p 本身爲葉子結點,直接刪除即可
if(!(*p)->lchild && !(*p)->rchild){
*p = NULL;
}
else if(!(*p)->lchild){ //左子樹爲空,只需用結點 p 的右子樹根結點代替結點 p 即可;
q = *p;
*p = (*p)->rchild;
free(q);
}
else if(!(*p)->rchild){//右子樹爲空,只需用結點 p 的左子樹根結點代替結點 p 即可;
q = *p;
*p = (*p)->lchild;//這裏不是指針 *p 指向左子樹,而是將左子樹存儲的結點的地址賦值給指針變量 p
free(q);
}
else{//左右子樹均不爲空,採用第 2 種方式
q = *p;
s = (*p)->lchild;
//遍歷,找到結點 p 的直接前驅
while(s->rchild)
{
q = s;
s = s->rchild;
}
//直接改變結點 p 的值
(*p)->data = s->data;
//判斷結點 p 的左子樹 s 是否有右子樹,分爲兩種情況討論
if( q != *p ){
q->rchild = s->lchild;//若有,則在刪除直接前驅結點的同時,令前驅的左孩子結點改爲 q 指向結點的孩子結點
}else{
q->lchild = s->lchild;//否則,直接將左子樹上移即可
}
free(s);
}
return TRUE;
}
int DeleteBST(BiTree *T, int key)
{
if( !(*T)){//不存在關鍵字等於key的數據元素
return FALSE;
}
else
{
if( key == (*T)->data ){
Delete(T);
return TRUE;
}
else if( key < (*T)->data){
//使用遞歸的方式
return DeleteBST(&(*T)->lchild, key);
}
else{
return DeleteBST(&(*T)->rchild, key);
}
}
}
(2)平衡二叉樹(AVL樹)
它或者是一顆空樹,或者具有以下性質的二叉排序樹;它的左子樹和右子樹的深度之差(平衡因子)的絕對值不超過1,且它的左子樹和右子樹都是一顆平衡二叉樹。
平衡因子:將二叉樹上節點的左子樹高度減去右子樹高度的值稱爲該節點的平衡因子BF(Balance Factor),BF範圍[-1,1];
最小不平衡子樹:距離插入節點最近的,且平衡因子的絕對值大於1的節點爲根的子樹.。
一顆AVL樹具有的必要條件:
1) 它必須是二叉查找樹。
2) 每個結點的左子樹和右子樹的高度差至多爲1。
下面舉一個例子:平衡二叉樹生成過程:
///平衡二叉樹結點結構:
typedef struct node{
int key;
struct node *left;
int height;
}BTnode;
平衡二叉樹通過插入元素,然後樹會出現不平衡,然後我們可以用四種類型去調整。
①LL型調整:由於在A的左孩子(L)的左子樹(L)上插入新結點。
例如:
BTnode *ll_rotate(BTnode *y)
{
BTnode *x = y->left;
y->left = x->right;
x->right = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
②RR型調整:由於在A的右孩子®的右子樹®上插入新結點。
BTnode *rr_rotate(struct node *y)
{
BTnode *x = y->right;
y->right = x->left;
x->left = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
③LR型調整:在A的左孩子(L)的右子樹®上插入新結點
BTNode* lr_rotate(BTNode* y)
{
BTNode* x = y->left;
y->left = rr_rotate(x);
return ll_rotate(y);
}
④RL型調整:在A的右孩子®的左子樹(L)上插入新結點
BTnode* lr_rotate(BTnode* y)
{
BTnode* x = y->left;
y->left = rr_rotate(x);
return ll_rotate(y);
}
三、參考資料
1、數據結構
2、https://blog.csdn.net/isunbin/article/details/81707606