查找,二分法,散列表,散列函数

6. 查找

6.1 查找分类

  • 静态表查找:查询某个元素是否在查找表中,不需要进行插入和删除操作(利用顺序表和散列表效率高)
  • 动态表查找:查找时插入数据元素,查找时删除数据元素(利用二叉搜索树保存效率高)

6.2 顺序表查找

  利用设置哨兵的方法,将第一个元素设置为要比较的值,这样可以减少几次判断

/* 暴力搜索的方法 */
int SequetialSearch(int *a, int n, int key)
{
    int i;
    a[0] = key;
    i = n;
    while (a[i] != key) {
        i--;
    }
    return i;
}

6.3 有序表查找

   如果事先再存储的过程中,就已经时有序的了,那么可以使用二分法查找Binary Search 二分法查找的判断次数,只需要完成 log(n)+1次的判断,就可以找到关键值。

#include <stdio.h>

/* 二分法查找 */
int BinarySearch(int *a, int len, int key)
{
    int low, high, mid;
    low = 0;
    high = len - 1;
    while (low <= high) {
        mid = low + (high - low) / 2;
        if (key < a[mid]) {
            high = mid - 1;
        } else if (key > a[mid]) {
            low = mid + 1;
        } else {
            return mid;
        }
    }
    return -1;
}


int main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    int key = 6;
    printf("%d", BinarySearch(a, 10, key));
    return 0;
}

二分法查找的改进版本可有插值法和斐波那契查找,可以应对要查找的值在很靠近两边的位置时,减少查询的次数

  • 插值法 mid = low + (high - low) * (key - a[low]) / (a[high] - a[low])

6.4 线性索引查找

  • 稠密索引:元素信息为关键码+指针,关键码时有序排列,指针指向各自数据地址
  • 分块索引:元素信息包括最大关键码+块长+快首地址,关键码为顺序排列,可以使用二分查找到,然后根据块长和指针计算得到存储地址,块内部的关键码信息不要求顺序排列,否则时间成本太大
  • 倒排索引(搜索引擎):建立出来单词表,元素信息包括英文单词+文章编号,这样就可以根据输入的单词信息,索引出包含该单词的文章编号。

6.5 散列表哈希表

  根据散列函数,哈希函数,建立出关键码对应的存储位置,散列表是一块连续存储的空间,这块空间叫做散列表或者哈希表,关键码对应的地址叫做哈希地址。

6.5.1 散列函数构造方法

  目的是为了让散列表分布的更均匀,设计合理的散列函数

  • 直接定址法:如果关键码信息进行线性计算,那可以按照:

f(key) = a * key + b

  但是需要实现知道关键字的分布情况,一般不这样使用

  • 数字分析法:通过提取部分关键数字,进行反转,左移或者右移,或者前后两位相加等方式,适用于数字位数很多的时候
  • 平方取中法:将关键码计算平方,取中间的三位作为关键码,该方法适用于不知道关键码分布,而且位数不是很大的情况下
  • 折叠法:将关键码按照三位一组进行拆分,然后进行求和,如9876543210可以分为987|654|321|0四组,然后求和得到值为1962,再根据散列表的长度,取后三位或者后两位作为散列地址。该方法适用于事先不知道关键码分布,适合关键字较长
  • 取余法:该方法为最常用的哈希函数,公式如下:

f(key) = key mod p (p <= m)

  通常p的值选择为不大于表长度m的最大质数(素数)

  • 基数转换法:将关键码看作是以r为基数的,将其转换为10进制或者2进制,然后再将得到的值用对叠法

6.5.2 散列冲突处理方法

  分为内消解方法和外消解方法两种

  • 内消解方法还是再线性表的范围内找一块地址存入
  1. 线性探测法:在原哈希函数的位置上加上正数线性偏移:

f(key) = (f(key) + d) mod p (d = 1,2,3,…,m-1)

  1. 二次探测法:线性探查只能往后探测,如果冲突位置的前面有空余,也可以使用

f(key) = (f(key) + d) mod p (d = 12, -12, 22, -22, … q2, -q 2 q < m/2)

  1. 再哈希法:在冲突的位置再利用一个新的哈希函数

f(key) =RH(key) RH代表一个新的哈希方法,例如基数转换,折叠法,平方取中

  • 外消解方法还是在线性表的范围内找一块地址存入
  1. 桶散列(链地址法):在冲突的哈希地址处,存储一个头指针,指向所有存储在该位置的关键码链表,因此不会出现找不到地址的情况,但是会造成遍历链表的性能损耗
  2. 公共溢出区法:在散列表的外部单独申请一块存储区域,当哈希冲突时,将值顺序的存储在溢出区内,在冲突数据较少的情况下,性能还是非常高的。

6.5.3 散列表性能分析

在这里插入图片描述
  当负载因子的值越大,冲突发生的可能性越高,因此探查就不是O(1)的时间复杂度了,当a < 0.7是,散列的查找,插入和删除可以看作是常量复杂度。

在这里插入图片描述

6.5.4 散列表实现

#include <stdio.h>
#define SUCCESS 0
#define FAIL 1
#define HASHSIZE 12
#define NULL -32768

typedef struct {
    int *elem;
    int count;
} HashTable;

int m = 0;
void 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] = NULL;
    }
}

void Hash(int key) 
{
    return key % m;
}

void InsertHash(HashTable *H, int key)
{
    int addr = Hash(key);
    while (H->elem[addr] != NULL) {
        addr = Hash(key+1) //线性探测
    }
    H->elem[addr] = key;
}

void SearchHash(HashTable *H, int key, int *addr)
{
    *addr = Hash(key);
    while (H->elem[*addr] != key) {
        *addr = Hash(*addr+1);
        if (H->elem[*addr] != NULL || *addr == Hash(key)) {
            return FAIL;
        }
    }
    return SUCCESS;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值