7/19作业

 一、链表的引入

1.1 总结顺序表的优缺点

1> 优点:能够直接通过下标进行定位元素,访问效率高,对元素进行查找和修改比较快

2> 不足:插入和删除元素需要移动大量的元素,效率较低

3> 缺点:存储数据元素有上限,当达到MAX后,就不能再添加元素了

1.2 链表的概念

1> 链式存储的线性表叫做链表

1、链式存储:表示数据元素的存储地址不一定连续

2、线性表:数据元素之间存在一对一的关系

2> 链表的原理图如下

3> 链表的基础概念

1、节点:节点是链表的基本单位,由数据域和指针域组成

2、数据域:存放数据元素的部分

3、指针域:存放下一个节点地址的部分

4、前驱节点:当前节点的上一个节点

5、后继节点:当前节点的下一个节点

6、头节点:虚设的一个节点,数据域不存放数据元素,可以存放链表的长度

7、头指针:指向第一个节点的指针称为头指针

8、第一个节点:实际存储数据元素的链表上的第一个节点

注意:头节点的指针域其实就是头指针,也可以单独定义一个指针,指向第一个节点

4> 链表的分类

1、单向链表:只能从头节点或第一个节点出发,单向访问其后继节点的链表称为单向链表 2、双向链表:从头部出发,既可以访问前驱节点,也可以访问后继节点

3、循环链表:首尾相接的链表称为循环链表

二、单向链表

只能从头节点或第一个节点出发,单向访问其后继节点的链表称为单向链表

2.1 节点结构体类型

1> 头节点和普通节点数据域可以合到一起,使用一格共用体表示

2> 指针域都是指向普通节点的地址

//定义数据类型
typedef int datatype;

//定义结点类型
typedef struct Node
{
    union
    {
        int len;    //头结点数据域
        datatype data;  //普通结点数据域
    };

    struct Node *next;   //指针域
};

2.2 创建链表

1> 在堆区申请一格头节点的空间,就创建了一个链表

2> 需要对头节点的数据域初始化链表长度,指针域初始化NULL

//创建链表
NodePtr list_create()
{
    //只需要在堆区申请一个头结点
    NodePtr L = (NodePtr)malloc(sizeof(Node));
    if(NULL == L)
    {
        printf("创建失败\n");
        return NULL;
    }

    //程序执行至此,说明头结点创建结束
    L->len = 0;    //表示链表长度为0
    L->next = NULL;      ///防止野指针

    printf("链表创建成功\n");
    return L;
}

2.3 申请节点封装数据

1> 需要将要封装的数据当做函数的参数进行传递

2> 同样在堆区申请节点,就传入的数据放入数据域

//申请结点封装数据函数
NodePtr apply_node(datatype e)
{
    //在堆区申请一个结点的大小
    NodePtr p = (NodePtr)malloc(sizeof(Node));
    if(NULL == p)
    {
        printf("结点申请失败\n");
        return NULL;
    }

    //给结点内容赋值
    p->data = e;          //数据域赋值
    p->next = NULL;        //指针域

    return p;
}

2.4 链表判空

1> 只需要判断头节点的指针域中是否为空即可

//链表判空
int list_empty(NodePtr L)
{
    return L->next == NULL;
}

2.5 头插

1> 表示将新插入的节点放入第一个节点中

2> 插入数据时,不能先将前面节点与后面节点先断开。一定要从新节点出发,指向后面的节点,然后将前驱节点指向字节

//头插
int list_insert_head(NodePtr L, datatype e)
{
    //判断逻辑
    if(NULL==L)
    {
        printf("链表不合法\n");
        return -1;
    }

    //申请结点封装数据
    NodePtr p = apply_node(e);
    if(NULL==p)
    {
        return -1;
    }

    //头插逻辑
    p->next = L->next;
    L->next = p;
    
    
    //表的变化
    L->len ++;
    printf("头插成功\n");
    return 0;
}

2.6 链表遍历

需要使用一个遍历指针,将每一个节点进行遍历一遍,如果该指针指向的节点不为空,就访问其数据域,向后偏移

//链表遍历函数
int list_show(NodePtr L)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("遍历失败\n");
        return -1;
    }
    
    printf("链表中的元素分别是:");
    //遍历逻辑
    NodePtr q = L->next;   //定义遍历指针从第一个结点出发
    while(q != NULL)
    {
        //输出数据域
        printf("%d\t", q->data);

        q = q->next;    //指针向后偏移一个
    }
}

2.7 通过位置查找节点

1> 参数:链表、位置

2> 返回值:对应节点的地址

//通过位置查找结点
NodePtr list_search_pos(NodePtr L, int pos)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<0 || pos>L->len)
    {
        printf("查找失败\n");
        return NULL;
    }

    //查找逻辑
    //定义遍历指针从头结点出发
    NodePtr q = L;
    for(int i=0; i<pos; i++)
    {
        q = q->next;
    }

    return q;     //将找到的结点地址返回
}

