僅供參考
利用哈希表的思想設計一個能快速查詢的學生通訊錄程序。每個學生的信息至少包括:學號(10個數字)、姓名(不超過20字符)、手機號碼(11個數字)。程序主要功能:從鍵盤輸入學生通訊錄,以學號爲關鍵字建立哈希表,酌情設計哈希函數和處理衝突的策略;採用哈希表方法根據輸入的學號顯示該學生的通訊錄信息;能夠修改學生的手機號碼;能夠添加和刪除某個學生的通訊錄信息。
要求:
(1) 請查閱參考文獻瞭解哈希表的發展歷史和應用背景,瞭解其優缺點和適用場合。
(2) 詳細闡述哈希函數和處理衝突的設計思路和內容。
(3) 詳細闡述程序主要數據的邏輯結構和存儲結構,並說明其理由。
(4) 給出帶有適當註釋的源程序。
(1)哈希表就是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做哈希函數,存放記錄的數組叫做哈希表。
哈希表的優點:不論哈希表中有多少數據,在進行查找、插入、刪除的操作時運算得非常快,因爲它的時間複雜度爲O(1)。
哈希表的缺點:哈希表是基於數組的,數組創建後難於擴展,某些哈希表被基本填滿時,性能下降得非常嚴重。
哈希表的適用場合:適用於加密,解決衝突問題;適用於那種查找性能要求高,數據元素之間無邏輯關係要求的情況,在計算機程序中,如果需要在一秒種內查找上千條記錄通常使用哈希表(例如拼寫檢查器)哈希表的速度明顯比樹快,樹的操作通常需要O(N)的時間級。哈希表不僅速度快,編程實現也相對容易。
(2)哈希函數設計思路就是在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關係f,使每個關鍵字和結構中一個唯一的存儲位置相對應,它依賴於鍵的類型,對於每一種可能使用的鍵我們需要不同的函數。爲了高效,通常避免使用顯示類型轉換,盡力代之以將鍵視爲機器字的二進制正數表示的思想,這樣有利於對其使用算術運算。一個優秀的哈希函數應該考慮到鍵的所有位,尤其對於由字符組成的鍵。要計算出長鍵的取模哈希函數,可以將鍵分塊轉換。或者用兩個或三個不同的hash函數,進行多次hash以減少鍵的衝突。
無論哈希函數設計有多麼精細,都會產生衝突現象,也就是2個關鍵字處理函數的結果映射在了同一位置上,因此,有一些方法可以避免衝突。拉鍊法,拉出一個動態鏈表代替靜態順序存儲結構,可以避免哈希函數的衝突,不過缺點就是鏈表的設計過於麻煩,增加了編程複雜度。此法可以完全避免哈希函數的衝突。多哈希法,設計二種甚至多種哈希函數,可以避免衝突,但是衝突機率還是有的,函數設計的越好或越多都可以將機率降到最低。開放定址法,從發生衝突的那個單元起,按照一定的次序,從哈希表中找到一個空閒的單元。然後把發生衝突的元素存入到該單元的一種方法。開放定址法需要的表長度要大於等於所需要存放的元素。在開放定址法中解決衝突的方法有:線行探查法、平方探查法、雙散列函數探查法。開放定址法的缺點在於刪除元素的時候不能真的刪除,否則會引起查找錯誤,只能做一個特殊標記。只到有下個元素插入才能真正刪除該元素。線行探查法,線行探查法是開放定址法中最簡單的衝突處理方法,它從發生衝突的單元起,依次判斷下一個單元是否爲空,當達到最後一個單元時,再從表首依次判斷。直到碰到空閒的單元或者探查完全部單元爲止。
(3)該程序的數據集合採用的是HashMap進行存儲的,在jdk1.8之前HashMap採用的是數組加鏈表來進行存儲的,在jdk1.8之後採用了數組加鏈表加紅黑樹來進行存儲,當HashMap數組元素爲鏈表的時候,插入直接使用頭插,插入複雜度O(1);當鏈表長度達到了八個時,就會轉換爲紅黑樹,複雜度爲O(logn),這樣雖然提高了查找的性能,但每次插入新的數據,都要維護紅黑樹的結構,這樣算是對查找和插入元素時性能的一個權衡。
使用HashMap的理由就是這樣的結構結合了鏈表在增刪方面的高效和數組在尋址上的優勢,使得程序運行效率提高。
(4)首先需要定義一個Student類用於存儲學生信息,該類包含了學生的學號、姓名、手機號碼三個屬性,然後需要定義一個Map集合用於存儲學生集合,因爲學生的學號是唯一的,所以key值採用學生學號,value值就是學生對象,然後該系統需要多次交互,所以需要使用while(true)來保證程序一直運行,爲了方便操作,每次程序開始和操作完成後系統都會顯示菜單,匹配用戶輸入的菜單編號,即可執行對應的方法,如果輸入的編號不對,則提示不支持該操作!當選擇查詢時,提示輸入學號,然後讀取輸入的學號,調用map的get方法,判斷結果是否爲空即可,也可以調用map的containsKey方法判斷是否爲true,如果沒有查詢到則輸出沒有查詢到該學生!否則輸入學生信息;在新增、刪除、修改手機號這幾個操作時,同樣使用get或containsKey方法進行查詢,新增時還需使用put方法,刪除時需要使用remove方法,修改手機號時也是使用的put方法,調用該方法時,如果key值不存在則新增,如果key值存在則會修改value值做到數據的更新。程序在編碼過程中均考慮了用戶輸入信息的合法性,如果信息不合法時,會抓住異常給予用戶提示,提高了程序的健壯性。
程序添加的實現效果如圖4.1所示,修改功能如圖4.2所示,刪除功能如圖4.3所,程序的查詢功能圖上均有用到,不再單獨列出。
圖片涉及個人隱私無法查看
該程序的實現代碼如下:
import java.util.HashMap;
import java.util.Scanner;
/**
* @author baikunlong
* @date 2020/6/23 22:33
*/
public class Main {
private static Scanner scanner;
/**
* 學生信息集合
*/
private static HashMap<String, Student> map;
public static void main(String[] args) {
System.out.println("歡迎使用本系統~");
scanner = new Scanner(System.in);
String line = "";
map = new HashMap<>();
while (true) {
System.out.println();
System.out.println("菜單(輸入對應序號即可進入操作)");
System.out.println("1、根據學號查詢");
System.out.println("2、根據學號修改手機號");
System.out.println("3、添加");
System.out.println("4、刪除");
System.out.println("5、退出");
line = scanner.nextLine().trim();
switch (line) {
case "1":
select();
break;
case "2":
update();
break;
case "3":
add();
break;
case "4":
del();
break;
case "5":
System.out.println("再見,歡迎下次使用~");
return;
default:
System.out.println("不支持該操作!");
break;
}
}
}
/**
* 修改學生手機號
*/
private static void update() {
try {
System.out.println("請輸入要修改手機號學生的學號和手機號,分別用空格隔開,輸入完成後按回車:");
String s = scanner.nextLine();
String[] strings = s.split(" ");
//如果包含該key,則存在該學生
if (map.containsKey(strings[0])) {
//取出該學生
Student student = map.get(strings[0]);
//修改手機號
student.setPhone(strings[1]);
//重新設置該key值的學生對象
map.put(student.sno, student);
System.out.println("修改成功!");
} else {
System.out.println("該學生不存在!");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("輸入信息有誤!操作失敗!");
}
}
/**
* 刪除學生
*/
private static void del() {
System.out.println("請輸入要刪除的學生的學號:");
String s = scanner.nextLine();
//如果包含該key
if (map.containsKey(s)) {
//移除該key
map.remove(s);
System.out.println("刪除成功!");
} else {
System.out.println("該學生不存在!");
}
}
/**
* 新增學生
*/
private static void add() {
try {
System.out.println("請輸入學號、姓名、手機號,分別用空格隔開,輸入完成後按回車");
String s = scanner.nextLine();
String[] strings = s.split(" ");
//如果已存在該key值,則不能插入了
if (map.containsKey(strings[0])) System.out.println("該學生已存在!");
else {
//新增學生信息
map.put(strings[0], new Student(strings[0], strings[1], strings[2]));
System.out.println("添加成功!");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("輸入信息有誤!操作失敗!");
}
}
/**
* 查詢學生
*/
private static void select() {
System.out.println("請輸入學號:");
String s = scanner.nextLine();
//根據key值獲取value值
Student student = map.get(s);
if (student == null) System.out.println("沒有查詢到該學生!");
else System.out.println(student.toString());
}
/**
* 學生類
*/
static class Student {
/**
* 學號
*/
private String sno;
/**
* 姓名
*/
private String name;
/**
* 手機號
*/
private String phone;
@Override
public String toString() {
return "學生信息{" +
"學號='" + sno + '\'' +
", 姓名='" + name + '\'' +
", 手機號='" + phone + '\'' +
'}';
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Student(String sno, String name, String phone) {
this.sno = sno;
this.name = name;
this.phone = phone;
}
}
}