哈希表应用(查找)

[问题描述]

设计散列表实现VIP客户发掘。对身份证号进行Hash, 通过对乘客某时间段内的乘机频率、里程数统计,发掘VIP客户。

[基本要求]

(1)设每个记录有下列数据项:身份证号码(虚构,位数和编码规则与真实一致即可)、姓名、航班号、航班日期、里程。

(2)从文件输入各记录,以身份证号码为关键字建立散列表。

(3)分别采用开放定址(自行选择和设计定址方案)和链地址两种方案解决冲突;显示发生冲突的次数、每次中解决冲突进行重定位的次数。

(4)记录条数至少在100条以上。

(5)从记录中实现乘客乘机频率、里程数统计,从而发掘VIP客户。

题目思考:

这道题首先要自己建立数据文件,这一步还挺麻烦的,毕竟样本至少要在100条以上。在建立数据时,我故意设置了几组会冲突的数据,便于检验程序。另外,题目的要求中并没有让我们实现查询客户乘坐信息的功能。但为了充分体现哈希表的应用,我自己增加了这个功能。关于VIP,我们假定里程数超过1000或乘坐次数大于等于3,则能成为VIP客户。

解题思路:

  1. 首先要构造哈希函数我们的关键字是身份证号码,身份证号码前6位表示地域,后面7-14位表示出生日期,最后4位是个人编号。这里,我们把个人编号的4位数字相乘(0除外),再加上出生日期的8位数字作为最后的哈希地址

  1. 然后建立散列表,并解决冲突。这里给出了两种解决冲突的方案:(1)、开放定址法:若某地址产生冲突,则依次探测下一个地址,直到找到一个空闲的哈希地址为止。(2)、链地址法:将产生冲突的所有关键字保留到一个链表中,在进行查找时,根据哈希函数来确定要遍历哪个链表。

  1. 查找。以开放地址法为例,先输入要查询的客户的身份证号,再根据哈希函数求得相应的哈希地址,若地址为空则查找不成功;否则,将客户身份证号与该地址中存储的身份证号做比较。若相等,则查找成功,输出客户乘坐信息;否则依次寻找下一个地址,直到查找成功为止。

程序代码:

# include <iostream>
# include <stdlib.h>
# include <fstream>
# include <algorithm>
# include <string.h>
# include <vector>
# define maxn 10
# define SIZE 10000
# define NEWSIZE 1024

using namespace std;

//开放地址法的结构体
typedef struct Passenger1 {
    char ID[20];         //身份证号
    char name[20];       //姓名
    char num[maxn][10];  //航班
    char date[maxn][12]; //时间
    int mileage[maxn];   //里程数
    int distance;        //总里程数
    int rate;            //乘坐频率
    int v;               //是否已成为VIP(1:是 0:否)
}Passenger1;
typedef struct HashTable1 {
    Passenger1* elem;  //哈希表
    int count;         //表中包含记录的个数
    int size;          //表的容量
}HashTable1;

//链地址法的结构体
typedef struct Passenger2 {
    char ID[20];         //身份证号
    char name[20];       //姓名
    char num[maxn][10];  //航班
    char date[maxn][12]; //时间
    int mileage[maxn];   //里程数
    int distance;        //总里程数
    int rate;            //乘坐频率
    int v;               //是否已成为VIP(1:是 0:否)
    struct Passenger2* nextarc;
}Passenger2;
typedef struct VexNode {
    Passenger2* firstarc;
}VexNode;
typedef struct HashTable2 {
    VexNode elem[SIZE];  //哈希表
    int count;           //表中包含记录的个数
}HashTable2;

int n1 = 0, n2 = 0;  //统计冲突次数
vector<int>VIP;      //保存VIP客户的地址
int ProduceHash();   //哈希函数取址
void CreateHash1(HashTable1& H1);  //开放地址法建立哈希表
void CreateHash2(HashTable2& H2);  //链地址法建立哈希表
void FindHash1(HashTable1 H1, char* s); //查询客户信息(开放地址)
void FindHash2(HashTable2 H2, char* s); //查询客户信息(链地址)
void ShowVIP(HashTable1 H1);           //显示VIP客户

