【基本要求】
(1)设每个记录有下列数据项:电话号码、用户名、地址;
(2)从键盘输入各记录,分别以电话号码和用户名为关键字建立散列表;
(3)采用一定的方法解决冲突;
(4)查找并显示给定电话号码的记录;
(5)查找并显示给定用户名的记录。
【选做内容】
(1)系统功能的完善;
(2)设计不同的散列函数,比较冲突率;
(3)在散列函数确定的前提下,尝试各种不同类型处理冲突的方法,考察平均查找长度的变化。
用的C++开发,基本实现了3种哈希函数+3种解决冲突的方法。因为要求同时有姓名散列与按号码散列,所以用了
flag标记每次的散列类型 ,针对不同的要求对散列函数做了个别优化。
哈希表类的结构如下
1 class HashTable{ 2 public: 3 HashTable(int size = MAXSIZE-1); 4 ~HashTable(){ delete[]E; delete[]tag; delete[]E2; delete[]tag2; } 5 int hash1(string name, int flag);//哈希函数1 除数求余法 6 int hash2(string tel);//哈希函数2 折叠法 7 int hash3(string tel);//哈希函数3 数字分析法 8 int solve1(int hashVal, int flag);//线性探测法解决冲突 9 int solve2(int hashVal, int flag);//二次探测法解决冲突 10 Node* solve3(int hashVal, int flag);//拉链法解决冲突 11 User input();//往电话薄中添加用户 12 void creat(int flag); //创建散列表 13 void show(int flag); //列出电话薄所有元素 14 void search(int flag,string at); //搜索指定用户 15 void searchByNode(int flag, string at); //拉链法搜索指定用户 16 void insert(int flag, User newUser); //插入 17 void del(int flag, string by);//删除 18 void save(int flag);//将电话薄保存至本地文件 19 int length; //要创建的电话本长度 20 Node** ht; 21 private: 22 User* E; //用户数组 按姓名散列 23 User* E2; //用户数组2 按电话号码散列 24 int* tag; //标记散列表1每个桶的存储状态 0为空 1为实 25 int* tag2;//标记散列表2每个桶的存储状态 26 int flag; //1表示是按姓名 2表示按电话号码 新建的哈希表 27 int maxSize; //哈希表最大长度 28 int f;//比例因子 主要用于折叠法 29 };
User类的结构
class User{ public: string name; string tel; string address; bool operator==(const User&target) { if (this->name == target.name&&this->address == target.address&&this->tel == target.tel) return true; else return false; } };
哈希函数1
int HashTable::hash1(string name,int flag) //除留求余法 { int hashValue; long a = 0; switch (flag) { case 1: for (int i = 0; i < name.length(); i++) a += int(name[i]); hashValue = a%maxSize; break; case 2: int temp = atof(name.c_str()); hashValue = temp%maxSize; break; } return hashValue; };
哈希函数2
int HashTable::hash2(string tel) //折叠法--移位法 { int hashValue; int temp; //移位法求和 temp = atof(tel.substr(0, 3).c_str()) + atof(tel.substr(3, 3).c_str()) + atof(tel.substr(6, 3).c_str()) + atof(tel.substr(9, 2).c_str()); //取计算之后的数的最后三位 if (temp >= 999) { char p[10]; sprintf(p, "%d", temp); string lastThree = p; lastThree = lastThree.substr(lastThree.length() - 3, 3); hashValue = atof(lastThree.c_str()); return hashValue; } hashValue = temp; return hashValue; };
哈希函数3
int HashTable::hash3(string tel)//数字分析法做哈希函数 { int hashValue; hashValue = atof(tel.substr(8, 3).c_str()); //因为电话号码一般后4位不同 return hashValue; };
解决冲突的方法
1.线性探测法
int HashTable::solve1(int hashVal,int flag) //线性探查法处理冲突 { int output = hashVal; switch (flag) { case 1: for (int j = 1; j < MAXSIZE; j++) { output = (hashVal + j) % MAXSIZE; if (tag[output] == 0) { tag[output] = 1; return output; } } return -1; break; case 2: for (int j = 1; j < MAXSIZE; j++) { output = (hashVal + j) % MAXSIZE; if (tag2[output] == 0) { tag2[output] = 1; return output; } } return -1; default: break; } };
2.二次探查法
int HashTable::solve2(int hashVal, int flag) //二次探查法解决冲突 { int i = hashVal; //i为初始桶号 int k = 0; //k为探查次数 int odd = 0; //odd为控制加减的标志 int save; //缓存上一次的桶号 switch (flag) { case 1: while (tag[i]==1) { if (odd == 0) { k++; save = i; i = (i + 2 * k-1) % MAXSIZE; odd = 1; } else { i = (save - 2 * k+1) % MAXSIZE; odd = 0; if (i<0) { i = i + MAXSIZE; } } } return i; break; case 2: while (tag2[i] == 1) { if (odd == 0) { k++; save = i; i = (i + 2 * k - 1) % MAXSIZE; odd = 1; } else { k++; i = (save - 2 * k + 1) % MAXSIZE; odd = 0; if (i<0) { i = i + MAXSIZE; } } } return i; break; default: break; } };
3.拉链法
Node* HashTable::solve3(int hashVal, int flag)//拉链法解决冲突 { int i = hashVal; //第i条链 Node*p = ht[i]; //该链上的头指针 while (p!=NULL) p = p->next;//往后遍历直到找到一个空节点用于存放user return p; };
所有代码如下
1 #include2 #include <string> 3 #include 4 5 using namespace std; 6 7 const int MAXSIZE = 12;//默认最大表长 8 9 10 11 12 //存储项 13 class User{ 14 public: 15 string name; 16 string tel; 17 string address; 18 bool operator==(const User&target) 19 { 20 if (this->name == target.name&&this->address == target.address&&this->tel == target.tel) 21 return true; 22 else 23 return false; 24 } 25 }; 26 27 //用于拉链法 28 struct Node{ 29 User user; 30 Node* next; 31 }; 32 class HashTable{ 33 public: 34 HashTable(int size = MAXSIZE-1); 35 ~HashTable(){ delete[]E; delete[]tag; delete[]E2; delete[]tag2; } 36 int hash1(string name, int flag);//哈希函数1 除数求余法 37 int hash2(string tel);//哈希函数2 折叠法 38 int hash3(string tel);//哈希函数3 数字分析法 39 int solve1(int hashVal, int flag);//线性探测法解决冲突 40 int solve2(int hashVal, int flag);//二次探测法解决冲突 41 Node* solve3(int hashVal, int flag);//拉链法解决冲突 42 User input();//往电话薄中添加用户 43 void creat(int flag); //创建散列表 44 void show(int flag); //列出电话薄所有元素 45 void search(int flag,string at); //搜索指定用户 46 void searchByNode(int flag, string at); //拉链法搜索指定用户 47 void insert(int flag, User newUser); //插入 48 void del(int flag, string by);//删除 49 void save(int flag);//将电话薄保存至本地文件 50 int length; //要创建的电话本长度 51 Node** ht; 52 private: 53 User* E; //用户数组 按姓名散列 54 User* E2; //用户数组2 按电话号码散列 55 int* tag; //标记散列表1每个桶的存储状态 0为空 1为实 56 int* tag2;//标记散列表2每个桶的存储状态 57 int flag; //1表示是按姓名 2表示按电话号码 新建的哈希表 58 int maxSize; //哈希表最大长度 59 int f;//比例因子 主要用于折叠法 60 }; 61 62 HashTable::HashTable(int size) 63 { 64 maxSize = size; //用作除数 65 E = new User[MAXSIZE]; 66 E2 = new User[MAXSIZE]; 67 tag = new int[MAXSIZE]; 68 tag2 = new int[MAXSIZE]; 69 for (int i = 0; i < MAXSIZE; i++) 70 { 71 tag[i] = 0; 72 tag2[i] = 0; 73 } 74 f = maxSize / 512; //用于折叠法产生的哈希值过大 保留3位数的地址范围为0~511 75 ht = new Node*[maxSize]; //存放节点的一维数组 拉链法 76 }; 77 78 int HashTable::hash1(string name,int flag) //除数求余法 79 { 80 int hashValue; long a = 0; 81 switch (flag) 82 { 83 case 1: 84 for (int i = 0; i < name.length(); i++) 85 a += int(name[i]); 86 hashValue = a%maxSize; 87 break; 88 case 2: 89 int temp = atof(name.c_str()); 90 hashValue = temp%maxSize; 91 break; 92 93 } 94 return hashValue; 95 }; 96 97 int HashTable::hash2(string tel) //折叠法--移位法 98 { 99 int hashValue; 100 int temp; //移位法求和 101 temp = atof(tel.substr(0, 3).c_str()) + atof(tel.substr(3, 3).c_str()) 102 + atof(tel.substr(6, 3).c_str()) + atof(tel.substr(9, 2).c_str()); 103 //取计算之后的数的最后三位 104 if (temp >= 999) 105 { 106 char p[10]; 107 sprintf(p, "%d", temp); 108 string lastThree = p; 109 lastThree = lastThree.substr(lastThree.length() - 3, 3); 110 hashValue = atof(lastThree.c_str()); 111 return hashValue; 112 } 113 hashValue = temp; 114 return hashValue; 115 }; 116 117 int HashTable::hash3(string tel)//数字分析法做哈希函数 118 { 119 int hashValue; 120 hashValue = atof(tel.substr(8, 3).c_str()); //因为电话号码一般后4位不同 121 return hashValue; 122 }; 123 124 int HashTable::solve1(int hashVal,int flag) //线性探查法处理冲突 125 { 126 int output = hashVal; 127 switch (flag) 128 { 129 case 1: 130 for (int j = 1; j < MAXSIZE; j++) 131 { 132 output = (hashVal + j) % MAXSIZE; 133 if (tag[output] == 0) 134 { 135 tag[output] = 1; 136 return output; 137 } 138 } 139 return -1; 140 break; 141 case 2: 142 for (int j = 1; j < MAXSIZE; j++) 143 { 144 output = (hashVal + j) % MAXSIZE; 145 if (tag2[output] == 0) 146 { 147 tag2[output] = 1; 148 return output; 149 } 150 } 151 return -1; 152 default: 153 break; 154 } 155 156 }; 157 158 int HashTable::solve2(int hashVal, int flag) //二次探查法解决冲突 159 { 160 int i = hashVal; //i为初始桶号 161 int k = 0; //k为探查次数 162 int odd = 0; //odd为控制加减的标志 163 int save; //缓存上一次的桶号 164 switch (flag) 165 { 166 case 1: 167 while (tag[i]==1) 168 { 169 if (odd == 0) 170 { 171 k++; save = i; 172 i = (i + 2 * k-1) % MAXSIZE; 173 odd = 1; 174 } 175 else 176 { 177 i = (save - 2 * k+1) % MAXSIZE; 178 odd = 0; 179 if (i<0) 180 { 181 i = i + MAXSIZE; 182 } 183 } 184 } 185 return i; 186 break; 187 case 2: 188 while (tag2[i] == 1) 189 { 190 if (odd == 0) 191 { 192 k++; save = i; 193 i = (i + 2 * k - 1) % MAXSIZE; 194 odd = 1; 195 } 196 else 197 { 198 k++; 199 i = (save - 2 * k + 1) % MAXSIZE; 200 odd = 0; 201 if (i<0) 202 { 203 i = i + MAXSIZE; 204 } 205 } 206 } 207 return i; 208 break; 209 default: 210 break; 211 } 212 213 }; 214 215 /* 216 Node* HashTable::solve3(int hashVal, int flag)//拉链法解决冲突 217 218 { 219 int i = hashVal; //第i条链 220 221 Node*p = ht[i]; //该链上的头指针 222 while (p!=NULL) 223 p = p->next;//往后遍历直到找到一个空节点用于存放user 224 return p; 225 }; 226 227 void HashTable::searchByNode(int flag, string at)//调用拉链法搜索 228 { 229 int i = hash1(at,1); 230 Node** ht = new Node*[maxSize]; //存放节点的一维数组 231 Node*p = ht[i]; //该链上的头指针 232 while (p!=NULL&&p->user.name!=at) 233 { 234 p = p->next; 235 } 236 }; 237 */ 238 User HashTable::input() 239 { 240 User user; 241 cout << "请输入姓名:" << endl; 242 cin >> user.name; 243 cout << "请输入电话号码:" << endl; 244 cin >> user.tel; 245 cout << "请输入地址:" << endl; 246 cin >> user.address; 247 return user; 248 }; 249 250 void HashTable::creat(int flag) 251 { 252 switch (flag) 253 { 254 case 1: //按姓名哈希创建哈希表 255 for (int i = 0; i < length; i++) 256 { 257 User newUser = input(); 258 int val = hash1(newUser.name,1); 259 if (tag[val] == 1) 260 val = solve1(val,1);//线性探测法解决冲突 261 E[val] = newUser; 262 tag[val] = 1; 263 } 264 break; 265 case 2: //按电话号码哈希创建哈希表 266 for (int i = 0; i < length; i++) 267 { 268 User newUser = input(); 269 int val = hash1(newUser.tel,2); 270 if(tag2[val] == 1) 271 val = solve1(val,2);//线性探测法解决冲突 272 E2[val] = newUser; 273 tag2[val] = 1; 274 } 275 break; 276 } 277 }; 278 void HashTable::show(int flag) 279 { 280 switch (flag) 281 { 282 case 1: 283 for (int i = 0; i < MAXSIZE; i++) 284 { 285 if (tag[i] == 1) 286 cout << E[i].name << " " << E[i].tel << " " << E[i].address << " 位于: " << i << endl; 287 } 288 break; 289 case 2: 290 for (int i = 0; i < MAXSIZE; i++) 291 { 292 if (tag2[i] == 1) 293 cout << E2[i].name << " " << E2[i].tel << " " << E2[i].address << " 位于: " << i << endl; 294 } 295 break; 296 } 297 298 }; 299 300 void HashTable::search(int flag,string at) //at表示索引内容 301 { 302 int i = 0; 303 switch (flag) 304 { 305 case 1: //调用线性探测法查找姓名 306 i = hash1(at,1); 307 if (tag[i] == 1 && E[i].name != at) 308 i = solve1(i, 2); 309 if (i < 0 || tag2[i] == 0) 310 { 311 cout << "查无此人!" << endl; 312 return; 313 } 314 if (tag[i] == 1 && E[i].name == at) 315 cout << E2[i].name << " " << E2[i].tel << " " << E2[i].address << endl; 316 break; 317 case 2: //调用二次探测法查找电话号码 318 i = hash2(at); 319 if (tag2[i] == 1&&E2[i].tel!=at) 320 i = solve2(i,2); 321 if (i < 0||tag2[i]==0) 322 { 323 cout << "查无此人!" << endl; 324 return; 325 } 326 if (tag2[i] == 1 && E2[i].tel==at) 327 cout << E2[i].name << " " << E2[i].tel << " " << E2[i].address << endl; 328 break; 329 } 330 }; 331 332 void HashTable::insert(int flag, User newUser){ 333 int i = -1; 334 switch (flag) 335 { 336 case 1: 337 i = hash1(newUser.name,1); 338 if (tag[i] == 1||E[i]==newUser) 339 i = solve1(i, 1); 340 if (i < 0) 341 { 342 cout << "表满!插入失败!" << endl; 343 return; 344 } 345 if (tag[i] == 0) 346 { 347 E[i] = newUser; 348 tag[i] = 1; 349 length++; 350 cout << "插入成功" << endl; 351 } 352 case 2: 353 i = hash1(newUser.tel,2); 354 if (tag2[i] == 1 || E2[i] == newUser) 355 i = solve1(i, 2); 356 if (i < 0) 357 { 358 cout << "表满!插入失败!" << endl; 359 return; 360 } 361 if (tag2[i] == 0) 362 { 363 E2[i] = newUser; 364 tag2[i] = 1; 365 length++; 366 cout << "插入成功" << endl; 367 } 368 default: 369 break; 370 } 371 }; 372 373 void HashTable::del(int flag, string by) //by表示按照何种标签进行删除 374 { 375 int i = -1; 376 int select;//选择是否删除 377 switch (flag) 378 { 379 case 1: //调用线性探测法查找姓名 380 i = hash1(by,1); 381 if (tag[i] == 1 && E[i].name != by) 382 i = solve1(i, 2); 383 if (i < 0 || tag2[i] == 0) 384 { 385 cout << "查无此人!" << endl; 386 return; 387 } 388 if (tag[i] == 1 && E[i].name == by) 389 { 390 cout << E2[i].name << " " << E2[i].tel << " " << E2[i].address << endl; 391 cout << "是否删除 0.删了 1.算了" << endl; 392 cin >> select; 393 if (select == 0) 394 tag[i] = 0;//伪删除 395 } 396 break; 397 case 2: //调用二次探测法查找电话号码 398 i = hash2(by); 399 if (tag2[i] == 1 && E2[i].tel != by) 400 i = solve2(i, 2); 401 if (i < 0 || tag2[i] == 0) 402 { 403 cout << "查无此人!" << endl; 404 return; 405 } 406 if (tag2[i] == 1 && E2[i].tel == by) 407 { 408 cout << E2[i].name << " " << E2[i].tel << " " << E2[i].address << endl; 409 cout << "是否删除 0.删了 1.算了" << endl; 410 cin >> select; 411 if (select == 0) 412 tag2[i] = 0;//伪删除 413 } 414 break; 415 } 416 }; 417 418 void HashTable::save(int flag) 419 { 420 fstream out1("电话薄(姓名散列).txt", ios::out); 421 fstream out2("电话薄(号码散列).txt", ios::out); 422 switch (flag) 423 { 424 case 1: 425 for (int i = 0; i < maxSize; i++) 426 { 427 if (tag[i] == 1) 428 out1 << E[i].name << " " << E[i].tel << " " << E[i].address << endl; 429 } 430 cout << "已存至电话薄(姓名散列).txt" << endl; 431 return; 432 break; 433 case 2: 434 for (int i = 0; i < maxSize; i++) 435 { 436 if (tag2[i] == 1) 437 out2 << E2[i].name << " " << E2[i].tel << " " << E2[i].address << endl; 438 } 439 cout << "已存至电话薄(号码散列).txt" << endl; 440 return; 441 break; 442 default: 443 break; 444 } 445 446 };
1 #include2 #include <string> 3 #include 4 #include "hashtable.h" 5 using namespace std; 6 7 //菜单 8 void menu() 9 { 10 cout << " ****************************" << endl; 11 cout << "|| 0.建表 ||" << endl; 12 cout << "|| 1.查看 ||" << endl; 13 cout << "|| 2.搜索 ||" << endl; 14 cout << "|| 3.添加 ||" << endl; 15 cout << "|| 4.删除 ||" << endl; 16 cout << "|| 5.保存 ||" << endl; 17 cout << "|| 6.退出 ||" << endl; 18 cout << " ****************************" << endl; 19 20 } 21 22 int main() 23 { 24 User user; 25 int size;//第一次创建的数据量大小 26 int select;//主菜单选项 27 int select_;//子菜单选项 28 cout << "欢迎使用电话簿" << endl; 29 HashTable ht; 30 while (1) 31 { 32 menu(); 33 cin >> select; 34 switch (select) 35 { 36 case 0: 37 cout << "第一次使用,请输入要新建的电话本大小:" << endl; 38 cin >> size; 39 ht.length = size; 40 cout << "1.姓名散列 2.电话号码散列" << endl; 41 cin >> select_; 42 ht.creat(select_); 43 break; 44 case 1: 45 cout << "1.姓名散列 2.电话号码散列" << endl; 46 cin >> select_; 47 ht.show(select_); 48 break; 49 case 2: 50 cout << "1.按姓名查找 2.按电话号码查找" << endl; 51 cin >> select_; 52 if (select_==1) 53 { 54 cout << "输入姓名" << endl; 55 string name; 56 cin >> name; 57 ht.search(1, name); 58 } 59 else if (select_ == 2) 60 { 61 cout << "输入号码" << endl; 62 string tel; 63 cin >> tel; 64 ht.search(2, tel); 65 } 66 else 67 cout << "不合法操作!!!" << endl; 68 break; 69 case 3: 70 user = ht.input(); 71 cout << "1.插入到姓名散列表 2.插入到电话号码散列" << endl; 72 cin >> select_; 73 ht.insert(select_,user); 74 break; 75 case 4: 76 cout << "1.根据姓名删除 2.根据电话号码删除" << endl; 77 cin >> select_; 78 if (select_ == 1) 79 { 80 cout << "输入姓名" << endl; 81 string name; 82 cin >> name; 83 ht.del(1, name); 84 } 85 else if (select_ == 2) 86 { 87 cout << "输入号码" << endl; 88 string tel; 89 cin >> tel; 90 ht.del(2, tel); 91 } 92 else 93 cout << "不合法操作!!!" << endl; 94 break; 95 case 5: 96 cout << "1.保存姓名散列表到本地 2.保存电话号码散列表到本地" << endl; 97 cin >> select_; 98 ht.save(select_); 99 case 6: 100 return 0; 101 } 102 } 103 }
通过这次课程设计,总结如下
1. C++技艺不精,语法不熟悉,比如模版类与运算符重载,指针更是不大熟练。要掌握C++还需要漫长的修炼。
2. 算法停留在理解阶段感觉远远不够,实现起来远没有当初想的那么简单。
3. 数据结构与算法很重要,很重要,很重要。同样的功能,一个优秀的算法节约了大量的时间,创造出巨大的经济效益!
我知道我还很菜,加油!