数据结构 查找

查找

概念
  1. key:关键字—数值类型(含bool)和串类型
  2. 浮点类型不可精确比较(等于),因为浮点是定长的,存储时会有精度的丢失
  3. ASL average search length==sigma i*Pi
typedef struct {
    type_r kind;//键的类型
    union {//联合的成员在同一时刻只有一个起作用,匿名联合内嵌在struct中,可以通过直接访问struct的名字来访问union成员
        int i;
        POINTER p;
    };
} SearchResult;
基于list的查找
SearchResult SeqSearch(POINTER list, KeyType k) {
    RecordList *l = (RecordList *)list;
    int i = l->length;//i从1到len,而不是从0开始
   // while (i >= 1 && l->r[i].key != k)
   //     --i;
    l->r[0].key = k;
    while (l->r[i].key != l->[0].key) --1;//又可能关键字不在0上?


    SearchResult r;
    r.kind = INT_R;
    r.i = i >= 1 ? i : -1;
    return r;  // 如果找到了匹配项,返回其索引;否则,返回 -1
}
  1. 数组元素不可与值进行比较
  2. 数组中有效的位置只有1-n,0号元素是不用的

基于这两点,则可以将key存在0号上,然后进行比较,若i减到0,则会

二叉排序树
  1. 任意一个点的左子树都小于他的右子树
  2. 查找效率:最好状况—左右等长 最差—退化为线性表
  3. 三值取中法
二叉树的创建
/*若在二叉排序树中不存在关键字等于key的元素,插入该元素*/
static void InsertTree_BSTree(BSTree *bst, KeyType key) {
    if (*bst == NULL) { /*递归结束条件*/
        BSTree s = (BSTree)malloc(sizeof(BSTNode)); /*申请新的结点s*/
        s->key = key;
        s->lchild = s->rchild = NULL;
        *bst = s;//bst指的是当前处理的结点
    } 
    
    
    else if (key < (*bst)->key)
        InsertTree_BSTree(&((*bst)->lchild), key); /*将s插入左子树*/
    else if (key > (*bst)->key)
        InsertTree_BSTree(&((*bst)->rchild), key); /*将s插入右子树*/
}
SearchResult BSTSearch(POINTER root, KeyType key) {
    //在调用此函数时,形参root对应的实参是数据集的地址,因此root实际上是指针的指针
    BSTree q = *(BSTree*)root;
   
    SearchResult r = {POINTER_R, {.p = NULL}};
    while (q != NULL) {
        if (q->key == key) {
            r.p = q;
            return r;
        } 
        if (q->key > key)
            q = q->lchild; /*在左子树中查找*/
        else
            q = q->rchild; /*在右子树中查找*/
    }
    return r; 
}

平衡因子cf:子树高度差,状态良好的二叉树,每一层bf不超过1

失去平衡,局部调整旋转

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

先左旋,后右旋,有三个指针分别指向ABC,还有一个指向A的父结点

红黑树,B+树

散列

  1. 哈希表的长度一般为大于原序列基数的质数

  2. 将连续数据分布到不连续的空间,数据直接没有关系

  3. 散列函数:将key与下标一一映射(即是原序列的值与存储位置一一对应)(哈希函数),上下文相同,key所对应下标不会改变

在这里插入图片描述

  • m为哈希表大小,一般选择比序列长的质数,质数可以降低冲突collision的概率

  • n为实际基数

  • 负载因子即n/m

  • p为进行哈希计算的质数,通常m和p是一样的

  • 哈希函数:H(哈希value)= key(原序列中key)%p

  1. 冲突处理:再散列法——一定能找到对应位置

    链值法:在此位置上存储链表

    在这里插入图片描述

    定义
typedef struct {
    KeyType key;
} RecordType;

#include <inttypes.h>

#include "record-search.h"

#define NULLKEY  __INT_MAX__

typedef RecordType* HashTable;//此时HashTable为一个动态的指针数组

