0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

設(shè)計一個信息管理系統(tǒng),你需要知道這些

UtFs_Zlgmcu7890 ? 來源:互聯(lián)網(wǎng) ? 作者:佚名 ? 2017-09-29 06:23 ? 次閱讀

近日周立功教授公開了數(shù)年的心血之作《程序設(shè)計與數(shù)據(jù)結(jié)構(gòu)》,電子版已無償性分享到電子工程師與高校群體下載,經(jīng)周立功教授授權(quán),特對本書內(nèi)容進行連載。

>>>>1.1 哈希表

>>>1.1.1 問題

假設(shè)需要設(shè)計一個信息管理系統(tǒng),用于管理大約一萬個學生的相關(guān)信息,可以通過學號查找到對應學生的信息,每條學生記錄包含學號、姓名、性別、身高、體重等信息。即:

typedef struct _student{

unsigned char id[6]; //學號(6字節(jié))

char name[10]; //姓名

char sex; //性別

float height, weight; //身高、體重

}student_t;

作為信息管理系統(tǒng),首先要能夠存儲學生記錄,這上萬條記錄如何存儲呢?簡單地,可以使用一段連續(xù)的內(nèi)存存儲學生記錄,比如,使用一個大數(shù)組存儲,每個數(shù)組元素都可以存儲一條學生記錄:

student_t student_db[10000];

當使用數(shù)組存儲學生信息時,如何通過學號查找相應的信息呢?如果學號編排是一種非常理想的情況,10000個學生的學號按照 0 ~ 9999順序排列,則可以直接將學號作為數(shù)組的索引值查找相應的數(shù)組元素,其存儲和查找的效率都非常高。但實際上學號往往不是如此簡單編排的,一種常見的編排方法是“年級+專業(yè)代碼+班級+班級內(nèi)序號”,比如,6字節(jié)學號為0x20, 0x16, 0x44, 0x70, 0x02, 0x39,即:201644700239,表示2016年入學,專業(yè)代碼為4470(即計算機專業(yè)),2班的39號同學。

此時,通過學號查找學生信息的方法也很簡單,直接從第一個學生記錄開始,順序遍歷各個學生記錄,將記錄中的學號與期望查找的學生學號相比較,學號相同即查找到了相應學生的信息,詳見程序清單3.61。

程序清單3.61順序查找范例程序

1 student_t * student_search(unsigned char id[6])

2 {

3 for (int i = 0; i < 10000; i++) {

4 if (memcmp(student_db[i].id, id, 6) == 0) { //比較

5 return &student_db[i]; //找到該學生的信息

6 }

7 }

8 return NULL; //未找到該學生的信息

9 }

顯然,如果采用順序查找法,學生記錄越多,則查找時需要比較的次數(shù)越多,效率也就越低。當學生記錄的條數(shù)上萬時,則可能需要比較上萬次才能找到相應的學生信息。

如何以更高的效率實現(xiàn)查找呢?在理想情況下,若將學號作為數(shù)組索引存儲數(shù)據(jù),則查找的效率非常高。既然如此,如果擴大數(shù)組容量至學號的最大值加1(以包含學號0),則可以直接以學號作為數(shù)組的索引值。由于學號是由6字節(jié)組成的,因此數(shù)組必須能夠容納248條記錄,需要占用多少存儲空間呢?就算一條記錄只占用一個字節(jié),也需要262144 G存儲空間,何況電腦硬盤沒這么大!如果只使用其中的10000條記錄,則剩下的(248-10000)空間就會造成極大的浪費,顯然這種方式是不可取的。

