查找
概念
- key:关键字—数值类型(含bool)和串类型
- 浮点类型不可精确比较(等于),因为浮点是定长的,存储时会有精度的丢失
- 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-n,0号元素是不用的
基于这两点,则可以将key存在0号上,然后进行比较,若i减到0,则会
二叉排序树
- 任意一个点的左子树都小于他的右子树
- 查找效率:最好状况—左右等长 最差—退化为线性表
- 三值取中法
二叉树的创建
/*若在二叉排序树中不存在关键字等于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+树
散列
-
哈希表的长度一般为大于原序列基数的质数
-
将连续数据分布到不连续的空间,数据直接没有关系
-
散列函数:将key与下标一一映射(即是原序列的值与存储位置一一对应)(哈希函数),上下文相同,key所对应下标不会改变
-
m为哈希表大小,一般选择比序列长的质数,质数可以降低冲突collision的概率
-
n为实际基数
-
负载因子即n/m
-
p为进行哈希计算的质数,通常m和p是一样的
-
哈希函数:H(哈希value)= key(原序列中key)%p
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;//不会被执行,如果执行至此,则哈希表已满,这不会发生,每一个元素肯定会找到位置
}
线性探测的特点:
- 循环性:由于使用了取模运算 mod 𝑃modP,当 ℎ0+𝑖h0+i 超出哈希表的边界时,它会循环回到表的开始位置,确保探测始终在哈希表的范围内进行。
- 线性增长:𝑖i 从 1 开始,每次增加 1,这保证了探测是线性增长的,即每次探测的位置都是前一个探测位置的后一个位置。
- 避免重复:由于取模运算的存在,即使 ℎ0+𝑖h0+i 的计算结果超出了 𝑃P,它也会被映射回哈希表的有效范围内,避免了探测过程中的重复访问。
- 探测终止:当 ℎ𝑖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}}; //没找到
}