BUAA数据结构第六次作业2023

第六次作业

1. 单词查找(查找-基本题)

题目

问题描述

从标准输入中读入一个英文单词及查找方式,在一个给定的英文常用单词字典文件dictionary3000.txt中查找该单词,返回查找结果(查找到返回1,否则返回0)和查找过程中单词的比较次数。查找前,先将所有字典中单词读入至一个单词表(数组)中,然后按要求进行查找。字典中单词总数不超过3500,单词中的字符都是英文小写字母,并已按字典序排好序(可从课件下载区下载该字典文件)。字典中的单词和待查找单词的字符个数不超过20

查找方式说明:查找方式以1~4数字表示,每个数字含义如下:

  1. 在单词表中以顺序查找方式查找,因为单词表已排好序,遇到相同的或第一个比待查找的单词大的单词,就要终止查找

  2. 在单词表中以折半查找方式查找;

  3. 在单词表中通过索引表来获取单词查找范围,并在该查找范围中以折半方式查找。索引表构建方式为:以26个英文字母为头字母的单词在字典中的起始位置和单词个数来构建索引表,如:

    字母起始位置单词个数
    a0248
    b248167

    该索引表表明以字母a开头的单词在单词表中的开始下标位置为0,单词个数为248。

  4. 按下面给定的hash函数为字典中单词构造一个hash表,hash冲突时按字典序依次存放单词。hash查找遇到冲突时,采用链地址法处理,在冲突链表中找到或未找到(遇到第一个比待查找的单词大的单词或链表结束)便结束查找

    /* compute hash value for string */

    #define NHASH 3001

    #define MULT 37

    unsigned int hash(char *str)

    {

    ​ unsigned int h=0;

    ​ char *p;

    ​ for(p=str; *p!=‘\0’; p++)

    ​ h = MULT*h + *p;

    ​ return h % NHASH;

    }

提示:hash表可以构建成指针数组,hash冲突的单词形成一有序链表。

输入形式

单词字典文件dictionary3000.txt存放在当前目录下,待查找单词和查找方式从标准输入读取。待查找单词只包含英文小写字母,与表示查找方式的整数之间以一个空格分隔。

输出形式

将查找结果和单词比较次数输出到标准输出上,两整数之间以一个空格分隔。

样例输入与输出

单词字典文件dictionary3000.txt与课件下载中提供的相同,下面两列中,左侧为待查找单词与查找方式,右侧为对应的输出结果:

wins 1 0 3314

wins 2 0 12

wins 3 0 7

wins 4 0 2

yes 1 1 3357

yes 2 1 10

yes 3 1 4

yes 4 1 1

样例说明

wins在单词字典中不存在,4种查找方式都输出结果0,顺序查找、折半查找、索引查找和hash查找的单词比较次数分别为:3314、12、7和2次(wins的hash位置与字典中physics和suggest相同)。

yes在单词字典中存在,4种查找方式都输出结果1,顺序查找、折半查找、索引查找和hash查找的单词比较次数分别为:3357、10、4和1。

评分标准

该题要求输出查找结果和查找过程中的单词比较次数,提交程序名为find.c。

问题分析

如题面,本题是查找的基本题,这里就简单讨论一下各类查找的实现思路吧。首先每种查找都对应了其自己的数据存储结构。对顺序查找和二分查找,只要把各数据按顺序存储到一个数组里就行(因为题面中说读入的单词已经有序了),设计函数时,顺序查找只要传入要查找的数据就行,二分查找一般而言还要传入查找的上下界,也建议这样去写,因为本题的索引查找里面用的也是二分查找,只要把传入的上下界改一下就行,实现了代码复用,我是一个查找一个查找写的,所以一开始没看索引查找,也图省事就没在二分查找里面传入上下界;对索引查找,我们只需要在读入数据的时候顺便记录一张索引表就行——记录以26个字母为首的单词第一个单词出现的位置;对哈希,这其实是个固定的板子,需要在读入的时候另构建一张哈希表,然后通过特定的查找函数去构建。

具体实现过程

首先定义常量与全局变量:

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

#define NHASH  3001
#define MULT  37

struct word
{
    char str[15];
    int time;
    struct word *next;
};  // 哈希表的单词结构类型
typedef struct word  word;
typedef struct word* wptr;

char word_gather[3505][15];  // 单词集合
int word_num = 0;  // 单词数量
int index_form[26];  // 26个字母的索引表
int letter_num  =0;  // 已读入的单词的首字母种类数目
wptr hash_form[3505];  // 哈希表