在查找算法中,非常經(jīng)典高效的算法是“二分法查找”,按10000條記錄算,最多也只需要比較14次(log210000)。但使用“二分法查找”的前提是信息必須有序排列,即要求學生記錄必須按照學號的順序存儲,這就導致在添加或刪除學生信息時,數(shù)據(jù)庫存儲的信息需要進行大量的移動操作。比如,數(shù)組中已經(jīng)按照學號從小到大的順序存儲了9999條記錄,現(xiàn)在寫入第10000條記錄,若該記錄的學號最小,需要寫入到所有記錄的前面,這就需要將之前存儲的9999條記錄全部向后移動一次,以預留出首元素的空間,然后將新的學生記錄寫入首元素對應的空間中。由此可見,雖然使用這種方法可以提高查找效率,卻犧牲了添加信息時的效率。

為了在添加信息時不進行大量的數(shù)據(jù)移動,能否換一種存儲方式呢?比如,使用存儲空間不連續(xù)的“單向鏈表”結(jié)構(gòu),將各個學生記錄“鏈”起來,其示意圖詳見圖3.23。

圖3.23 使用單向鏈表管理學生記錄

當使用鏈表管理學生記錄時,實現(xiàn)有序排列只需每次插入新結(jié)點時,找到正確的插入位置,無需進行大量數(shù)據(jù)的移動。由于存儲空間不連續(xù),因此無法使用“二分法”查找學生信息,則實現(xiàn)有序排列也沒有解決查找效率低下的問題,無論是否有序,查找時都需要從頭開始順序查找。

由此可見,使用“二分法查找”必須犧牲記錄寫入的效率以實現(xiàn)所有記錄有序排列,使得寫入記錄的效率非常低。雖然基礎(chǔ)的“順序查找”對寫入記錄的效率完全不影響,但查找效率極為低下。因此,這兩種情況都太極端了,要么選擇極低的寫入效率,要么選擇極低的查找效率。何不將二者結(jié)合一下,以折中寫入的效率和查找的效率呢?比如,將記錄“二分”為兩部分,使用兩個數(shù)組來存儲:

student_t student_db0[5000];

student_t student_db1[5000];

假設(shè)規(guī)定,學號小于某值(即201044700239)時,記錄存儲在student_db0,反之,記錄存儲在student_db1中。如此一來,在寫入記錄時,只需要多一條判斷語句,對性能并沒太大影響。而在查找時,只要根據(jù)學號判斷記錄在哪一個數(shù)組中,即可按照順序查找的方式查找。此時,查找需要比較的次數(shù)就從最大的10000次降低到了5000次。由此可見,通過一個簡單的方法,將信息分別存儲在兩個數(shù)組中,就可以明顯地提高查找效率。為了繼續(xù)提高查找的效率,還可以繼續(xù)分組,比如,分成250組,每組的大小為40:

student_t student_db0[40];

student_t student_db1[40];

……

student_t student_db248[40];

student_t student_db249[40];

顯然,采用這種定義方式太繁瑣了,由于每個數(shù)組的大小是相同的,因此可以直接將存儲40個學生記錄的數(shù)組定義為一個類型:

typedef student_t student_group_t[40];

student_group_t student_db[250];

此時,每個分組的大小為40,從而使得查找記錄時,最多只需要比較40次。接下來,需要定義分組規(guī)則,以通過學號找到該記錄屬于哪個組。在定義規(guī)則時,應盡可能地使所有記錄平均地分布在各個組中,不應該出現(xiàn)一些組存儲的記錄非常多,而一些組存儲的記錄非常少的情況。但這并不是一件容易的事情,需要對學號的數(shù)據(jù)分布進行精確的分析。

如果分成250組,假定學號是均勻分布的,則可以將6字節(jié)學號數(shù)求和除以250(分組數(shù)目)所得的余數(shù)(取余法)作為分組的索引,由于寫入和查找時,都需要通過學號找到該記錄應該屬于哪個組,因此可以根據(jù)學號分組的依據(jù),編寫一個通過學號找到對應分組索引的函數(shù),詳見程序清單3.62

程序清單3.62通過學號分組范例程序

1 int db_id_to_idx(unsigned char id[6])

