查找

166 阅读4分钟
  • 查找:在数据集中寻找满足某种条件的数据元素的过程
  • 查找表(查找结构):用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成
  • 关键字:数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的

对查找表的常见操作

  1. 查找符合条件的数据元素
    静态查找表--仅关注查找速度即可
  2. 插入、删除某个数据元素
    动态查找表--除了查找速度,也要关注插/删操作是否方便实现

查找算法的评价指标

  • 查找长度--在查找运算中,需要对比关键字的次数
  • 平均查找长度(ASL)--所有查找过程中进行关键字的比较次数的平均值
    • ASL的数量级反映了查找算法时间复杂度
    • 评价一个查找算法的效率时,通常考虑查找成功/查找失败两种情况的ASL

顺序查找(线性查找)

/*
    通常用于线性表--顺序--链表
    从头到脚挨个查找
    时间复杂度O(n)
    顺序查找的优化(对有序表)
*/

typedef struct{
    ElemType *elem;
    int TableLen;
}SSTable;
int Search_Seq(SSTable ST,ElemType key){
    //ST.elem[0] = key;     //0号位置存哨兵
    int i;
    //for(i=ST.TableLen;ST.elem[i] != key;--i);//从后往前找
    for(i=0;i<ST.TableLen && ST.elem[i] != key;++i);
    //return i;//查找成功,则返回元素下标;查找失败,则返回0
    return i == ST.TableLen?-1:i;
    
    
    /*
    int Search_Seq(SSTable ST,ElemType key){
        ST.elem[0] = key;     //0号位置存哨兵
        int i;
        for(i=ST.TableLen;ST.elem[i] != key;--i);//从后往前找
        return i;//查找成功,则返回元素下标;查找失败,则返回0
    }
    */
}



/*
    用查找判定树分析ASL
    一个成功结点的查找长度 = 自身所在层数
    一个失败结点的查找长度 = 其父结点所在层数
    
    优化(被查概率不相等)
    被查概率大的放在靠前位置
    但当查找失败时还是需要全部对比
*/


折半查找(二分查找)

  • 仅适用于有序的顺序表
  • low和high分别指向目前要搜索的范围,mid为low和high的中间,搜索数大于mid,则low变为mid,小于mid,则high变为mid
    • low = 0;high = TableLen-1
    • min = (low + high)/2
  • 折半查找的判定树一定是平衡二叉树
  • 只有最下面一层是不满的,树高h=log₂(n+1)
  • 判定树结点关键字:左<中<右,满足二叉排序树的定义
  • 失败结点:n+1个(等于成功结点的空链域数量) 查找成功的ASL≤h,折半查找的时间复杂度O(log₂n)
//基于升序排列的折半查找
typedef struet{
    ElemType *elem;
    int TableLen;
}SSTable;
int Binary_Search(SSTable L,ElemType key){
    int low = 0;
    int high = L.TableLen-1;
    int mid;
    while(low < high){
        mid = (low + high)/2;      //取中间位置
        if(L.elem[mid] == key)
            return mid;            //查找成功则返回所在位置
        else if(L.elem[mid] > key)
            high = mid - 1;        //从前半部分继续查找
        else
            low = mid + 1;         //从后半部分继续查找
    }
    return -1;                     //查找失败,返回-1
}

/*
    如果当前low和high有奇数个元素,则mid分隔后,左右两部分元素个数相等
    如果当前low和high有偶数个元素,则mid分隔后,左半部分比右半部分少一个元素
    
    折半查找的判定树中,若mid = (low + high)/2,则对于任何一个结点,必有
        右子树结点数 - 左子树结点数= 0或1
*/

分块查找

  • 特点:块内无序,块间有序
  • "索引表"中保存每个分块的最大关键字和分块的存储区就
//索引表
typedef struct{
    ElemType maxValue;
    int low,high;
}Index;
//顺序表存储实际元素
ElemType List[100];


/*
    又称索引顺序查宅,算法过程如下:
    1、在索引表中确定待查记录所属的分块(可顺序、可折半)
    2、在块内顺序查找
    若索引表中不包含目标关键字,则折半查找索引表最终停在low>high,要在low所指分块中查找
    low超出索引表范围,查找失败
    设索引查找和块内查找的平均查找长度为L1,Ls,则分块查找的平均查找长度为:
        用顺序查找查索引表,则L1=(b+1)/2,Ls=(s+1)/2
            则ASL=(b+1)/2 + (s+1)/2 = (s²+2s+n)/2s,求最小值min
*/

二叉排序树

  • 二叉查找树(BST)
  • 可用于元素的有序组织、搜索
  • 进行中序遍历,可以得到一个递增的有序序列