然后在主函数中进行初始化、读入单词并建立单词表,索引表,哈希表:

// 主函数中  
    for (int i = 0; i < 26; i++) {
        index_form[i] = -1;  // 初始化为-1,因为a的索引为0。如果初始化为0,则字典中若没有某个字母开头的单词,其索引没变过也是0
    }

    FILE* in = fopen("dictionary3000.txt", "r");
    char tmp_word[15];  // 临时的单词容器
    char now_letter = '\0';
    while (get_a_word(in, tmp_word) != EOF) {
        strcpy(word_gather[word_num], tmp_word);  // 插入到单词集合中
        char this_letter = tmp_word[0];
        if (now_letter != this_letter) {  // 判断是否更新索引表(出现新的首字母)
            index_form[this_letter - 'a'] = word_num;
            now_letter = this_letter;
        }
        insert_to_hash(tmp_word);  // 插入哈希表
        word_num++;
    }

然后在主函数中读入查找的的字符串由与方式,并进行查找,输出,框架应当如下:

    int time = 0;
    int op;
    scanf("%s%d", tmp_word, &op);
    if (op == 1) {
        printf("%d ", sequential_search(tmp_word, &time));
        printf("%d", time);
    } else if (op == 2) {
        printf("%d ", binary_search(tmp_word, &time));
        printf("%d", time);        
    } else if (op == 3) {
        printf("%d ", index_search(tmp_word, &time));
        printf("%d", time);        
    } else {
        wptr tmp = find_in_hash(tmp_word, &time);
        if (tmp != NULL) printf("%d ", 1);
        else printf("%d ", 0);
        printf("%d", time);
    }

在主函数中出现的这些函数实现如下:

首先是读入单词的函数,它从指定的文件里读入一个单词返回:

int get_a_word(FILE* fp, char* word)  // 读取一个单词
{
    if (fscanf(fp, "%s", word) != EOF) return 0;
    return EOF;
}

在哈希表中查找的函数实现如下:

wptr find_in_hash(char *str, int *search_time)  // 在哈希表中寻找结点,存在则返回结点位置,否则返回NULL
{
    (*search_time) = 0;
    int val = hash(str);
    for(wptr i = hash_form[val]; i ; i = i -> next) {
        (*search_time)++;
        if(strcmp(str,i -> str) == 0)
            return i;
    }
    
    return NULL; // 没找到
}

而插入哈希表的实现可以调用这个函数先查一遍判断情况后再采取对应的策略(这样肯定时间效率不高,但是思路简单直白):

void insert_to_hash(char *target_word)
{
    int val = hash(target_word);  // 计算哈希值
    int rubbish;  // 插入的时候不用记录查找次数,随便传一个指针进去就行
    wptr tmp_pos = find_in_hash(target_word, &rubbish);
    if (tmp_pos != NULL) {
        tmp_pos ->time ++;
    } else {
        // 创建新结点
        wptr p = (wptr) malloc(sizeof(word));
        p -> time = 1;
        strcpy(p -> str, target_word);
        p -> next = NULL;


        // 找到要插入的目标结点
        if (hash_form[val] == NULL) {  // 特判当前哈希值的位置还没有被插入过

            hash_form[val] = p;
        }

        if (strcmp(target_word, hash_form[val] -> str) < 0) {  // 特判是否要插入头部  
            p -> next = hash_form[val];
            hash_form[val] = p;
        }   

        for (wptr i = hash_form[val]; strcmp(target_word, i -> str) > 0; i = i -> next) {
            if (i -> next == NULL) {  // 特判当前已经是链表表尾了
                i -> next = p;
                break;
            }

            if (strcmp(target_word, i -> next ->str) < 0) {  // 要把p插入到当前这个i后面
                p -> next = i -> next;
                i -> next = p;
                break;
            }
        }
    }
}

顺序查找:

int sequential_search(char *target_word, int *search_time)  // 顺序查找,找到返回1,否则为0,用search_time返回查找次数
{
    (*search_time) = 0;
    for (int i = 0; i < word_num; i++) {
        (*search_time)++;
        int tmp = strcmp(target_word, word_gather[i]);
        if (tmp == 0) return 1;
        if (tmp < 0) return 0;
    }
    return 0;
}

二分查找:

int binary_search(char *target_word, int *search_time)  // 二分查找
{
    (*search_time) = 0;
    int left = 0;
    int right = word_num - 1;
    int mid = 0;
    while (left <= right) {
        (*search_time)++;
        mid = (left + right) / 2;
        int tmp = strcmp(word_gather[mid], target_word);
        if (tmp < 0) {
            left = mid + 1;
        } else if (tmp > 0) {
            right = mid - 1;
        } else {
            return 1;
        }
    }
    return 0;
}

