基于散列文件的学生信息管理系统的设计与实现

/*基于散列文件的通信录信息管理系统的设计与实现

 日志1: --6.11

* 一、初始化:
*   1.fopen(fhead, "rb");
*   先判断散列文件是否已存在
*   若不存在则fopen(fhead, "wb+");新建散列文件
*   2.head[i] = i;//初始化目录表
*   bucked[i].next = -1;//初始无溢出桶
*   head[N] = -1; //目录表多添加一个空间用于存储回收桶的指针
*   3.将目录表与数据桶分别存入两个文件

  日志2: --6.12
*
*    更新: 结构体Contacts中增加flag标志位,
*           初始化中添加bucked[i].contacts[j].flag = 0;
*           以便于检查桶中有无空位
*
* 二、插入与打印:
* 1.插入:
* fopen(fhead, "rb+");打开文件
* checkHash检查桶中有无空位
*   1).基桶未满  --  直接存入空位(桶未满时无需更新目录表)
*   2).基桶满,且无溢出桶  --  优先考虑回收桶
*           ①存在回收桶  -- 新数据存入回收桶,并将此回收桶改为溢出桶(头插法更新目录表)
*           ②无回收桶  添加新桶(溢出桶)并将数据插入新桶的第一个空位
*                       新桶存至文件末尾,并用头插法更新目录表
*   3).基桶满,且有溢出桶  --  同1)
*   4).基桶满,且溢出桶满  --  同2)
* 注意:使用头插法无需考虑溢出桶的位置,可以提高插入与查找效率。

  日志3: --6.13
*
*   更新:插入函数中增加了检测输入数据是否已存在的判断
*
* 三、查找与删除:
* 1.查找:
*   1)查找元素的散列地址
*   2)读取文件信息,比对姓名
*   3)查找成功--打印,查找失败
* 2.删除
*   1)用户输入姓名,比对查找
*   2)
*       a.在基桶中
*           1.若此基桶存在溢出桶  -->  将溢出桶中的一个数据移入此位置
*               1> 若溢出桶里的数据移出后,溢出桶为空,
*                   ① 若已有其它回收桶,头插法插入回收桶链表,更新目录表最后一个位置的指针
*                   ② 若无其它回收桶,将其指针存入目录表最后一个位置。  √
*               2> 若移出后溢出桶不为空,仅将溢出桶里的位置回收  √
*           2.若无溢出桶 --- 直接回收(flag = 0)  √
*       b.在溢出桶中:
*           1.删去后尚有已存在的其它数据 -- 仅回收此位置   √
*           2.删去后溢出桶为空 
*                 1> 若此溢出桶不是末尾的溢出桶,不将其改为回收桶,而是从末尾的溢出桶中取出一个填进来
*                       ①若取出后末尾的溢出桶空了,则将其改为回收桶。
*                                 一、已有回收桶
*                                 二、无
*                       ②直接删
*                 2> 直接删
* ......

   日志4  --6.14

*       更新:对插入函数进行了修改,使之桶满时优先考虑回收桶
*             完善多处对回收桶的操作,使之适应插入、删除操作中的各种情况

   日志5  --6.15

*  应考虑的更多情况:
*   1.删除
*       b.在溢出桶中:
*           1.删去后尚有已存在的其它数据 -- 仅回收此位置   √
*           2.删去后溢出桶为空 
*                 1> 若此溢出桶不是末尾的溢出桶,不将其改为回收桶,而是从末尾的溢出桶中取出一个填进来
*                       ①若取出后末尾的溢出桶空了,则将其改为回收桶。
* ......
*                       
    日志6  --6.16  --23;16

    通过对各种情况的验证不断纠正bug最终完成本次作品   --  十分耗费时间的一个过程    
*/

state.h 文件

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<conio.h>
#include <windows.h>

#define ID_SIZE 20      //学号
#define NAME_SIZE 10    //姓名长度
#define PHONE_SIZE 20   //电话号长度
#define MAIL_SIZE 30    //邮箱长度
#define N 7             //基桶的数量
#define capacity 3      //每个桶的容量

