数据结构 散列表(哈希表)线性开型寻址 c++实现

本文深入解析了散列表的插入、查询和删除操作,通过具体的代码实现展示了如何处理散列冲突,包括元素的搜索、插入及删除过程中元素移动的计数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

具体实现的问题描述

给定散列函数的除数D和操作数m,输出每次操作后的状态。

有以下三种操作:
1. 插入x,若散列表已存在x,输出“Existed”,否则插入x到散列表中,输出所在的下标。
2. 查询x,若散列表不含有x,输出“-1”,否则输出x对应下标。
3. 删除x,若散列表不含有x,输出“Not Found”,否则输出删除x过程中移动元素的个数。

输入格式

第一行两个整数D(1\leq≤ D \leq≤ 3000)和m(1\leq≤ m \leq≤ 3000),其中D为散列函数的除数,m为操作数。
接下来的m行,每行两个整数opt和x,分别代表操作类型和操作数。
若opt为0,则代表向散列表中插入x;
若opt为1,代表查询散列表中x是否存在;
若opt为2,(如果散列表中含有x),删除x。
数据保证散列表不会溢出。

输出格式

m行,每行一个整数,代表对应查询的答案。

样例输入1

7 12
1 21
0 1
0 13
0 5
0 23
0 26
0 33
1 33
1 33
1 13
1 5
1 1

样例输出1

-1
1
6
5
2
0
3
3
3
6
5
1

样例输入2

7 15
2 10
0 10
0 10
2 10
1 10
0 10
1 10
0 17
0 2
0 16
0 11
2 2
2 10
1 11
1 17

样例输出2

Not Found
3
Existed
0
-1
3
3
4
2
5
6
2
2
4
3

首先先定义哈希表的私有变量和成员函数    HashTable.h

#ifndef SORTEDCHAIN_HASHTABLE_H
#define SORTEDCHAIN_HASHTABLE_H

template <class E,class K>
class HashTable{
  
public:
    HashTable(int divisor);
    ~HashTable(){
        delete []ht;
        delete []empty;
    }
    int Search(const K &k);
    int Insert(const E & e);
    int Delete(const K &k);
    void Output(std::ostream & put)const;
    friend std::ostream &operator<<(std::ostream &out,const HashTable<E,K> & ht){
        ht.Output(out);
        return out;
    }
private:
    int hSearch(const K &k)const;
    int D;
    E *ht;
    bool *empty;
};




#endif //SORTEDCHAIN_HASHTABLE_H
哈希表的构造函数 设置除数
template <class E,class K>
HashTable<E,K>::HashTable(int divisor) {
    D=divisor;   //读入除数

    ht=new E[D];   //分配散列数组
    empty=new bool[D];  //判断数组,对应散列数组,判断对应位置是否已经有元素
    for(int i=0;i<D;i++){
        empty[i]=true;    //初始化判断数组
    }
}

设置进行搜索的两个函数 具体实现搜索功能的是

search()函数,hsearch()函数对于后续进行删除操作,确定一个特定元素在哈希表中的 实际位置 和 理论位置 具有重要作用

template <class E,class K>
int HashTable<E,K>::hSearch(const K &k) const {
    //查找查找hash表中是否存在key=k的元素
    // 如果存在,则返回k的位置j
    // 否则如有足够空间,那么 j 表示可以插入的数组下标
    int i=k%D;   //i记录k在散列数组中的对应索引值,起始桶
    int j=i;
    do{
        if(empty[j]||ht[j]==k)  //2种情况直接返回数值j
            return j;
        j=(j+1)%D;   //表为环形的,找到下一个桶
    }while(j!=i);
    return j;    //回到起始桶

    // Q:那回到起始桶和一开始就找到有什么区别?
    //再次检查那个位置上的值,会发现回到起始桶的那种情况,ht[j]!=k
}

template <class E,class K>
int  HashTable<E,K>::Search(const K &k) {
    int b=hSearch(k);
    if(empty[b]||ht[b]!=k)  //b表示可插入的空位置索引或者起始桶时
        return -1;
        //e=ht[b];    //如果确实找到了key=k的元素
    else return b;
}

