数据结构课程设计(含5个课程设计题目)

本文介绍了一次数据结构课程设计,涵盖了5个题目:成绩分析问题、八皇后问题、迷宫问题、农夫过河问题的求解和哈夫曼编码器。所有题目均使用C语言实现,涉及链表数据结构、文件读写、排序算法和哈夫曼编码。在成绩分析问题中,设计了一个学生信息管理系统,具备添加、查询、排序和统计功能。哈夫曼编码器部分,实现了从输入字符集和权值构建哈夫曼树,进行编码和解码文件的操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                          课程设计报告

  • 课程设计概述

本次数据结构课程设计共完成5个题目:成绩分析问题,哈夫曼编码器,迷宫问题,八皇后问题,农夫过河问题的求解。

使用环境:C语言。

编译环境:Visual Studio 2022。

  • 课程设计题目

                                          1.《成绩分析问题》

(1)实验内容:

成绩分析问题。

(2)问题描述:

设计并实现一个成绩分析系统,能够实现录入,保存一个班级学生多门课程的成绩,并对成绩进行分析等功能。

(3)需求分析:

经过分析,本系统需完成的主要功能如下:

  1. 通过键盘输入各学生的多门课程的成绩,建立相应的文件input.dat。
  2. 对文件input.dat中的数据进行处理,要求具有如下功能:
  1. 按各门课程成绩排序,并生成相应的文件输出。
  2. 计算每个人的平均成绩,按平均成绩排序,并生成文件。
  3. 求出各门课程的平均成绩,最高分,最低分,不及格人数,60~69分人数,70~79分人数,80~89分人数,90分以上人数。
  4. 根据姓名或学号查询某人的各门课程成绩,重名也要能处理。

(4)概要设计:

--=ADT=--

{

Student * SearchByName(Student *head, char *name)// 根据姓名查询学生信息

Student * SearchById(Student *head, char *id)// 根据学号查询学生信息

void Add(Student **head)// 添加一个学生

void Read(Student **head)// 读取文件中的学生信息

void Save(Student * head)// 保存学生信息到文件

void SortAndOutput(Student * head)// 按三门课程成绩排序并输出

void CalculateAverage(Student * head)// 计算每人三门课程的平均成绩并输出

void SortByAverageScoreAndOutput(Student * head)// 按三门课程平均成绩排序并输出

void Statistics(Student * head)// 统计三门课程各自的平均成绩,最高分,最低分,不及格人数,60~69分人数,70~79分人数,80~89分人数,90分以上人数

void ShowMenu() // 显示菜单 

}

(5)存储结构:

typedef struct student {

    char id[20]; // 学号

    char name[20]; // 姓名

    float math_score; // 数学成绩

    float english_score; // 英语成绩

    float computer_score; // 计算机成绩

    float average; // 平均成绩

    struct student * next; // 指向下一个学生的指针

} Student;

(6)设计思路:

 链表数据结构:使用链表来存储学生信息,每个节点表示一个学生。链表的好处是可以动态地添加和删除节点,灵活处理不同数量的学生信息。

   - 学生结构体:定义一个学生结构体,包含以下字段:学号、姓名、数学成绩、英语成绩、计算机成绩、平均成绩和指向下一个学生节点的指针。这样可以方便地存储和访问每个学生的相关信息。

   - 功能函数:实现各种功能函数,如添加学生、读取文件、保存文件、排序等。这些函数通过操作链表来完成相关操作。

(7)关键算法:

  - 冒泡排序算法:用于将学生根据指定的排序规则(如成绩)进行排序。冒泡排序算法的基本思想是比较相邻的两个元素,如果它们的顺序错误就交换位置,继续比较直到完成排序。在学生信息管理系统中,可以根据数学成绩、英语成绩或计算机成绩来排序学生信息。

   - 遍历链表:通过使用循环遍历链表的每个节点,可以实现对链表中的数据进行访问、处理和输出等操作。遍历链表是获取每个学生信息、计算平均成绩以及输出结果的关键步骤。

   - 文件读写:通过使用二进制文件读写方式,可以将学生信息保存到文件中,或从文件中读取学生信息。在学生信息管理系统中,可以将学生信息以二进制形式写入文件,并在需要时从文件中读取,并重新构建链表。

使用链表作为数据结构,结合冒泡排序算法、链表遍历和文件读写等算法,可以实现学生信息的管理、排序和存储等功能

(8)详细设计:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

// 定义学生结构体

typedef struct student {

    char id[20]; // 学号

    char name[20]; // 姓名

    float math_score; // 数学成绩

    float english_score; // 英语成绩

    float computer_score; // 计算机成绩

    float average; // 平均成绩

    struct student * next; // 指向下一个学生的指针

} Student;

// 根据姓名查询学生信息

Student * SearchByName(Student *head, char *name) {

    Student *p = head;

    while (p != NULL) {

        if (strcmp(p->name, name) == 0) { // 找到该学生

            return p;

        }

        p = p->next;

    }

    return NULL; // 没有找到该学生

}

// 根据学号查询学生信息

Student * SearchById(Student *head, char *id) {

    Student *p = head;

    while (p != NULL) {

        if (strcmp(p->id, id) == 0) { // 找到该学生

            return p;

        }

        p = p->next;

    }

    return NULL; // 没有找到该学生

}

// 添加一个学生

void Add(Student **head) {

    Student * p = (Student *)malloc(sizeof(Student));

    printf("请输入学生的学号:");

    scanf("%s", p->id);

    printf("请输入学生的姓名:");

    scanf("%s", p->name);

    printf("请输入学生的数学成绩:");

    scanf("%f", &p->math_score);

    printf("请输入学生的英语成绩:");

    scanf("%f", &p->english_score);

    printf("请输入学生的计算机成绩:");

    scanf("%f", &p->computer_score);

    p->average = (p->math_score + p->english_score + p->computer_score) / 3; // 计算平均成绩

    // 插入到链表中

    Student * temp = NULL;

    Student * cur = *head;

    while(cur && p->average < cur->average) { // 按平均成绩排序插入

        temp = cur;

        cur = cur->next;

    }

    if(temp == NULL) {

        p->next = *head;

        *head = p;        

    } else {

        temp->next = p;

        p->next = cur;

    }

    printf("添加成功\n");

}

// 读取文件中的学生信息