int main()
{
    HashTable1 H1;  //开放地址法的哈希表
    HashTable2 H2;  //链地址法的哈希表
    cout << "开放地址法:" << endl;
    CreateHash1(H1);
    cout << "\n链地址法:" << endl;
    CreateHash2(H2);
    int select;
    while (1) {
        cout << "\n(1)查询客户乘坐信息(开放地址)" << endl;
        cout << "(2)查询客户乘坐信息(链地址)" << endl;
        cout << "(3)显示VIP客户信息" << endl;
        cout << "(4)退出程序" << endl;
        cout << "请选择:";
        cin >> select;
        if (select == 1) {
            char s[20];
            cout << "\n请输入要查询的客户的身份证号:";
            cin >> s;
            FindHash1(H1, s);
        }
        else if (select == 2) {
            char s[20];
            cout << "\n请输入要查询的客户的身份证号:";
            cin >> s;
            FindHash2(H2, s);
        }
        else if (select == 3) {
            ShowVIP(H1);
        }
        else {
            break;
        }
    }
    return 0;
}

//哈希函数取址
int ProduceHash(char* s)
{
    int sum = 0;
    for (int i = 6; i < 14; i++) {
        sum = sum + s[i] - 48;
    }
    int a = 1;
    for (int i = 14; i < 18; i++) {
        if (s[i] - 48 > 0) {
            a = a * (s[i] - 48);
        }
    }
    sum += a;
    return sum;
}

//开放地址法建立哈希表
void CreateHash1(HashTable1& H1)
{
    H1.elem = (Passenger1*)malloc(SIZE * sizeof(Passenger1));
    H1.size = SIZE;
    H1.count = 0;
    //哈希表的初始化
    for (int i = 0; i < SIZE; i++) {
        strcpy(H1.elem[i].ID, "\0");
        H1.elem[i].distance = 0;
        H1.elem[i].rate = 0;
        H1.elem[i].v = 0;
    }
    fstream file;
    file.open("Hash表.txt", ios::in);
    if (file.fail()) {
        cout << "文件打开失败" << endl;
        exit(0);
    }
    char str[100];
    int t = 1;
    file.getline(str, 100);
    while (t) {
        if (file.eof()) {
            t = 0;
        }
        char s[5][20];
        int j = 0, k = 0, address;
        for (int i = 0; i < strlen(str); i++) {
            if (str[i] == ' ') {
                s[k][j] = '\0';
                if (k == 0) {
                    //s[k]是身份证号,开始寻址
                    int m = 0;  //统计每次中解决冲突进行重定位的次数
                    address = ProduceHash(s[k]);
                    while (H1.elem[address].rate && strcmp(H1.elem[address].ID, s[k])) {
                        //该地址已被占用,且不是同一个人,则发生冲突
                        //依次探测下一个地址,直到找到一个空闲的哈希地址为止
                        address = (address + 1) % H1.size;
                        m++;
                    }
                    if (strcmp(H1.elem[address].ID, s[k]) != 0) {
                        //有新人加入
                        H1.count++;
                    }
                    if (m) {
                        //发生了冲突
                        n1++;
                        cout << "发生第" << n1 << "次冲突," << "进行了" << m << "次重定位" << endl;
                    }
                }
                switch (k)
                {
                case 0: strcpy(H1.elem[address].ID, s[k]);   break;
                case 1: strcpy(H1.elem[address].name, s[k]); break;
                case 2: strcpy(H1.elem[address].num[H1.elem[address].rate], s[k]);  break;
                case 3: strcpy(H1.elem[address].date[H1.elem[address].rate], s[k]); break;
                }
                k++;
                j = 0;
            }
            else {
                s[k][j] = str[i];
                j++;
            }
        }
        s[k][j] = '\0';
        //s[k]是里程数
        int d = 0, len = strlen(s[k]);
        for (int i = 0; i < len; i++) {
            d = d + (s[k][i] - 48) * pow(10, len - i - 1);
        }
        H1.elem[address].mileage[H1.elem[address].rate] = d;
        H1.elem[address].distance += d;
        H1.elem[address].rate++;
        if (!H1.elem[address].v && (H1.elem[address].distance >= 1000 || H1.elem[address].rate >= 3)) {
            //里程数超过1000或乘坐次数大于等于3,则能成为VIP
            H1.elem[address].v = 1;
            VIP.push_back(address);
        }
        if (H1.size <= H1.count) {
            //根据人的个数动态分配空间
            H1.elem = (Passenger1*)realloc(H1.elem, (H1.size + NEWSIZE) * sizeof(Passenger1));
            H1.size += NEWSIZE;
        }
        if (t) {
            file.getline(str, 100);
        }
    }
    file.close();
}

