数据结构学习日记六:查找

数据结构学习笔记

九、查找

  • 顺序查找 O(N)

  • 二分查找(静态查找) O(log2N)

  • 二叉搜索树 O(h) h 为二叉查找树的高度

    平衡二叉树 O(log2N) 平衡二叉树怎么构造呢?

  • 散列查找 几乎是O(1),以空间换时间

9.5 散列查找

关键点:

计算位置:构造散列函数确定关键词存储位置;

解决冲突:应用某种策略解决多个关键词位置相同的问题。

9.5.1 散列表(哈希表)

装填因子(Loading Factor):设散列表空间大小为m,填入表中元素的个数是n,则称α = n/m 为散列表的装填因子。

散列函数: 以关键字key为自变量,计算出数据的存储地址。

冲突: 不同关键字通过散列函数计算得到同一个地址。

9.5.2 散列函数的构造

  1. 计算简单;
  2. 地址空间分布均匀。
9.5.2.1 数字关键词的散列函数构造

1、直接定址法

h(key) = a * key + b

2、除留余数法

h(key) = key mod p

p一般是表的大小,而且为了地址均匀,取素数

3、数字分析法

分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址。

例如:11位手机号码(前几位代表地区比较固定,取后4位为地址)

​ 散列函数:h(key) = atoi(key+7)

4、折叠法

把关键词分割成位数相同的几个部分,然后叠加。

折叠法

5、平方取中法

把数字平方,取结果的中间几位数。

9.5.2.2 字符关键词的散列函数构造

1、ASCII码加和法(冲突严重)

h(key) = (∑key[i]) mod TableSize

2、前3个字符移位法(仍然冲突,空间浪费≈30%)

h(key) = (key[0]*272 + key[1]*27 + key[2]) mod TableSize

27的原因是:26个字母加上空格。

3、移位法

考虑所有的字符。

h(key) = (∑key[n-i-1]*32i) mod TableSize

级数计算的处理方法

移位法的计算过程:(key[0]*32 + key[1])*32 + key[2]…

因为乘以32,相当于左移5位,(因为25=32)

Index Hash(const char* Key, int TableSize){
    unsigned int h = 0;	//当前结果
    while(*Key != '\0')
        h = (h << 5) + *Key++;	//上一轮结果*32 + 当前字符值
    return h % TableSize;
}

9.5.3 冲突处理方法

1、开发地址法:换个位置;

2、链地址法:同一位置的冲突对象组织在一起。

9.5.3.1 开放地址法

若发生了第 i 次冲突,试探的下一个地址将增加 di ,基本公式是:

hi(key) = (h(key) + di) mod TableSize

di的取值方法:线性探测、平方探测、双散列

1、线性探测:di = i (第一次加1,第二次加2,第三次加3)

2、平方探测(二次探测):di = ± i2 (第一次+1,第二次-1,第三次+22,第四次-22……)

3、双散列:di = i * h2(key) (第二个散列函数用来计算偏移量)

线性探测法(Linear Probing)

线性探测法

线性探测的缺点:会产生聚集现象,冲突后,地址集中在一块。

平方探测法(Quadratic Probing)

平方探测

二次探测优点:减少了聚集;缺点:空间还有的时候,可能找不到空位。

定理:散列表长度是某个 4k+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间

typedef struct{
    ElementType Element;
    int Info;	//标志:是否为空,删除是逻辑删除
}Cell;

typedef struct HashTbl* HashTable;
struct HashTbl{
    int TableSize;
    Cell* TheCells;
};


HashTable InitializeTable(int TableSize){
    HashTable H;
    int i;
    if(TableSize < MinTableSize){
        Error("散列表太小");
        return NULL;
    }
    
    H = (HashTable)malloc(sizeof(struct HashTbl));
    if(H == NULL)
        FatalError("空间溢出!!");
    //if当前表大小不符合素数,就修改成素数。
    H->TableSize = NextPrime(TableSize); 
    //分配Cells空间
    H->TheCell = (Cell*)malloc(sizeof(Cell)*H->TableSize);
    if(H->TheCells == NULL)
        FatalError("空间溢出!!");
    for(i=0; i<H->TableSize; i++)
        H->TheCells[i].Info = Empty;
    return H;
}