void Read(Student **head) {

    FILE * fp = fopen("input.dat", "rb");

    if (fp == NULL) {

        printf("文件打开失败\n");

        return;

    }

    Student * p = (Student *)malloc(sizeof(Student));

    fread(p, sizeof(Student), 1, fp);

    while (!feof(fp)) {

        // 插入到链表后面

        p->next = *head;

        *head = p;

        p = (Student *)malloc(sizeof(Student));

        fread(p, sizeof(Student), 1, fp);

    }

    printf("读取成功\n");

    fclose(fp);

}

// 保存学生信息到文件

void Save(Student * head) {

    FILE * fp = fopen("input.dat", "wb");

    if (fp == NULL) {

        printf("文件打开失败\n");

        return;

    }

    Student * p = head;

    while (p != NULL) {

        fwrite(p, sizeof(Student), 1, fp);

        p = p->next;

    }

    printf("保存成功\n");

    fclose(fp);

}

// 按三门课程成绩排序并输出

void SortAndOutput(Student * head) {

    Student * p = head;

    int count = 0;

    while (p != NULL) {

        count++;

        p = p->next;

    }

    Student ** array = (Student **)malloc(sizeof(Student *) * count); // 存储指针的数组

    p = head;

    for (int i = 0; i < count; i++) {

        array[i] = p;

        p = p->next;

    }

    for (int i = 0; i < count - 1; i++) { // 冒泡排序

        for (int j = 0; j < count - 1 - i; j++) {

            if (array[j]->math_score + array[j]->english_score + array[j]->computer_score < array[j + 1]->math_score + array[j + 1]->english_score + array[j + 1]->computer_score) {

                Student * temp = array[j];

                array[j] = array[j + 1];

                array[j + 1] = temp;

            }

        }

    }

    FILE * fp = fopen("sorted_by_score.dat", "w");

    fprintf(fp, "%-20s%-20s%-20s%-20s%-20s%-20s\n", "学号", "姓名", "数学成绩", "英语成绩", "计算机成绩", "平均成绩");

    for (int i = 0; i < count; i++) {

        fprintf(fp, "%-20s%-20s%-20.1f%-20.1f%-20.1f%-20.1f\n", array[i]->id, array[i]->name, array[i]->math_score, array[i]->english_score, array[i]->computer_score, array[i]->average);

    }

    printf("排序成功并已保存到sorted_by_score.dat文件中\n");

    fclose(fp);

}

// 计算每人三门课程的平均成绩并输出

void CalculateAverage(Student * head) {

    FILE * fp = fopen("average_score.dat", "w");

    fprintf(fp, "%-20s%-20s%-20s%-20s%-20s%-20s\n", "学号", "姓名", "数学成绩", "英语成绩", "计算机成绩", "平均成绩");

    Student * p = head;

    while (p != NULL) {

        fprintf(fp, "%-20s%-20s%-20.1f%-20.1f%-20.1f%-20.1f\n", p->id, p->name, p->math_score, p->english_score, p->computer_score, p->average);

        p = p->next;

    }

    printf("每人三门课程的平均成绩已计算并保存到average_score.dat文件中\n");

    fclose(fp);

}

// 按三门课程平均成绩排序并输出

void SortByAverageScoreAndOutput(Student * head) {

    Student * p = head;

    int count = 0;

    while (p != NULL) {

        count++;

        p = p->next;

    }

    Student ** array = (Student **)malloc(sizeof(Student *) * count); // 存储指针的数组

    p = head;

    for (int i = 0; i < count; i++) {

        array[i] = p;

        p = p->next;

    }

    for (int i = 0; i < count - 1; i++) { // 冒泡排序

        for (int j = 0; j < count - 1 - i; j++) {

            if (array[j]->average < array[j + 1]->average) {

                Student * temp = array[j];

                array[j] = array[j + 1];

                array[j + 1] = temp;

            }

        }

    }

    FILE * fp = fopen("sorted_by_average_score.dat", "w");

    fprintf(fp, "%-20s%-20s%-20s%-20s%-20s%-20s\n", "学号", "姓名", "数学成绩", "英语成绩", "计算机成绩", "平均成绩");

    for (int i = 0; i < count; i++) {

        fprintf(fp, "%-20s%-20s%-20.1f%-20.1f%-20.1f%-20.1f\n", array[i]->id, array[i]->name, array[i]->math_score, array[i]->english_score, array[i]->computer_score, array[i]->average);

    }

    printf("排序成功并已保存到sorted_by_average_score.dat文件中\n");

    fclose(fp);

}

// 统计三门课程各自的平均成绩,最高分,最低分,不及格人数,60~69分人数,70~79分人数,80~89分人数,90分以上人数

void Statistics(Student * head) {

    float math_total = 0;

    float english_total = 0;

    float computer_total = 0;

    int math_count = 0;

    int english_count = 0;

    int computer_count = 0;

    int fail_count = 0;

    int sixty_nine_count = 0;

    int seventy_nine_count = 0;

    int eighty_nine_count = 0;

    int ninety_count = 0;

    float math_max = 0;

    float english_max = 0;

    float computer_max = 0;

    float math_min = 100;

    float english_min = 100;

    float computer_min = 100;

    Student * p = head;

    while (p != NULL) {

        math_count++;

        math_total += p->math_score;

        if (p->math_score > math_max) math_max = p->math_score;

        if (p->math_score < math_min) math_min = p->math_score;

        english_count++;

        english_total += p->english_score;

        if (p->english_score > english_max) english_max = p->english_score;

        if (p->english_score < english_min) english_min = p->english_score;

        computer_count++;

        computer_total += p->computer_score;

        if (p->computer_score > computer_max) computer_max = p->computer_score;

        if (p->computer_score < computer_min) computer_min = p->computer_score;

        if (p->math_score < 60 || p->english_score < 60 || p->computer_score < 60) {

            fail_count++;

        } else if (p->math_score >= 60 && p->math_score <= 69 && p->english_score >= 60 && p->english_score <= 69 && p->computer_score >= 60 && p->computer_score <= 69) {

            sixty_nine_count++;

        } else if (p->math_score >= 70 && p->math_score <= 79 && p->english_score >= 70 && p->english_score <= 79 && p->computer_score >= 70 && p->computer_score <= 79) {

            seventy_nine_count++;

        } else if (p->math_score >= 80 && p->math_score <= 89 && p->english_score >= 80 && p->english_score <= 89 && p->computer_score >= 80 && p->computer_score <= 89) {

            eighty_nine_count++;

        } else if (p->math_score >= 90 && p->english_score >= 90 && p->computer_score >= 90) {

            ninety_count++;

        }

        p = p->next;

    }

    printf("数学平均成绩:%f\n", math_total / math_count);

    printf("数学最高分:%f\n", math_max);

    printf("数学最低分:%f\n", math_min);

    printf("英语平均成绩:%f\n", english_total / english_count);

    printf("英语最高分:%f\n", english_max);

    printf("英语最低分:%f\n", english_min);

    printf("计算机平均成绩:%f\n", computer_total / computer_count);

    printf("计算机最高分:%f\n", computer_max);

    printf("计算机最低分:%f\n", computer_min);

    printf("不及格人数:%d\n", fail_count);

    printf("60~69分人数:%d\n", sixty_nine_count);

    printf("70~79分人数:%d\n", seventy_nine_count);

    printf("80~89分人数:%d\n", eighty_nine_count);

    printf("90分以上人数:%d\n", ninety_count);

}

