数据结构之---散列表(hash table)

散列表是一种常数时间复杂度的插入、查询和删除数据结构,但不支持排序等操作。关键在于选择合适的散列表大小(通常是素数)和散列函数。冲突解决方法包括分离链接法和开放地址法。分离链接法利用链表解决冲突,而开放地址法通过线性探测、平方探测或双散列等方式避免冲突。开放地址法的删除操作需谨慎,避免破坏查找路径。当表过于拥挤时,可采用再散列策略重建更大表。
摘要由CSDN通过智能技术生成

散列是一种以常数的时间进行插入,查询,删除的数据结构,但是需要排序等信息的操作不会得到支持。散列首先考虑的两个问题是散列表的大小以及散列函数的选择,这两个问题都要结合具体的问题,但是在散列的表的选择上应该尽量保持为素数。

在这种结构中主要解决的一个问题就是冲突的解决,一般来说有分离链接法以及开放地址法。下面分别来说明。

分离链接法的思路是,如果发生了冲突,则将这些冲突的元素都保存到一个表到中,通过执行hash函数找到相应的表,接下来便是对表的操作了。冲突的元素既可以插入到最后也可一插入到前面,首先给出这种方法的结构定义,以及常见的函数:

struct listnode;
typedef struct listnode *position;
struct hash;
typedef struct hash *hashtable;
struct listnode
{
elementtype element;
position next;
};

struct hash
{
int tablesize;
position *list;/*由position这种类型指针组成的数组,list是一个指向指针的指针*/
};
/*支持的基本操作*/
void insert(elementtype key,hashtable h);
void search(elementtype key,hashtable h);
hashtable init(int tablesize);
void destory(hashtable h);
紧接着便是对这些操作的实现了,其中最重要的操作便是find和search。当然我们还可以添加一个删除,为了避免重复工作我就没有写了

hashtable init(int tablesize)
{
hashtable h;
h=(hashtable)malloc(sizeof(struct hash));
if(h==NULL)
fatalerror("out of space");
h->list=(position*)malloc(tablesize*sizeof(struct listnode));
if(h->list==NULL)
fataerror("out of space");
h->tablesize=tablesize;
int i;
for(i=0;i<h->tablesize;i++)
{
h->list[i]->next=NULL;
h->list[i]->element=flag;/*这个flag用于标记第一个节点是否被使用*/
}
return h;
}
插入操作:

void insert(elementtype key,hashtable h)
{
int index;
index=hash(key,h->tablesize);/*通过设定的hash函数来获取下标值*/
position temp;
temp=h->list[index];
if(temp->element==flag)/*第一个节点尚未被使用*/
{
temp->element=key;
}
else/*已经被使用*/
{
while(temp!=NULL)
{
if(temp->element==key)
break;
temp=temp->next;
}
if(temp==NULL)/*没有找到相同的节点,则把它放在第一个节点*/
{
position insert;
insert=(position)malloc(sizeof(struct listnode));
insert->element=key;
insert->next=h->list[index];
h->list[index]=insert;
}
}
}
查找操作:

position search(elementtype key,hashtable h)
{
int index;
index=hash(key,h->tablesize);
position temp;
temp=h->list[index];
while(temp!=NULL)
{
if(temp->element==key)
break;
temp=temp->next;
}
return temp;/*找到了则返回这个节点指针,否则就返回NULL*/
}
销毁hash表,释放空间:


void destory(hashtable h)
{
int i;
position temp,post;
for(i=0;i<h->tablesize;i++)
{
temp=h->list[i];
while(temp!=NULL)
{
post=temp->next;
free(temp);
temp=post;
}
}
}
注意的是这里面的代码并不能直接运行,需要根据具体情况进行修改,比如如果元素是字符串类型的话,那么应该采用strcmp和strcpy来进行比较和赋值操作。

分离链接法的一般法则是使得表的大小与预料的元素个数差不多,使得填充因子尽量接近于1.同时使得表的大小是一个素数,以保证一个好的分布。


紧接着便是开放地址法了,分离链接法中的缺点是需要使用指针,给新单元分配空间时还会造成一些额外开销。开放地址法的思路则是使得所有的节点都插入这个表内,因为这个原因其表的大小比分离地址法大,一般来说应该保证填充因子低于0.5。对于地址冲突的解决方法中常见的有线性探测,平方探测,双散列等方法。

线性探测有一个明显的缺点是会形成一次聚集的区块,于是任何散列到区块中的关键字都需要多次探测才能找到合适的单元解决冲突,这浪费大量时间。忽略推算,证明的是如果表有一半被填满的话,那么线性探测就不是一个好想法。

平方探测中有一个更明显的缺点是一旦表被填满超过一半,当表的大小不是素数甚至在一半之前,就不能保证一次找到一个空单元了。但是能证明的是,表有一半是空的并且表大小为素数,则总能保证能够插入一个新元素。

同时有一点需要特别注意,在开放地址散列表中,所有的标准删除操作都不能运行,因为这些单元很可能发生过冲突,那么如果删除掉它们的话,其它元素则会失去了访问信息了,从而find等操作便不能运行了。这里可以采用懒惰删除,我们只是做一个标记就行了。

当然开放地址法中采用数组就行了:


*开放地址法*/
typedef struct hash *hashtable;
typedef struct node cell;
typedef int index;
struct hash
{
int tablesize;
cell *cells;
};
struct node
{
elementtype element;
int flag;
};
#define empty 0
#define deleted 1 
#define occupyed 3

hashtable init(int tablesize);
void insert(elementtype key,hashtable h);
index find(elementtype key,hashtable h);
void destory(hashtable h);

给出一个init操作:

hashtable init(int tablesize)
{
hashtable h;
h=(hashtable)malloc(sizeof(struct hash));
if(h==NULL)
fatalerror("out of space");
h->cells=(cell*)malloc(sizeof(struct cell)*tablesize);
if(h->cells==NULL)
fatalerror("out of space");
h->tablesize=tablesize;
int i;
for(i=0;i<tablesize;i++)
h->cells[i].flag=empty;
return h;
}

至于其它操作这里就不给出了,因为都很简单。注意的一点是标记的使用和判断。

对于使用开放地址的散列还有一个常见操作是再散列,因为散列中元素填得太满时,操作的运行时间就会变长,同时insert操作也可能失败。一种解决方案便是再散列,建立一个大约两倍大的表,并且使用一个相关的新散列函数,扫描原始散列表,将其中元素,将其中未删除的元素重新计算散列值插入到新表中。




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值