const char fhead[9] = "hashhead";//表头文件
const char fnode[9] = "hashnode";//存储信息

typedef struct Contacts {
    int flag;     //0未使用,1已使用
    char ID[ID_SIZE];
    char name[NAME_SIZE];
    char phone[PHONE_SIZE];
    char mail[MAIL_SIZE];
}Contacts;

typedef struct Bucked {
    Contacts contacts[capacity];
    int next;   //下一个桶的指针
}Bucked;

void haveHash();//初始化
void hashSearch();  //查找
void hashInsert();  //插入
void hashPrt();     //打印
void deleteHashfile(); //删除
void system_menu();   //主菜单

// 散列函数:将ID的ASCLL转换为数字
unsigned int hash(char ID[]) {
    unsigned int hash = 0;
    for (int i = 0; i < strlen(ID); ++i) {
        hash += ID[i];
    }
    return hash % N;
}

//找空位
int checkHash(Bucked fct) {  
    for (int i = 0; i < capacity; i++)
        if (fct.contacts[i].flag == 0)
            return i; //桶仍有空位
    return -1;  //桶fct已满
}

//找非空位置
int checkHash_1(Bucked fct) {  
    for (int i = 0; i < capacity; i++)
        if (fct.contacts[i].flag == 1)
            return i; //桶仍有空位
    return -1;  //桶fct已满
}

//记录fct桶中尚存多少个数据
int checkHash_num(Bucked fct) {
    int i, j = 0;
    for (i = 0; i < capacity; i++)
        if (fct.contacts[i].flag == 1)
            j++;
    return j;
}

function.h文件

#pragma once
#include "state.h"

void haveHash() {//初始化
    FILE* fp1, * fp2;
    fp1 = fopen(fhead, "rb");
    fp2 = fopen(fnode, "rb");
    if (fp1 == NULL || fp2 == NULL) {
        fp1 = fopen(fhead, "wb+");
        // 读写打开建立一个二进制文件,若文件不存在则建立该文件
        fp2 = fopen(fnode, "wb+");
        if (fp1 == NULL || fp2 == NULL) {
            printf("打开文件失败!");
            exit(1); // 异常退出
        }

        int* head, i;
        Bucked* bucked = (Bucked*)malloc(sizeof(Bucked) * N);  //初始化N个基桶存入文件
        head = (int*)malloc(sizeof(int) * (N + 1));//多申请一个位置用于储存回收桶的位置
        if (head == NULL) {
            printf("内存申请失败!");
            exit(1);//异常退出
        }
        for (i = 0; i < N; i++)
        {
            head[i] = i;//初始化目录表
            for (int j = 0; j < capacity; j++) {
                bucked[i].contacts[j].flag = 0; //桶均为空
                bucked[i].next = -1;//无溢出桶
            }
        }
        head[i] = -1;  //回收桶指针-1  --表示初始无回收桶
        fwrite(head, sizeof(int), N + 1, fp1);//存入目录表表文件
        fwrite(bucked, sizeof(Bucked), N, fp2);
        free(head);//释放空间
        fclose(fp1);
        fclose(fp2);
    }
    else {
        //printf("已存在散列函数!\n");
    }
}