2.8 任意位置插入元素

1> 参数:链表、位置、要插入的元素

2> 返回值:int

3> 注意:必须找到要插入位置的节点的前驱节点,将前驱节点当作头节点,进行头插操作

//任意位置插入
int list_insert_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if(NULL==L || pos<1 || pos>L->len+1)
    {
        printf("插入失败\n");
        return -1;
    }
    
    //申请结点封装数据
    NodePtr p = apply_node(e);
    if(NULL==p)
    {
        return -1;
    }
    
    //调用函数查找前驱结点
    NodePtr q = list_search_pos(L, pos-1);
    
    //插入逻辑
    p->next = q->next;
    q->next = p;
    
    //表的变化
    L->len++;
    printf("插入成功\n");
    return 0;
}

2.9 链表头删

1> 参数:链表

2> 返回值:int

3> 注意:需要将要删除的节点先标记一下,头节点的指针,指向第二个节点后,将标记的节点释放

//链表头删
int list_delete_head(NodePtr L)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("删除失败\n");
        return -1;
    }
    
    //删除三部曲
    NodePtr p = L->next;    //标记
    L->next = p->next;  //L->next->next  孤立
    free(p);            //释放
    p = NULL;

    //表长变化
    L->len--;

    printf("头删成功\n");
    return 0;
}

2.10 任意位置删除函数

1> 参数:链表、要删除的位置

2> 返回值:int

3> 注意:需要找到要删除的节点的前驱节点,将其当作头节点,进行头删逻辑

//链表任意位置删除
int list_delete_pos(NodePtr L, int pos)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<1 || pos>L->len)
    {
        printf("删除失败\n");
        return -1;
    }
    
    //找到前驱结点
    NodePtr q = list_search_pos(L, pos-1);
    
    //删除逻辑
    NodePtr p = q->next;           //标记
    q->next = q->next->next;   //p->next 孤立
    free(p);                   //释放
    p = NULL;

    
    //表的变化
    L->len--;
    printf("删除成功\n");
    return 0;
}

2.11 按值查找返回位置

1> 参数:链表、要查找的值

2> 返回值:元素在链表中的位置

//链表按值查找返回位置
int list_search_value(NodePtr L, datatype e)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("查找失败\n");
        return -1;
    }
    
    //查找逻辑
    //定义遍历指针从第一个结点出发
    NodePtr q = L->next;
    for(int index=1; index<=L->len; index++)
    {
        //判断当前结点的值是否为要找的数据
        if(q->data == e)
        {
            return index;
        }

        q = q->next;     //继续向后遍历
    }

    //程序执行至此,表示没找到
    printf("没找到\n");
    return -1;
}

2.12 按位置修改

1> 参数:链表、要修改的元素位置、要被更新的值

2> 返回值:int

3> 注意:先通过位置,找到对应的元素,更改该元素中的内容即可

//链表按位置进行修改
int list_update_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<1 || pos>L->len)
    {
        printf("修改失败\n");
        return -1;
    }
    
    //按位置查找逻辑
    NodePtr p = list_search_pos(L, pos);
    
    //修改逻辑
    p->data = e;

    printf("修改成功\n");
    return 0;
}

2.13 按值进行修改函数

1> 参数:链表、旧值、新值

2> 返回值:int

3> 思路:先通过旧值找到位置,通过位置进行修改

//按值进行修改
int list_update_value(NodePtr L, datatype old_e, datatype new_e)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("修改失败\n");
        return -1;
    }
    
    //按值查找位置
    int res = list_search_value(L, old_e);
    if(res == -1)
    {
        printf("没有要修改的值\n");
        return -1;
    }
    
    //按位置修改
    list_update_pos(L, res, new_e);
    
    printf("修改成功\n");
    return 0;
}

2.14 链表的反转

1> 参数:链表

2> 返回值:int

3> 注意:在该操作中,没有节点被删除,也没有节点被释放

//将链表进行翻转
void list_reverse(NodePtr L)
{
    //判断逻辑
    if(NULL==L || L->len<=1)
    {
        printf("翻转失败\n");
        return ;
    }
    
    //翻转逻辑
    NodePtr H = L->next;   //将链表元素进行托付

    L->next = NULL;        //自己白手起家

    NodePtr p = NULL;         //结点的搬运工

    while(H != NULL)
    {
        p = H;      //搬运第一个结点
        H = H->next;     //头指针后移

        //将p以头插的方式放入L中
        p->next = L->next;
        L->next = p;
    }

    printf("翻转成功\n");
    
}

2.15 链表的释放

1> 参数:链表

2> 返回值:无

3> 注意:需要先将所有的节点内存全部释放后,再将头节点释放

