数据结构实验二:单链表的基本操作

单链表操作与算法解析
实验课总体安排:
1.课后作业题目讲解
2.头插法创建单链表
3.尾插法创建单链表
4.LeetCode算法练习题带刷、讲解
5.实验一报告书优秀作业展示

一、课后作业题目讲解

        程序中基本语句“y--”或“x++”,执行的次数是由x和y决定的,而在咱们的题目中,x和y都是一个常数,所以T(n)=O(1)。

        部分同学写成了0(1000),这样是不对的,时间复杂度的常量级表达是用O(1),统一表示。

        还有部分同学写成了O(y),这样也是不对的,已经找到了执行次数是由x和y决定的,而咱们的算法度量时间公式是T(n) =O(f(n)),是一个关于问题规模n的某个函数f(n).


1、逐步分析

  1. 初始值:i = 1

  2. 循环更新:每次 i 变为原来的 3 倍

    • 第 1 次循环:i = 3

    • 第 2 次循环:i = 9

    • 第 3 次循环:i = 27

    • 第 k 次循环:i = 3^k

  3. 循环终止条件:i > n

    • 即 3^k > n

2、求循环次数

        要满足退出条件:

                       3k>n3k>n

        取对数:

                k>log⁡3nk>log3​n

所以循环大约执行k = O(log_3 n) ] 次。

3、时间复杂度

        因为常数底数不影响数量级:

                O(log_3 n) = O(log n)

二、两种方法创建单链表代码讲解

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

typedef char ElemType; // 存储的数据类型

// 单链表结点定义
typedef struct Lnode {
    ElemType data;
    struct Lnode* next;
} LNode, *LinkList;

// 全局指针,指向链表头结点
LinkList phead = NULL;

// 菜单展示
void show_menu() {
    printf("\n==== 单链表基本操作 ====\n");
    printf("1 -- 头插法创建链表\n");
    printf("2 -- 尾插法创建链表\n");
    printf("3 -- 遍历显示链表\n");
    printf("0 -- 退出\n");
    printf("请输入功能编号: ");
}

// 创建空链表
LinkList create_empty_list() {
    LinkList phead = (LinkList)malloc(sizeof(LNode));
    if (!phead) {
        printf("内存分配失败!\n");
        exit(1);
    }
    phead->next = NULL;
    return phead;
}

// 头插法建表
LinkList create_list_head() {
    LinkList phead = create_empty_list();
    int n;
    ElemType temp;
    printf("请输入元素个数: ");
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        printf("请输入第 %d 个元素: ", i + 1);
        scanf(" %c", &temp);
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = temp;
        node->next = phead->next;
        phead->next = node;
    }
    return phead;
}

// 尾插法建表
LinkList create_list_tail() {
    LinkList phead = create_empty_list();
    LNode* r = phead; // r为尾指针
    int n;
    ElemType temp;
    printf("请输入元素个数: ");
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        printf("请输入第 %d 个元素: ", i + 1);
        scanf(" %c", &temp);
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = temp;
        node->next = NULL;
        r->next = node;
        r = node;
    }
    return phead;
}

// 遍历链表
int show_list(LinkList phead) {
    if (!phead) {
        printf("链表不存在!\n");
        return 0;
    }
    LNode* p = phead->next;
    if (!p) {
        printf("链表为空!\n");
        return 0;
    }
    printf("链表元素:");
    while (p) {
        printf("%c ", p->data);
        p = p->next;
    }
    printf("\n");
    return 1;
}

int main() {
    int choice;
    do {
        show_menu();
        scanf("%d", &choice);
        switch (choice) {
            case 1:
                printf("头插法创建链表\n");
                phead = create_list_head();
                show_list(phead);
                break;
            case 2:
                printf("尾插法创建链表\n");
                phead = create_list_tail();
                show_list(phead);
                break;
            case 3:
                printf("遍历链表:\n");
                show_list(phead);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("无效选择,请重新输入!\n");
        }
    } while (choice != 0);

    return 0;
}

一、单链表结点定义

// 单链表结点定义
typedef struct Lnode {
    ElemType data;
    struct Lnode* next;
} LNode, *LinkList;

单链表:结点包含两个部分,一个是数据域(data)存放结点本身,另一个是指针域(next域)存放指向下一个结点的地址。

  • ElemType data → 这一格专门放数据
  • struct Lnode* next → 这一格专门放“下一个结点的地址”
typedef struct Lnode {
    ElemType data;
    LNode* next; // ❌ 这里会报错
} LNode;
  • next 是个 指针,它指向的是 相同类型的结点(struct Lnode)
  • 如果写成 LNode* next;,这时 LNode 还没 typedef 完成,编译器会报错。
    所以要写 struct Lnode* next;。
  • 结构体体内部还在定义 struct Lnode

  • LNode 这个名字是 typedef 之后才生效的别名,在定义体内还不存在。

  • 所以编译器找不到 LNode,会报错。等 typedef 完成之后,才可以直接用 LNode* 或 LinkList

struct Lnode* next; // ✅
  • struct Lnode 是结构体的“原名”,已经存在,可以在内部引用自己。
  • typedef 是在 } 后才生效,所以在结构体内部不能用别名。

二、创建空链表


