数据结构与算法之查找专题

442 阅读8分钟

静态查找与动态查找

查找表的方式分为静态查找和动态查找

静态查找

只做查找操作的查找表,它主要是

  • 查询某个特定数据元素是否在查找表中
  • 检索某个特定数据元素和各种属性

动态查找

在查找的过程中同时插入表中不存在的数据元素,或者从表中删除已经存在的数据元素。它的特性有

  • 查找时插入数据
  • 查找时删除数据

静态查找

顺序查找

  • 又称线性查找,它从表中的第一个或最后一个开始,逐个将关键字跟特定元素数据比对,直到找到为止
  • 核心代码实现

    #pragma mark - 顺序查找
    int OrderSearch(int target[],int length, int keyWord){
        for (int i = 0; i<length; i++) {
            if (target[i] == keyWord) {//找到则返回位置
                return i;
            }
        }
        //找不到则返回-1
        return -1;
    }
    #pragma mark - 顺序查找优化
    /**
     思路:数组中第一个元素位置是哨兵
     */
    int OrderSearchBetter(int target[], int length, int keyWord){
        int i = length;
        //设置哨兵位置存储关键字
        target[0] = keyWord;
        while (target[i] != keyWord) {
            i--;
        }
        //注意返回i == 0时表示没有查找到
        return i;
    }
    

折半查找(二分查找)

  • 折半查找:折半查找又叫二分查找,它需要查找的表是有序的(通常是从小到大),且表的存储方式也是顺序存储
  • 核心代码实现

    #pragma mark - 折半查找
    /**
     思路:条件:查找的表是从小到大排序的
        1、从中间位置开始查找,如果等于关键字则返回
        2、如果中间位置大于关键字,则从左半部查找
        3、如果中间位置小于关键字,则从右半部查找
        4、重复上述过程,直到找到或查找结束位置
     */
    int BinanrySearch(int target[], int lenght, int keyWord){
        int low,height,mid;
        //地位low为1,0位置是哨兵
        low = 1;
        height = lenght;
        while (low <= height) {//low不大于height就表示还没有找到,继续循环
            mid = (low + height)/2;
            if (keyWord < target[mid]) {//关键字在中间位置的左侧,更新最高位下标
                //将最高位下标设置成中位减一
                height = mid - 1;
            }else if (keyWord > target[mid]){ //关键字在中间位置的右侧,更新最低位下标
                //将最低位下标设置成中位加一
                low = mid + 1;
                
            }else{//中间位置就是keyWord
                return mid;
            }
        }
        return 0;
    }
  • 插值查找优化:由于要查找的表是顺序排序的,因此我们只要找到mid在表中的分布区域就能缩小查找范围,所以mid的计算公式改为:

        mid = low + ((keyWord - target[low])/(target[height] - target[low]))*(height - low),其            中target为要查找的表,keyWord是要查找的关键字

  • 插值查找代码实现

#pragma mark - 插值查找
/**
 思路:同折半查找相同,唯一的区别在于计算mid的同,mid计算公式为:mid =  low + ((keyWord - target[low])/(target[height] - target[low]))*(height - low)
 */
int InsertBinanrySearch(int target[], int length, int keyWord){
    int low,height,mid;
    //地位low为1,0位置是哨兵
    low = 1;
    height = length;
    while (low <= height) {//low不大于height就表示还没有找到,继续循环
        mid = low + ((keyWord - target[low])/(target[height] - target[low]))*(height - low);
        if (keyWord < target[mid]) {//关键字在中间位置的左侧,更新最高位下标
            //将最高位下标设置成中位减一
            height = mid - 1;
        }else if (keyWord > target[mid]){ //关键字在中间位置的右侧,更新最低位下标
            //将最低位下标设置成中位加一
            low = mid + 1;
            
        }else{//中间位置就是keyWord
            return mid;
        }
    }
    return 0;
}

斐波拉契查找

  • 斐波拉契:一个有序递增数组中,从第三个数据开始,F[n] = F[n - 1] + F[n - 2],其中n>=3,


  • 斐波拉契查找:首先找到顺序表的长度n在斐波拉契中的位置,


  • 斐波拉契查找的mid算法:mid = low + F[k - 1] - 1;

  • 斐波拉契查找核心代码实现

    //定义斐波拉契序列
    int F[100];
    //斐波拉契查找,这个有哨兵
    int FibonacciSearch(int target[], int length, int key){
        int low,height,mid;
        //地位low为1,0位置是哨兵
        low = 1;
        height = length;
        //先生成斐波拉契序列
        F[0] = 0;
        F[1] = 1;
        for (int i = 2; i<100; i++) {
            F[i] = F[i - 1] + F[i - 2];
        }
        //找到length在斐波拉契序列中的位置
        int k = 0;
        while (length > F[k] - 1) {
            k++;
        }
        //当F[k]大于n时补足数组中缺失的数据
        for (int i = length; i<F[k] - 1; i++) {
            //用最后一位数据补足
            target[i] = target[length];
        }
        while (low <= height) {
            mid = low + F[k - 1] - 1;
            if (key > target[mid]) {
                low = mid + 1;
                //斐波拉契下标-2
                k = k - 2;
            }else if (key < target[mid]){
                height = mid - 1;
                //斐波拉契下标-1
                k = k - 1;
            }else{//找到了
                if (mid <= length) {//没有超过原来的length长度找到了
                    return mid;
                }else{//超过了原来的长度找到了,则位置一定是最后一个
                    return length;
                }
            }
        }
        return 0;
    }
    
    