//释放链表
void list_destroy(NodePtr L)
{
    //判断逻辑
    if(NULL == L)
    {
        return;
    }

    //将所有结点进行释放
    while(!list_empty(L))
    {
        //头删
        list_delete_head(L);
    }

    //释放头结点
    free(L);
    L = NULL;

    printf("释放成功\n");
}

1> 将链表的相关操作自己手动完成一下

链表的排序

// 链表的排序
int list_sort(NodePtr L)
{
    if (NULL == L || list_empty(L) || L->len < 1)
    {
        printf("排序失败");
        return -1;
    }
    for (int i = 1; i < L->len; i++)
    {
        NodePtr P = L->next;
        for (int j = 0; j < L->len - i; j++)
        {
            if (P->data > P->next->data)
            {
                datatype x = P->data;
                P->data = P->next->data;
                P->next->data = x;
            }
            P = P->next;
        }
    }
    printf("排序成功\n");
    return 0;
}

链表的反转(递归实现)

void list_digui(NodePtr L)
{
    if (L->next->next == NULL)
    {
        printf("递归翻转成功\n");
        return;
    }

    NodePtr H = L;
    while (H->next != NULL)
    {
        H = H->next;
    }
    NodePtr P = L;
    while (P->next != H)
    {
        P = P->next;
    }
    H->next = L->next;
    L->next = P->next;
    P->next = NULL;
    list_digui(L->next);
}

链表去重

// 链表去重
int list_unique(NodePtr L)
{
    if (NULL == L || list_empty(L))
    {
        printf("去重失败\n");
        return -1;
    }
    NodePtr N = L;
    while (N->next != NULL)
    {
        NodePtr P = N->next;
        while (P->next != NULL)
        {
            if (P->next->data == N->next->data)
            {
                // NodePtr q = P;
                // 删除三部曲
                NodePtr q = P->next;
                P->next = q->next;
                N->len--;
                printf("删除成功\n");
            }else
            P = P->next;
            
        }
        N = N->next;
        
    }
    printf("去重成功\n");
    return 0;
}

2> 使用链表完成班级管理系统(君子作业)

3> 使用顺序表完成班级管理系统

zy.h

#ifndef ZY_H
#define ZY_H
#include <myhead.h>
#define MAX 100

typedef struct
{
    int number;
    char name[20];
    double score;
}Stu;
typedef struct
{
    Stu student[MAX];
    int size;
}Class,*ClassPtr;

//主菜单
void caidan();
// 创建系统
ClassPtr class_create();
// 显示学生信息修改菜单
void print_stu_caidan();
// 学生信息修改菜单
void stu_caidan(ClassPtr P);
// 查看所有学生成绩
void output_stu(const ClassPtr P);
// 显示最高和最低分
void max_min(const ClassPtr P);
// 爆炸
void stu_boom(ClassPtr P);
// 排序
void paixv(ClassPtr P);
// 判空
int stu_empty(ClassPtr P);
// 判满函数,满返回真,非返回假
int stu_full(ClassPtr P);
// 增加学生信息
int stu_add(ClassPtr P);
// 删除学生信息
int stu_delete_pos(ClassPtr L);
// 按位置更改
int stu_update_pos(ClassPtr L);










#endif

zy.c

#include "zy.h"

// 显示菜单
void print_menu()
{
    printf("学生管理系统\n");
    printf("1.学生信息修改\n");
    printf("2.查看所有学生成绩\n");
    printf("3.查看最高分和最低分\n");
    printf("4.销毁\n");
    printf("5.按成绩排序\n");
    printf("0.退出\n");
}
// 创建系统
ClassPtr class_create()
{
    ClassPtr P = (ClassPtr)malloc(sizeof(Class));
    if (NULL == P)
    {
        printf("创建失败\n");
        return NULL;
    }

    memset(P->student, 0, sizeof(P->student));
    P->size = 0;
    printf("请输入最大人数:");
    scanf("%d", &P->size);
    printf("\n");
    for (int i = 0; i < P->size; i++)
    {
        printf("请输入第%d个学生学号", i + 1);
        scanf("%d", &P->student[i].number);
        printf("请输入第%d个学生姓名", i + 1);
        scanf("%s", P->student[i].name);
        printf("请输入第%d个学生成绩", i + 1);
        scanf("%lf", &P->student[i].score);
        printf("\n");
    }
    printf("创建成功\n");
    return P;
}
// 显示学生信息修改菜单
void print_stu_caidan()
{
    printf("学生信息修改菜单\n");
    printf("1.增加学生\n");
    printf("2.删除学生\n");
    printf("3.更改学生\n");
    printf("4.查找学生\n");
    printf("0.退出");
}
// 学生信息修改菜单
void stu_caidan(ClassPtr P)
{
    int nm = 0;
    while (1)
    {
        print_stu_caidan();
        scanf("%d", &nm);
        switch (nm)
        {
        case 1:
            stu_add(P);
            break;

        case 2:
            stu_delete_pos(P);
            break;
        case 3:
            stu_update_pos(P);
            break;
        case 4:
            //
            break;
        case 0:
            return;
        }
    }
}