//链地址法建立哈希表
void CreateHash2(HashTable2& H2)
{
    H2.count = 0;
    //哈希表的初始化
    for (int i = 0; i < SIZE; i++) {
        H2.elem[i].firstarc = NULL;
    }
    fstream file;
    file.open("Hash表.txt", ios::in);
    if (file.fail()) {
        cout << "文件打开失败" << endl;
        exit(0);
    }
    Passenger2* p, * q;
    char str[100];
    int t = 1;
    file.getline(str, 100);
    while (t) {
        if (file.eof()) {
            t = 0;
        }
        char s[5][20];
        int j = 0, k = 0, address;
        for (int i = 0; i < strlen(str); i++) {
            if (str[i] == ' ') {
                s[k][j] = '\0';
                if (k == 0) {
                    //s[k]是身份证号,开始寻址
                    int m = 0;  //统计每次中解决冲突进行重定位的次数
                    address = ProduceHash(s[k]);
                    p = H2.elem[address].firstarc;
                    q = H2.elem[address].firstarc;
                    while (p != NULL && strcmp(p->ID, s[k])) {
                        //该地址已被占用,且不是同一个人,则发生冲突
                        q = p;
                        p = p->nextarc;
                        m++;
                    }
                    if (p == NULL) {
                        //有新人加入,建立新节点
                        H2.count++;
                        p = (Passenger2*)malloc(sizeof(Passenger2));
                        if (H2.elem[address].firstarc == NULL) {
                            H2.elem[address].firstarc = p;
                        }
                        else {
                            q->nextarc = p;
                        }
                        p->distance = 0;
                        p->rate = 0;
                        p->v = 0;
                        p->nextarc = NULL;
                    }
                    if (m) {
                        //发生了冲突
                        n2++;
                        cout << "发生第" << n2 << "次冲突," << "进行了" << m << "次重定位" << endl;
                    }
                }
                switch (k)
                {
                case 0: strcpy(p->ID, s[k]);      break;
                case 1: strcpy(p->name, s[k]);    break;
                case 2: strcpy(p->num[p->rate], s[k]);  break;
                case 3: strcpy(p->date[p->rate], s[k]); break;
                }
                k++;
                j = 0;
            }
            else {
                s[k][j] = str[i];
                j++;
            }
        }
        s[k][j] = '\0';
        //s[k]是里程数
        int d = 0, len = strlen(s[k]);
        for (int i = 0; i < len; i++) {
            d = d + (s[k][i] - 48) * pow(10, len - i - 1);
        }
        p->mileage[p->rate] = d;
        p->distance += d;
        p->rate++;
        if (t) {
            file.getline(str, 100);
        }
    }
    file.close();
}

