查找

查找

一、查找概论

  • 查找表:由同一类型的数据元素(或记录)构成的集合
  • 关键字:数据元素中某个数据项的值,又称键值,用它可以标识一个数据元素,也可以标识一个记录的某个数据项(字段),称为关键码
  • 主关键字:此关键字可以唯一地标识一个记录
  • 次关键字:可以识别多个数据元素(或记录)的关键字
  • 查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)

查找表按照操作方式可分为:静态查找表与动态查找表

  • 静态查找表:只作查找操作的查找表
  • 动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素

二、顺序表查找

顺序查找(静态查找):从表中第一个或最后一个记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值都不等时,则表中没有所查的记录,查找不成功。

顺序表查找算法

//顺序查找,a为数组,n为要查找的数组长度,key为要查找的关键字
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;
}

顺序查找表优化

//增加哨兵顺序查找
int Sequential_Search2(int *a,int n,int key)
{
    int i;
    a[0]=key;//设置a[0]为关键字值,称为哨兵
    i=n;//循环从数组尾部开始
    while(a[i]!=key)
    {
        i--;
    }
    return i;
}

三、有序表查找

(1)折半查找

折半查找(二分查找):前提是线性表中的记录必须是关键码有序,线性表必须采用顺序存储。

基本思想:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所查找区域无记录,查找失败。

假设有序表数组{0,1,16,24,35,47,,59,62,73,88,99},查找是否存在62

int Binary_Search(int *a,int n,int key)
{
    int low,high,mid;
    low=1;//定义最低下标为记录首位
    high=n;//定义最高下标为记录末位
    while(low<=hihg)
    {
        mid=(low+high)/2;
        if(key<a[mid])
        {
            high=mid-1;
        }
        else if(key>a[mid])
        {
            low=mid+1;
        }
        else
        {
            return mid;
        }
    }
    return 0;
}

(2)插值查找

插值查找:根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法

核心公式:(key-a[low])/(a[high]-a[low])

int Binary_Search(int *a,int n,int key)
{
    int low,high,mid;
    low=1;//定义最低下标为记录首位
    high=n;//定义最高下标为记录末位
    while(low<=hihg)
    {
        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;
        }
    }
    return 0;
}

(3)斐波那契查找

int Fibonacci_Search(int *a,int n,int key)
{
    int low,high,mid,i,k;
    low=1;//定义最低下标为记录首位
    high=n;//定义最高下标为记录末位
    k=0;
    while(n>F[k]-1)//计算n位于斐波那契数列的位置
    {
        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;//最高下标调整到分割下标mid-1处
            k=k-1;//斐波那契数列下标减一位
        }
        else if(key>a[mid])//若查找记录大于当前分割记录
        {
            low=mid+1;//最低下标调整到分割下标mid+1处
            k=k-2;//斐波那契数列下标减两位
        }
        else
        {
            if(mid<=n)
            {
                return mid;//若相等则说明mid即为查找到的位置
            }
            else
            {
                return n;//若mid>n说明是补全数值,返回n
            }
        }
    }
    return 0}

四、线性索引查找

索引:把一个关键字与它对应的记录相关联的过程

索引按结构可分为线性索引、树形索引与多级索引

线性索引:将索引项集合组织为线性结构,也称为索引表

三种线性索引:稠密索引、分块索引、倒排索引

(1)稠密索引

稠密索引:在线性索引中,将数据集中的每个记录对应一个索引项,索引项一定是按照关键码有序的排列

(2)分块索引

分块索引:是把数据集的记录分成了若干块,并且这些快需要满足两个条件

  • 块内无序:即每一块的记录不要求有序。
  • 块间有序

定义的分块索引的索引项结构分三个数据项

  • 最大关键码存储每一块中的最大关键字
  • 存储了块中的记录个数,以便于循环时使用
  • 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历

(3)倒排索引

索引项的通用结构:

  • 次关键码
  • 记录号表:存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者该记录的主关键字)

五、二叉排序树

