第7章 查找(散列表)

  在前6章中学习几种基本类型的数据结构,其中也有一些查找的操作,第7章就是专门讲比较具体的查找算法,还有各种优化。

  首先,顺序查找和折半查找是比较熟悉的,平常用处挺大的,不过两种查找方法都有很明显的劣势,而折半查找时间复杂度O(log2N),相对来说查找效率比较高,但它只限于有序表。为了解决查找算法的局限性:1.只能有序存储;2.动态插入问题;3.数据量太大等等;有了后面的二叉排序树、平衡二叉树、B-树、B+树和散列表(哈希查找法),逐步深入,算法难度也逐渐增加。

  二叉排序树就实现了动态插入的功能,根据它的性质,我们在数据插入到二叉树中的时候,已经进行了相当于一次有序遍历,插入的过程基本是查找,时间复杂度为O(log2N)。之前的一道实践题,就运用了二叉排序树的性质(一串相同数据,输入顺序不同,创建的树结构可能不同),判断建立的树是否为同一棵树。

第7章 有关查找的两道题 这道题主要是在插入的算法,也是查找时候的关键,同时也可以看出二叉搜索树也有不足之处,当有很大数据量的时候,n很大,查找时间也增加。所以,另外一种查找算法,散列表查找法(Hash Search)理论说是可以实现O(1)的时间复杂度,还是比较有争议的。问题出现在不同元素值有着相同的哈希值,然后需要处理这些碰撞,把它们分别放到不同的地址,所以在查找的时候也需要通过一定时间来遍历,找到需要的信息的地址。

  而且有一种最差的情况就是,我们采用链地址法处理冲突,比如向其中连续插入n个元素。这n个元素的值均不相同,但是全部有相同的哈希值,也就是说会被插入到同一个位置上去。(如:2、4、6、8、10.....) 每一次插入时,首先都找到相同的地址,然后判断现在这个值确实不在表中,就将新的元素加到这个散列地址后面的单链表,这样一来,插入第1个元素需要1次比较操作;插入第2个元素需要1+1次比较操作;……;插入第n个元素时需要n次比较操作。最后总共需要1+2+…+n=0.5n(n+1)=O(n2)个常数时间操作,才能把n个元素插入到哈希表中。所以这样插入的时间复杂度要O(n2),查找时候的遍历操作O(1)觉得不太可能。

  不过在建立哈希表的时候避免大量碰撞还是可以实现的。处理冲突的方法有开放地址法和链地址法,开放地址法中有线性探测法和二次探测法,它们几个优劣之分,具体应用要具体选择合适的方法。在这一章练习题,是采用了二次探测法(增量为正)。

 Hashing

The task of this problem is simple: insert a sequence of distinct positive integers into a hash table, and output the positions of the input numbers. The hash function is defined to be H(key) = key % TSizeH(key)=key%TSize where TSizeTSizeis the maximum size of the hash table. Quadratic probing (with positive increments only) is used to solve the collisions.

Note that the table size is better to be prime. If the maximum size given by the user is not prime, you must re-define the table size to be the smallest prime number which is larger than the size given by the user.

Input Specification:

Each input file contains one test case. For each case, the first line contains two positive numbers: MSizeMSize (\le 10^4104​​) and NN (\le MSizeMSize) which are the user-defined table size and the number of input numbers, respectively. Then NN distinct positive integers are given in the next line. All the numbers in a line are separated by a space.

Output Specification:

For each test case, print the corresponding positions (index starts from 0) of the input numbers in one line. All the numbers in a line are separated by a space, and there must be no extra space at the end of the line. In case it is impossible to insert the number, print "-" instead.

Sample Input:

4 4

10 6 4 15

Sample Output:

0 1 4 -

 刚刚开始做题目没看清楚,以为如果有冲突就将后面的数记录为无法插入,后来才发现要使用二次探测法处理冲突,解题过程思路刷新了一次,我的思路比较直接,想把数据对应的散列地址用一个数组记录起来,遍历这个数组就可以知道相应数据的地址信息或者无法插入。

在输入数据同时并将结果顺便算出的版本:

 

#include<iostream>
using namespace std;
int visit[10000]={0};//访问标志数组

