数据结构与算法(五) - 查找算法

数据结构与算法(五)-查找算法

1.顺序查找

1.1 数组

顺序查找就是逐一对比,发现有相同的值,就返回下标。

迭代
public int seqSearch(int[] arr, int entry){
    int index = 0;
    while (index < arr.length){
        if(entry == arr[index])
            return index;
        index++;
    }//end while
    return -1;
}//end seqSearch
递归
public int seqSearch(int[] arr,int start,int end,int entry){
    if(end < start) return -1;
    if(entry == arr[start]) return start;
    else return seqSearch(arr,start+1,end,entry);
}// end search
顺序查找数组的效率

迭代和递归二者顺序查找比较的次数是一致的

  • 最优情况下,在数组的第一个位置找到说要找的项,只进行一次比较,时间复杂度是O(1)
  • 最坏情况下,将查找整个数组,在数组的最后找到所需要的项,或者完全没有找到,时间复杂度是O(n)
  • 一般的,要查找数组中差不多一半的项,所以平均O(n/2),即平均时间复杂度O(n)

1.2 链表

迭代
//模仿Map,List中的contains方法
public <T> boolean  contains(T entry){
    boolean flag = false;
    Node curNode = root;//root根节点
    while (!flag && (curNode != null)){
        if(entry.equals(curNode.getData()))
            flag = true;
        else curNode = curNode.next;
    }//end while
    return flag;
}// end contains
递归
public <T> boolean  search(Node curNode,T entry){
    boolean flag = false;
    if(curNode == null) return false;
    else if(entry.equals(curNode.getData()))
        flag = true;
    else
        flag = search(curNode.next,entry);
    return flag;
}// end search
顺序查找链表的效率
  • 最优:O(1);
  • 最坏:O(n);
  • 平均:O(n)。

2.二分查找

二分查找只适用于有序的数组。

2.1 递归查找

思路分析:

  1. 确定数组的中间下标mid = (start + end)/2
  2. arr[mid]与目标元素entry比较
    • arr[mid] == entry,找到元素,返回下标mid;
    • arr[mid] > entry,说明要查找的元素在mid左边,递归向左查找;
    • arr[mid] < entry,说明要查找的元素在mid右边,递归向右查找;
  3. 递归结束没有找到目标元素,返回-1。
public int binarySearch(int[] arr,int start,int end,int entry){
    //可行性判断
    if(start > end) return -1;
    int mid = start + (end - start) / 2;//不使用mid = (start + end)/2 看下面注释分析
    if(arr[mid] == entry) return mid;
    else if(arr[mid] > entry)
        return binarySearch(arr,start,mid-1,entry);
    else
        return binarySearch(arr,mid+1,end,entry);
}

2.2 迭代查找

public int binarySearch(int[] arr,int entry){
    int start = 0,end = arr.length-1,mid;
    while (start <= end){
        mid = start + (end - start)/2;
        if(arr[mid] == entry) return mid;
        if(arr[mid] > entry) end = mid - 1;
        else start = mid + 1;
    }
    return -1;
}

注释:寻找数组的中点,使用mid = start + (end - start) / 2来代替mid = (start + end)/2

如果查找至少有2^30时start + end将超出最大可能的整数值2^31-1,数值溢出就可能会得到负数,发生异常。另外,我们知道对于电脑来说二进制运算效率高于十进制运算,寻找中点mid还可以写成mid = start + ((end - start)>>1),但是它的可读性不强。

2.3 效率

在这里插入图片描述

数据量太小或太大都不适合用二分查找。另外,在Java类库中也有定义binarySearch,位于java.util中的Arrays类中,感兴趣的话可以查看以下API。

2.4 扩展

对于数据量较大时,可以使用插值查找

在这里插入图片描述

代码就不写了,就是把二分查找中的mid获取方式更改一下。


3.分块查找

3.1 算法思想

将原表分成若干块,各块内部不一定有序,但第i块内所有记录的关键字都小于第i+1块内所有记录的关键字,这样的表叫做“分块有序”。抽取各块中的最大关键字以及起始位置建立索引表,因为原表是分块有序的,所以所有必定有序。分块查找就是先用二分查找或顺序查找确定待查节点在哪一块,然后在已确定的中进行顺序查找。效率介于顺序查找和二分查找之间。

在这里插入图片描述

3.2 算法流程

  1. 先选取各块中的最大关键字构成一个索引表;
  2. 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;
  3. 在已确定的块中用顺序法进行查找。
/** 
 * 分块查找 
 *  
 * @param index 
 *            索引表,其中放的是各块的最大值 
 * @param st 
 *            顺序表, 
 * @param key 
 *            要查找的值 
 * @param m 
 *            顺序表中各块的长度相等,为m 
 * @return 
 */  
public static int blockSearch(int[] index, int[] st, int key, int m) {  
    // 在序列st数组中,用分块查找方法查找关键字为key的记录  
    // 1.在index[ ] 中折半查找,确定要查找的key属于哪个块中  
    int i = binarySearch(index, key);  
    if (i >= 0) {  
        int j = i > 0 ? i * m : i;  
        int len = (i + 1) * m;  
        // 在确定的块中用顺序查找方法查找key  
        for (int k = j; k < len; k++) 
            if (key == st[k]) return k;
    }
    return -1; 
}  