//二叉排序树结点
typedef struct{
    int key;
    struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;

//在二叉排序树中查找值为key的结点,非递归的实现--最坏空间复杂度O(1)
BSTNode *BST_Search(BSTree T,int key){
    while(T!=NULL && key!=T->key){  //若树空或等于根结点值,则结束循环
        if(key<T->key)              //小于,则在左子树上查找
            T = T->rchild;
        else  //大于,则在右子树上查找
            T = T->rchild;
    }
    return T;
}

//递归实现--最坏空间复杂度O(h)
BSTNode *BST_Search(BSTree T,int key){
    if(T == NULL)
        return NULL;//查找失败
    if(key == T->key)
        return T;//查找成功
    else if(key < T->key)
        return BSTSearch(T->lchild);//在左子树中找
    else
        return BSTSearch(T->rchild,key);//在右子树中找
}


//插入:若原二叉排序树为空,则直接插入结点。若非空,则比大小.

//插入关键字为k的新结点(递归实现)
int BST_Insert(BSTree &T,int k){
    if(T == NULL){   //原树为空,新插入的结点为根结点
        T = (BSTree)malloc(sizeof(BSTNode));
        T->key = k;
        T->lchild = T->rchild = NULL;
        return 1;  //返回1,插入成功
    }
    else if(k == T->key)  //树中存在相同关键字的结点,插入失败
        return 0;
    else if(k<T->key)    //插入到T的左子树
        return BST_Insert(T->lchild,k);
    else   //插入到T的右子树
        return BST_Insert(T->rchild,k);
}

//按照str[]中的关键字序列建立二叉排序树
void Creat_BST(BSTree &T,int str[],int n){
    T = NULL;  //初始时T为空树
    int i = 0;
    while(i<n){//依次将每个关键字插入到二叉排序树中
        BST_Insert(T,str[]);
        i++;
    }
}
//不同的关键字序列可能得到同款二叉排序树,也可能不同

/*

    删除
    1、若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质
    2、若结点z只有一颗左子树或右子树,则让z的子树称为z父结点的子树,替代z的位置
    3、若结点z有左右两棵子树,则令z的直接后继(前驱)替代z,然后从二叉排序树中删去这一个直接后继(前驱),转换成第1或第2种情况
    z的后继:z的右子树中最左下结点(该结点一定没有左子树)
    z的后继:z的左子树中最右下结点(该结点一定没有右子树)
*/

平衡二叉树

平衡二叉树的插入操作:

  • 插入新结点后,要保持二叉排序树的特性不变(左<中<右)
  • 若插入新结点导致不平衡,则需要调整平衡 平衡二叉树的删除操作:
  • 删除结点后,要保持二叉排序树的特性不变(左<中<右)
  • 若删除结点导致不平衡,则需要调整平衡
/*
    删除操作具体步骤:
    1、删除结点
        - 若删除的结点是叶子,直接删
        - 若删除的结点只有一个子树,用子树顶替删除位置
        - 若删除的结点有两棵子树,用前驱(或后继)结点顶替,并转换为对前驱(或后继)结点的删除
    2、一路向北找到最小不平衡子树,找不到就结束
    3、找最小不平衡子树下,"个头"最高的儿子、孙子
    4、根据孙子的位置,调整平衡(LL/RR/LR/RL)
        - 孙子在LL:儿子右单旋
        - 孙子在RR:儿子左单旋
        - 孙子在LR:孙子先左旋,再右旋
        - 孙子在RL:孙子先右旋,再左旋
    5、如果不平衡向上传导,继续2
        对最小不平衡子树的旋转可能导致树变矮,从而导致上层祖先不平衡
*/

红黑树RBT

插入/删除很多时候不会破坏"红黑"特性,无需频繁调整树的形态,即便需要调整,一般都可在常数级时间内完成

  • 首先是二叉排序树--->左子树结点值<根结点值<右子树根结点值
  • 与普通BST相比
    • 每个结点或是红色,或是黑色的
    • 根结点是黑色的
    • 叶结点(外部结点、NULL结点、失败结点)均是黑色的
    • 不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色)
    • 对每个结点,从该结点到任一叶结点的简单路径上,所含黑结点的数目相同
  • 左根右,根叶黑,不红红,黑路同
  • 结点的黑高bh -- 从某结点出发(不含该结点)到达任一空叶结点的路径上黑结点总数
  • 性质
    • 1、从根结点到叶结点的最长路径不大于最短路径的2倍
    • 2、有n个内部结点的红黑树高度h≤2log₂(n+1)--->红黑树查找操作时间复杂度O(log₂n)
//红黑树的
typedef RBnode{
    int key;        //关键字的值
    RBnode *parent; //父结点指针
    RBnode *lChild; //左孩子指针
    RBnode *rChild; //右孩子指针
    int color;      //红黑
}