// 显示菜单

void ShowMenu() {

    printf("\n\n");

    printf("请输入数字选择对应功能:\n");

    printf("1. 添加一个学生\n");

    printf("2. 读取文件中的学生信息\n");

    printf("3. 保存学生信息到文件\n");

    printf("4. 按三门课程成绩排序并输出\n");

    printf("5. 计算每人三门课程的平均成绩并输出\n");

    printf("6. 按三门课程平均成绩排序并输出\n");

    printf("7. 统计各门课程成绩情况\n");

    printf("8. 根据姓名查询该学生各门课成绩\n");

    printf("9. 根据学号查询该学生各门课成绩\n");

    printf("0. 退出程序\n");

    printf("\n\n");

}

int main() {

    int choice = -1;

    Student * head = NULL; // 链表头指针

    while (choice != 0) {

        ShowMenu();

        scanf("%d", &choice);

        switch (choice) {

            case 1:

                Add(&head);

                break;

            case 2:

                Read(&head);

                break;

            case 3:

                Save(head);

                break;

            case 4:

                SortAndOutput(head);

                break;

            case 5:

                CalculateAverage(head);

                break;

            case 6:

                SortByAverageScoreAndOutput(head);

                break;

            case 7:

                Statistics(head);

                break;

            case 8:

                {

                    char name[20];

                    printf("请输入学生的姓名:");

                    scanf("%s", name);

                    Student * p = SearchByName(head, name);

                    if (p != NULL) {

                        printf("%-20s%-20s%-20s%-20s%-20s\n", "数学成绩", "英语成绩", "计算机成绩", "平均成绩", "总成绩");

                        printf("%-20.1f%-20.1f%-20.1f%-20.1f%-20.1f\n", p->math_score, p->english_score, p->computer_score, p->average, p->math_score + p->english_score + p->computer_score);

                    } else {

                        printf("没有找到该学生\n");

                    }

                    break;

                }

            case 9:

                {

                    char id[20];

                    printf("请输入学生的学号:");

                    scanf("%s", id);

                    Student * p = SearchById(head, id);

                    if (p != NULL) {

                        printf("%-20s%-20s%-20s%-20s%-20s\n", "数学成绩", "英语成绩", "计算机成绩", "平均成绩", "总成绩");

                        printf("%-20.1f%-20.1f%-20.1f%-20.1f%-20.1f\n", p->math_score, p->english_score, p->computer_score, p->average, p->math_score + p->english_score + p->computer_score);

                    } else {

                        printf("没有找到该学生\n");

                    }

                    break;

                }

            case 0:

                printf("程序已退出\n");

                break;

            default:

                printf("无效的输入,请重新输入\n");

                break;

        }

    }

    return 0;

}

(9)调试分析:

本程序主要的操作对象是记录数组,使用的存储结构是结构体数组。另外还有对C语言中关于文件的操作,这是本程序中的一个重点也是难点,是此程序出现问题的重要原因

问题:

  现象:文件创建读取问题。在将记录存入文件以后再从文件中读取时就出现错误。

原因:在使用fwrite和fread命令的时候函数的参数没有写正确。fwrite和fread命令的第一个参数是存储数据的首地址,如果没有地址就不正确,那么就不能正常地将数据存到文件中也不能正常地读取。

(10)运行结果:

根据姓名或学号查询某人的各门课程成绩,重名也要能处理。

 

 

计算每个人的平均成绩,按平均成绩排序,并生成文件。

 

 

求出各门课程的平均成绩,最高分,最低分,不及格人数,60~69分人数,70~79分人数,80~89分人数,90分以上人数。

 

通过键盘输入各学生的多门课程的成绩,建立相应的文件input.dat,按各门课程成绩排序,并生成相应的文件输出。

 

参考文献:

[1] 谭浩强. C程序设计(第2版).北京:清华大学出版社,2004

[2] 严蔚敏,吴伟民.数据结构.北京:清华大学出版社,2005

                                        2.《八皇后问题》

  • 课程设计概述

本次数据结构课程设计共完成5个题目:成绩分析问题,哈夫曼编码器,迷宫问题,八皇后问题,农夫过河问题的求解。

使用环境:C语言。

编译环境:Visual Studio 2022。

(1)实验内容:

八皇后问题。

(2)问题描述:

在8*8的棋盘上,放置8个皇后。要求使这八个皇后不能互相攻击,及每一横行,没一列,每一对角线上均只能放置一个皇后

(3)需求分析:

求出所有可能的方案,输出这些方案,并统计方案总数。

(4)概要设计:

--=ADT=--

{

bool is_valid(int row, int col)// 判断当前位置是否可以放置皇后

void display() // 输出当前皇后的位置

void dfs(int row)// 回溯算法求解

}

(5)存储结构:

int queen[BOARD_SIZE]; // 存储皇后的位置

int count = 0; // 记录方案数

(6)设计思路:

下面是解决八皇后问题的设计思路和关键算法:

1. **数据结构设计**:

   - 使用数组 `queen` 存储每一行皇后所在的列号。

   - 定义了整型变量 `count`,用于记录找到的方案数。