时间复杂度:O(log(m)+N/m)


4.哈希查找

对应的名词有:哈希函数hash(key)、哈希地址、哈希表、哈希地址冲突

哈希查找也叫散列查找,整个散列查找过程分为两步

  • 在存储数据时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录;
  • 在数据查找时,通过散列函数计算记录的散列地址,然后访问散列地址的记录。

4.1 哈希函数的构造方法

  1. 直接定址法

    取关键字的某个线性函数值为哈希地址,适合用于关键字集合中的值分布连续或基本连续的情况,如果关键字值分布不连续则会造成空间的大量浪费。

在这里插入图片描述

  1. 数字分析法

    根据关键字在各个位上的分布情况,选取分布比较均匀的若干位组成哈希地址。适用于处理关键字位数较大的情况。例如记录一个班的学生记录,其关键字为学号,学号的前几位相同,而最后两位或者三位编号分布比较均匀,因此可选择最后的两位作为哈希地址。

  2. 平方取中法

    对关键字平方后,按散列表大小,取中间的若干位作为散列地址。适用于预先不知道关键字的全部情况,取其中哪几位都不一定合适。

  3. 折叠法

    将关键字从左到右分割成位数相等的几个部分,最后一位数可以短些,然后将这几个部分叠加求和,并按哈希表表长,取后几位作为哈希地址。

    叠加的方法有两种,一种是移位叠加,即每一段最低为对齐后相加;另一种是间界叠加,即从一端向另一端沿分割界来回折叠对齐后相加。比如关键字是9876543210,散列表表长为三位,我们将它分成四组,987|654|321|0

    • 移位叠加:987+654+321+0=1962,再求后三位得到散列地址962;
    • 移位叠加:987+456+321+0=1764,再求后三位得到散列地址764。

    适合事先不知道关键字分布,关键字位数较多的情况。

  4. 除留余数法

    选择某个适当的整数p,以关键字除以p的余数作为哈希地址。此方法为最常用的构造散列函数的方法。

在这里插入图片描述

  1. 乘余取整法

    以关键字乘以常数A,取其小数部分乘以整数B,取其最后的整数部分作为哈希地址。B的选择依赖于哈希表的表长m,A的选择依赖于关键字集合的特征。

  2. 随机数法

    选择一个随机数,取关键字值的随机函数值作为相应记录的哈希地址。h(key) = random()

4.2 处理哈希冲突的方法

  1. 开放地址法

    把哈希表中空位置向向处理地址冲突开放。具体做法是在发生哈希冲突后,从冲突位置开始,使用某种方法在哈希表中形成一个探查序列,找到空地址。

    • 线性探查法

    在这里插入图片描述

    • 平方探查法

      在这里插入图片描述

    • 随机探查法

    在这里插入图片描述

  2. 再散列函数法

    事先准备多几个散列函数。这里的RHi就是不同的散列函数

    在这里插入图片描述

  3. 链地址法

    将哈希地址相同的数据存储到同一个单链表中。即使hashmap就是采用这一的解决方法解决哈希冲突。

在这里插入图片描述

4.3 散列表查找算法实现

  • 首先定义一个散列表结构
  • 对散列表进行初始化
  • 对散列表进行插入操作
  • 根据不同的情况选择散列函数和处理冲突的方法(这里选用的是除留余数法和链地址法)
public class HashTable<T>{
    LinkedList<T> table[];
    public HashTable(int len){
        //len是哈希表长度,如果len不是素数,则取大于len的最小素数作为哈希表的长度
        int np;//大于len的最小素数
        if(HashTable.isPrime(len)) np = len;
        else {
            if(len % 2 == 0) len +=1;
            for (np = len;;np++){
                if(HashTable.isPrime(np))
                    break;
            }
        }
        table = new LinkedList[np];
        for (int i = 0; i < table.length; i++)
            table[i] = new LinkedList<T>();
    }
    //哈希函数
    public int hashCode(T key){
        int hc = Math.abs(key.hashCode());
        return hc % table.length;//除留余数法
    }
    //向哈希表中插入元素
    public boolean add(T key){
        int ha = hashCode(key);
        table[ha].add(key);
        return true;
    }
    //删除元素
    public boolean remove(T key){
        int ha = hashCode(key);
        return table[ha].remove(key);
    }
    //哈希查找
    public boolean contains(T key){
        int ha = hashCode(key);
        return table[ha].contains(ha);
    }
    //判断n是否为素数
    public static boolean isPrime(int n){
        if(n < 2) return false;//小于2探查失败
        int m = (int)Math.sqrt(n);
        for (int i = 2; i < m; i++) //从最小的素数2开始
            if(0 == n%i) return false;//能整除则不是素数
        return true;
    }
}

4.4 哈希查找算法性能分析

对于哈希表查找,关键字比较的次数取决于产生的冲突次数,而影响冲突产生的因素有:

  • 哈希函数是否均匀
  • 处理冲突的方法
  • 哈希表的装载因子。转载因子定义为:表中数据元素个数/表的长度。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值