以上部分知识比较简单就不特意做笔记了
重点学习一下 二叉排序树,平衡二叉树,多路查找树和哈希表。
二叉排序树(二叉查找树)
二叉排序树(Binary Sort Tree),它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不为空,则左子树上所有结点的值均小于它的根结构的值;
- 若它的右子树不为空,则右子树上所有结点的值均大于它的根结构的值;
- 它的左、右子树也分别为二叉排序树(递归)。
举例:下图就是用二叉排序树处理的一组数据
做出二叉排序表后,中序遍历(左根右)得到的序列就是有序序列了。
哈希表 (散列表)
通过查找关键字不需要比较就可获得需要记录的储存位置——散列技术
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。
这种对应关系f称为散列函数,又称为哈希( Hash )函数。采用散列技术将记录存储在一块连续的存储空间中 ,这块连续存储空间成为散列表或哈希表( Hash table )。
散列表的查找步骤
- 当存储记录时,通过散列函数计算出记录的散列地址
- 当查找记录时,我们通过同样的是散列函数计算记录的散列地址,并按此散列地址访问该记录
(数据之间没有任何逻辑关系,散列表主要是面向查找的存储结构)
两种情况不适合用散列表解决
- 同样的关键字对应多项纪录时
- 范围查找
还有一个问题就是——冲突 :简而言之就是地址相同,内容不同 ,我们应该尽可能的避免。
优秀散列表的特性
- 计算简单
- 分布均匀
散列函数的6种构造方法
1.直接定址法(最不常用)
取关键字的某个线性函数值为散列地址,即: f(key)= a*key + b。此方法简单但不常用。
2.数字分析法
抽取关键字的一部分来计算散列存储位置的方法。通常适合处理关键字位数比较多的情况。
3.平方取中法
知名达意,把关键字平方后取中间若干位数字作为散列地址。通常适用于不知道分布但位数不多的情况。
4.折叠法
将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按散列表表长取后几位作为散列地址。通常适用于不知道分布但位数比较多的情况。
5.除留余数法(最常用)
对于散列表长为m的散列函数计算公式为:
取好这个p至关重要!!!
若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或者不包含小于20质因子的合数。
6.随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址。即:
f(key) = random(key)
这里的random是随机函数,当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。
处理散列冲突的方法
1.开发定址法
一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
如果发生冲突再使用以下公式哦!
公式:fi(key) = (f(key)+di) MOD m (di=1,2,3...,m-1) (线性探测法)
(tip:公式中的f(key)是原本的散列函数)
优化:fi(key) = (f(key)+di) MOD m (di=1^2,-1^2, 2, -2^2...,q^2,-q^2,q<=m/2) (二次探测法)
(tip:一正一负:双向寻找空位 平方:不让关键字都聚集再某一块区域)
法三:fi(key) = (f(key)+di) MOD m (di是 由一个随机函数获得的数列) (随机探测法)
(关于随机函数随机种子的知识写在了这篇博客中 C/C++怎样产生随机数_m0_62742402的博客-CSDN博客)
2.再散列函数法
事先准备多个散列函数,如果发生冲突就换一个散列函数,这种方法使得关键字不产生聚集,但是增加了计算的时间。(小声嘀咕:我觉得这方法不咋地)
公式:fi(key) =RHi(key) (i=1,2,3,..k)(这里的RHi就是不同的散列函数)
3.链地址法(运用单链表)
将所以关键字为同义词的记录储存在一个单链表中,这种表被称为同义词子表
在散列表中只储存所有同义词子表的头指针,遇到冲突直接增加结点即可,但是这样带来了查找时需要遍历单链表的性能损耗。
4.公共溢出区法
为所以的有冲突的关键字建立一个公共的溢出区来存放。在查找时,先于基本表的相应位置进行比对,如果不相等再去溢出表顺序查找,这种方法适合冲突不多的情况。
散列表的代码实现
首先来了解一个东西
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 //定义散列表长度
#define NULLKEY -32768
typedef struct
{
int *elem; //数据元素的基址,动态分配数值
int count; //当前数据元素的个数
}HashTable;
//初始化散列表
Status IninHashTable(HashTable *H)
{
H->count = HASHSIZE;
H->elem = (int*)malloc(HASHSIZE * sizeod(int));
if(! H->elem)
{
return ERROR;
}
for(int i=0;i<HASHSIZE;i++)
{
H->elem[i] =NULLKEY;
}
return OK;
}
//散列函数
int Hash(int key)
{
return key % HASHSIZE; //使用除留余数法
}
//插入关键字到散列表
void InsertHash(HashTable *H,int key)
{
int addr;
addr=Hash(key); //求散列地址
while(H->elem[addr]!=NULLKEY)//如果不为空,则冲突出现
{
add =(addr + 1) % HASHSIZE;//开放定址法的线性探测
}
H->elem[addr]=key;
}
//散列表中查找关键字
Status SearchHash(HashTable *H,int key, int *addr)
{
*addr=Hash(key); //求散列地址
while(H.elem[*addr]!=key)
{
*addr=(*addr + 1) % HASHSIZE;
if(H.elem[*addr]==NULLKEY||*addr ==Hash(key))
return UNSUCCESS;
}
return SUCCESS;
}