2. **合法性判断**:

   - `is_valid` 函数用于判断当前位置是否可以放置皇后。

   - 通过遍历已放置的皇后,检查是否在同一列或同一对角线上存在其他皇后。

   - 如果存在同一列或同一对角线上的皇后,则判断当前位置不合法;否则,判断当前位置合法。

3. **输出方案**:

   - `display` 函数用于输出当前皇后的位置。

   - 遍历 `queen` 数组,按行打印每个皇后所在的列号。

4. **回溯求解**:

   - `dfs` 函数使用回溯算法求解八皇后问题。

   - 初始调用 `dfs(0)`,从第 0 行开始搜索。

   - 在未放置的列中循环尝试放置皇后,如果当前位置合法,则将皇后放置在该位置,并递归调用 `dfs(row + 1)` 继续搜索下一行。

   - 当搜索到最后一行时,找到一个解,增加方案数并调用 `display` 输出当前皇后的位置。

   - 回溯到上一行时,将当前行的皇后位置重置为 -1。

5. **主函数**:

   - 在 `main` 函数中初始化 `queen` 数组,将每个元素初始化为 -1。

   - 调用 `dfs(0)` 开始求解八皇后问题。

   - 输出找到的方案数。

(7)详细设计:

#include <stdio.h>

#include <stdbool.h>

#include <stdlib.h>

#define BOARD_SIZE 8 // 棋盘大小为8*8

int queen[BOARD_SIZE]; // 存储皇后的位置

int count = 0; // 记录方案数

// 判断当前位置是否可以放置皇后

bool is_valid(int row, int col) {

    for (int i = 0; i < row; i++) {

        if (queen[i] == col || abs(row - i) == abs(col - queen[i]))

            return false; // 在同一列或同一对角线上已经有皇后

    }

    return true;

}

// 输出当前皇后的位置

void display() {

    for (int i = 0; i < BOARD_SIZE; i++)

        printf("%d ", queen[i]);

    printf("\n");

}

// 回溯算法求解

void dfs(int row) {

    if (row == BOARD_SIZE) { // 找到一个解

        count++;

        display();

    } else { // 在未放置的列中尝试放置皇后

        for (int col = 0; col < BOARD_SIZE; col++) {

            if (is_valid(row, col)) { // 如果当前位置可以放置皇后

                queen[row] = col;

                dfs(row + 1);

                queen[row] = -1; // 回溯

            }

        }

    }

}

int main() {

    // 初始化queen数组

    for (int i = 0; i < BOARD_SIZE; i++)

        queen[i] = -1;

    dfs(0); // 从第0行开始搜索

    printf("一共找到 %d 种方案\n", count);

    return 0;

}

(8)调试分析:

递归算法是解决本问题的关键,此问题难点在于如何把控递归函数的返回条件,是此程序出现问题的重要原因

一种条件是8个皇后放置完成后,返回成功,一种条件是该行中已经没有可以放置的位置,此时返回失败,需要重新放置返回

问题:

     现象:只出现一种解,没有列出所有的解法

     原因:递归函数的返回条件出现问题,一种条件是8个皇后放置完成后,返回成功,一种条件是该行中已经没有可以放置的位置,此时返回失败,需要重新放置返回上一层,将上一个导致本次放置失败的皇后进行清除,然后重新更新其位置,通过逐级放置、或逐级回溯可以达到遍历所有情况找到所有解应该选择第二种条件。

(9)运行结果:

 

求出所有可能的方案,输出这些方案,并统计方案总数。

参考文献:

[1] 谭浩强. C程序设计(第2版).北京:清华大学出版社,2004

[2] 严蔚敏,吴伟民.数据结构.北京:清华大学出版社,2005

                                       3.《迷宫问题》

  • 课程设计概述

本次数据结构课程设计共完成5个题目:成绩分析问题,哈夫曼编码器,迷宫问题,八皇后问题,农夫过河问题的求解。

使用环境:C语言。

编译环境:Visual Studio 2022。

(1)实验内容:

迷宫问题。

(2)问题描述:

以一个m*n(1<=m,n<=100)的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得出没有通路的结论。

(3)需求分析:

  1. 找出迷宫中的一条通路。求得的通路以三元组(i,j,d)的形式输出,其中:(i,j)指示迷宫中的一个坐标,d表示走到下一坐标的方向。若迷宫中没有通路,则输出“无解”。
  2. 找出迷宫中所有可能的通路。
  3. 如果有通路,找出最短通路。
  4. 以方阵形式输出迷宫及其通路。

(4)概要设计:

--=ADT=--