2 {

3 int i, sum = 0;

4

5 for (i = 0; i < 6; i++) {

6 sum += id[0];

7 }

8 return sum % 250;

9 }

即將分組數(shù)為250看作一個大小為250的表格,每個表項可以存儲40個學生記錄的數(shù)組,通過db_id_to_idx()函數(shù)找到關(guān)鍵字學號ID對應在該表中的位置。其中,大小為250的表格就是“哈希表”,詳見圖3.24。db_id_to_idx()函數(shù)就是“哈希函數(shù)”,哈希函數(shù)的結(jié)果(分組索引)稱之為“哈希值”。

圖3.24 哈希

哈希表的核心工作在于哈希函數(shù)的選擇,將查找的關(guān)鍵字送給哈希函數(shù)產(chǎn)生一個哈希值,哈希函數(shù)的選擇直接決定了記錄的分布,必須盡可能地確保所有記錄均勻地分布在各個組中。在上面的示例中,每個分組中都定義了大小相同的數(shù)組作為記錄存儲的空間。如果按照分組規(guī)則,能夠確保恰好均勻地分布在各個分組中,這是最佳的。

而實際上學生記錄是會變動的,可能增加或刪除,則很難保證按照現(xiàn)在定義的分組規(guī)則,保證100%的完全平均。如果每個分組都使用大小相同的數(shù)組作為記錄存儲的空間,則可能會導致部分數(shù)組未存滿,部分數(shù)組卻存不下的情況,就會導致部分學生記錄無處可存,造成嚴重的數(shù)據(jù)管理問題。

由于數(shù)組都是提前定義好大小的,動態(tài)性能差,而鏈表的動態(tài)性能更好,可以根據(jù)需要增加、刪除結(jié)點,改變鏈表長度,因此可以使用鏈表管理學生記錄,就算分布不均勻,也只存在鏈表長度的差異,不會出現(xiàn)數(shù)據(jù)存儲不了的問題,其示意圖詳見圖3.25

圖3.25 鏈式哈希表

當使用鏈表管理學生記錄時,哈希表每個表項的實際內(nèi)容就是該組鏈表的表頭。鏈表頭結(jié)點的類型slist_head_t(slist.h)的定義如下:

typedef struct _slist_node{

struct _slist_node *p_next; //向下一個結(jié)點的指針

}slist_node_t;

typedef slist_node_t slist_head_t;

基于此,在哈希表的每個表項中存儲一個slist_head_t類型的鏈表頭結(jié)點即可,哈希表的定義如下:

typedef slist_head_t student_group_t;

student_group_t student_db[250];

根據(jù)對鏈式哈希表結(jié)構(gòu)的分析,編寫一個基于鏈式哈希表的信息管理系統(tǒng)作為示例僅提供增加、刪除、查找三種功能。當然,在使用這些功能前,還必須定義一個哈希表對象的類型,以便使用該類型定義具體的哈希表實例,進而使用各個功能接口對該實例進行操作。

>>>>1.1.2 哈希表的類型

哈希表類型struct _hash_db定義如下:

typedef struct _hash_db hash_db_t;

在結(jié)構(gòu)體中,需要包含哪些哈希表的相關(guān)信息呢?鏈式哈希表的核心是一個slist_head_t類型的數(shù)組,其大小與分組數(shù)目相關(guān)。為了通用,分組數(shù)目應由用戶根據(jù)實際情況確定。slist_head_t類型的數(shù)組信息由一個指向數(shù)組首地址的slist_head_t*類型的指針和一個指定數(shù)組大小的size構(gòu)成,哈希表結(jié)構(gòu)體類型的定義如下:

struct _hash_db{

slist_head_t *p_head; //指向數(shù)組首地址

unsigned int size; //數(shù)組成員數(shù)

};