索引表查找:

int index_search(char *target_word, int *search_time)  // 索引表查找
{
    (*search_time) = 0;
    int begin = index_form[target_word[0] - 'a'];
    int end = 0;
    if (begin == -1) return 0;  // 若字典中没有以该单词的首字母为首的单词,直接返回0
    int i = 1;
    while(index_form[target_word[0] - 'a' + i] == -1) {
        i++;
        if (i ==26) {  // 到单词表最后一位了
            end = word_num;
            break;
        }
    }
    
    if (end == 0) end = index_form[target_word[0] - 'a' + i] - 1;
    int left = begin;
    int right = end;
    
    int mid = 0;
    while (left <= right) {
        (*search_time)++;
        mid = (left + right) / 2;
        int tmp = strcmp(word_gather[mid], target_word);
        if (tmp < 0) {
            left = mid + 1;
        } else if (tmp > 0) {
            right = mid - 1;
        } else {
            return 1;
        }
    }
    return 0;
}

完整代码

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

#define NHASH  3001
#define MULT  37


struct word
{
    char str[15];
    int time;
    struct word *next;
};  // 哈希表的单词结构类型
typedef struct word  word;
typedef struct word* wptr;


char word_gather[3505][15];  // 单词集合
int word_num = 0;  // 单词数量
int index_form[26];  // 26个字母的索引表
int letter_num  =0;  // 已读入的单词的首字母种类数目

wptr hash_form[3505];  // 哈希表


int get_a_word(FILE* fp, char* word)  // 读取一个单词
{
    if (fscanf(fp, "%s", word) != EOF) return 0;
    return EOF;
}


unsigned int hash(char *str)  // 计算哈希值的函数
{
    unsigned int h=0;
    char *p;
    for(p=str; *p!='\0'; p++)
        h = MULT*h + *p;
    return h % NHASH;
}


wptr find_in_hash(char *str, int *search_time)  // 在哈希表中寻找结点,存在则返回结点位置,否则返回NULL
{
    (*search_time) = 0;
    int val = hash(str);
    for(wptr i = hash_form[val]; i ; i = i -> next) {
        (*search_time)++;
        if(strcmp(str,i -> str) == 0)
            return i;
    }
    
    return NULL; // 没找到
}


void insert_to_hash(char *target_word)
{
    int val = hash(target_word);  // 计算哈希值
    int rubbish;  // 插入的时候不用记录查找次数,随便传一个指针进去就行
    wptr tmp_pos = find_in_hash(target_word, &rubbish);
    if (tmp_pos != NULL) {
        tmp_pos ->time ++;
    } else {
        // 创建新结点
        wptr p = (wptr) malloc(sizeof(word));
        p -> time = 1;
        strcpy(p -> str, target_word);
        p -> next = NULL;


        // 找到要插入的目标结点
        if (hash_form[val] == NULL) {  // 特判当前哈希值的位置还没有被插入过

            hash_form[val] = p;
        }

        if (strcmp(target_word, hash_form[val] -> str) < 0) {  // 特判是否要插入头部  
            p -> next = hash_form[val];
            hash_form[val] = p;
        }   

        for (wptr i = hash_form[val]; strcmp(target_word, i -> str) > 0; i = i -> next) {
            if (i -> next == NULL) {  // 特判当前已经是链表表尾了
                i -> next = p;
                break;
            }

            if (strcmp(target_word, i -> next ->str) < 0) {  // 要把p插入到当前这个i后面
                p -> next = i -> next;
                i -> next = p;
                break;
            }
        }
    }
}


int sequential_search(char *target_word, int *search_time)  // 顺序查找,找到返回1,否则为0,用search_time返回查找次数
{
    (*search_time) = 0;
    for (int i = 0; i < word_num; i++) {
        (*search_time)++;
        int tmp = strcmp(target_word, word_gather[i]);
        if (tmp == 0) return 1;
        if (tmp < 0) return 0;
    }
    return 0;
}


int binary_search(char *target_word, int *search_time)  // 二分查找
{
    (*search_time) = 0;
    int left = 0;
    int right = word_num - 1;
    int mid = 0;
    while (left <= right) {
        (*search_time)++;
        mid = (left + right) / 2;
        int tmp = strcmp(word_gather[mid], target_word);
        if (tmp < 0) {
            left = mid + 1;
        } else if (tmp > 0) {
            right = mid - 1;
        } else {
            return 1;
        }
    }
    return 0;
}