动态查找

什么是二叉排序树

二叉排序树又叫二叉查找树,它是一棵空树或是具有以下特定的二叉树

  • 二叉排序树性质:
  1. 若它的左子树不为空,则左子树上的所有节点的值小于双亲节点
  2. 若它的右子树不为空,则右子树上的所有节点的值大于双亲节点
  3. 它的左右子树也是二叉排序树

二叉排序树的查找

  • 算法思路:根据二叉排序树的性质递归查找
  • 核心代码实现

    /**
     思路:T为二叉排序树,key为节点的值,fater为查找结点的双亲,p指向查找到的节点
        1、递归查找
        2、当key大于T->data,表明查找的节点在右孩子这边
        3、当key小于T->data,表明查找的节点在左孩子这边
     */
    Status SearchBiTree(BiTree T, int key, BiTree fater, BiTree *p){
        if (!T) {//查找失败
            *p = fater;
            return ERROR;
        }
        if (key < T->data) {//在左孩子这边查找
            return SearchBiTree(T->leftChild, key, T, p);
        }else if (key > T->data){//在右孩子这边查找
            return SearchBiTree(T->rightChild, key, T, p);
        }else {//找到了
            *p = T;
            return TRUE;
        }
    }
    
    

二叉排序树的插入

核心代码实现

/**
 思路:
    1、先查找节点是否存在,如果不存在则插入
    2、当插入的节点值小于双亲节点则插入左孩子,否则插入右孩子
 */
Status InsertBiTree(BiTree *T, int data){
    BiTree p;
    if (!SearchBiTree(*T, data, NULL, &p)) {//当没有找到该节点时插入数据
        BiTree node = (BiTree)malloc(sizeof(BiTNode));
        node->data = data;
        node->leftChild = NULL;
        node->rightChild = NULL;
        if (!p) {//如果p不存在,即第一次插入,则s为跟节点
            *T = node;
        }else if (p->data > data){//插入左孩子
            p->leftChild = node;
        }else{//插入右孩子
            p->rightChild = node;
        }
        return TRUE;
    }
    return ERROR;
}

二叉排序树的删除

  • 需要注意:根据查找到的位置删除节点,当该节点非叶子节点的时候需要连接二叉树,此时需要注意的是:
  1. 删除节点是否有左孩子
  2. 删除节点是否有右孩子
  3. 删除节点是否左右孩子都有
  • 核心代码实现

    /**
     思路:
        1、当删除的是叶子节点时则直接删除
        2、当删除的是非叶子节点
          ①删除的节点只有左孩子,则删除节点的父节点连接左孩子
          ②删除的节点只有右孩子,则删除节点的父节点连接右孩子
          ③删除的节点左右孩子均有时,如果连接左孩子,那么需要遍历左孩子的右孩子,如果右孩子不存在则直接连接删除节点的左孩子;如果连接右孩子则遍历右孩子的左孩子,如果左孩子不存在,则直接连接删除节点的右孩子
     */
    Status Delete(BiTree *T){
        BiTree temp,p;
        //记录要删除的节点
        temp = *T;
        if (!(*T)->leftChild) {//如果删除的节点左孩子不存在,则直接连接右孩子即可(不用管右孩子是否存在)
            p = temp->rightChild;
            *T = p;
            free(temp);
        }else if (!(*T)->rightChild){//如果删除的节点右孩子不存在,则直接连接左孩子即可(不用管左孩子是否存在)
            p = temp->rightChild;
            *T = p;
            free(temp);
        }else{//左右孩子都存在
            /*   连接左孩子代码开始  */
    //        p = temp->leftChild;
    //        //查找左孩子的右孩子的节点
    //        while (p->rightChild) {
    //            //temp记录p
    //            temp = p;
    //            p = p->rightChild;
    //        }
    //        //寻找右孩子结束
    //        //更改删除节点的data值
    //        (*T)->data = p->data;
    //        if (temp != *T) {//找到了删除节点左孩子的右孩子
    //            temp->rightChild = p->leftChild;
    //        }else{
    //            temp->leftChild = p->leftChild;
    //        }
            /*   连接左孩子代码结束  */
             //连接右孩子代码开始
            p = temp->rightChild;
            while (p->leftChild) {
                temp = p;
                p = p->leftChild;
            }
             //更改删除节点的data值
             (*T)->data = p->data;
            if (temp != *T) {//找到了删除节点左孩子的右孩子
                temp->rightChild = p->leftChild;
            }else{
                temp->rightChild = p->rightChild;
            }
             //连接右孩子代码结束
             
            free(p);
        }
        return OK;
    }
    Status DeleteBiTree(BiTree *T, int data){
        if (!*T) {
            return ERROR;
        }
        if ((*T)->data == data) {
            return Delete(&(*T));
        }else if ((*T)->data > data){//左孩子查找删除
            return DeleteBiTree(&(*T)->leftChild, data);
        }else {//右孩子查找删除
            return DeleteBiTree(&(*T)->rightChild, data);
        }
    }
    void VisitBiTree(BiTree T){
        if (T) {
            if (T->leftChild) {
                VisitBiTree(T->leftChild);
            }
            printf("%4d",T->data);
            if (T->rightChild) {
                VisitBiTree(T->rightChild);
            }
        }
    }
    
    

删除添加了删除节点连接左孩子、右孩子的代码,感兴趣的话可以试试