extern SearchResult HashSearch(POINTER ht, KeyType k);
extern void CreateHash(POINTER ht, KeyType *a, int n);
extern void DestroyHash(POINTER ht);
找质数P
static int FindP(int tablelen) {//定义静态函数限制作用域
    //TODO
    for (int n = tablelen; n >= 2; --n)
        if (isPrime(n))
            return n;

    return 2;
}
进行散列和再散列
static int FindRoom(HashTable ht, KeyType K) {
    //TODO
    int h0 = hash(K), hi, i;

    if (ht[h0].key == NULLKEY)
        return h0;//如果对应位置为空就填入
    
    else { /* 用线性探测再散列解决冲突 *///再散列
        for (i = 1; i <= P - 1; ++i) {
            //hi = (h0+i) % P;//为什么不对p取模??
            hi = hash(h0 + i);
            if (ht[hi].key == NULLKEY)//找到的是下一个哈希位置,而不是在h0附近找位置
                return hi;
        }
    }

    return -1;//不会被执行,如果执行至此,则哈希表已满,这不会发生,每一个元素肯定会找到位置
}

线性探测的特点:

  1. 循环性:由于使用了取模运算 mod  𝑃modP,当 ℎ0+𝑖h0+i 超出哈希表的边界时,它会循环回到表的开始位置,确保探测始终在哈希表的范围内进行。
  2. 线性增长:𝑖i 从 1 开始,每次增加 1,这保证了探测是线性增长的,即每次探测的位置都是前一个探测位置的后一个位置。
  3. 避免重复:由于取模运算的存在,即使 ℎ0+𝑖h0+i 的计算结果超出了 𝑃P,它也会被映射回哈希表的有效范围内,避免了探测过程中的重复访问。
  4. 探测终止:当 ℎ𝑖hi 指向一个空的位置或所有位置都被探测过后,探测终止,此时如果找到了空位,则将元素插入;如果没有找到,则可能需要采取其他措施,如扩容哈希表。
    链址法解决冲突

在这里插入图片描述

定义
#include <inttypes.h>

#include "record-search.h"

#define NULLKEY  __INT_MAX__

typedef struct _record {
    KeyType key;
    struct _record *next;
} HashNode, *HashNodePtr;

typedef HashNodePtr *HashLATable;//此时HashLATable为二阶指针

extern SearchResult HashLASearch(POINTER ht, KeyType k);
extern void CreateHashLA(POINTER ht, KeyType *a, int n);
extern void DestroyHashLA(POINTER ht);
寻找质数,比计算质数效率高
int find_P(int n) {
    //根据数据集的长度计算预估的哈希表长度
    //再查表得到最合适的长度
    int l = (int)(n / loadfactor); //计算m的值
    for (int i = 0; i < sizeof(prime_list) / sizeof(int); ++i) 
        if (l <= prime_list[i]) return prime_list[i];

    return prime_list[0];
}
创建哈希数组,连入链表
void CreateHashLA(POINTER h, KeyType *a, int n) {//a为原序列键值集
    P = find_P(n); //尝试找到最佳的哈希表长度

    HashLATable *ht = (HashLATable *)h;//强制转换为了二阶指针类型
    //*ht是元素为指针的数组,ht为HashLATable *类型
    *ht = (HashLATable)malloc(sizeof(HashNodePtr) * P);
    for (int i = 0; i < P; ++i) (*ht)[i] = NULL; //初始化哈希表(指针数组,每一个元素为一个指向链表的头指针)
    
    
   //注意上面这一部分的P是大写P
    

    int j; //散列到哈希表的位置
    HashNodePtr p; //指向新结点的指针
    for (int i = 0; i < n; ++i) { //将每一个key散列到哈希表中
        //计算j,创建结点,然后用头插法插入结点
        j = hash(a[i]);//将原序列中元素哈希化
        
        //引入数组元素-----链表
        
        p = (HashNodePtr)malloc(sizeof(HashNode));//p是在各个槽位之间移到的工作指针
        p->key = a[i];//文本域
        
        //头指针的更新
        p->next = (*ht)[j];//*ht数组,这里是j号元素(即原来对应位置上的头指针)
        (*ht)[j] = p;//头结点一般只会通过更新来连接,而不是通过next
    }
}
//原哈希表中对应位置是指向链表的头指针
SearchResult HashLASearch(POINTER ht, KeyType key) {
    int h = hash(key);
    HashLATable *_ht = (HashLATable *)ht;

    HashNodePtr p; //指向哈希表入口的指针
    //遍历链表,查找key是否存在
    for (p = *_ht[h]; p != NULL; p = p->next)
        if (p->key == key) return (SearchResult){INT_R, {.i = h}};

    return (SearchResult){INT_R, {.i = -1}}; //没找到
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值