int searchHash(Contacts* contacts) {
    FILE* fp1, * fp2; // fP1表头,fp2储存
    fp1 = fopen(fhead, "rb+");
    // 读写打开一个二进制文件
    fp2 = fopen(fnode, "rb+");
    if (fp1 == NULL || fp2 == NULL)
    {
        printf("打开文件失败!");
        exit(1); // 异常退出
    }
    Bucked p; // P为工作结点
    int* head, d, loc;
    head = new int[N + 1];
    if (head == NULL)
    {
        printf("内存申请失败!");
        exit(1); // 异常退出
    }
    fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
    d = hash(contacts->ID); // 查找元素的散列地址
    loc = head[d];
    while (loc != -1)
    {
        fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
        fread(&p, sizeof(Bucked), 1, fp2);   // 读取loc位置的结点信息
        for (int j = 0; j < capacity; j++)
            if (strcmp(p.contacts[j].ID, contacts->ID) == 0)
                if (p.contacts[j].flag == 1) {
                    *contacts = p.contacts[j]; // 被找到结点的元素值赋给r
                    free(head);
                    fclose(fp1);
                    fclose(fp2);
                    return 1;    // 结束循环
                }
        loc = p.next;// 工作位置后移到后继的位置
    }
    free(head);
    fclose(fp1);
    fclose(fp2);
    return 0;
}

void hashSearch() {
    system("cls");
    Contacts contacts;
    printf("请输入查找的学生ID:");
    scanf("%s", contacts.ID);
    int  k = searchHash(&contacts);
    if (k == 1)
    {
        printf("查找成功!\n");
        printf("ID:%s;\n", contacts.ID);
        printf("name:%s;\n", contacts.name);
        printf("phone:%s;\n", contacts.phone);
        printf("mail:%s\n\n", contacts.mail);
        getchar();
    }
    else
    {
        printf("您查找的ID不存在!");
        getchar();
    }
    printf("\n按任意键继续...\n");
    getchar();
    //system_menu();
}