{

void stackinit(ST* p);

void stackpush(ST* p, datatype x);

datatype stacktop(ST* p);

void stackpop(ST* p);

int stacksize(ST* p);

bool stackempty(ST* p);

void stackdestroy(ST* p);

存储结构:

typedef PT datatype;//将数据类型改为结构体

typedef struct stack

{

    datatype* a;

    int top;

    int capacity;

}ST;

(5)设计思路:

下面是迷宫问题的设计思路和关键算法:

通过递归方式,不断地选择可通过的下一个位置,并将选择的路径保存在栈中,直到找到迷宫出口或者所有路径都尝试完毕。最终,根据栈中存储的路径信息,打印出正确的路径

1. 定义结构体和类型别名:

   - `PT`:表示迷宫中位置的结构体,包含行和列两个成员变量。

   - `datatype`:将数据类型改为结构体,即为 `PT` 类型。

   - `ST`:表示栈的结构体,包含一个存储元素的数组、栈顶指针和栈的容量。

2. 实现栈的相关操作:

   - `stackinit`:初始化栈。

   - `stackpush`:入栈操作。

   - `stackpop`:移除栈顶元素。

   - `stacktop`:获取栈顶元素。

   - `stackempty`:判断栈是否为空。

   - `stacksize`:获取栈中元素个数。

   - `stackdestroy`:销毁栈。

3. 实现判断迷宫中指定位置是否可以通过的函数:

   - `ispass`:根据给定的迷宫二维数组、迷宫的行数和列数以及当前位置,判断该位置是否可以通过。即判断当前位置是否在迷宫范围内,并且迷宫中当前位置是否为可通过的路径(值为0)。

4. 实现递归函数求解迷宫路径:

   - `getmazepath`:根据给定的迷宫二维数组、迷宫的行数和列数以及当前位置,通过递归方式求解迷宫路径。首先将当前位置入栈,然后判断当前位置是否为迷宫出口,如果是则返回真;否则,将当前位置标记为已访问,并依次判断上、下、左、右四个方向的位置是否满足继续递归的条件,如果满足则递归调用该函数。如果四个方向都不满足条件,则将当前位置出栈,并返回假。

5. 实现打印路径的函数:

   - `printpath`:根据给定的栈对象,将栈中的元素按正确顺序打印出来。由于栈先进后出的特性,需要使用一个新的栈将原始栈中的元素倒序存储,然后再从新栈中依次取出栈顶元素打印。

6. 主函数:

   - 在主函数中,首先读取迷宫的行数和列数。

   - 动态分配二维数组存储迷宫的数据。

   - 读取迷宫数据。

   - 初始化路径栈,并调用递归函数求解迷宫路径。

   - 如果找到了路径,调用打印路径的函数输出路径;否则,输出"没有通路"。

   - 销毁路径栈。

   - 释放动态分配的内存空间。

(6)详细设计:

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>

#include<stdlib.h>

#include<stdbool.h>

#include<assert.h>

typedef struct postion

{

    int row;//行

    int col;//列

}PT;

typedef PT datatype;//将数据类型改为结构体

typedef struct stack

{

    datatype* a;

    int top;

    int capacity;

}ST;

void stackinit(ST* p);

void stackpush(ST* p, datatype x);

datatype stacktop(ST* p);

void stackpop(ST* p);

int stacksize(ST* p);

bool stackempty(ST* p);

void stackdestroy(ST* p);

void stackinit(ST* p)//栈的初始化

{

    assert(p);

    p->a = NULL;

    p->top = 0;

    p->capacity = 0;

}

void stackpush(ST* p, datatype x)//入栈

{

    assert(p);

    if (p->top == p->capacity)

    {

        int newcapacity = p->capacity == 0 ? 4 : 2 * p->capacity;

        datatype* tmp = (datatype*)realloc(p->a, sizeof(datatype) * newcapacity);

        if (tmp != NULL)

        {

            p->a = tmp;

            p->capacity = newcapacity;

        }

    }

    p->a[p->top] = x;

    p->top++;

}

void stackpop(ST* p)//移除栈顶元素

{

    assert(p);

    assert(p->top > 0);

    p->top--;

}

datatype  stacktop(ST* p)//出栈

{

    assert(p);

    assert(p->top > 0);

    return p->a[p->top - 1];

}

bool  stackempty(ST* p)//是否为空

{

    return p->top == 0;

}

int stacksize(ST* p)//栈中元素个数

{

    assert(p);

    return p->top;

}

void stackdestroy(ST* p)//内存销毁

{

    assert(p);

    free(p->a);

    p->a = NULL;

    p->top = 0;

    p->capacity = 0;

}

 

 

 

bool ispass(int** maze, int N, int M, PT pos)

{

    if (pos.row >= 0 && pos.row < N && pos.col >= 0 && pos.col < M && maze[pos.row][pos.col] == 0)

    {   //坐标不越界并且该处位置==0

        return true;

    }

    return false;

}

ST path;

bool getmazepath(int** maze, int N, int M, PT cur)

{

    stackpush(&path, cur);//入栈

    if (cur.row == N - 1 && cur.col == M - 1)//找到出口就返回真

    {

        return true;

    }

    maze[cur.row][cur.col] = 2;//先将目前所处位置赋值为2

    PT next;

 

    next = cur;//上

    next.row -= 1;

    if (ispass(maze, N, M, next))//判断上的位置是否满足继续的条件

    {

        if (getmazepath(maze, N, M, next))//满足条件就递归

        {

            return true;//为了防止找到继续递归下去 返回真

        }

    }

 

    next = cur;//下

    next.row += 1;

    if (ispass(maze, N, M, next))//判断下的位置是否满足继续的条件

    {

        if (getmazepath(maze, N, M, next))//满足条件就递归

        {

            return true;//为了防止找到继续递归下去 返回真

        }

    }

 

    next = cur;//左

    next.col -= 1;

    if (ispass(maze, N, M, next))//判断左的位置是否满足继续的条件

    {

        if (getmazepath(maze, N, M, next))//满足条件就递归

        {

            return true;//为了防止找到继续递归下去 返回真

        }

    }

 

    next = cur;//右

    next.col += 1;

    if (ispass(maze, N, M, next))//判断右的位置是否满足继续的条件

    {

        if (getmazepath(maze, N, M, next))//满足条件就递归

        {

            return true;//为了防止找到继续递归下去 返回真

        }

    }

    stackpop(&path);   //如果上下左右都不满足就移除栈顶元素

    return false;//如果上下左右都不满足就返回false

 

}

void printpath(ST* ps)//由于此时的path栈要打印出来会倒着出,所以又重新创建了一个栈,将数据导进去

{

    ST rpath;

    stackinit(&rpath);

    while (!stackempty(&path))

    {

        stackpush(&rpath, stacktop(&path));

        stackpop(&path);

    }

    while (!stackempty(&rpath))

    {

        PT top = stacktop(&rpath);//此时数据类型被改为PT

        printf("(%d,%d)", top.row, top.col);

        printf("\n");

        stackpop(&rpath);

    }

    stackdestroy(&rpath);//内存销毁

}

int main()

{

    int N = 0;

    int M = 0;

    while (scanf("%d%d", &N, &M) != EOF)//多组输入

    {

        //动态开辟二维数组

        //1.开辟N个指针数组

        int** maze = (int**)malloc(sizeof(int*) * N);

        //2.开辟M个空间

        int i = 0;

        for (i = 0; i < N; i++)

        {

            maze[i] = (int*)malloc(sizeof(int) * M);

        }

 

        int j = 0;

        for (i = 0; i < N; i++)

        {

            for (j = 0; j < M; j++)

            {

                scanf("%d", &maze[i][j]);

            }

        }

        PT  entry = { 0,0 };

        stackinit(&path);

        if (getmazepath(maze, N, M, entry))

        {

            printpath(&path);//输出通路的路径

        }

        else

        {

            printf("没有通路\n");

        }

        stackdestroy(&path);

 

        //释放空间

        //1.释放N个数组指针指向的空间

        for (i = 0; i < N; i++)

        {

            free(maze[i]);

        }

        //2.将N个指针数组整体释放

        free(maze);

        maze = NULL;

    }

 

    return 0;

}

(7)调试分析

回溯法作为本题的重要算法,此问题难点在于如何让循环停止,不无限循环下去是此程序出现问题的重要原因

问题:

现象:程序一直循环,程序崩溃

原因:没有判断下一个的位置是否满足继续的条件,在回溯时导致重复循环,判断该条件后,如果满足则继续递归,返回true,否则false。

(8)运行结果:

 

找出迷宫中的一条通路。求得的通路以三元组(i,j,d)的形式输出,其中:(i,j)指示迷宫中的一个坐标,d表示走到下一坐标的方向。若迷宫中没有通路,则输出“无解”。

参考文献:

[1] 谭浩强. C程序设计(第2版).北京:清华大学出版社,2004

[2] 严蔚敏,吴伟民.数据结构.北京:清华大学出版社,2005

                             4.《农夫过河问题的求解》

  • 课程设计概述

本次数据结构课程设计共完成5个题目:成绩分析问题,哈夫曼编码器,迷宫问题,八皇后问题,农夫过河问题的求解。

使用环境:C语言。

编译环境:Visual Studio 2022。

(1)实验内容:

农夫过河问题的求解。

(2)问题描述:

一个农夫带着一只狼,一只羊和一颗白菜,身处河的南岸。他要把这些东西全部运到北岸。他面前只有一条小船,船只能容下他和一件物品。只有农夫才能乘船。如果农夫在场,则狼不能吃羊,羊不能吃白菜,否则狼会吃羊,羊会吃白菜,所以农夫不能留下羊和白菜自己离开,也不能留下狼和羊自己离开,而狼不吃白菜。

(3)需求分析:

编写程序求农夫将所有东西运过河的方案

--=ADT=--

{

int safe(int f, int w, int s, int v)//判断是否安全

int connected(int p, int q)// 判断顶点vx[p]和vx[q]是否连接

void create_graph()// 创建图

void print_path(int u, int v)// 输出方法

void dfs_path(int u, int v) //深度搜索路径

}

存储结构:

typedef struct {

  int farmer, wolf, sheep, vegetable;

} VERTEX;

VERTEX vx[MAX];

//而为数组存处有向图的邻接矩阵

int matrix[MAX][MAX], vnum, visited[MAX], path[MAX];

(4)设计思路:

下面是农夫过河问题的设计思路和关键算法:

1. **数据结构设计**:

   - 使用结构体 `VERTEX` 表示一个状态,其中包括了农夫、狼、羊和白菜的位置信息。

   - 定义了顶点数组 `vx`,用于存储所有可能的状态。

   - 声明了邻接矩阵 `matrix`,用于表示状态之间的连接关系。

   - 定义了标记数组 `visited`,用于记录顶点是否被访问过。

   - 声明了路径数组 `path`,用于存储路径信息。

2. **安全性判断**:

   - `safe` 函数用于判断当前状态是否安全。

   - 根据题目规则,如果农夫和羊不在同一岸边,并且(狼与羊在同一岸边,或者羊与白菜在同一岸边),则认为当前状态是不安全的。否则,认为是安全的。

3. **连接性判断**:

   - `connected` 函数用于判断两个顶点是否相连。

   - 根据题目规则,只有当农夫和另一样物体在同一岸边,并且其他物体在两个顶点中的位置相同的情况下,两个顶点才能相连。

4. **图的创建**:

   - `create_graph` 函数用于创建图。

   - 使用四重嵌套的循环遍历了所有可能的顶点状态,并利用 `safe` 函数判断当前状态是否安全。

   - 如果当前状态安全,则将该状态下各个物体的位置信息赋值给对应的顶点结构体 `vx[vnum]`,并增加 `vnum` 的值,表示顶点数量增加了一个。

   - 接下来,再次使用两重循环遍历每对顶点,通过调用 `connected` 函数判断两个顶点是否相连。如果两个顶点是相连的,则在邻接矩阵 `matrix` 中设置对应位置的值为 1,表示它们之间有一条边;否则,将对应位置的值设置为 0。

5. **深度优先搜索**:

   - `dfs_path` 函数用于进行深度优先搜索,并找到从起点到终点的路径。

   - 先将起点标记为已访问。

   - 遍历与当前节点相连的未访问过的节点,将其添加到路径中,并以该节点作为新的起点递归调用 `dfs_path` 函数。

   - 通过递归的方式,不断向前搜索路径,直到回溯到起点节点。

6. **输出路径**:

   - `print_path` 函数用于输出路径。

   - 从终点节点开始回溯,沿着路径向前输出节点,直到回溯到起点节点。

7. **主函数**:

   - 在 `main` 函数中调用 `create_graph` 创建图,然后初始化标记数组 `visited` 。

   - 设置起点和终点的索引,并调用 `dfs_path` 进行深度优先搜索。

   - 如果终点被访问过,则输出路径。

(5)详细设计:

#include "stdio.h"

#define MAX 20

typedef struct {

  int farmer, wolf, sheep, vegetable;

} VERTEX;

VERTEX vx[MAX];

//而为数组存处有向图的邻接矩阵

int matrix[MAX][MAX], vnum, visited[MAX], path[MAX];

//判断是否安全

int safe(int f, int w, int s, int v)

{

  if (f != s &&

      (w == s ||

       s == v)) //当农夫和羊,(羊或者白菜,浪或者羊中的一种),不再同一岸边时

    return (0);

  else

    return (1);

}

int connected(int p, int q) { // 判断顶点vx[p]和vx[q]是否连接

  int k = 0;

  if (vx[p].wolf != vx[q].wolf)

    k++;

  if (vx[p].sheep != vx[q].sheep)

    k++;

  if (vx[p].vegetable != vx[q].vegetable)

    k++;

  if (vx[p].farmer != vx[q].farmer && k <= 1) {

    return (1);

  } else

    return (0);

}

void create_graph() { // 创建图

  int i, j, f, w, s, v;

  vnum = 0;

  for (f = 0; f <= 1; f++)

    for (w = 0; w <= 1; w++)

      for (s = 0; s <= 1; s++)

        for (v = 0; v <= 1; v++)

          if (safe(f, w, s, v) == 1) {

            vx[vnum].farmer = f;

            vx[vnum].wolf = w;

            vx[vnum].sheep = s;

            vx[vnum].vegetable = v;

            vnum++;

          }

  for (i = 0; i < vnum; i++)

    for (j = 0; j < vnum; j++)

      if (connected(i, j) == 1) {

        matrix[i][j] = 1;

      } else

        matrix[i][j] = 0;

  return;

}

void print_path(int u, int v) { // 输出方法

  int k = u;

  while (k != v) {

    printf("%d %d %d %d\n", vx[k].farmer, vx[k].wolf, vx[k].sheep,

           vx[k].vegetable);

    k = path[k];

  }

  printf("%d %d %d %d\n", vx[k].farmer, vx[k].wolf, vx[k].sheep,

         vx[k].vegetable);

}

void dfs_path(int u, int v) {

  int j;

  visited[u] = 1;

  for (j = 0; j < vnum; j++)

    if (matrix[u][j] == 1 && visited[j] == 0 && visited[v] == 0) {

      path[u] = j;

      dfs_path(j, v);

    }

}

int main() {

  int i, j;

  create_graph();

  for (i = 0; i < vnum; ++i)

    visited[i] = 0;

  i = 0;

  j = vnum - 1;

  dfs_path(i, j);

  if (visited[j] == 1) {

    printf(">>>>the path is >>>\n");

    print_path(i, j);

  }

  getchar();

}

(6)调试分析:

本题主要采用构建农夫,狼,羊,白菜的图,并进行图的深度优先遍历,本题中对图的创建和深度搜索是此程序出现问题的重要原因

问题:

  现象:程序运行结果只有最后状态农夫狼羊白菜,没有列出过程。

  原因:每一次深度搜索成功的时候没有将结果打印出来,导致最后只有最终状态。

(7)运行结果:

求农夫将所有东西运过河的方案

 

参考文献:

[1] 谭浩强. C程序设计(第2版).北京:清华大学出版社,2004

[2] 严蔚敏,吴伟民.数据结构.北京:清华大学出版社,2005

                                   5.《哈夫曼编码器》

  • 课程设计概述

本次数据结构课程设计共完成5个题目:成绩分析问题,哈夫曼编码器,迷宫问题,八皇后问题,农夫过河问题的求解。

使用环境:C语言。

编译环境:Visual Studio 2022。

(1)实验内容:

哈夫曼编码器

(2)问题描述:

设计并实现一个哈夫曼码的编译码系统

(3)需求分析:

初始化:从终端读入字符集大小n,及n个字符和m个权值,建立哈夫曼树,并将其保存在磁盘huffman文件中

编码:利用已建好的哈夫曼树对待发送电文(读取来自文件tobetrans.dat)进行编码,然后将结果保存在磁盘文件codefile中。

解码:利用已建好的哈夫曼树,对文件codefile中代码进行译码,结果存入文件textfile中。

打印代码文件:将文件codefile显示在终端上,每行50个代码,同时将此字符形式的编码文件写入文件codefile中。

(4)概要设计:

--=ADT=--

{

int read_input(HuffNode nodes[])输入字符集及其权值

void init_tree(HuffTree tree[], int n)初始化哈夫曼树

void build_tree(HuffTree tree[], HuffNode nodes[], int n)建立哈夫曼树

void get_code(HuffTree tree[], HuffNode nodes[], int n)  获取哈夫曼编码

void encode_file(HuffNode nodes[], int n, char* filename)编码文件

void decode_file(HuffTree tree[], HuffNode nodes[], int n, char* infile, char* outfile) 解码文件

}

(5)存储结构:

// 结构体:哈夫曼树节点

typedef struct {

    char ch; // 字符

    int weight; // 权值

    char code[CODE_MAX_LEN]; // 编码

} HuffNode;

// 结构体:哈夫曼树

typedef struct {

    int lchild, rchild, parent; // 左子树、右子树、父节点位置

    int weight; // 权值

} HuffTree;

(6)设计思路:

该代码实现了哈夫曼编码和解码的功能。下面是代码的设计思路和关键算法

1. 结构体的定义:

   - HuffNode:保存字符、权值以及编码。

   - HuffTree:保存哈夫曼树节点的信息,包括左子树、右子树、父节点位置和权值。

2. read_input函数:用于读取字符集及其权值。

   - 从用户输入中获取字符集的大小n。

   - 循环n次,读取每个字符和对应的权值,并保存到HuffNode数组中。

3. init_tree函数:用于初始化哈夫曼树。

   - 遍历HuffTree数组,将每个节点的成员变量初始化为默认值(-1或0)。

4. build_tree函数:用于构建哈夫曼树。

   - 首先将叶子节点的权值初始化为对应字符的权值。

   - 循环n-1次,每次找到权值最小和次小的两个节点,并合并它们构成新的节点。

   - 将新节点的父节点设置为n+i,左子树和右子树分别设置为权值最小和次小的节点。

   - 更新新节点的权值为合并后的权值。

5. get_code函数:用于获取哈夫曼编码。

   - 从根节点开始,向下遍历每个叶子节点,生成对应叶子节点的编码。

   - 编码的生成通过从叶子节点往上遍历,如果该节点为左子树,则编码为'0',否则为'1'。

   - 将编码保存到对应的HuffNode节点的code数组中。

6. encode_file函数:用于编码文件。

   - 打开输入文件和输出文件。

   - 逐个读取输入文件的字符,查找对应字符的编码,并将编码写入输出文件。

7. decode_file函数:用于解码文件。

   - 打开输入文件和输出文件。

   - 初始化解码起始位置为根节点的位置。

   - 逐个读取输入文件的字符,根据字符值('0'或'1')选择左子树或右子树,并更新当前位置。

   - 如果当前位置为叶子节点,则将对应字符写入输出文件,并将当前位置重置为根节点。

8. 主函数main:

   - 声明并定义HuffNode数组和HuffTree数组。

   - 调用read_input函数读取字符集及其权值。

   - 调用init_tree函数初始化哈夫曼树。

   - 调用build_tree函数构建哈夫曼树。

   - 调用get_code函数获取哈夫曼编码。

   - 调用encode_file函数编码文件。

   - 调用decode_file函数解码文件。

该代码通过构建哈夫曼树来实现字符编码和解码的功能,可以通过输入字符集及其权值和指定的文件来进行编码和解码操作。

(7)详细设计:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define MAX_N 100 // 字符集大小上限

#define CODE_MAX_LEN 20 // 编码长度上限

// 结构体:哈夫曼树节点

typedef struct {

    char ch; // 字符

    int weight; // 权值

    char code[CODE_MAX_LEN]; // 编码

} HuffNode;

// 结构体:哈夫曼树

typedef struct {

    int lchild, rchild, parent; // 左子树、右子树、父节点位置

    int weight; // 权值

} HuffTree;

// 输入字符集及其权值

int read_input(HuffNode nodes[]) {

    int n;

    printf("Please enter the character set size: ");

    scanf("%d", &n);

    printf("Please enter the characters and their weights:\n");

    for (int i = 0; i < n; i++) {

        printf("[%d]: ", i + 1);

        getchar();

        scanf("%c%d", &nodes[i].ch, &nodes[i].weight);

    }

    return n;

}

// 初始化哈夫曼树

void init_tree(HuffTree tree[], int n) {

    for (int i = 0; i < 2 * n - 1; i++) {

        tree[i].lchild = -1;

        tree[i].rchild = -1;

        tree[i].parent = -1;

        tree[i].weight = 0;

    }

}

// 建立哈夫曼树

void build_tree(HuffTree tree[], HuffNode nodes[], int n) {

    // 初始化叶子节点

    for (int i = 0; i < n; i++) {

        tree[i].weight = nodes[i].weight;

    }

    // 构建哈夫曼树

    int min1, min2, pos1, pos2;

    for (int i = 0; i < n - 1; i++) {

        min1 = min2 = __INT_MAX__;

        pos1 = pos2 = -1;

        // 找到权值最小和次小的两个节点

        for (int j = 0; j < n + i; j++) {

            if (tree[j].parent == -1) { // 如果该节点未被处理过

                if (tree[j].weight <= min1) {

                    min2 = min1;

                    pos2 = pos1;

                    min1 = tree[j].weight;

                    pos1 = j;

                } else if (tree[j].weight <= min2) {

                    min2 = tree[j].weight;

                    pos2 = j;

                }

            }

        }

        // 合并两个节点,构成新的节点

        tree[pos1].parent = n + i;

        tree[pos2].parent = n + i;

        tree[n + i].weight = min1 + min2;

        tree[n + i].lchild = pos1;

        tree[n + i].rchild = pos2;

    }

}

// 获取哈夫曼编码

void get_code(HuffTree tree[], HuffNode nodes[], int n) {

    char code[CODE_MAX_LEN];

    int top;

    for (int i = 0; i < n; i++) {

        top = CODE_MAX_LEN - 1;

        int p = i;

        while (tree[p].parent != -1) { // 从叶子节点往上遍历

            if (tree[tree[p].parent].lchild == p) { // 如果该节点是左子树

                code[--top] = '0';

            } else { // 如果该节点是右子树

                code[--top] = '1';

            }

            p = tree[p].parent;

        }

        strcpy(nodes[i].code, &code[top]);

    }

}

// 编码文件

void encode_file(HuffNode nodes[], int n, char* filename) {

    FILE* fin = fopen(filename, "r");

    if (fin == NULL) {

        printf("Failed to open file.\n");

        exit(1);

    }

    FILE* fout = fopen("codefile", "w");

    if (fout == NULL) {

        printf("Failed to open file.\n");

        exit(1);

    }

    char ch;

    while ((ch = fgetc(fin)) != EOF) {

        for (int i = 0; i < n; i++) {

            if (nodes[i].ch == ch) {

                fprintf(fout, "%s", nodes[i].code);

                break;

            }

        }

    }

    fclose(fin);

    fclose(fout);

    printf("Code file has been saved to 'codefile'.\n");

}

// 解码文件

void decode_file(HuffTree tree[], HuffNode nodes[], int n, char* infile, char* outfile) {

    FILE* fin = fopen(infile, "r");

    if (fin == NULL) {

        printf("Failed to open file.\n");

        exit(1);

    }

    FILE* fout = fopen(outfile, "w");

    if (fout == NULL) {

        printf("Failed to open file.\n");

        exit(1);

    }

    int p = 2 * n - 2;

    char ch;

    while ((ch = fgetc(fin)) != EOF) {

        if (ch == '0') {

            p = tree[p].lchild;

        } else {

            p = tree[p].rchild;

        }

        if (tree[p].lchild == -1 && tree[p].rchild == -1) { // 如果已到达叶子节点

            fprintf(fout, "%c", nodes[p].ch);

            p = 2 * n - 2;

        }

    }

    fclose(fin);

    fclose(fout);

    printf("Decoded file has been saved to '%s'.\n", outfile);

}

// 主函数

int main() {

    HuffNode nodes[MAX_N];

    HuffTree tree[MAX_N * 2 - 1];

    int n = read_input(nodes); // 读入字符集及其权值

    init_tree(tree, n); // 初始化哈夫曼树

    build_tree(tree, nodes, n); // 构建哈夫曼树

    get_code(tree, nodes, n); // 获取哈夫曼编码

    encode_file(nodes, n, "datafile"); // 编码文件

    decode_file(tree, nodes, n, "codefile", "outputfile"); // 解码文件

    return 0;

}

(8)调试分析:

哈夫曼编码问题其难点在于哈夫曼树的建立和生成哈夫曼编码表,这也是程序易出现错误的原因。

问题:

   现象:生成哈夫曼编码表时字符编码重复,解码错误。

   原因:权值相同的节点没有按照特定顺序进行排序,或者在创建父节点时没有正确处理权值相等。

(9)运行结果:

初始化:从终端读入字符集大小n,及n个字符和m个权值,建立哈夫曼树,并将其保存在磁盘huffman文件中

 

编码:利用已建好的哈夫曼树对待发送电文(读取来自文件tobetrans.dat)进行编码,然后将结果保存在磁盘文件codefile中。

 

解码:利用已建好的哈夫曼树,对文件codefile中代码进行译码,结果存入文件textfile中。

 

参考文献:

[1] 谭浩强. C程序设计(第2版).北京:清华大学出版社,2004

[2] 严蔚敏,吴伟民.数据结构.北京:清华大学出版社,2005

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

buyehou_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值