//查询客户信息(开放地址)
void FindHash1(HashTable1 H1, char* s)
{
    int a;
    a = ProduceHash(s);   //获得哈希地址
    while (H1.elem[a].rate > 0 && strcmp(H1.elem[a].ID, s) != 0) {
        //如果地址不为空且不是同一个人
        a = (a + 1) % H1.size;
    }
    if (H1.elem[a].rate > 0 && strcmp(H1.elem[a].ID, s) == 0) {
        cout << "\n客户乘坐信息已导出" << endl;
        for (int i = 0; i < H1.elem[a].rate; i++) {
            cout << H1.elem[a].ID << '\t' << H1.elem[a].name << '\t' << H1.elem[a].num[i];
            cout << '\t' << H1.elem[a].date[i] << '\t' << H1.elem[a].mileage[i] << endl;
        }
        cout << "客户乘坐信息导出完毕" << endl;
    }
    else {
        cout << "查无此人" << endl;
    }
}

//查询客户信息(链地址)
void FindHash2(HashTable2 H2, char* s)
{
    int a;
    a = ProduceHash(s);   //获得哈希地址
    Passenger2* p = H2.elem[a].firstarc;
    while (p != NULL && strcmp(p->ID, s) != 0) {
        //如果地址不为空且不是同一个人
        p = p->nextarc;
    }
    if (p != NULL && strcmp(p->ID, s) == 0) {
        cout << "\n客户乘坐信息已导出" << endl;
        for (int i = 0; i < p->rate; i++) {
            cout << p->ID << '\t' << p->name << '\t' << p->num[i];
            cout << '\t' << p->date[i] << '\t' << p->mileage[i] << endl;
        }
        cout << "客户乘坐信息导出完毕" << endl;
    }
    else {
        cout << "查无此人" << endl;
    }
}

//显示VIP客户
void ShowVIP(HashTable1 H1)
{
    if (VIP.empty()) {
        cout << "\n暂无VIP客户" << endl;
        return;
    }
    cout << "\nVIP客户名单已导出" << endl;
    for (int i = 0; i < VIP.size(); i++) {
        cout << H1.elem[VIP[i]].ID << '\t' << H1.elem[VIP[i]].name << endl;
    }
    cout << "VIP客户名单导出完毕" << endl;
}

运行示例:

开放地址法:
发生第1次冲突,进行了1次重定位
发生第2次冲突,进行了2次重定位
发生第3次冲突,进行了1次重定位
发生第4次冲突,进行了1次重定位
发生第5次冲突,进行了1次重定位
发生第6次冲突,进行了1次重定位
发生第7次冲突,进行了1次重定位
发生第8次冲突,进行了1次重定位
发生第9次冲突,进行了1次重定位
发生第10次冲突,进行了1次重定位
发生第11次冲突,进行了2次重定位
发生第12次冲突,进行了1次重定位
发生第13次冲突,进行了1次重定位
发生第14次冲突,进行了1次重定位

链地址法:
发生第1次冲突,进行了1次重定位
发生第2次冲突,进行了2次重定位
发生第3次冲突,进行了1次重定位
发生第4次冲突,进行了1次重定位
发生第5次冲突,进行了1次重定位
发生第6次冲突,进行了1次重定位
发生第7次冲突,进行了1次重定位
发生第8次冲突,进行了1次重定位
发生第9次冲突,进行了1次重定位
发生第10次冲突,进行了1次重定位
发生第11次冲突,进行了1次重定位
发生第12次冲突,进行了1次重定位

(1)查询客户乘坐信息(开放地址)
(2)查询客户乘坐信息(链地址)
(3)显示VIP客户信息
(4)退出程序
请选择:1

请输入要查询的客户的身份证号:111111197011292934

客户乘坐信息已导出
111111197011292934      Alan    CA1503  2002.03.05      420
111111197011292934      Alan    CA1503  2005.03.05      240
111111197011292934      Alan    CA1503  2008.03.05      330
客户乘坐信息导出完毕

(1)查询客户乘坐信息(开放地址)
(2)查询客户乘坐信息(链地址)
(3)显示VIP客户信息
(4)退出程序
请选择:2

请输入要查询的客户的身份证号:444444196902170041