在哈希表中插入元素

template <class E,class K>
int  HashTable<E,K>::Insert(const E &e) {
    K k=e;
    int b=hSearch(k);   //b的值可能->空桶 也可能->找到一样的元素已存在的位置 也可能是起始桶
    if(empty[b]){  //空桶的情况
        empty[b]=false;
        ht[b]=e;
        return b;
    }
    if(!empty[b]&&ht[b]==k){//返回元素存在的位置
        return -1;
    }
    else if(!empty[b]&&ht[b]!=k){   //返回的是起始桶
        for(;!empty[b];b=(b+1)%D); //不断往后找空桶
        empty[b]=false;
        ht[b]=e;
        return b;
    }

}

最难实现的一个删除函数 博主写了两种实现 前一种对于特定数据时间复杂度比较高 但比较容易理解

第二种的时间复杂度低  比较难理解

template <class E,class K>
int HashTable<E,K>::Delete(const K &k) {
    int b = hSearch(k);
    if (empty[b] || ht[b] != k) {  //如果找到的是空或者回到起始桶,那么 Not Found 没找到
        return -1;
    } else if (ht[b] == k) {  //找到想要的元素 ,删除
        ht[b] = 0;   //虚拟置空,只用来判断程序运行过程
        empty[b] = true;   //置空
        int swapTimes = 0;

        int after = (b + 1) % D;
        for (; !empty[after]; after = (after + 1) % D) {  //向删除元素节点的后续节点开始遍历
            K tmp = ht[after]; //用tmp去记录被删除元素后一位元素的值
            int tf = hSearch(tmp); //tf为后一位元素在散列为空时应该在的位置
            if (empty[tf]) {
                empty[after] = true;
                empty[tf] = false;
                ht[tf] = tmp;
                ++swapTimes;
            }

        }
        return swapTimes;

    }    //O(n^2)的做法
template <class E,class K>
    int HashTable<E,K>::Delete(const K &k) {
      int b=hSearch(k); //找到想要删除的元素所在的位置
    if(empty[b]||ht[b]!=k)
        return -1;    //没有找到
    else if(ht[b]==k){     //正好找到了元素k
        empty[b]=true;     //显性置空
        int swapTimes=0;
        int move=b;   //元素k实际所在的位置索引--move
        int index=b;
        int a;

         do{
             move=(move+1)%D;   //向后遍历
             if(empty[move]) //遇到空值,那么跳出循环
                 break;
             a=ht[move]%D;     //a表示x理论上所在的位置的索引
             if((a<=index&&index<move)||(move<a&&a<=index)||(index<move&&move<a)){
                 empty[index]=false;
                 empty[move]=true;
                 ht[index]=ht[move];
                 index=move;
                     swapTimes++;
             }
             //其他情况不用移动

         }while(!empty[(move+1)%D]&&move!=b);
        return swapTimes;
             }

    }      //O(n)做法

第一种通过代码比较容易解释 不再解释

第二种方法   要想明白   删除元素k之后    后续的元素需要如何进行移位和重排 有多少种情况

记传入元素k的实际索引为move ,即插入的元素经过碰撞后在数组中的索引

记a表示删除元素后的一个特定元素x当散列为空时 理论上拥有的数组索引

 main()函数最后实现

int main() {
    int D,m;
    cin>>D>>m;
    HashTable<int,int> T(D);
    for(int i=0;i<m;i++){
        int opt,x;
        cin>>opt>>x;
        switch(opt)
        {
            case 0:
                int goal;
                goal=T.Insert(x);
                if(goal == -1)
                    cout<<"Existed"<<endl;
                else
                    cout<<goal<<endl;
                break;
            case 1:
                int element;
                element=T.Search(x);
                cout<<element<<endl;
                break;
            case 2:
                int times;
                times=T.Delete(x);
                if(times==-1)
                    cout<<"Not Found"<<endl;
                else
                    cout<<times<<endl;
                break;

        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值