二叉排序树(二叉查找树),是一棵空树或者是具有下列性质的二叉树

  • 若其左子树不空,则左子树上所有结点的值均小于它的根结构的值
  • 若其右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 其左右子树也分别为二叉排序树

(1)二叉排序树查找操作

二叉树结构

//二叉树的二叉链表结点结构定义
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);//在右子树继续查找
    }
}

(2)二叉排序树插入操作

//当二叉排序树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;//树中已有关键字相同的结点,不再插入
    }
}

(3)二叉排序树删除操作

//若二叉排序树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);
        }
    }
}

Delete代码

//从二叉排序树中删除结点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;
}

六、平衡二叉树(AVL树)

平衡二叉树:是一种二叉排列树,其中每一个节点的左子树和右子树的高度差至多等于1

平衡因子BF:二叉树上结点的左子树深度减去右子树深度的值

最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树

(1)平衡二叉树实现算法

改进二叉排列树的结点结构,增加一个bf,用于存储平衡因子

//二叉树的二叉链表结点结构定义
typedef struct BiTNode//结点结构
{
    int data;//结点数据
    int bf;//结点的平衡因子
    struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTNode;

右璇操作代码

//对以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)->lchild;
    (*p)->lchild=R->lchild;
    R->lchild=(*p);
    *p=R;
}

左平衡旋转处理的函数代码

#define LH+1//左高
#define EH 0;//等高
#define RH-1;//右高
//对以指针T所指结点为根的二叉树作左平衡旋转处理
//本算法结束时,指针T指向新的根结点
void LeftBalance(BiTree *T)
{
    BiTree L,Lr;
    L=(*T)->rchild;//L指向T的左子树根结点
    switch(L->bf)
    {
    	//检查T的左子树的平衡度,并作相应平衡处理
        case LH;//新结点插入在T的左孩子的左子树上,要作单右旋处理
            {
                (*T)->bf=L=>bf=EH;
                R_Rotata(T);
                break;
            }
        case R
            
                Lr=L->rchild;//Lr指向T的左孩子的右子树根
                switch(Lr->bf)//修改T及其左孩子的平衡因子
                {
                    case LH:(*T)->bf=RH;
                        {
                            L->bf=EH;
                            break;
                        }
                    case EH:(*T)->bf=L->bf=EH;
                        {
                            break;
                        }
                    case RH:(*T)->bf=EH;
                        {
                            L->bf=LH;
                            break;
                        }
                }
            Lr->bf=EH;
            L_Rotate(&(*T)->lchild);//对T的左子树作左旋平衡处理
            R_Rotate(T);//对T做右旋平衡处理
            }
    }
}

主函数