Position Find(ElementType Key, HashTable H){//平方探测
    Position CurrentPos, NewPos;
    int CNum;	//记录冲突次数
    NewPos = CurrentPos = Hash(Key, H->TableSize);
    //产生冲突
    while(H->TheCells[NewPos].Info != Empty &&
      H->TheCells[NewPos].Element != Key){//字符串关键字用strcmp
        if(++CNum % 2){	//判断冲突的奇偶
            NewPos = CurrentPos + (CNum+1)/2*(CNum+1)/2;
            while(NewPos >= H->TableSize)
                NewPos -= H->TableSize;
        } else{
            NewPos = CurrentPos - CNum/2 * CNum/2;
            while(NewPos < 0)
                NewPos += H->TableSize;
        }
    }//while结束
    return NewPos;
}

//插入
void Insert(ElementType Key, HashTable H){
    Position Pos;
    Pos = Find(Key, H);
    if(H->TheCells[Pos].Info != Legitimate){
        H->TheCells[Pos].Info = Legitimate;
        H->TheCells[Pos].Element = Key;
        //字符串关键词用strcpy函数。
    }
}
双散列探测法(Double Hashing)

为了能实现探测到所有地址的效果,建议h2(key) = p - (key mod p)

其中,p < TableSize, p、TableSize都是素数

再散列(Rehashing)

当散列表元素太多(即装填因子过大)时,查找效率会下降。

最大装填因子一般取 0.5 <= α <= 0.85

扩大散列表;同时重新计算Hash值,塞入新表。

9.5.3.2 链地址法

分离链接法(Separate Chaining)

将相应位置上冲突的所有关键词存储在同一个单链表中

#define KEYLENGTH 15                   /* 关键词字符串的最大长度 */
typedef char ElementType[KEYLENGTH+1]; /* 关键词类型用字符串 */
typedef int Index;                     /* 散列地址类型 */
/******** 以下是单链表的定义 ********/
typedef struct LNode *PtrToLNode;
struct LNode {
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;
/******** 以上是单链表的定义 ********/

typedef struct TblNode *HashTable; /* 散列表类型 */
struct TblNode {   /* 散列表结点定义 */
    int TableSize; /* 表的最大长度 */
    List Heads;    /* 指向链表头结点的数组 */
};

int NextPrime( int N )
{ /* 返回大于N且不超过MAXTABLESIZE的最小素数 */
    int i, p = (N%2)? N+2 : N+1; /*从大于N的下一个奇数开始 */

    while( p <= MAXTABLESIZE ) {
        for( i=(int)sqrt(p); i>2; i-- )
            if ( !(p%i) ) break; /* p不是素数 */
        if ( i==2 ) break; /* for正常结束,说明p是素数 */
        else  p += 2; /* 否则试探下一个奇数 */
    }
    return p;
}

HashTable CreateTable( int TableSize )
{
    HashTable H;
    int i;

    H = (HashTable)malloc(sizeof(struct TblNode));
    /* 保证散列表最大长度是素数*/
    H->TableSize = NextPrime(TableSize);

    /* 以下分配链表头结点数组 */
    H->Heads = (List)malloc(H->TableSize*sizeof(struct LNode));
    /* 初始化表头结点 */
    for( i=0; i<H->TableSize; i++ ) {
         H->Heads[i].Data[0] = '\0';
         H->Heads[i].Next = NULL;
    }

    return H;
}

Position Find( HashTable H, ElementType Key )
{
    Position P;
    Index Pos;
    
    Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
    P = H->Heads[Pos].Next; /* 从该链表的第1个结点开始 */
    /* 当未到表尾,并且Key未找到时 */ 
    while( P && strcmp(P->Data, Key) )
        P = P->Next;

    return P; /* 此时P或者指向找到的结点,或者为NULL */
}

bool Insert( HashTable H, ElementType Key )
{
    Position P, NewCell;
    Index Pos;
    
    P = Find( H, Key );
    if ( !P ) { /* 关键词未找到,可以插入 */
        NewCell = (Position)malloc(sizeof(struct LNode));
        strcpy(NewCell->Data, Key);
        Pos = Hash( Key, H->TableSize ); /* 初始散列位置 */
        /* 将NewCell插入为H->Heads[Pos]链表的第1个结点 */
        NewCell->Next = H->Heads[Pos].Next;
        H->Heads[Pos].Next = NewCell; 
        return true;
    }
    else { /* 关键词已存在 */
        printf("键值已存在");
        return false;
    }
}

void DestroyTable( HashTable H )
{
    int i;
    Position P, Tmp;
    
    /* 释放每个链表的结点 */
    for( i=0; i<H->TableSize; i++ ) {
        P = H->Heads[i].Next;
        while( P ) {
            Tmp = P->Next;
            free( P );
            P = Tmp;
        }
    }
    free( H->Heads ); /* 释放头结点数组 */
    free( H );        /* 释放散列表结点 */
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值