提示:文章寫完後,目錄可以自動生成,如何生成可參考右邊的幫助文檔
文章目錄
- 前言
- 一、問題描述
- 二、問題描述
-
- (1)選用的散列函數
- (2)散列因子
- (3)解決沖突的方法
- 三、實驗結果及分析
-
- (1)實驗資料描述
- (2)實驗結果
- (3)性能分析
- 四、實驗總結
- 五、源代碼
-
- (1)随機生成電話号碼系統代碼
- (2)電話号碼查詢系統代碼
前言
記錄下上學期的資料結構實驗
本電話号碼查詢系統基于兩種散列方法和兩種解決沖突的方法實作
提示:以下是本篇文章正文内容,下面案例可供參考
一、問題描述
設計散清單,實作電話号碼查找系統。設電話号碼簿長度為n(0≤n≤10000),系統應該實作如下工作:
⑴ 電話号碼簿儲存在磁盤檔案中,每一條電話号碼記錄包含資料項:編号(唯一),使用者名,通信位址,電話号碼(手機号)
⑵ 建立散清單:系統運作時,讀取磁盤檔案的電話号碼,建構散清單,用于查詢。要求:自選散列函數(至少2種),自選解決沖突的方法(至少2種),分别以電話号碼和使用者名為關鍵字,建立散清單。
⑶ 查詢:根據輸入的使用者名,查找并顯示給定使用者的資訊。
⑷ 性能分析:
① 計算并輸出不同散列函數、不同解決沖突方法的平均查找長度。
② 通過改變散列因子、改變哈希函數等方式,改善平均查找長度:通過資料表、柱形圖、折線圖等方式,記錄實驗資料的變化情況,對影響平均查找長度變化的原因進行分析。
二、問題描述
(1)選用的散列函數
①除留餘數法
分析:
ⅰ.以電話号碼為關鍵字時,将字元串類型的電話号碼轉換成long型資料,除以表長,剩下的餘數作為其在散清單中的位址,即pos值。
ⅱ.以姓名為關鍵字時,将字元串類型的姓名的每一位上的字母轉換成ascii碼,此時還是内容為數字的字元串,再将字元串轉換成long型資料,除以表長,剩下的餘數作為其在散清單中的位址,即pos值。
②折疊法
分析:
ⅰ.以電話号碼為關鍵字時,将字元串類型的電話号碼,切割成四組資料,每組資料的個數為3 3 3 2,轉化成int型資料,取第一組資料和第三組資料、第二組和第四組的逆置數相加,得到的資料取後四位數,作為其在散清單中的位址,即pos值。
ⅱ.以姓名為關鍵字時,将字元串類型的姓名的每一位上的字母轉換成ascii碼,此時還是内容為數字的字元串,再切割成四組資料,每組資料的個數為3 3 3 2,轉化成int型資料,取第一組資料和第三組資料、第二組和第四組的逆置數相加,得到的資料取後四位數,作為其在散清單中的位址,即pos值。
(2)散列因子
散清單的散列因子定義為:α= 填入表中的元素個數/散清單的長度。α是散清單裝滿程度的标志因子。由于表長是定值,α與元素個數成正比,是以,α越大,填入表中的元素較多,産生沖突的可能性就越大;α越小,填入表中的元素較少,産生沖突的可能性就越小。
為了探究不同散列因子對平均查找長度ASL的影響,在利用線性探測法解決沖突時,本實驗中
拟取用 α 的值為 0.85、0.75、0.65、0.55;利用拉鍊法解決沖突時,本實驗中 α 拟選用 1、1.2、
1.4、1.6。
(3)解決沖突的方法
①線性探測法
該方法的基本思想是,當關鍵字key的哈希位址p出現沖突時,順序檢視表中下一單元,以p為基礎産生另一個哈希位址p1,如果p1仍然沖突,再以p為基礎産生p2,……,直到找到一個不沖突的哈希位址pi,将相應元素存入其中。沖突發生時,順序檢視表中下一單元,,直到找出一個空單元或者查遍全表。
缺點:容易造成元素聚集,降低查找效率
②拉鍊法
該方法基本思想是将所有的哈希位址為i的元素構成一個同義詞鍊的單連結清單,并将單連結清單的頭指針存在哈希表的第i個單元中。
優點:避免了動态調整的開銷
三、實驗結果及分析
(1)實驗資料描述
1.資料集規模
本實驗拟采用 10000 組聯系人記錄。每一行記錄一位聯系人的編号、姓名、位址、電話号碼。
檔案存儲于FinalDataSet_10000.txt。檔案存儲格式如圖 1 所示:
圖1 實驗資料集内容
2.資料集來源
本次實驗的資料全部随機生成。
資料内容:
編号:1-10000,按順序輸出即可;
姓名:三個英文字母,字元串。随機生成0-25的int型資料,再通過循環從char型字母表數組中利用下标讀出并存儲到data數組的姓名域中去;
位址:長度不等,字元串。這裡使用的城市資料僅為20組,每組城市資料存儲在Country結構體中,結構體中有city[],用于存放每組城市名稱,所有的城市資料存儲在Country類型的數組中。随機生成0-20的int型資料,再通過循環從Country類型的數組中利用下标讀出并存儲到data數組的位址域中去。
電話号碼:11位0-9的資料,字元串。根據一般電話号碼的規律,首位都是1,是以其他10位是随機生成的。随機生成0-9的int型資料,再通過循環從char型數字表數組中利用下标讀出并存儲到data數組的電話号碼域中去;
3.磁盤檔案存儲格式:.txt格式。
(2)實驗結果
1.以電話号碼為關鍵字
①哈希函數為除留餘數法,解決沖突的方法為線性探測法,查找成功,實驗結果如圖2、3所示。
圖2 以電話号碼為關鍵字查找成功的實驗結果
圖3 對應在磁盤檔案中的資料
②哈希函數為除留餘數法,解決沖突的方法為拉鍊法,查找失敗,實驗結果如圖4所示。
圖4 以電話号碼為關鍵字查找失敗的實驗結果
2.以姓名為關鍵字
①哈希函數為除留餘數法,解決沖突的方法為線性探測法,查找成功,實驗結果如圖5、6所示。
圖5 以姓名為關鍵字查找成功的實驗結果
圖6對應在磁盤檔案中的資料
②哈希函數為除留餘數法,解決沖突的方法為拉鍊法,查找失敗,實驗結果如圖7所示。
圖7 以電話号碼為關鍵字查找失敗的實驗結果
(3)性能分析
①分析填充因子和沖突方法與 ASL 的關系
表1 在不同的散列因子和解決沖突方法下,查找成功和查找失敗的ASL
圖8 在不同的散列因子和解決沖突的方法下,查找成功的ASL折線圖
圖9 在不同的散列因子和解決沖突的方法下,查找失敗的ASL折線圖
圖10 線上性檢測法下,不同的散列因子,查找成功和查找失敗的ASL折線圖
圖11 在拉鍊法下,不同的散列因子,查找成功和查找失敗的ASL折線圖
由表1和圖8-11可見,在采用相同的解決沖突的方法時,ASL随散列因子增大而變大。當解決沖突方法為線性探測法時,查找失敗比查找成功的ASL大,且增幅也随散列因子增大而變大。但當解決沖突方法為拉鍊法時,查找成功比查找失敗的ASL大,且增幅并不随散列因子增大而改變。
②分析資料規模與 ASL 的關系
圖12 在散列因子為0.75時,不同的資料規模與ASL之間的折線圖
在散列因子α為0.75的情況下,進行了實驗,實驗資料如圖12所示。顯然由圖可知,在相同的散列因子的情況下,随着資料規模的增大,ASL并沒有明顯的變化,資料基本都浮動在2.5上下。我認為資料規模與ASL之間沒有直接的關系。
四、實驗總結
在測試了不同的解決沖突辦法、不同的散列因子和不同的資料規模對ASL的影響後,我得到了以下的結論:
a.當散列因子小于1時,解決沖突的方法可以選擇線性探測法。ASL随着散列因子α的增大而增大,且增幅随之變大。是以,當解決沖突方法為線性探測法時,要慎重選擇散列因子α。散列因子α過大,平均查找長度ASL過大,查找效果差;散列因子α過小,平均查找長度ASL雖然會較小,但是需要的存儲空間随之變大了,是以在設計解決沖突方法為線性探測法的散清單時,要選擇合适的散列因子α。
b.當散列因子大于1時,解決沖突的方法可以選擇拉鍊法法。ASL随着散列因子α的增大而增大,但增幅并不随散列因子α的增大而改變,而是幾乎不變。是以,适當選擇拉鍊法的散列因子,可以表現出良好的查找性能。
c.由圖表分析可得,解決沖突方式為拉鍊法受散列因子α的影響較小,解決沖突方式為線性探測法受散列因子α的影響較大。是以,拉鍊法更為穩定,性能更好。
d.資料規模較小時,解決沖突方式采用線性探測法、拉鍊法,性能差别都不是很大,均能表現出良好的查找性能,但是當資料規模變大的時候,采用線性探測法解決沖突的方法會使沖突增多,此時采用拉鍊法可以表現出更好的查找性能。
e.在實際操作的過程中,特别是在處理以姓名為關鍵字的時候,我發現有很多人的名字是重複的,這種情況在現實生活中也會存在,這也是沖突的其中一種方式,是以針對這種情況,我分不同的解決沖突方法進行讨論。
①線性探測法。當發現關鍵字重複時,再次通過線性探測,在與之重複的關鍵字周圍尋找一個空表,将其填入,即可解決沖突,但要注意,要使用一個标志flag來記錄某關鍵字重複的次數,借助該标志flag在查詢關鍵字時友善找到所有重複的元素。
②拉鍊法。當發現關鍵字重複時,直接将該節點插入散清單的與重複關鍵字相同的位置後的頭結點,即可解決沖突,但這個方法也需要使用一個标志flag來記錄某關鍵字重複的次數,用來友善找到所有重複的元素。
五、源代碼
(1)随機生成電話号碼系統代碼
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#define N 10000 //元素最大個數
typedef struct {
int no; //編号
char name[3];//名字
char address[10];//位址
char tel[12];//電話号碼
} NODE;
typedef struct {
char data[10];
} Country;
Country country[10];
void creatfile(NODE data[], int *n);//建立磁盤檔案f:\resource.dat
int isTelRepeated(char tel[]);
void initCountry();
int main() {
NODE DATA[N];
int n;
creatfile(DATA, &n);
return 0;
}
void creatfile(NODE data[], int *n) {
FILE *fp;
int i, key, flag;
int temp_n;
char temp_tel[11];
unsigned seed;
*n = N;
char num[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
char alphabet[26] = {'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'};//字母表
/*char ALPHABET[26] = {'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'};//字母表*/
initCountry();
if ((fp = fopen("/Users/xiaoyee/Desktop/資料結構實驗作業/實驗報告/電話号碼查詢系統/1/TelDataSet_10000.txt", "w")) == NULL) {
printf("can't open the file!\n");
exit(0);
}
seed = time(NULL);
srand(seed); //設定随機種子
for (i = 0; i < *n;i++) {
for (int k = 0; k < 3; k++) {
key = rand() % 26;
data[i].name[k] = alphabet[key];
}
data[i].name[3] = '\0';
}
for (i = 0; i < *n;i++) {
key = rand() % 20;
for (int k = 0; k < 10; k++) {
data[i].address[k] = country[key].data[k];
}
}
for (i = 0; i < *n;) {
temp_tel[0] = '1';
for (int k = 1; k < 11; k++) {
key = rand() % 10;
temp_tel[k] = num[key];
}
temp_tel[11] = '\0';
//寫一個整數到磁盤檔案
flag = 1;
if (isTelRepeated(temp_tel)) {
flag = 0;
break;
}
if (flag) {
for (int k = 0; k < 11; k++) {
data[i].tel[k] = temp_tel[k];
}
i++;
}
}
for (i = 0; i < *n; i++) {
fprintf(fp, "%d ", i + 1); //寫一個整數到磁盤檔案
fprintf(fp, "%s ", data[i].name); //寫一個整數到磁盤檔案
fprintf(fp, "%s ", data[i].address); //寫一個整數到磁盤檔案
fprintf(fp, "%s\n", data[i].tel); //寫一個整數到磁盤檔案
}
fclose(fp);
}
void initCountry() {
FILE *fp;
char temp[10];
if ((fp = fopen("/Users/xiaoyee/Desktop/資料結構實驗作業/實驗報告/電話号碼查詢系統/1/Country_data.txt", "r")) == NULL) {
printf("can't open the file!\n");
exit(0);
}
for (int i = 0; i < 20; i++) {
fscanf(fp, "%s", temp);
for (int j = 0; j < 10; j++) {
country[i].data[j] = temp[j];
}
}
}
int isTelRepeated(char tel[]) {
FILE *fp;
char temp_tel[11];
if ((fp = fopen("/Users/xiaoyee/Desktop/資料結構實驗作業/實驗報告/電話号碼查詢系統/1/TelDataSet_10000.txt", "r")) == NULL) {
printf("can't open the file!\n");
exit(0);
}
for (int i = 1; i <= N; i++) {
fscanf(fp, "%s", temp_tel);
if (strcmp(tel, temp_tel) == 0) {
return 1;
}
}
return 0;
}
(2)電話号碼查詢系統代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NIL -1 //定義初始值
#define M 13333 //表長
#define N 10000 //關鍵字個數
// α=0.75
typedef long keytype;
//散清單結點類型
typedef struct {
int no; //編号
char *name;//名字
char *address;//位址
char *tel;//電話号碼
} NODE;
typedef struct pos {
keytype pos;
int flag;
struct pos *next;
} POS;
//初始化哈希表,将姓名和電話号碼初始化!
void init(POS *table);
int increment(int i);//某種探測方法
int HASH_1(keytype key, int i);//将某鍵值轉換成位置
//在散清單中搜尋指定的鍵值
int search_tel(POS *table, char *tel, int *pos);
//在散清單中搜尋指定的鍵值
int search_name(POS *table, char *name, int *pos);
//pos傳回鍵值的位置
//将一個關鍵字添加到哈希表中(以電話号碼為關鍵字)
void insert_tel(POS *table_tel, int no, char *name);
//将一個關鍵字添加到哈希表中(以姓名為關鍵字)
void insert_name(POS *table_name, int no, char *tel);
//void insert_pos(POS *table_name, int i, int pos);
//建立哈希表(以電話号碼為關鍵字)
void creat(NODE *htable);
//将姓名轉化為數字
long converse_name(char *name);
//輸出想要搜尋的關鍵字的相關資訊
void prn(NODE *htable, int no);
//輸出哈希表
void prnnn(NODE *htable, POS *table_name, int pos);
//成功查找的ASL
float success_ASL();
//失敗查找的ASL
float fail_ASL();
//*******************************************************
//除留餘數法作為散列函數,線性探測法解決沖突
//*******************************************************
void menu();
//********************************************************
//主函數
//********************************************************
NODE htable[N];//定義結點表
POS table_tel[M];//定義哈希表(以電話号碼為關鍵字)
POS table_name[M];//定義哈希表(以姓名為關鍵字)
int main() {
int op;//菜單選擇
int i;
int pos;
char *tel;
char *name;
init(table_tel); //哈希表初始化
init(table_name); //哈希表初始化
creat(htable);
menu();
scanf("%d", &op);
printf("----------------------------------------------\n");
while (op != 3) {
switch (op) {
case 1: {
tel = (char *) malloc(sizeof(char));
printf("請輸入你想要查詢的電話号碼:");
scanf("%s", tel);
i = search_tel(table_tel, tel, &pos);
if (i) { //搜尋指定鍵值
printf("找到該電話号碼!!!\n");
printf("成功查找的平均查找長度ASL:%f\n", success_ASL());
prn(htable, table_tel[pos].pos);//table_tel[pos].pos=no
} else {
printf("未找到該電話号碼!!!\n");
printf("失敗查找的平均查找長度ASL:%f\n", fail_ASL());
}
}
break;
case 2: {//?
name = (char *) malloc(sizeof(char));
printf("請輸入你想要查詢的姓名:");
scanf("%s", name);
i = search_name(table_name, name, &pos);//在散清單中查找被插入的鍵值
if (i) { //搜尋指定鍵值
printf("找到該姓名!!!\n");
printf("成功查找的平均查找長度ASL:%f\n", success_ASL());
if (table_name[pos].flag != 1)
prnnn(htable, table_name, pos);
else
prn(htable, table_name[pos].pos);//table_name[pos].pos=no
} else {
printf("未找到該姓名!!!\n");
printf("失敗查找的平均查找長度ASL:%f\n", fail_ASL());
}
}
break;
case 3:
exit(0);
default: {
printf("您輸入的操作不合法,請重新輸入!\n");
fflush(stdin);//防止不斷從緩沖區取數,造成循環
break;
}
}
printf("\n");
menu();
scanf("%d", &op);
}
return 0;
}
//********************************************************
//初始化哈希表
//********************************************************
void init(POS *table) {
POS *p = table;
for (; p < table + M; p++) {
p->pos = NIL;//初始化表元素的鍵值
p->flag = 0;
p->next = NULL;
}
}
//********************************************************
//開放定址的哈希函數:折疊法
//構造哈希函數
//********************************************************
//********************************************************
//開放定址的哈希函數:除留餘數法
//構造哈希函數
//********************************************************
int increment(int i) //某種探測方法
{
return i; //增量為i
}
int HASH_1(keytype key, int i) //将某鍵值轉換成位置
{
return ((int) (key % M) + increment(i)) % M; //線性探測
}
//********************************************************
//在散清單中搜尋指定的鍵值
//pos傳回鍵值的位置
//********************************************************
int search_tel(POS *table_tel, char *tel, int *pos) {
int i = 0;
long s;
do {
s = atol(tel);//字元串電話号碼轉換為long型資料
*pos = HASH_1(s, i);//開放定址的散列函數
if (table_tel[*pos].pos == NIL)
return 0; //表未滿,沒找到
if (strcmp(htable[table_tel[*pos].pos].tel, tel) == 0) return *pos; //找到
} while (++i < M);
return -1; //表滿,沒找到
}
//将姓名轉化為數字
long converse_name(char *name) {
long temp = 0;
int i = 10000;
while (*name != '\0') {
temp += ((*name - 'a') * i);
i /= 100;
name++;
}
return temp;
}
int search_name(POS *table_name, char *name, int *pos) {
int i = 0;
long k;
k = converse_name(name);
do {
*pos = HASH_1(k, i);//開放定址的散列函數
if (table_name[*pos].pos == NIL)
return 0; //表未滿,沒找到
if (strcmp(htable[table_name[*pos].pos].name, name) == 0) return *pos; //找到
} while (++i < M);
return -1; //表滿,沒找到
}
//********************************************************
//将一個關鍵字添加到哈希表中
//********************************************************
void insert_tel(POS *table_tel, int no, char *tel) {//将一個關鍵字添加到哈希表中
int i;
int pos;
i = search_tel(table_tel, tel, &pos); //在散清單中查找被插入的鍵值
if (i == 0) { //表不滿,該結點不存在
table_tel[pos].pos = no;
} else if (i == -1)
printf("表滿,無法插入!\n");
else printf("關鍵字重複,無法插入!\n");
}
void insert_name(POS *table_name, int no, char *name) {
int i, pos;
int t = 1;
long k;
i = search_name(table_name, name, &pos); //在散清單中查找被插入的鍵值
if (i == 0) { //表不滿,該結點不存在
table_name[pos].pos = no;
table_name[pos].flag = 1;
} else if (i == -1)
printf("表滿,無法插入!\n");
else {
k = converse_name(name);
table_name[i].flag++;//重複的次數+1
do {
pos = HASH_1(k, t);//開放定址的散列函數
if (table_name[pos].flag == 0) {
table_name[pos].pos = no;
table_name[pos].flag = 1;
//insert_pos(table_name, i, pos);
break;
}
} while (++t < M);
// printf("關鍵字重複,無法插入!\n");
}
}
/*
void insert_pos(POS *table_name, int i, int position) {
Pos p;
p = table_name[i].next;
while (p != NULL)
p = p->next;
p->next = &table_name[position];
printf(p->next->flag);
}*/
//********************************************************
//從磁盤檔案中讀入資料,并存入結點表中
//********************************************************
void creat(NODE *htable) {
FILE *fp;
int no;
char *name;
char *address;
char *tel;
if (M < N) {
printf("散列因子>1,結點個數超過表長,無法建立!\n");
return;
}
if ((fp = fopen("/Users/xiaoyee/Desktop/資料結構實驗作業/實驗報告/電話号碼查詢系統/1/FinalDataSet_10000.txt", "r")) == NULL) {
printf("can't open the file!!");
exit(0);
}
while (!(feof(fp))) {
name = (char *) malloc(sizeof(char));
address = (char *) malloc(sizeof(char));
tel = (char *) malloc(sizeof(char));//非常重要!!!
fscanf(fp, "%d", &no);
fscanf(fp, "%s", name);
fscanf(fp, "%s", address);
fscanf(fp, "%s", tel);
htable[no].no = no;
htable[no].name = name;
htable[no].address = address;
htable[no].tel = tel; //找到開放位置,将鍵值加入
insert_tel(table_tel, no, tel);
insert_name(table_name, no, name);
}
fclose(fp);
}
//********************************************************
//輸出哈希表
//********************************************************
void prn(NODE *htable, int no) {
printf("編号:%d\t", htable[no].no);
printf("姓名:%s\t", htable[no].name);
printf("城市:%s\t", htable[no].address);
printf("城市:%s\n", htable[no].tel);
printf("\n");
}
//輸出哈希表
void prnnn(NODE *htable, POS *table_name, int pos) {
int no = 0;
int i = pos;
POS *p;
p = (POS *) malloc(sizeof(POS));
//令p為table_name[i],即重複元素值相同的第一個元素
p->pos = table_name[pos].pos;
p->flag = table_name[pos].flag;
p->next = table_name[pos].next;
printf("查詢到該姓名在該電話号碼查詢系統中重複!\n");
printf("現将所有是該姓名的人查詢如下:\n");
for (int k = 0; k < table_name[pos].flag;) {
if (strcmp(htable[table_name[pos].pos].name, htable[table_name[i].pos].name) == 0) {
no = table_name[i].pos;
printf("編号:%d\t", htable[no].no);
printf("姓名:%s\t", htable[no].name);
printf("城市:%s\t", htable[no].address);
printf("城市:%s\n", htable[no].tel);
k++;
}
i++;
}
}
//成功查找的ASL
float success_ASL() {
float i = 1 - (1.0 * N) / M;
return (1 + 1 / i) / 2;
}
//失敗查找的ASL
float fail_ASL() {
float a = (1.0 * N) / M;
float i = 1 - a;
return (1 + 1 / (i * i)) / 2;
}
/*void prnnn(NODE *htable) {
NODE *p = htable;
for (; p < htable + M; p++)
if (p->tel != NIL) {
printf("編号:%d\t", p->no);
printf("姓名:%s\t", p->name);
printf("城市:%s\t", p->address);
printf("電話号碼:%ld\t", p->tel);//如果某位址不開放,則輸出相應的鍵值
}
printf("\n");
}*/
void menu() {
printf("----------------------------------------------\n");
printf("*************歡迎使用電話号碼查詢系統*************\n");
printf("----------------------------------------------\n");
printf("請選擇你想要進行的查詢:");
printf(" 1.以電話号碼為關鍵字進行查詢\n");
printf("\t\t\t\t 2.以姓名為關鍵字進行查詢\n");
printf("\t\t\t\t 3.退出\n");
printf("----------------------------------------------\n");
printf("請選擇你想要進行的操作:");