// 查看所有学生成绩
void output_stu(const ClassPtr P)
{
    printf("学号\t姓名\t成绩\n");
    for (int i = 0; i < P->size; i++)
    {
        printf("%d\t%s\t%lf\n", P->student[i].number, P->student[i].name, P->student[i].score);
    }
}
// 显示最高和最低分
void max_min(const ClassPtr P)
{
    double max;
    double min;
    min = P->student[0].score;
    for (int i = 0; i < P->size; i++)
    {
        if (P->student[i].score < min)
        {
            min = P->student[i].score;
        }
        if (P->student[i].score > max)
        {
            max = P->student[i].score;
        }
    }
    printf("最高分是:\n");
    printf("%lf\n", max);
    printf("最低分是:\n");
    printf("%lf\n", min);
}
// 爆炸
void stu_boom(ClassPtr P)
{

    // 释放内存
    if (NULL != P)
    {
        free(P); // 释放空间
        P = NULL;
    }
    printf("销毁成功\n");
}

// 排序
void paixv(ClassPtr P)
{

    for (int i = 1; i < P->size; i++)
    {
        for (int j = 0; j < P->size - i; j++)
        {

            if (P->student[j].score < P->student[j + 1].score)
            {
                Stu temp = P->student[j];
                P->student[j] = P->student[j + 1];
                P->student[j + 1] = temp;
            }
        }
    }
}

// 主菜单
void caidan()
{
    ClassPtr P = class_create();
    int num = 0;
    while (1)
    {
        print_menu();
        scanf("%d", &num);
        switch (num)
        {
        case 1:
            stu_caidan(P);
            break;

        case 2:
            output_stu(P);
            break;
        case 3:
            max_min(P);
            break;
        case 4:
            stu_boom(P);
            return;
            break;
        case 5:
            paixv(P);
            break;
        case 0:
            return;
        }
    }
}
// 判空
int stu_empty(ClassPtr P)
{
    return P->size == 0;
}
// 判满函数,满返回真,非返回假
int stu_full(ClassPtr P)
{
    return P->size == MAX;
}
// 增加学生信息
int stu_add(ClassPtr P)
{
    // 判断逻辑
    if (NULL == P || stu_full(P))
    {
        printf("添加失败\n");
        return -1;
    }
    printf("请输入新学生的学号:");
    scanf("%d", &P->student[P->size].number);
    printf("请输入新学生的姓名:");
    scanf("%s", P->student[P->size].name);
    printf("请输入新学生的成绩:");
    scanf("%lf", &P->student[P->size].score);
    printf("\n");
    P->size++;
    printf("添加成功\n");
    return 0;
}

// 删除学生信息
int stu_delete_pos(ClassPtr L)
{
    int n;
    int m;
    printf("请输入要删除的学生序号:");
    scanf("%d", &n);
    printf("要删除的学生学号为:%d 姓名为:%s 成绩为:%lf\n", L->student[n - 1].number, L->student[n - 1].name, L->student[n - 1].score);
    printf("请键入1继续删除\n");
    scanf("%d", &m);
    if (m != 1)
    {
        printf("删除已取消\n");
        return -1;
    }
    // 判断逻辑
    if (NULL == L || stu_empty(L) || n >= L->size || n < 1)
    {
        printf("删除失败\n");
        return -1;
    }

    // 腾空逻辑
    for (int i = n - 1; i < L->size; i++)
    {
        L->student[i] = L->student[i + 1];
    }
    // 删除数据
    L->size--;
    printf("删除成功\n");
    return 0;
}

// 更改学生信息

// 按位置更改
int stu_update_pos(ClassPtr L)
{
    int n;
    int m;
    printf("请输入要更改的学生序号:");
    scanf("%d", &n);
    printf("要更改的学生学号为:%d 姓名为:%s 成绩为:%lf\n", L->student[n - 1].number, L->student[n - 1].name, L->student[n - 1].score);
    printf("请键入1继续更改\n");
    scanf("%d", &m);

    if (NULL == L || n<1, n> L->size || stu_empty(L))
    {
        printf("修改失败\n");
        return -1;
    }
    // 正常进行修改
    printf("请输入新的学号:");
    scanf("%d",&L->student[n-1].number);
    printf("请输入新的姓名:");
    scanf("%s",L->student[n-1].name);
    printf("请输入新的成绩:");
    scanf("%lf",&L->student[n-1].score);

    printf("修改成功\n");
    return 0;
}

main.c

#include"zy.h"



// 主函数
int main(int argc, char const *argv[])
{
    caidan();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值