散列表的原理和应用

本来以为,散列表就是map,今天做了道题看答案的时候,才发现我原来搞错了,于是简单的学习了一下散列表的原理和简单实现,发博客分享一下~

我们知道,数组的动态操作(删除、插入)是非常耗时的,链表的静态操作(查找)也是颇为麻烦的。

在既涉及静态操作,又涉及动态操作的场合,这两种基本的数据结构就非常乏力了。

如何达到平衡呢?

我们想象一个场景,在一个特殊的学校里,一开始并没有班级的概念,几千名学生就像是同一个班的。他们有一一对应的学号,

我们顺着他们某一个人的学号找到他们也比较轻松。但问题来了,当有学生转学转走时,这名学生学号后面的其它学生学号要统统减少1,有学生转来时,要统统加上1,这可麻烦透了,怎么解决呢?

我们想,如果将这几千名学生,分成很多个(很多个的原因下面自己体会)班,每个班有自己的学号编排,每个班转走或转来学生,对其它班不会造成影响则,同时,要找到一名学生也并没有变麻烦,只需要找到它对应的班级,在一个班级有限个(有限个的原因自己体会)学生里面一个一个看是不是要找的学生即可。

怎么实现呢?我们将上述场景里的学校想作是一个数组,每个班级是数组里的一个元素,班级想作是一个链表,班级里的学生则是一个又一个节点,查找一个学生的话,先用数组下标定义到学生所在的班级,然后通过链表的遍历找到这名学生。因为班级分的特别多,每个班级里的学生特别少,链表遍历找到这名学生并不是很耗时。如果要删除或者插入一位学生的话,则也是先定位到班级(或应该去的班级)链表遍历到这名学生直接删除,或直接插入。

实现的话我们直接看题目吧。

 

 

问题描述

你有一个盒子,你可以往里面放数,也可以从里面取出数。

初始时,盒子是空的,你会依次做 Q 个操作,操作分为两类:

  1. 插入操作:询问盒子中是否存在数 x,如果不存在则把数 x 丢到盒子里。
  2. 删除操作:询问盒子中是否存在数 x,如果存在则取出 x。

对于每个操作,你需要输出是否成功插入或删除。

输入

第一行一个正整数 Q,表示操作个数。

接下来 Q 行依次描述每个操作。每行 2 个用空格隔开的非负整数 op,x 描述一个操作:op 表示操作类型,op=1 则表示这是一个插入操作,op=2 则表示这是一个删除操作;x 的意义与操作类型有关,具体见题目描述。

输出

按顺序对所有操作输出,对于每个操作输出一行,如果成功则输出“Succeeded”(不含引号),如果失败则输出“Failed”(不含引号)。

样例输入

6
1 100
1 100
2 100
1 200
2 100
2 200

样例输出

Succeeded
Failed
Succeeded
Succeeded
Failed
Succeeded
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int Mod = 1000003;
vector<ll> table[Mod];
int has(int x){
    return x%Mod;
}
bool check(int op, ll x)
{
    int h = has(x);
    vector<ll>::iterator ptr = table[h].end();
    for(vector<ll>::iterator it = table[h].begin(); it!=table[h].end(); it++ )
    {
       if( *it == x)
       {
           ptr = it;
           break;
       }
    }
    if( op == 1){ //插入操作
        if( ptr!= table[h].end()) //如果已经有这个元素了
        {
            return 0;
        }
        else//没有的话
        {
            table[h].push_back(x);
            return 1;
        }
    }else{ //删除操作
        if( ptr != table[h].end())//如果有这个元素
        {
            *ptr = table[h].back(); //将这个元素被向量最后一个元素替代
            table[h].pop_back();
            return 1;
        }
        else
            return 0;
    }
}
int main()
{
    int n; scanf("%d",&n);
    int op; ll x;
    while(n--)
    {
        scanf("%d %lld",&op,&x);
        if( check(op, x)){
            printf("Succeded\n");
        }
        else{
            printf("Failed\n");
        }
    }
    return 0;
}

因为本人很不喜欢用链表。因此就用向量来模拟了哈。用向量模拟的话,插入操作自然是在向量尾部添加一个元素,只需要O(1)时间,删除的话步骤就像下图所示,因为实际的删除操作是在向量最末端,没有元素需要前移,因此也只需要O(1)的时间

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页