数据结构/C语言_单链表_图书信息管理系统

图书管理系统(顺序表)的姊妹篇来了。

这次是用单链表实现的。

数据结构 严蔚敏 第2章习题答案

1、结构体定义

结构体的数据域依然是ISBN、书名和书的价格。

和顺序表的不同是,每个结点除了数据域之外,
还需要存储一个指针(指针域)。

每个结点的结构类型都是——struct BookList,因此,指向结点的指针类型,便是struct BookList* 。

typedef struct BookList
{
    int isbn;
    char bookName[20];
    int price;
    struct BookList* next;  //指向直接后继
}Node, * List;

2、单链表的创建

这个函数称为CreateList。这很合理。

为了知道创建是否成功,把函数定义为bool类型。

返回值是True 或者 False。

  • 返回值为true - 创建链表成功。
  • 返回值为false - 创建失败。

传进函数两个参数。

  • 链表L //事实上,我们把头结点的名字就称为链表的名字
  • n - 需要创建的结点总数。

bool CreateList(List& L, int n)// L - 链表头结点,n - 结点总数

函数体内创建一个for循环,循环次数由结点总数n控制。

for (int i = 0; i < n; ++i) // Q:这和 i++有区别吗?

在循环体中创建一个新的结点book,

Node* book = new Node;

将用户的输入逐个保存到结点book里。

std::cout << “正在添加第” << i + 1 << “本图书--------------\n”;
printf(“请输入图书的ISBN,以回车结束:”);// 这里可以进行ISBN查重
std::cin >> book->isbn;
printf(“请输入书名,以回车结束:”);
std::cin >> book->bookName;
printf(“请输入图书价格,以回车结束:”);
std::cin >> book->price;

有心人可以关注到我在代码里写的注释——“ISBN查重”。这句话是什么意思呢?

让我们联想一下,在以前数据库课程的学习中,一张图书表的主键往往是谁?

主键是书的名字?中文书名包含有中文字符,换一个国家又需要换一种语言。作为主键着实是负担。

主键是书的价格?来看看全世界一共出版过的书的数量。而这个数字还不断的在更新。

谷歌图书终于数出——全世界总共出版过1 2986 4880本书!

要让每本书的定价都不一样,假设用2进制表示,则大约需要2的27次方=1 3421 7728,才能表示完所有图书的价格。

而且,如果书打折了,怎么办?

如果你想到的是图书的ISBN,那就对了。

因此,可以对用户输入的ISBN进行查重检测,如果链表里存在相同ISBN的结点,则提示。伪代码描述如下。

if (Search(L, isbn) != NULL)// 如果找到了相同ISBN的一个结点
{
printf(“已存在相同ISBN的书籍,请重新输入\n”);
return;
}

创建单链表中,最核心的两句代码如下:

// 重点理解这两句指令
book->next = L->next;
L->next = book;

这两句代码,是用来改变上一个结点和新结点的指针域。大概可以这么理解:

  • 新节点(book) 指向 上一个结点后面的结点
  • 上一个节点 指向 新节点(book)

根据以上思路,最终得到创建单链表的函数:

bool CreateList(List& L, int n)// L - 链表的头结点,n - 结点总数
{
    for (int i = 0; i < n; ++i)
    {
        Node* book = new Node;
        std::cout << "正在添加第" << i + 1 << "本图书--------------\n";
        printf("请输入图书的ISBN,以回车结束:");// 这里可以进行ISBN查重
        std::cin >> book->isbn;
        printf("请输入书名,以回车结束:");
        std::cin >> book->bookName;
        printf("请输入图书价格,以回车结束:");
        std::cin >> book->price;

        // 重点理解这两句指令
        book->next = L->next;
        L->next = book;
    }
    printf("\n\t结束添加\n");
    return true;
}

运行结果

  • 提示界面
    在这里插入图片描述

  • 添加图书
    在这里插入图片描述

  • 打印图书信息
    从运行结果也可以看出,创建单链表用的是头插法。
    在这里插入图片描述

  • 插入图书
    默认插在输入序号的位置。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 查找图书
    在这里插入图片描述

  • 修改图书信息
    有ISBN重复的情况就会提示无法编辑。在这里插入图片描述
    没有找到重复的ISBN就可以修改成功。在这里插入图片描述

  • 删除图书
    在这里插入图片描述

完整代码

好了,不废话了。放一下完整代码。

#include <iostream>
typedef struct BookList
{
    int isbn;
    char bookName[20];
    int price;
    struct BookList* next;  //指向直接后继
}Node, * List;

void InitList(List &L)
{
    Node* head = new Node;// *head - 头结点; head - 指向头结点的指针
    L = head;// 头指针L 指向头结点head
    L->next = NULL;
    printf("初始化成功\n");
}

bool CreateList(List& L, int n)
{
    for (int i = 0; i < n; ++i)
    {
        Node* book = new Node;
        std::cout << "正在添加第" << i + 1 << "本图书--------------\n";
        printf("请输入图书的ISBN,以回车结束:");// 这里可以进行ISBN查重
        std::cin >> book->isbn;
        printf("请输入书名,以回车结束:");
        std::cin >> book->bookName;
        printf("请输入图书价格,以回车结束:");
        std::cin >> book->price;

        // 重点理解这两句指令
        book->next = L->next;
        L->next = book;
    }
    printf("\n\t结束添加\n");
    return true;
}

void Display(List& L)
{
    if (L->next == NULL) 
    {
        printf("链表为空!\n");
        return;
    }
    int i = 0;
    Node* p = L->next;
    while (p)
    {
        i++;
        std::cout << "序号 " << i << " :"
            << " ISBN:" << p->isbn
            << " 书名:" << p->bookName
            << " 价格:" << p->price << "\n";
        p = p->next;
    }
}