//若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个
//数据元素为e的新结点并返回1,否则返回0.若因插入而使二叉排序树失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否
Status InsertAVL(BiTree *T,int e,Status *taller)
{
    if(!*T)
    {
        //插入新结点,树“长高”,置taller为TRUE
        *T=(BiTree)malloc(sizeof(BiTNode));
        (*T)->data=e;
        (*T)->data=e;
        (*T)->lchild=(*T)->rchild=NULL;
        (*T)->bf=EH;
        &taller=TRUE;
    }
    else
    {
        if(e==(*T)->data)
        {
            //树中已存在和e有相同关键字的结点则不再插入
            *taller=FALSE;
            return FALSE;
        }
        if(e<(*T)->data)
        {
            //应继续在T的左子树中进行搜索
            if(!InsertAVL(&(*T)->lchild,e,taller))//为插入
            {
                return FALSE;
            }
            if(*taller)//已插入到T的左子树中且左子树“长高”
            {
                switch((*T)->bf)//检查T的平衡度
                {
                    case LH://原本左子树比右子树高,需要作左平衡处理
                        {
                            LeftBalance(T);
                            *taller=FALSE;
                            break;
                        }
                    case EH://原本左右子树等高,现因左子树增高而树增高、
                        {
                            (*T)->bf=LH;
                            *taller=TRUE;
                            break;
                        }
                    case RH://原本右子树比左子树高。现左右子树等高
                        {
                            (*T)->bf=EH;
                            *taller=FALSE;
                            break;
                        }
                           
                        
                }
            }
            else
            {
                //应继续在T的右子树中进行搜索
                if(!InsertAVL(&(*T)->rchild,e,taller))//为插入
                {
                    return FALSE;
                }
                if(*taller)//已插入到T的右子树“长高”
                {
                    switch((*T)->bf)//检查T的平衡度
                {
                    case LH://原本左子树比右子树高,现左右子树等高
                        {
                           (*T)->bf=EH;
                            *taller=FALSE;
                            break;
                        }
                    case EH://原本左右子树等高,现因右子树增高而树增高、
                        {
                            (*T)->bf=RH;
                            *taller=TRUE;
                            break;
                        }
                    case RH://原本右子树比左子树高。需要作右平衡处理
                        {
                            RightBalance(T);
                            *taller=FALSE;
                            break;
                        }
                }
            }
        }
    }
        return TRUE;
}

七、多路查找树(B树)

多路查找树其每个结点的孩子树可以多于两个,且每个结点处可以存储多个元素

(1)2-3树

2-3树:其中的每个结点都具有两个孩子(2结点)或三个孩子(3结点)

  • 一个2结点包含一个元素和两个孩子(或没有孩子)
  • 一个3结点包含一小一大两个元素和三个孩子(或没有孩子)

2-3树的插入与删除实现

(2)2-3-4树

一个4结点包含小中大三个元素和四个孩子(或没有孩子)

(3)B树

B树是一种平衡的多路查找树。结点最大的孩子数目称为B树的阶。

(4)B+树

八、散列表查找(哈希表)概述

(1)散列表查找定义

散列技术:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。对应关系f称为散列函数,又称哈希函数。

散列表(哈希表):采用散列技术将记录存储在一块连续的存储空间中

(2)散列表查找步骤

  1. 在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录
  2. 当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录

冲突:两个关键字可key1不等于key2,但是f(key1)-f(key2)

key1与key2称为此散列函数的同义词

(3)散列函数的构造方法

  • 直接定址法:取关键字的某个线性函数值为散列地址,即f(key)=a*key+b

  • 数字分析法

  • 平方取中法

  • 折叠法

  • 除留余数法

  • 随机数法

(4)处理散列冲突的方法

  • 开放定址法:一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
    • 公式:Fi(key)=(F(key)+di)MODm(di=1,2,3,…,m-1)
  • 再散列函数法
  • 链地址法
  • 公共溢出区法

(5)散列表查找实现

定义散列表结构及相关常数

HashTable为散列表结构,elem为一个动态数组

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12//定义散列表为数组的长度
#define NULLKEY -32768
typedef struct
{
    int *elem;//数据元素存储基质,动态分配数组
    int count;//当前数组元素个数
}HashTable;
int m=0;//散列表表长,全局变量

散列表初始化

//初始化散列表
Status InitHashTable(HashTable *H)
{
    int i;
    m=HASHSIZE;
    H->count=m;
    H->elem=(int*)malloc(m*sizeof(int));
    for(i=0;i<m;i++)
    {
        H->elem[i]=NULLKEY;
    }
    return OK;
}

定义散列函数

//散列函数
int Hash(int key)
{
    return key&m;//除留余数法
}

对散列表进行插入

//插入关键字进散列表
void InsertHash(HashTable *H,int key)
{
    int addr=Hash(key);//求散列地址
    while(H->elem[addr]!=NULLKEY)//如果不为空,则冲突
    {
        addr=(addr+1)%m;//开放定地址的线性探测
    }
    H->elem[addr]=key;//直到有空位后插入关键字
}

散列表查找关键字

Status SearchHash(HashTable H,int key,int *addr)
{
    *addr=Hash(key);//求散列地址
    while(H.elem[*addr]!=key)//如果不为空,则冲突
    {
        *addr=(*addr+1)%m;//开发定地址的线性探测
        if(H.elem[*addr]==NULLKEY||*addr==Hash(key))
        {
            //如果循环回到原点
            return UNSUCCESS;//则说明关键字不存在
        }
    }
    return SUCCESS;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值