int index_search(char *target_word, int *search_time)  // 索引表查找
{
    (*search_time) = 0;
    int begin = index_form[target_word[0] - 'a'];
    int end = 0;
    if (begin == -1) return 0;  // 若字典中没有以该单词的首字母为首的单词,直接返回0
    int i = 1;
    while(index_form[target_word[0] - 'a' + i] == -1) {
        i++;
        if (i ==26) {  // 到单词表最后一位了
            end = word_num;
            break;
        }
    }
    
    if (end == 0) end = index_form[target_word[0] - 'a' + i] - 1;
    int left = begin;
    int right = end;
    
    int mid = 0;
    while (left <= right) {
        (*search_time)++;
        mid = (left + right) / 2;
        int tmp = strcmp(word_gather[mid], target_word);
        if (tmp < 0) {
            left = mid + 1;
        } else if (tmp > 0) {
            right = mid - 1;
        } else {
            return 1;
        }
    }
    return 0;
}


int main()
{
    for (int i = 0; i < 26; i++) {
        index_form[i] = -1;  // 初始化为-1,因为a的索引为0。如果初始化为0,则字典中若没有某个字母开头的单词,其索引没变过也是0
    }

    FILE* in = fopen("dictionary3000.txt", "r");
    char tmp_word[15];  // 临时的单词容器
    char now_letter = '\0';
    while (get_a_word(in, tmp_word) != EOF) {
        strcpy(word_gather[word_num], tmp_word);  // 插入到单词集合中
        char this_letter = tmp_word[0];
        if (now_letter != this_letter) {  // 判断是否更新索引表(出现新的首字母)
            index_form[this_letter - 'a'] = word_num;
            now_letter = this_letter;
        }
        insert_to_hash(tmp_word);  // 插入哈希表
        word_num++;
    }

    int time = 0;
    int op;
    scanf("%s%d", tmp_word, &op);
    if (op == 1) {
        printf("%d ", sequential_search(tmp_word, &time));
        printf("%d", time);
    } else if (op == 2) {
        printf("%d ", binary_search(tmp_word, &time));
        printf("%d", time);        
    } else if (op == 3) {
        printf("%d ", index_search(tmp_word, &time));
        printf("%d", time);        
    } else {
        wptr tmp = find_in_hash(tmp_word, &time);
        if (tmp != NULL) printf("%d ", 1);
        else printf("%d ", 0);
        printf("%d", time);
    }
    return 0;
}

2. 排座位(简)a

题目

问题描述

某班级要进行期末考试,准备考试时打乱座次,现已按照学号顺序人工为学生随机安排了座位号,但其中可能会出现漏排和重复安排座位的情况。编写程序读入人工安排的考试座位安排表T1,对安排情况进行检查,并对漏排和重复安排座位的情况进行修正,修正后,若学生人数为N,则每位学生考试座位安排应在1~N之间,其中没有缺号和重号。假设T1中学号信息不会出现重复,同一座位号最多有两位同学的座位号相同,并且座位号不会连续漏排;初始考试座位安排表存放在当前目录下的in.txt中,其中包括每位学生的学号、姓名和座位号,要求修正后的考试座位安排表输出到当前目录下的out.txt文件中。程序检查座位号的规则如下:

  1. 首先对考试座位安排表T1按座位号从小到大的顺序排序(原始考试安排可能有座位号相同情况,座位号相同时则按原始学号顺序排序),得到按座位号排序的安排表T2;

  2. 对表T2从头开始检查漏排座位号情况:假设当前表中安排的最大座位号为M,取M和N的较小值Q;从1号开始检查,若某个小于等于Q的座位序号没有安排学生,则将表T2的最后学生的座位设置为该座位号;若存在多个漏排座位,则从表T2最后依次向前设置;

  3. 然后再检查表T2中重排座位号情况:假设当前表中安排的最大座位号为m,将座位号重复的、学号较大的学生的座位号依次设置为m+1、m+2、m+3…;

  4. 将调整好的表T2按学号由小到大序排序后按输出格式要求输出至指定输出文件中。

输入形式

从标准输入中读入学生人数(不超过100的正整数)。

初始考试座位安排表存储在当前目录下的in.txt文件中,已按照学号由小到大的顺序分行存储每位学生座位信息,依次为学生学号(不超过8位的正整数)、姓名(由不超过20位的英文字母组成)和座位号(不超过100的正整数),各数据间以一个空格分隔。最后一个学生座位信息后有回车换行。

输出形式