void hashInsert() {
    system("cls");
    Contacts ct;
    printf("请输入学生ID:");
    scanf("%s", &ct.ID);
    if (searchHash(&ct) == 1) {//元素在散列文件中已存在
        printf("该元素在散列文件中已存在,无法插入!\n");
        printf("\n按任意键继续...\n");
        getchar();
        getchar();
        return;
    }
    printf("请输入姓名:");
    scanf("%s", &ct.name);
    printf("请输入phone:");
    scanf("%s", &ct.phone);
    printf("请输入mail:");
    scanf("%s", &ct.mail);



    FILE* fp1, * fp2;
    fp1 = fopen(fhead, "rb+");
    fp2 = fopen(fnode, "rb+");
    if (fp1 == NULL || fp2 == NULL)
    {
        printf("打开文件失败!");
        exit(1);//异常退出
    }

    Bucked prev_bucked;
    int* head, hashIndex;
    head = (int*)malloc(sizeof(int) * (N + 1));
    if (head == NULL)
    {
        printf("内存申请失败!");
        exit(1);//异常退出
    }
    fread(head, sizeof(int), N + 1, fp1);
    hashIndex = hash(ct.ID);   //计算哈希值
    int head_node = head[hashIndex];
    fseek(fp2, head_node * sizeof(Bucked), 0);
    fread(&prev_bucked, sizeof(Bucked), 1, fp2);

    int contacts_index = checkHash(prev_bucked); //检查桶中有无空位
    if (contacts_index != -1) { //桶未满
        prev_bucked.contacts[contacts_index] = ct; //数据存入桶中
        prev_bucked.contacts[contacts_index].flag = 1; //标记为已使用

        fseek(fp2, head_node * sizeof(Bucked), 0);
        fwrite(&prev_bucked, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
        free(head);
        fclose(fp1);
        fclose(fp2);
    }
    else {  //桶满
        if (head[N] != -1) { //优先考虑回收桶
            int new_head_node = head[N];
            fseek(fp2, new_head_node * sizeof(Bucked), 0);
            fread(&prev_bucked, sizeof(Bucked), 1, fp2);
            prev_bucked.contacts[0] = ct; //数据存入桶中
            prev_bucked.contacts[0].flag = 1; //标记为已使用
            head[hashIndex] = new_head_node;  //改为溢出桶更新目录表

            if (prev_bucked.next == -1) { //只有一个回收桶时
                head[N] = -1;
                prev_bucked.next = head_node;
                
            }
            else {  //存在多个回收桶
                head[N] = prev_bucked.next;
                prev_bucked.next = head_node;
            }
            
            fseek(fp1, 0, 0);
            fwrite(head, sizeof(int), N + 1, fp1); //更新目录表
            fseek(fp2, new_head_node * sizeof(Bucked), 0);
            fwrite(&prev_bucked, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
            free(head);
            fclose(fp1);
            fclose(fp2);
        }

        else { //无回收桶 添加溢出桶
            Bucked newbucked;
            for (int x = 0; x < capacity; x++)
                newbucked.contacts[x].flag = 0;
            newbucked.contacts[0] = ct;
            newbucked.contacts[0].flag = 1;
            newbucked.next = head_node;
            fseek(fp2, 0, 2);//文件指针移到文件尾
            int loc = ftell(fp2) / sizeof(Bucked);//文件尾结点位置序号
            fwrite(&newbucked, sizeof(Bucked), 1, fp2);//fr结点存入文件尾
            head[hashIndex] = loc;//表头指向新插入结点
            fseek(fp1, 0, 0);
            fwrite(head, sizeof(int), N + 1, fp1); //更新目录表
            free(head);
            fclose(fp1);
            fclose(fp2);
        }
    }
    printf("添加成功!\n");
    printf("\n按任意键继续...\n");
    getchar();
    getchar();
    //system_menu();
}

void hashPrt() {
    system("cls");
    FILE* fp1, * fp2; // fP1表头,fp2储存
    fp1 = fopen(fhead, "rb+");
    // 读写打开建立一个二进制文件
    fp2 = fopen(fnode, "rb+");
    if (fp1 == NULL || fp2 == NULL)
    {
        printf("打开文件失败!");
        exit(1); // 异常退出
    }
    Bucked p; // P为工作结点
    int* head, loc, i, x = 0;
    head = (int*)malloc(sizeof(int) * (N + 1));
    if (head == NULL)
    {
        printf("内存申请失败!");
        exit(1); // 异常退出
    }
    fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
    for (i = 0; i < N; i++)
    {
        //printf("%d:", i);
        loc = head[i];
        while (loc != -1)
        {
            printf("%d:\n", loc);
            fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
            fread(&p, sizeof(Bucked), 1, fp2);   // 读取loc位置的结点信息
            for (int j = 0; j < capacity; j++)
                if (p.contacts[j].flag == 1) {
                    //printf("%d:\n", ++x);
                    printf("ID:%s;\t", p.contacts[j].ID);
                    printf("name:%s;\t", p.contacts[j].name);
                    printf("phone:%s;\t", p.contacts[j].phone);
                    printf("mail:%s\t\n", p.contacts[j].mail);
                }
            loc = p.next;
            //if (loc != -1)
                //printf("->\n");
        }
        printf("\n");
    }
    free(head);
    fclose(fp1);
    fclose(fp2);
    printf("\n按任意键继续...\n");
    getchar();
    //system_menu();
}

void deleteHashfile() {
    system("cls");
    Contacts contacts;
    printf("请输入要删除学生的ID:");
    scanf("%s", &contacts.ID);
    if (searchHash(&contacts) == 0)
    {
        printf("该ID在散列文件中不存在,无法删除\n");
        printf("\n按任意键继续...\n");
        getchar();
        getchar();
        return;
    }
    FILE* fp1, * fp2; // fP1表头,fp2储存
    fp1 = fopen(fhead, "rb+");
    fp2 = fopen(fnode, "rb+");
    if (fp1 == NULL || fp2 == NULL)
    {
        printf("打开文件失败!");
        exit(1);//异常退出
    }

    Bucked  p, q;
    int* head, d, loc;
    head = (int*)malloc(sizeof(int) * (N + 1));
    if (head == NULL)
    {
        printf("内存申请失败!");
        exit(1);//异常退出
    }
    fread(head, sizeof(int), N + 1, fp1);//读取表头文件
    d = hash(contacts.ID);//计算插入元素的散列地址
    loc = head[d];

    int flag = 0;  // 0 --的数据在基桶中;1 -- 在溢出桶
    int excess, locx = loc;

    fseek(fp2, loc * sizeof(Bucked), 0);//文件指针移动到loc位置
    fread(&q, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
    if (loc >= N) {
        flag = 1;  // 先访问的溢出桶
        excess = checkHash_num(q);  //纪录溢出桶中数据位置,便于回收溢出桶中的数据(移出基桶数据时)
        locx = loc;  //记录溢出桶指针,以便回收溢出桶
    }

    while (loc != -1){
        fseek(fp2, loc * sizeof(Bucked), 0);//文件指针移动到loc位置
        fread(&p, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
        
        for (int j = 0; j < capacity; j++)
            if (strcmp(p.contacts[j].name, contacts.name) == 0 && p.contacts[j].flag == 1) {
                if (loc < N) {  //数据在基桶中
                    p.contacts[j].flag = 0;
                    if (flag == 1 && excess == 1) {  //基桶存在溢出桶且溢出桶只有一个数据 -- 此时将溢出桶中的数据移入基桶,并将此空的溢出桶改为回收桶
                        excess = checkHash_1(q);
                        p.contacts[j] = q.contacts[excess];
                        q.contacts[excess].flag = 0;
                        if (head[N] == -1) {
                            head[loc] = q.next;  //更新目录表
                            q.next = -1;
                            head[N] = locx;  //记录回收桶的指针
                            
                        }
                        else {
                            head[loc] = q.next;  //更新目录表
                            q.next = head[N];
                            head[N] = locx;
                        }

                    }
                    else if (flag == 1) { //基桶存在溢出桶且溢出桶存在不止一个数据
                        
                        excess = checkHash_1(p);
                        p.contacts[j] = q.contacts[excess];
                        q.contacts[excess].flag = 0;
                        
                    }
                    fseek(fp2, locx * sizeof(Bucked), 0);//文件指针移动到locx位置
                    fwrite(&q, sizeof(Bucked), 1, fp2);//读取loc位置的结点信息
                    fseek(fp2, loc * sizeof(Bucked), 0);
                    fwrite(&p, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
                    flag = 2;
                    break;

                }  //if数据在基桶
                else {   //待删除的数据在溢出桶中
                    if (checkHash_num(p) > 1) {
                        p.contacts[j].flag = 0; //溢出桶中存在不止一个数据  -- 则直接回收此位置
                        flag = 2; //跳出while循环
                        fseek(fp2, loc * sizeof(Bucked), 0);
                        fwrite(&p, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
                        break;
                    }
                    else {  //溢出桶只有一个数据
                        if (head[d] == loc) {    //溢出桶在末尾  --  删去数据后将此溢出桶改为回收桶
                            p.contacts[j].flag = 0;
                            head[d] = p.next;  //更新目录表
                            if (head[N] == -1) {  //无回收桶
                                head[N] = loc;  //记录回收桶指针
                                p.next = -1;
                            }
                            else {  //已有其他回收桶
                                p.next = head[N];
                                head[N] = loc;
                            }
                        }
                        else {    //溢出桶不在末尾  --将此溢出桶视作基桶,从末尾溢出桶中取出一个数据补进来。
                            if (flag == 1 && excess == 1) {  //末尾的溢出桶只有一个数据 -- 此时将末尾溢出桶中的数据移入基桶,并将此空的溢出桶改为回收桶
                                excess = checkHash_1(q);
                                p.contacts[j] = q.contacts[excess];
                                q.contacts[excess].flag = 0;
                                if (head[N] == -1) {
                                    head[d] = q.next;  //更新目录表
                                    q.next = -1;
                                    head[N] = locx;  //记录回收桶的指针
                                    
                                }
                                else {
                                    head[d] = q.next;  //更新目录表
                                    q.next = head[N];
                                    head[N] = locx;
                                }
                            }
                            else if (flag == 1) { //基桶存在溢出桶且溢出桶存在不止一个数据

                                excess = checkHash_1(p);
                                p.contacts[j] = q.contacts[excess];
                                q.contacts[excess].flag = 0;
                                
                            }
                        }
                        fseek(fp2, locx * sizeof(Bucked), 0);
                        fwrite(&q, sizeof(Bucked), 1, fp2);  //将更新后的溢出桶存入文件
                        fseek(fp2, loc * sizeof(Bucked), 0);
                        fwrite(&p, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
                        flag = 2;
                        break;
                    }
                }

                //fseek(fp2, loc * sizeof(Bucked), 0);
                //fwrite(&prev_bucked, sizeof(Bucked), 1, fp2);  //将更新后的桶存入文件
            }
        if (flag == 2)
            break;
        loc = p.next;
    }
    fseek(fp1, 0, 0);//head表头重新存入散列表头文件
    fwrite(head, sizeof(int), N + 1, fp1);
    free(head);
    fclose(fp1);
    fclose(fp2);
    //printf("删除成功!");
    printf("已删除!");
    printf("\n按任意键继续...\n");
    getchar();
    getchar();
    //system_menu();
}

void huishoutong() {
    system("cls");
    FILE* fp1, * fp2; // fP1表头,fp2储存
    fp1 = fopen(fhead, "rb+");
    // 读写打开建立一个二进制文件
    fp2 = fopen(fnode, "rb+");
    if (fp1 == NULL || fp2 == NULL)
    {
        printf("打开文件失败!");
        exit(1); // 异常退出
    }
    Bucked p; // P为工作结点
    int* head, loc;
    head = new int[N + 1];
    if (head == NULL)
    {
        printf("内存申请失败!");
        exit(1); // 异常退出
    }
    fread(head, sizeof(int), N + 1, fp1); // 读取表头文件
    loc = head[N];
    while (loc != -1)
    {
        printf("N = %d,回收桶编号:%d\n", N, loc);
        fseek(fp2, loc * sizeof(Bucked), 0); // 文件指针移动到loc位置
        fread(&p, sizeof(Bucked), 1, fp2);   // 读取loc位置的结点信息
        for (int j = 0; j < capacity; j++) {
            printf("flag:%d;\t", p.contacts[j].flag);
            //printf("name:%s;\t", p.contacts[j].name);
            //printf("phone:%s;\t", p.contacts[j].phone);
            //printf("mail:%s\t\n", p.contacts[j].mail);
        }
        printf("\n");
        loc = p.next;
        //if (loc != -1)
            //printf("->\n");
    }
    
    free(head);
    fclose(fp1);
    fclose(fp2);
    printf("\n按任意键继续...\n");
    getchar();
}

void system_menu() {
    while (1) {
        system("cls");
        printf("\t\t\t欢迎使用yt通信录信息管理系统!\t\n\n");
        printf("\t++========================主菜单==========================++\t\n");
        printf("\t||请输入选项所对应的序号:                                ||\t\n");
        printf("\t||                 ===   1.新建联系人   ===               ||\t\n");
        printf("\t||                 ===   2.查找联系人   ===               ||\t\n");
        printf("\t||                 ===   3.删除联系人   ===               ||\t\n");
        printf("\t||                 ===   4.查看通讯录   ===               ||\t\n");
        printf("\t||                 ===   0.退出系统     ===               ||\t\n");
        printf("\t++========================================================++\t\n");
        int a;
        scanf("%d", &a);
        getchar();
        switch (a) {
        case 1:hashInsert(); break;
        case 2:hashSearch(); break;
        case 3:deleteHashfile(); break;
        case 4:hashPrt(); break;
        case 10:huishoutong(); break;
        case 0:printf("感谢您的使用~"); Sleep(1000); exit(0);
        default:printf("不要调皮哦,请重新输入~"); getchar(); system_menu(); break;
        }

    }
}

最后是main.c

#include "function.h"

int main() {
    haveHash();
    system_menu();

    system("pause");
    return 0;
}

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值