int prime(int a)//判断是否为素数
{
    int i;
    if(a<=1) return 0; //这一步判断很重要,题目有一个测试点就是检测最小值
    for(i=2;i<a;i++)
    {    
        if(a%i==0)
        return 0;    //不是素数返回0
    }
    if(a>=i)
        return a;//是素数
}

void hashlocate(int m,int n,int c)
{    
    int i,h,d,data;
    
    for(i=0;i<n;i++)
    {
        cin>>data;
        d=0;//每次输入数据,d为增量,从0增加
        h=data%c; d++;//先计算第一个值对应的散列表地址(下标)
        if(i) cout<<" ";//第一个输出前不带空格
        
      while(d<m&&visit[h])//循环是对已经访问过的地址进行操作,直到找到可放入的位置
        {
            h=(data+d*d)%c;d++;
        }
        
        if(!visit[h]) //对未访问过地址进行输出
        {
            cout<<h;
            visit[h]=1;//输出访问值之后对应标志数组位置记为1
        }
        else 
            cout<<'-'; //在while之后仍没有位置的数据,表示无法插入
    }
}

int main()
{
    int m,n,c;
    cin>>m>>n;
    c=m;
    while(prime(c)==0)//如果输入的数不是素数,找到大于此数的最小素数
    {
        c++;    
    }

    hashlocate(m,n,c);
    return 0;
} 
View Code

 

 

 

题目有一步是找一个合数的下一个紧接着的素数,开始觉得挺麻烦的,后来发现将判断一个数是否为素数的代码改一下就好,所以我的方法是用一个while进行计算。

用辅助数组保存地址后在输出的版本:

#include<iostream>
using namespace std;
int visit[10000]={0};

void Hashlocate(int m,int n)
{
    int i,flag=0,h,d;
    int *H1,*H;
    H1=new int[n];
    H=new int[n];
    for(i=0;i<n;i++)
    {    
        cin>>H1[i];
        h=H1[i]%m;//先计算第一个值对应的散列表地址(下标)
        d=0;d++;//每次输入数据,d为增量,从0增加
        
        while(d<m&&visit[h])//循环是对已经访问过的地址进行操作,直到找到可放入的位置
        {
            h=(H1[i]+d*d)%m;d++;
        }
        
        if(!visit[h])//对未访问过地址进行输出
        {
            H[i]=h;visit[h]=1;//将地址保存到辅助数组,输出访问值之后对应标志数组位置记为1
        }
        else H[i]=-1;//经过二次探测法,仍然无位置的,标记一下无法插入的数据 
    }
    
    for(i=0;i<n;i++)
    {
        if(H[i]!=-1)
        {
            if(flag==0)//处理空格输出 
            {
                cout<<H[i];flag=1;
            }
            else cout<<" "<<H[i];
        }
        else
        cout<<" "<<'-';//没有位置可放的数据,表示无法插入
    }
    
}

int prime(int a)//判断是否为素数
{
    int i;
    if(a<=1) return 0;//这一步判断很重要,题目有一个测试点就是检测最小值
    for(i=2;i<a;i++)
    {    
        if(a%i==0)
        return 0;    
    }
    if(a>=i)
        return a;
}


int main()
{
    int m,n;
    cin>>m>>n;
    while(prime(m)==0)//如果输入的数不是素数,找到大于此数的最小素数
    {
        m++;    
    }

    Hashlocate(m,n);
    return 0;
} 
View Code

 

 

遇到的困难是在增量那里的计算,对于每个输入数据,要求出它的散列地址,如果冲突,要用二次探测法重新寻找位置,这一点思考许久,最后又是用了while辅助计算,结合visit数组和表长m来判断,debug几次后,测试几组数据才做好这一步。还有一个细节问题,卡了我很长时间一直没注意到,就是测试点2 最小值,因为在判断素数的函数中忽略了最小值1,没有处理,一直检查其他部分是否出错,等到最后才知道是细节问题,自己对问题还是没有考虑周全,以后需要注意这一点。

 

 

posted @ 2019-06-02 15:40  Charzueus  阅读(295)  评论(0编辑  收藏  举报