int Count(List& L)
{
    int num = 0;
    Node* p = L->next;
    while (p)
    {
        p = p->next;
        num++;
    }
    return num;
}

bool Insert(List& L, int i)
{
    if (i<1 || i>Count(L) + 1)
        return false;

    Node* book = new Node;
    printf("请输入图书的ISBN,以回车结束:");// 这里也可以ISBN查重
    std::cin >> book->isbn;
    printf("请输入书名,以回车结束:");
    std::cin >> book->bookName;
    printf("请输入图书价格,以回车结束:");
    std::cin >> book->price;

    int j = 0;
    Node* p = L;// P指向要插入结点的前驱结点
    while (p->next && j != i-1)
    {
        j++;
        p = p->next;
    }
    book->next = p->next;
    p->next = book;
    return true;
}

Node* SearchElem(List& L, int isbn)
{
    int j = 0;
    Node* p = L;
    while (p->isbn != isbn && p->next)
    {
        p = p->next;
        j++;
    }
    if (p->isbn == isbn)
    {
        return p;
    }
    else
    {
        return NULL;
    }
}

bool Edit(List& L, int i)
{
    if (Count(L) < i || i < 1)// 输入的序号 大于 书籍总数
        return false;

    int j = 0;
    Node* p = L;
    while (i != j && p->next)
    {
        p = p->next;
        j++;
    }

    Node* q = new Node;
    printf("请输入图书的ISBN,以回车结束:");
    std::cin >> q->isbn;
    if (SearchElem(L, q->isbn) != NULL)// ISBN是书籍的主键 需要查重
    {
        printf("已存在相同ISBN的书籍,请重新输入\n");
        return false;
    }

    printf("请输入书名,以回车结束:");
    std::cin >> p->bookName;
    printf("请输入图书价格,以回车结束:");
    std::cin >> p->price;
    p->isbn = q->isbn;
    return true;
}

bool Delete(List& L, int i)
{
    if (Count(L) < i)
        return false;

    int j = 0;
    Node* p = L;
    while (p->next && j != i - 1)
    {
        p = p->next;
        j++;
    }
    if (j > i - 1)
    {
        printf("删除失败\n");
        return false;
    }
    Node* q = p->next;// 为什么需要q呢 因为要释放的是p的下一个结点
    p->next = p->next->next;// 也可以写成p->next = q->next;
    delete q;// 如果写delete p->next,接着直接删除第1条书籍,由于后续结点找不到了,则程序意外终止。
    return true;
}

void main()
{
    int code;// 操作码 1 - 添加图书 2 - 打印图书信息 3 - 插入图书 4 - 查找图书 5 - 删除图书 6 - 修改图书信息
    List L;
    int bookID;// 图书序号
    int bookNum;// 图书数量

    printf("******************************************\n\t欢迎来到图书管理系统\n");
    InitList(L);
    do
    {
        //这句可以放循环体外面。放在循环体里面,是为了方便查看操作码含义,减少使用者的记忆负担
        std::cout << "\n\t0 - 退出系统\n\t1 - 添加图书\n\t2 - 打印图书信息"
            << "\n\t3 - 插入图书\n\t4 - 查找图书\n\t5 - 删除图书\n\t6 - 修改图书信息"
            << "\n******************************************"
            << "\n\t请输入操作代码,以回车结束:";
        std::cin >> code;
        switch (code)
        {
        case 0:
            printf("\n\t退出系统\n");
            break;
        case 1:
            printf("\n添加图书\n\n请输入图书数量:");
            std::cin >> bookNum;
            if (bookNum <= 0)
                break;
            CreateList(L, bookNum);
            break;
        case 2:
            Display(L);
            break;
        case 3:
            printf("请输入图书插入的序号,以回车结束。\n");
            std::cin >> bookID;
           /* if (bookID == 0)
            {
                printf("\n\t图书插入操作结束\n");
                break;
            }*/
            if (Insert(L, bookID) == false)
            {
                printf("插入位置非法\n");
                continue;
            }
            Display(L);// 输出当前图书信息
            printf("\n\t图书插入操作结束\n");
            break;
        case 4:
            printf("请输入需要查找的 ISBN,以回车结束。\n");
            std::cin >> bookID;// 这里bookID 存储的值是 ISBN

            if (SearchElem(L, bookID) == NULL)
            {
                printf("图书未找到\n");
            }
            else 
            {
                Node* q = SearchElem(L, bookID);
                std::cout << "图书找到了----------"
                    //<< "序号 "  << j << " : "
                    << " ISBN:" << q->isbn
                    << " 书名:" << q->bookName
                    << " 价格:" << q->price << "\n";
            }
            printf("\n\t查找结束\n");
            break;
        case 5:
            if (Count(L) == 0) {
                printf("图书表为空\n");
                break;
            }

            printf("请输入需要删除的图书序号,以回车结束。\n");
            std::cin >> bookID;
           /* if (bookID == 0)
                break;*/
            if (Delete(L, bookID) == false)// 这里已经执行Delete语句 并得到返回值
            {
                printf("图书无法删除\n");
                continue;
            }
            Display(L);
            printf("\n\t图书删除操作结束\n");
            continue;
        case 6:
            if (Count(L) == 0) {
                printf("图书表为空\n");
                break;
            }

            printf("请输入需要编辑的图书序号,以回车结束。\n");
            std::cin >> bookID;
            if (Edit(L, bookID) == false)
            {
                printf("图书无法编辑\n");
                continue;
            }
            Display(L);
            printf("\n\t图书编辑操作结束\n");
            continue;
        default:
            printf("\n\t操作码无效\n");
            break;
        }
    } while (code != 0);

    printf("\n感谢使用\n");
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值