按照学号由小到大的顺序将修正后的考试座位安排表输出到当前目录下的out.txt文件中,每行依次为学号、姓名和座位号,各数据之间以一个空格分隔

样例输入

24

假设当前目录下的in.txt文件内容如下:
18373001 ShiTian 7
18373002 WangLi 15
18373003 LiGuoHong 23
18373005 QianSanQiang 26
18373006 ZhangQiang 8
18373007 SunXiXi 2
18373010 LiXing 12
18373011 TangYing 20
18373012 YangYang 4
18373013 ZhaoGang 27
18373014 ZhouLiang 18
18373015 WuShuo 9
18373016 ZhengSiSi 13
18373017 WangGong 27
18373018 TianTian 21
18373020 LeiLei 16
18373021 ZhangLou 10
18373022 WangLei 17
18373025 SunTian 24
18373026 JinXiang 18
18373028 PangHong 11
18373029 GaoLiang 2
18373030 GaoHang 6
18373031 YangYang 22

样例输出

当前目录下的out.txt文件内容应为:
18373001 ShiTian 7
18373002 WangLi 15
18373003 LiGuoHong 19
18373005 QianSanQiang 5
18373006 ZhangQiang 8
18373007 SunXiXi 2
18373010 LiXing 12
18373011 TangYing 20
18373012 YangYang 4
18373013 ZhaoGang 3
18373014 ZhouLiang 18
18373015 WuShuo 9
18373016 ZhengSiSi 13
18373017 WangGong 1
18373018 TianTian 21
18373020 LeiLei 16
18373021 ZhangLou 10
18373022 WangLei 17
18373025 SunTian 14
18373026 JinXiang 24
18373028 PangHong 11
18373029 GaoLiang 23
18373030 GaoHang 6
18373031 YangYang 22

样例说明

初始考试座位安排表中有24位学生的排位信息,正确情况下这些学生安排的座位号应为1~24。初始人工安排的座位信息有1、3、5、14和19号座位漏排,有2、18和27号座位重复安排学生。先对漏排的座位进行修正:已安排的最大座位号为27,学号为18373017和18373013的学生分别安排了27号座位,按照漏排座位修正规则,先将18373017学生安排在1号座位,再将18373013学生安排在3号座位;同理分别将18373005学生安排在5号座位,将18373025学生安排在14号座位,18373003号学生安排在19号座位。当前安排的最大座位号为22,还有2号和18号座位重复,将2号重复的学号较大的18373029号学生安排在23号座位,将18号重复的学号较大的18373026号学生安排在24号座位。这样修正后按照学号由小到大的顺序输出学生座位信息到out.txt中。

评分标准

按照要求修正学生座位信息,提交程序文件名为seat.c。

问题分析