在實際的應用中,信息可以是任意數(shù)據(jù)類型(void *),其次還需要知道該void *指針指向的記錄的長度,比如,學生記錄的長度是sizeof(student_t),因此更新哈希表結(jié)構(gòu)體類型的定義如下:

struct _hash_db{

slist_head_t *p_head; //指向數(shù)組首地址

unsigned int size; //數(shù)組成員數(shù)

unsigned int value_len; //一條記錄的長度

};

在存儲或查找記錄時,可以通過與關(guān)鍵字(比如,學號ID)比較找到哈希表中的索引值,然后在對應的表項中添加或查找記錄。在存儲記錄時,需要提供關(guān)鍵字和記錄;而在查找記錄時,僅需提供關(guān)鍵字。由此可見,關(guān)鍵字和記錄是兩個不同的概念,關(guān)鍵字具有特殊的作用,因此關(guān)鍵字和記錄應該分別對待。對于學生信息管理系統(tǒng)來說,其關(guān)鍵字為學號,長度是6字節(jié),記錄包含姓名、性別、身高、體重等信息。因此,在學生記錄結(jié)構(gòu)體的定義中,將關(guān)鍵字ID分離出來。學生記錄的定義如下:

typedef struct _student{

char name[10]; //姓名

char sex; //性別

float height, weight; //身高、體重

}student_t;

同理,關(guān)鍵字的長度也是由用戶決定的,在存儲一條記錄時,需要分配內(nèi)存存儲關(guān)鍵字,以便查詢時讀取該關(guān)鍵字與查詢使用的關(guān)鍵字進行比較。因此在哈希表的結(jié)構(gòu)體類型中,需要包含關(guān)鍵字長度信息,更新哈希表結(jié)構(gòu)體類型的定義如下:

struct _hash_db {

slist_head_t *p_head; //指向數(shù)組首地址

unsigned int size; //數(shù)組成員數(shù)

unsigned int value_len; //一條記錄的長度

unsigned int key_len; //關(guān)鍵字的長度

};

特別地,在前面的分析中,哈希表最重要的一個概念就是“哈希函數(shù)”,哈希函數(shù)的作用是通過關(guān)鍵字(如學號ID)得到其對應記錄在哈希表中的索引值,哈希函數(shù)要盡可能確保記錄均分地分布在哈希表的各個表項中。對于不同的數(shù)據(jù),用戶可能選擇不同的哈希函數(shù),因此哈希函數(shù)應該由用戶指定。基于此,在哈希表結(jié)構(gòu)體中新增一個函數(shù)指針,用于指向用戶自定義的哈希函數(shù)。完整的哈希表結(jié)構(gòu)體類型定義如下(hash_db.h):

typedef unsigned int (*hash_func_t) (const void *key); //定義哈希函數(shù)類型

struct _hash_db {

slist_head_t *p_head; //指向數(shù)組首地址

unsigned int size; //數(shù)組大小

unsigned int value_len; //一條記錄的長度

unsigned int key_len; //關(guān)鍵字的長度

hash_func_t pfn_hash; //哈希函數(shù)

};

在使用哈希表的各個接口函數(shù)前,首先需要使用該類型定義一個哈希表實例

hash_db_t hash;

如果系統(tǒng)中需要使用多張哈希表,則只需要使用該類型定義多個哈希表實例即可

hash_db_t hash1;

hash_db_t hash2;
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴

原文標題:周立功:哈希表=高效率的信息管理