客户乘坐信息已导出
444444196902170041      Jary    CA1506  2021.04.15      250
客户乘坐信息导出完毕

(1)查询客户乘坐信息(开放地址)
(2)查询客户乘坐信息(链地址)
(3)显示VIP客户信息
(4)退出程序
请选择:3

VIP客户名单已导出
111111197011292934      Alan
333333200205215987      Eane
111111201009017831      Mlan
444444198308274541      Hary
444444199511234430      Lary
444444199411309731      Dary
555555195205059109      Nhon
444444200408120000      Xicn
555555201704127305      Xhon
555555201805179872      Rhon
VIP客户名单导出完毕

(1)查询客户乘坐信息(开放地址)
(2)查询客户乘坐信息(链地址)
(3)显示VIP客户信息
(4)退出程序
请选择:4

总结:

由于冲突处理的方式不同,两种方式发生冲突的次数也可能不同。采用开放定址法,发生冲突时,会占用后面空闲的空间,如果下一次刚好定址在这个空闲空间,就会导致发生新的冲突。这就是开放定址法容易发生的堆积现象,而链地址法则不会出现这种情况。所以,在这道题中,链地址法要优于开放定址法

以上便是我对这道题目的看法,很高兴与大家分享。为了方便大家测试运行,我再分享一下自己的 Hash表.txt 文件的内容(如果你使用了我的数据,拜托点个赞嘞,手动建这个文件真的很麻烦)。