本题还是挺有技巧性的,而且题目故意隐藏了正确的思路,而把问题描述得相当复杂。实际上,我们可以把题面的思路改写如下:

  1. 读入数据,按照规则排序,得到座位表T2;
  2. **本步的核心想法就是检查有没有漏排的!**而且根据题目的意思,我们只要从头到尾把表遍历一遍就可以实现这一点。题目说的话很不好理解,代码实现的话时间复杂度也挺大,我们自己来想想看如何能从头到尾把表遍历一次就把漏排的座位补全。其实也很好想,如果遍历到当前的位置时,该位置的座位号和上一个位置的座位号不是“连着”(排重了,这一步还没检查重排,只检查漏排)的或者是相等的,那就说明中间肯定漏号了。漏号了怎么办?肯定要找一个位置给补上啊,由于我们的遍历序列是从头到尾的,为了保证已经遍历过的位置不被破坏,题目的做法是从表尾开始补上漏排的位置号。比如从头开始的遍历序列是1 2 3 5 6… 发现漏了一个4号,那就只需要把表尾的座位号改成4号就行,以此类推。(题目那个描述其实就是这个意思,只是不说人话
  3. 这一步说白了就是检查重排,和上面一样,核心就是如何判断重排的,其实能够想到,就是当前位置的座位号和上一个位置的座位号相等了时候才会重排呗。那应当把重排的座位号置几呢?**当然是置这张表里当前还没有的!**而几是肯定没有的呢?比表中当前最大值还大的值一定不在表里(这不废话吗),所以题目就说,你把它置成当前表中最大的数+1吧。
  4. 再排个序,输出序列,没有难度。

具体处理过程

首先声明座位数据类型,并把座位表建起来:

// 主函数外
typedef struct {
    int id;  // 学号
    char name[20];
    int seat_number;  // 座位号
} seat;

seat gather[100];  // 座位表

主函数中应当先读入数据,而后进行第一次排序:

// 主函数中
   FILE *in = fopen("in.txt", "r");
    FILE *out = fopen("out.txt", "w");

    int num;  // 总共座位数
    scanf("%d", &num);

    for (int i = 0; i < num; i++) {
        fscanf(in, "%d %s %d", &gather[i].id, gather[i].name, &gather[i].seat_number);
    }
    fclose(in);
    qsort(gather, num, sizeof(seat), cmp);

其中cmp函数设计如下:

int cmp(const void *a, const void *b)
{
    seat* e1 = (seat*)a;
    seat* e2 = (seat*)b;
    if (e1 -> seat_number != e2 ->seat_number) return e1 -> seat_number - e2 -> seat_number;
    return e1 -> id - e2 -> id;
}

然后检查漏排,我们要用一个int型变量记录“如果有漏排的话,把座位号置成漏排的座位号的位置的下标”,初始时该值为数组的最后一个元素的下标。同时,由于第0个位置没有前一个位置,所以要特判一下第0个位置前面是否有漏排(正常第0个位置的座位号应当是1,如果不是,就说明前面有漏排的)——这一点容易忽略。同时,由于下一步要找出改完之后的表中座位号的的最大值,所以我们在检查漏排时就把这个最大值找出来(最大值不可能在最后没有遍历的位置,因为那些位置的数据都是漏排的座位号,一定小于等于当前遍历到的位置的座位号),代码如下:

// 主函数中    
	// 检查漏排
    int m = 0;  // 记录检查漏排后的最大值
    int rep = num - 1;  // 要是有漏排的,下一个替换的下标

    for (int i = 1; i < gather[0].seat_number; i++) {  // 第0个位置特判
        gather[rep--].seat_number = i;
    }

    for (int i = 1; i < num; i++) {  // 接下来从1开始遍历
        if (gather[i].seat_number > m) m = gather[i].seat_number;
        if (gather[i].seat_number < gather[i - 1].seat_number) {  // 当前的座位号比上一位小,说明漏排的检查已经完成了,退出循环
            break;
        }
        if (gather[i].seat_number - gather[i - 1].seat_number > 1) {  // 前后两者之间的座位号差大于1,说明邮件的编号有漏排的
            for (int j = gather[i - 1].seat_number + 1; j < gather[i].seat_number; j++) {
                gather[rep--].seat_number = j;                
            }
        }
        
    }

检查重排更简单,也不用特判,代码如下:

// 主函数中
    // 检查重排
    int last_number = gather[0].seat_number;  // 遍历到的上“1”个位置的座位号
    for (int i = 1; i < num; i++) {
        if (gather[i].seat_number == last_number) {  // 有重复的
            gather[i].seat_number = ++m;
        } else {
            last_number = gather[i].seat_number;
        }
    }

然后还要按照题目要求排序,由于排序要求不一样,所以再设计一个cmp_2函数:

int cmp_2(const void *a, const void *b)
{
    return ((seat*)a) -> id - ((seat*)b) -> id;
}

而后进行输出即可:

// 主函数中
    qsort(gather, num, sizeof(seat), cmp_2);
    for (int i = 0; i < num; i++) {
        fprintf(out, "%d %s %d\n", gather[i].id, gather[i].name, gather[i].seat_number);
    }
    fclose(out);

完整代码

# include <stdio.h>
# include <stdlib.h>

typedef struct {
    int id;  // 学号
    char name[20];
    int seat_number;  // 座位号
} seat;


seat gather[100];  // 座位表

int cmp_1(const void *a, const void *b)
{
    seat* e1 = (seat*)a;
    seat* e2 = (seat*)b;
    if (e1 -> seat_number != e2 ->seat_number) return e1 -> seat_number - e2 -> seat_number;
    return e1 -> id - e2 -> id;
}


int cmp_2(const void *a, const void *b)
{
    return ((seat*)a) -> id - ((seat*)b) -> id;
}


int main()
{
    FILE *in = fopen("in.txt", "r");
    FILE *out = fopen("out.txt", "w");

    int num;  // 总共座位数
    scanf("%d", &num);

    for (int i = 0; i < num; i++) {
        fscanf(in, "%d %s %d", &gather[i].id, gather[i].name, &gather[i].seat_number);
    }
    fclose(in);
    qsort(gather, num, sizeof(seat), cmp_1);

    // 检查漏排
    int m = 0;  // 记录检查漏排后的最大值
    int rep = num - 1;  // 要是有漏排的,下一个替换的下标

    for (int i = 1; i < gather[0].seat_number; i++) {  // 第0个位置特判
        gather[rep--].seat_number = i;
    }

    for (int i = 1; i < num; i++) {  // 接下来从1开始遍历
        if (gather[i].seat_number > m) m = gather[i].seat_number;
        if (gather[i].seat_number < gather[i - 1].seat_number) {  // 当前的座位号比上一位小,说明漏排的检查已经完成了,退出循环
            break;
        }
        if (gather[i].seat_number - gather[i - 1].seat_number > 1) {  // 前后两者之间的座位号差大于1,说明邮件的编号有漏排的
            for (int j = gather[i - 1].seat_number + 1; j < gather[i].seat_number; j++) {
                gather[rep--].seat_number = j;                
            }
        }
        
    }

    // 检查重排
    int last_number = gather[0].seat_number;  // 遍历到的上“1”个位置的座位号
    for (int i = 1; i < num; i++) {
        if (gather[i].seat_number == last_number) {  // 有重复的
            gather[i].seat_number = ++m;
        } else {
            last_number = gather[i].seat_number;
        }
    }

    qsort(gather, num, sizeof(seat), cmp_2);
    for (int i = 0; i < num; i++) {
        fprintf(out, "%d %s %d\n", gather[i].id, gather[i].name, gather[i].seat_number);
    }
    fclose(out);
    return 0;
}

3. 整数排序

题目

问题描述

从标准输入中输入一组互不相同的整数(个数不超过100)及排序方式,按照从小到大排序,输出按某种算法排序的结果及元素的比较次数。

说明:排序方式为一个1~5的整数,分别表示:

1:选择排序,比较次数是指选择未排序部分的最小元素时的比较次数。

2:冒泡排序,比较次数是指相邻元素的比较次数,若某趟排序中没有进行数据交换,就认为排序结束。

3:堆排序,比较次数是指根元素调整过程中根元素与子树根结点的比较次数,即下面算法中红色语句的执行次数:

void adjust(int k[ ],int i,int n)

{

int j,temp;

temp=k[i];

j=2*i+1;

while(j<n){

​ if(j<n-1 && k[j]<k[j+1])

​ j++;

​ if(temp>=k[j])

​ break;

​ k[(j-1)/2]=k[j];

​ j=2*j+1;

}

k[(j-1)/2]=temp;

}

4:二路归并排序,比较次数是指两组有序数据合并成一组时的比较次数,即下面算法中红色语句的执行次数(注意:调用 merge时,要使用上课讲的递归算法):

void merge(int x[ ],int tmp[ ],int left,int leftend,int rightend)

{

int i=left, j=leftend+1, q=left;

while(i<=leftend && j<=rightend)

{

​ if(x[i]<=x[j])

​ tmp[q++]=x[i++];

​ else

​ tmp[q++]=x[j++];

}

while(i<=leftend)

​ tmp[q++]=x[i++];

while(j<=rightend)

​ tmp[q++]=x[j++];

for(i=left; i<=rightend; i++)

​ x[i]=tmp[i];

}

5:快速排序,比较次数是指分界元素与其它元素的比较次数,即下面算法中红色语句的执行次数:

void quickSort(int k[ ],int left,int right)

{

int i, last;

if(left<right){

​ last=left;

​ for(i=left+1;i<=right;i++)

​ if(k[i]<k[left])

​ swap(&k[++last],&k[i]);

​ swap(&k[left],&k[last]);

​ quickSort(k,left,last-1);

​ quickSort(k,last+1,right);

}

}

输入形式

首先在屏幕上输入2个整数,分别表示待排序的整数个数及排序方式,然后在下一行依次输入待排序的整数。各整数之间都以一个空格分隔。

输出形式

先在一行上输出排序结果,各整数间以一个空格分隔。然后在下一行上输出排序过程中的元素比较次数。

样例1输入

20 1
38 356 98 -102 126 46 65 -9 100 0 21 2 90 8 18 12 78 16 189 23

样例1输出

-102 -9 0 2 8 12 16 18 21 23 38 46 65 78 90 98 100 126 189 356
190

样例1说明

输入了20个整数数据,要求按照选择排序算法对输入的数据进行从小到大排序,输出排序结果,排序过程中元素的比较次数为190次。

其他样例说明

若输入的待排序数据与样例1完全相同,要求的排序算法不同,则输出的排序结果与样例1完全一样,但比较次数不同,为了方便说明,下面左侧为排序方式,右侧为对应的比较次数:

2 162

3 58

4 66

5 75

评分标准

该题要求按照指定算法对输入的数据进行排序,提交程序名为sort.c。

本题的代码都能在课件上找到,只需要把void型函数改成int型即可,代码如下:

# include <stdio.h>
# include <stdlib.h>

int gather[10000];


int select_sort(int k[], int n)   
{   
    int time = 0;
    int i, j, d;
    int  temp;
    for(i = 0; i < n - 1; i++) {
        d = i;
        for(j = i + 1; j < n; j++) {
            time++;
            if(k[j] < k[d])
                d=j;
        }
            
        if(d != i) {    
                temp = k[d] ;
                k[d] = k[i];
                k[i] = temp;
        }
    }
    return time;
}


int bubble_sort(int a[], int n) {
    int time = 0;
	int i, j, hold, flag;
	for (i = 0; i < n - 1; i++) {
		flag = 0;
		for (j = 0; j < n - 1 - i; j++) {
            time++;
			if (a[j] > a[j + 1]) {
				hold = a[j];
				a[j] = a[j + 1];
				a[j + 1] = hold;
				flag = 1;
			}
		}
		if (0 == flag)
			break;
	}
    return time;
}


int adjust(int k[], int i, int n)
{
    int time = 0;
    int j, temp;
    temp = k[i];
    j = 2 * i + 1;
    while(j < n) {
        if(j < n - 1 && k[j] < k[j+1])
            j++;
        time++;
        if(temp >= k[j]) 
            break;
        k[(j - 1) / 2] = k[j];
        j = 2 * j + 1;
    }
    k[(j - 1) / 2] = temp;
    return time;
}


int heap_sort(int k[], int n)	//堆排序
{
    int time = 0;

    int temp;
    for(int i = n / 2 - 1; i >= 0; i--)
        time += adjust(k, i, n);		//构建堆 
    for(int i = n - 1; i >= 1; i--) {
        temp = k[i];			//把最大的移到最后,然后处理前面的数 
        k[i] = k[0];
        k[0] = temp;
        time += adjust(k, 0, i);		//处理前面的数 
    }
    return time;
}


int merge(int x[], int tmp[], int left, int leftend, int rightend)
{     
    int time = 0;
    int i = left, j = leftend + 1, q = left;
    while(i <= leftend && j <= rightend)
    {
        time++;
        if(x[i] <= x[j]) 
            tmp[q++] = x[i++];
        else
            tmp[q++] = x[j++];
    }
    while(i <= leftend)
        tmp[q++] = x[i++];
    while(j <= rightend)
        tmp[q++] = x[j++];
    for(i = left; i <= rightend; i++)
        x[i] = tmp[i];
        return time;
}


int mSort(int k[], int tmp[], int left, int right)
{
    int time = 0;
    int center;
    if(left < right) {
        /* 不断对分这个待排序的序列*/
        center = (left+right)/2;
        time += mSort(k, tmp, left, center);
        time += mSort(k, tmp, center+1, right);
        /* left 等于或者大于 right 开始合并 */
        time += merge(k, tmp, left, center, right);
    }
    return time;
}


int merge_sort(int k[], int n)
{
    int time = 0;
    int *tmp;
    tmp = (int *)malloc(sizeof(int) * n);   //保存排序结果用
    if(tmp != NULL) {
        time += mSort(k, tmp, 0, n-1);
        free(tmp);
    } 
    return time;
}


void swap(int *a, int *b)
{
    int p = *a;
    (*a) = *b;
    (*b) = p;
}


int quick_sort(int k[],int left, int right)
{     
    int time = 0;
    int i, last;
    if(left < right){
        last = left; 
        for(i = left + 1; i <= right; i++) {
            time++;
            if(k[i] < k[left])
                swap(&k[++last], &k[i]); 
        }
            
        swap(&k[left], &k[last]);
        time += quick_sort(k, left, last - 1); 
        time += quick_sort(k, last + 1, right);   
    }
    return time;
}


int main()
{

    int num, op;
    scanf("%d%d", &num, &op);
    for (int i = 0; i < num; i++) {
        scanf("%d", &gather[i]);
    }
    int time;
    switch(op) {
        case 1:
            time = select_sort(gather, num);
            break;
        case 2:
            time = bubble_sort(gather, num);
            break;
        case 3:
            time = heap_sort(gather, num);
            break;
        case 4:
            time = merge_sort(gather, num);
            break;
        case 5:
            time = quick_sort(gather, 0, num - 1);
            break;
    }
    for (int i = 0; i < num; i++) {
        printf("%d ", gather[i]);
    }
    printf("\n%d", time);
    return 0;
}
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值