文章出處:【微信號:Zlgmcu7890,微信公眾號:周立功單片機】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    信息管理信息系統(tǒng)專業(yè)計算機類課程體系設(shè)計

    ;;課程體系【DOI】:CNKI:SUN:JYJS.0.2010-06-007【正文快照】:信息管理信息系統(tǒng)專業(yè)是信息科學、
    發(fā)表于 04-24 09:45

    基于B_S的學生信息管理系統(tǒng)信息管理設(shè)計

    基于B_S的學生信息管理系統(tǒng)信息管理設(shè)計
    發(fā)表于 08-06 12:05

    java學生信息管理系統(tǒng)

    java學生信息管理系統(tǒng)
    發(fā)表于 10-03 14:47

    【轉(zhuǎn)】如果LED,需要知道

    需要知道的當談到?jīng)Q定購買哪類LED 時,事實證明有點困難。如果些時間在學習上,這是
    發(fā)表于 10-03 20:40

    labview簡單的信息管理系統(tǒng)

    labview簡單的信息管理系統(tǒng)
    發(fā)表于 06-08 21:50

    公交車信息管理系統(tǒng)的設(shè)計原理是什么?

    公交車作為目前國內(nèi)客運量最大的公共交通工具,它的管理及服務上直存在些漏洞.鑒于此.種基于RFID技術(shù)的公交信息管理
    發(fā)表于 10-15 07:52

    這些LED知識一定要知道

    LED在生活中隨處可見,作為嵌入式工程師,這些LED知識一定要知道!——LED的圖形標號——LED的基本性質(zhì)——1.最大工作電流——2.導通電壓——LED檢測方法——1.極性判斷——2.好壞檢測
    發(fā)表于 12-21 07:12

    醫(yī)院信息管理系統(tǒng)源代碼

    醫(yī)院信息管理系統(tǒng)源代
    發(fā)表于 07-19 11:10 ?14次下載

    基于面向?qū)ο髷?shù)據(jù)模型的信息管理系統(tǒng)

    探討了面向?qū)ο髷?shù)據(jù)模型信息管理系統(tǒng)的結(jié)構(gòu)設(shè)計和信息管理系統(tǒng)實現(xiàn)技術(shù)。系統(tǒng)設(shè)計采用面向?qū)ο髷?shù)據(jù)模型,數(shù)據(jù)庫結(jié)構(gòu)采用對象-關(guān)系數(shù)據(jù)庫。結(jié)合
    發(fā)表于 02-21 11:35 ?14次下載

    基于C/S模式的網(wǎng)絡信息管理系統(tǒng)設(shè)計與實現(xiàn)

    本文提出了基于C/S 模式的三層結(jié)構(gòu)信息管理系統(tǒng)的設(shè)計思想,并以道路運政管理 系統(tǒng)的實現(xiàn)為例進行介紹。根據(jù)業(yè)務需求進行業(yè)務分析,設(shè)計了
    發(fā)表于 09-14 15:00 ?26次下載

    繼電保護技術(shù)信息管理系統(tǒng)研究

    繼電保護技術(shù)信息管理系統(tǒng)研究 隨著網(wǎng)絡技術(shù)的飛速發(fā)展,通過Web方式實現(xiàn)繼電保護技術(shù)信息管理系統(tǒng)已經(jīng)成為
    發(fā)表于 07-26 22:50 ?971次閱讀

    信息管理系統(tǒng)

    此為信息管理系統(tǒng)c語言源代碼 有需要的同學歡迎來交流
    發(fā)表于 05-25 10:26 ?2次下載

    探究車輛統(tǒng)籌信息管理系統(tǒng)

    探究車輛統(tǒng)籌信息管理系統(tǒng)
    發(fā)表于 10-30 12:01 ?2次下載

    對于汽車中的Bluetooth Smart,需要知道的內(nèi)容

    對于汽車中的Bluetooth Smart,需要知道的內(nèi)容
    發(fā)表于 11-04 09:50 ?1次下載
    對于汽車中的Bluetooth Smart,<b class='flag-5'>你</b><b class='flag-5'>需要知道</b>的內(nèi)容

    關(guān)于步進電機需要知道

    關(guān)于步進電機需要知道
    發(fā)表于 03-07 16:58 ?2037次閱讀
    關(guān)于步進電機<b class='flag-5'>你</b><b class='flag-5'>需要知道</b>的<b class='flag-5'>一</b>切