222222198304054329 Bob CA1504 2003.03.15 280
111111197011292934 Alan CA1503 2002.03.05 420
333333199403048309 Jane CA1505 2013.05.15 180
444444200910261658 Mary CA1506 2021.04.15 150
555555201711172538 Jhon CA1507 2011.07.14 350
666666199312018307 Eric CA1508 2004.06.24 412
777777199302020476 Rutc CA1401 2007.12.10 363
888888197810237324 Lucy CA3424 2020.09.03 432
999999197505064234 Kfsfd CA4732 2000.07.13 542
000000194307123789 Ahiu CA4732 2003.04.13 252
111111197011292934 Alan CA1503 2005.03.05 240
111111196805076732 Dlan CA1603 2007.03.05 120
111111197011292934 Alan CA1503 2008.03.05 330
111111199003291234 Blan CA1203 2004.03.01 340
111111200001097892 Clan CA1503 2005.03.05 140
111111200402247832 Elan CA1503 2009.03.05 220
111111199106024234 Flan CA1103 2010.04.05 260
111111200210287892 Glan CA1503 2005.03.05 240
111111200111286731 Hlan CA1203 2012.02.05 540
111111201210187792 Jlan CA1503 2005.03.05 440
111111200407284802 Klan CA1503 2005.03.05 340
111111201009017831 Mlan CA1503 2005.03.05 244
111111201410287092 Nlan CA1503 2005.03.05 250
111111201103286902 Qlan CA1503 2005.03.05 145
111111201510210024 Plan CA1503 2005.03.05 280
111111201902149700 Wlan CA1503 2005.03.05 242
111111202104061011 Rlan CA1503 2005.03.05 249
111111200210287892 Glan CA1502 2008.03.05 490
111111201210187792 Jlan CA1501 2009.03.05 420
111111201009017831 Mlan CA1503 2010.04.05 244
333333198703218634 Aane CA1504 2013.05.15 182
333333195807300803 Bane CA1505 2013.05.15 204
333333199903148319 Cane CA1405 2013.05.15 280
333333198412047491 Dane CA1506 2013.05.15 480
333333200205215987 Eane CA1305 2013.05.15 340
333333202105142709 Fane CA1405 2015.06.15 480
333333201510055598 Gane CA1505 2013.05.15 580
333333199203040023 Hane CA1204 2013.05.15 280
333333198308316473 Kane CA1701 2013.06.15 190
333333200602048339 Mane CA1505 2013.05.15 380
333333189406247109 Nane CA1507 2013.05.15 370
333333199906148529 Qane CA1405 2013.05.15 220
333333199104148334 Pane CA1415 2013.05.15 360
333333197609017233 Wane CA1405 2013.05.15 280
333333197910143419 Rane CA1405 2013.05.15 380
333333200411042421 Lane CA1506 2013.05.15 480
333333197807230103 Xane CA1505 2013.05.15 204
333333201205215287 Yane CA1306 2013.05.15 340
333333200205215987 Eane CA1305 2015.05.15 540
333333199903148319 Cane CA1405 2016.05.15 380
333333200205215987 Eane CA1305 2017.05.15 540
111111201009017831 Mlan CA1503 2015.04.05 244
333333200205215987 Eane CA1305 2020.05.15 540
333333199906148529 Qane CA1405 2018.05.15 220
444444201911160001 Aary CA1506 2021.04.15 350
444444198908174341 Bary CA1506 2021.04.15 250
444444197408234230 Cary CA1506 2021.04.15 320
444444199411309731 Dary CA1506 2021.04.15 450
444444200506296663 Eary CA1506 2021.04.15 252
444444201312210908 Fary CA1506 2021.04.15 490
444444201412260301 Gary CA1506 2021.04.15 350
444444198308274541 Hary CA1506 2021.04.15 550
444444196902170041 Jary CA1506 2021.04.15 250
444444198308274541 Hary CA1506 2021.05.15 550
444444197706233030 Kary CA1506 2021.04.15 620
444444199511234430 Lary CA1506 2021.04.15 1320
444444198408218630 Nary CA1506 2021.04.15 330
444444200710257783 Qary CA1506 2021.04.15 520
444444200408120000 Xicn CA1506 2021.04.15 521
444444200711017423 Pary CA1506 2021.04.15 420
444444199411309731 Dary CA1506 2021.06.15 150
444444201112070341 Rary CA1506 2021.04.15 450
444444198808270344 Tary CA1506 2021.04.15 250
444444199704174328 Wary CA1506 2021.04.15 150
444444201412010318 Xary CA1506 2021.04.15 490
444444202005267901 Yary CA1506 2021.04.15 350
444444199704174328 Wary CA1506 2021.06.15 250
444444199411309731 Dary CA1506 2021.08.15 450
555555201812174729 Ahon CA1507 2011.07.14 350
555555198709280087 Bhon CA1507 2011.07.14 330
555555200710070147 Chon CA1507 2011.07.14 650
555555202101179283 Dhon CA1507 2011.07.14 250
555555200607070142 Ehon CA1507 2011.07.14 420
555555201612070398 Fhon CA1507 2011.07.14 350
555555200102239732 Ghon CA1507 2011.07.14 370
555555198204194729 Hhon CA1507 2011.07.14 570
555555196303170937 Khon CA1507 2011.07.14 470
555555194306268931 Lhon CA1507 2011.07.14 534
555555195511230123 Mhon CA1507 2011.07.14 360
555555195205059109 Nhon CA1507 2011.07.14 770
555555196312190316 Qhon CA1507 2011.07.14 440
555555195205059109 Nhon CA1507 2013.07.14 770
555555201607270294 Phon CA1507 2011.07.14 320
555555201805179872 Rhon CA1507 2011.07.14 522
444444200408120000 Xicn CA1506 2021.08.15 521
555555201103166812 Thon CA1507 2011.07.14 622
555555201011250734 Whon CA1507 2011.07.14 222
555555201704127305 Xhon CA1507 2011.07.14 1320
555555197201174329 Yhon CA1407 2011.07.14 525
555555201203290182 Zhon CA1507 2011.07.14 322
555555201607270294 Phon CA1507 2011.07.24 320
555555196312190316 Qhon CA1507 2011.08.14 440
555555201805179872 Rhon CA1507 2011.09.14 522

注:数据纯属虚构。

猜你喜欢

转载自blog.csdn.net/CXR_XC/article/details/128847886