// 创建空链表
LinkList create_empty_list() {
    LinkList phead = (LinkList)malloc(sizeof(LNode));
    if (!phead) {
        printf("内存分配失败!\n");
        exit(1);
    }
    phead->next = NULL;
    return phead;
}
    • create_empty_list() 的作用是创建一个空链表
    • 空链表是指:有一个头结点,但头结点的 next 指针为空,没有实际数据。

    1.第一行

    LinkList phead = (LinkList)malloc(sizeof(LNode));

    • malloc(sizeof(LNode)):向堆申请一块内存,大小正好存放一个结点。
    • (LinkList):把 malloc 返回的 void* 转换为 LinkList 类型(即 LNode*)。
    • phead:指向新分配的头结点。

    2.第二行

    if (!phead) {

        printf("内存分配失败!\n");

        exit(1);

    }

    • 检查内存分配是否成功。
    • 如果 malloc 返回 NULL,说明分配失败,就打印错误信息并退出程序。

    3.第三行

    phead->next = NULL;

    • 给头结点的指针域 next 赋值为空。
    • 表示这个链表目前没有任何实际结点。

    4.第四行

    return phead;

    • 返回链表的头指针。
    • 别忘了,链表虽然空,但头结点已经存在,后续可以用头插法或尾插法往里面加数据。

    小结

    • 空链表 ≠ NULL
      • 空链表:有头结点,但 head->next = NULL
      • NULL:链表根本没有头结点
    • 这是链表操作的基础,后面所有插入、遍历操作都从这个头结点开始。

    三、头插法创建单链表

    
    // 头插法建表
    LinkList create_list_head() {
        LinkList phead = create_empty_list();
        int n;
        ElemType temp;
        printf("请输入元素个数: ");
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            printf("请输入第 %d 个元素: ", i + 1);
            scanf(" %c", &temp);
            LNode* node = (LNode*)malloc(sizeof(LNode));
            node->data = temp;
            node->next = phead->next;
            phead->next = node;
        }
        return phead;
    }
    • 一、创建头结点

    LinkList phead = create_empty_list();

    • 调用 create_empty_list() 创建一个空链表(有头结点,next = NULL)。
    • phead 是链表的起点。

    二、读入元素个数

    printf("请输入元素个数: ");

    scanf("%d", &n);

    • 用户输入要插入的结点数量 n。

    三、循环读入每个元素

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

    • 每次循环完成一个结点的创建和插入。

    四、创建新结点

    LNode* node = (LNode*)malloc(sizeof(LNode));

    node->data = temp;

    • 申请内存创建新结点。
    • 把用户输入的数据存入新结点的 data 域。

    五、插入新结点到链表前面

    node->next = phead->next;

    phead->next = node;

    • 第一行:把新结点的 next 指向原来的第一个结点(可能是 NULL)。
    • 第二行:把头结点的 next 指向新结点。

    ✅ 这样每次新结点都会“插到前面”,所以叫头插法


    六、返回头指针

    return phead;

    • 返回链表的头结点指针,方便后续操作(遍历、插入、删除等)。

    小结

    • 链表一开始只有头结点。
    • 每次插入一个新结点,都把它放在链表最前面
    • 新结点的 next 指向原来的第一个结点,头结点 next 更新为新结点。
    • 最终链表顺序 = 输入顺序的逆序(头插法特点)。

    四、尾插法创建单链表

    // 尾插法建表
    LinkList create_list_tail() {
        LinkList phead = create_empty_list();
        LNode* r = phead; // r为尾指针
        int n;
        ElemType temp;
        printf("请输入元素个数: ");
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            printf("请输入第 %d 个元素: ", i + 1);
            scanf(" %c", &temp);
            LNode* node = (LNode*)malloc(sizeof(LNode));
            node->data = temp;
            node->next = NULL;
            r->next = node;
            r = node;
        }
        return phead;
    }
    
    1. 一、创建头结点

    LinkList phead = create_empty_list();

    • 创建一个空链表,有头结点(next = NULL)。
    • phead 是链表起点。

    二、设置尾指针

    LNode* r = phead; // r为尾指针

    • r 永远指向链表的最后一个结点
    • 开始时链表只有头结点,所以尾指针指向头结点。

    三、读入元素个数

    printf("请输入元素个数: ");

    scanf("%d", &n);

    • 用户输入要插入的结点数量。

    四、循环创建并插入新结点

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

    • 每次循环处理一个新结点。

    五、创建新结点并存数据

    LNode* node = (LNode*)malloc(sizeof(LNode));

    node->data = temp;

    node->next = NULL;

    • 新结点数据域存用户输入的值。
    • 指针域 next = NULL,表示它暂时是链表的最后一个结点。

    六、将新结点插入链表末尾

    r->next = node;

    r = node;

    • 第一行:把尾结点的 next 指向新结点,把它加到链表末尾。
    • 第二行:更新尾指针 r 指向新结点,下次插入继续在末尾插。

    ✅ 这样每次新结点都加到链表最后,所以叫尾插法

    七、返回头指针

    return phead;

    • 返回链表头结点,方便后续操作(遍历、插入、删除等)。

    小结

    • 链表一开始只有头结点。
    • 每次插入一个新结点,都加在链表末尾
    • 尾指针 r 永远指向最后一个结点,保证插入操作时间短且顺序不变。
    • 最终链表顺序 = 输入顺序一致(尾插法特点)。

    三、LeetCode算法练习题带刷、讲解

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值