C语言数据结构单链表

目录

单链表

1 单链表的结构

2 创建单链表

3 在单链表中插入数据 e

3 删除单链表中第i个结点

4 清空单链表

5 完整代码及运行结果

基于C语言单链表的图书管理系统


单链表

概述: 单链表是一种常见的线性数据结构,用于存储一系列元素。单链表中的每个元素被称为节点(Node),每个节点包含两个部分:数据域(存储数据)和指针域(指向下一个节点的指针)。

单链表中的第一个结点的存储位置叫做头指针,规定最后一个节点的指针为空。有时候为了方便对链表操作,会在链表的第一个结点前加一个头节点,头结点可以不存储任何信息。

1 单链表的结构

概述: 单链表的结构为结构体,结构体中有两个元素,一个存放当前的数据,另一个为结构体指针保存的是下一节点的地址

// 单链表结构模型
struct list
{
	int data;			// 存储数据
	struct list* next;	 // 存储下一节点的地址
};

2 创建单链表

概述:在创建单链表的时候一般会先创建一个头结点,然后利用头结点去创建单链表,头结点的数据域一般忽略,指针域为空。

// 创建头结点 并初始化
struct list head;
head.next = NULL;

void Creatlist(struct list* head, int n)
{
	// 创建 尾结点指针 当做 中间变量来 操作节点
	struct list* tail = (struct list *) malloc(sizeof (struct list));
	// tail 是指针 指向的是 头节点 
	tail = head;	// 头节点指针赋值给尾节点 此时 通过tail 可控制头节点的数据
	
	for (int i = 0; i < n; ++i)
	{	
		// 创建新节点
		struct list* temp = (struct list*)malloc(sizeof(struct list));
		// 给新节点赋值 
		// 初始化节点的指针 和 数据
		temp->next = NULL;	
		temp->data = i + 1;

		// 尾节点的指针域 指向下一节点 
		// 第一次时 tail 操作的是 头节点的下一节点 指向新节点
		// 第二次...时 tail 操作的是 上次的节点    把它的下一节点指向 新节点 
		tail->next = temp;
	
		// 新加入了一个节点 此时的尾结点 应该是新的节点 
		// 所以把 temp 赋值给tail  即更新 tail 操作的对象 此时 tail 操作新赋值的对象
		// 且第一次时 tail 放弃对 head 的操作权
		// 且第二...此时 tail放弃对 上一节点的操作权 
		tail = temp;
		// 把尾节点 移动到最后一个   此时 尾结点的的数据为?  指针域为?
		// 数据为i  指针域为NULL 因为 tail 指向的是temp 
	}
}

上例先创建一个头节点,然后在函数中用for循环对这个头结点连续插入结点。 要注意在对单链表进行操作的时候,会创建一个指针指向它的头结点然后用这个指针对节点进行添加操作。

3 在单链表中插入数据 e

思路:

  1. 创建新结点e

  2. 找到要插入的位置结点p

  3. 把p结点的下一结点地址p->next 的值赋值给 e结点的下一结点的 e->next

  4. 再把e的地址赋值给p节点的指针域存入即 p->next = e;

// 在单链表中插入数值
// 参数1 传入单链表指针
// 参数2 插入的位置
// 参数3 插入的数值
void InsetList(struct list* head, int pos, int num)
{
    // 创建新节点
    struct list* new_node = (struct list*)malloc(sizeof(struct list));
    new_node->data = num;

    struct list* temp = head; 	// 创建工作结点 指向头结点
    for (int i = 0; i < pos - 1 && temp != NULL; i++)  // 从头结点遍历到要插入的结点位置
    {
        temp = temp->next;
    }

    if (temp == NULL) 
    {
        printf("插入失败,插入位置越界\n");
        free(new_node);  // 释放动态分配的内存
        return;
    }

    new_node->next = temp->next;  // 把前一结点的下一结点 赋值给新节点的下一结点
    temp->next = new_node;		 // 把新结点的地址赋值给前一节点
}

注意: 插入的时候赋值顺序不能反,要先把p的后继节点赋值给e的后继,再把e赋值给p的后继。

3 删除单链表中第i个结点

思路:

  1. 声明一指针指向头结点 struct list* temp = head;

  2. 遍历 temp 到要删除的结点的前一个结点

  3. 创建指针指向要删除的结点 struct list* dele = temp->next;

// 删除单链表中的节点
// 参数1 传入单链表头结点指针
// 参数2 要删除的位置
void Delet_List(struct list* head, int pos)
{
    if (head->next == NULL)
    {
        printf("链表为空,无法删除\n");
        return;
    }

    struct list* temp = head;			// 创建临时结点指向头结点
    for (int i = 0; i < pos - 1 && temp->next != NULL; i++) // 遍历结点到指定的位置
    {
        temp = temp->next;
    }

    if (temp->next == NULL)  // temp->为要删除的结点 若为空则说明结点不存在 则不能删除
    {
        printf("删除失败,删除位置越界\n");
        return;
    }

    struct list* dele = temp->next;  // temp为要删除的前一结点 temp->next 为要删除的结点
    temp->next = dele->next;		// 把要删除的结点的下一节点赋值给 前一结点的下一结点
    free(dele);  				   // 释放删除节点的内存
}

4 清空单链表

思路: 从头指针开始直到空指针 一个一个删除

// 删除整个单链表
// 参数1 单链表 头指针
void DELET(struct list* head)
{
    // 定义工作指针current
    struct list* current = head->next;
    while (current != NULL)
    {
        // 保存当前的下一个指针域
        struct list* next = current->next;
        // 删除当前指针
        free(current);
        // 把前面保存的下一个指针 赋值给当前
        current = next;
    }
    // 删除完链表 也要把头指针置空
    head->next = NULL; 
}

5 完整代码及运行结果

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

struct list
{
    int data;
    struct list* next;
};

// 创建单链表
// 参数1 传入头结点指针
// 参数2 链表长度
void CreatList(struct list* head, int n)
{
    struct list* tail = head;

    for (int i = 0; i < n; ++i)
    {
        struct list* new_node = (struct list*)malloc(sizeof(struct list));
        new_node->data = i + 1;
        new_node->next = NULL;

        tail->next = new_node;
        tail = new_node;
    }
}

// 在单链表中插入数值
// 参数1 传入单链表指针
// 参数2 插入的位置
// 参数3 插入的数值
void InsetList(struct list* head, int pos, int num)
{
    // 创建新节点
    struct list* new_node = (struct list*)malloc(sizeof(struct list));
    new_node->data = num;

    struct list* temp = head;
    for (int i = 0; i < pos - 1 && temp != NULL; i++)
    {
        temp = temp->next;
    }

    if (temp == NULL)
    {
        printf("插入失败,插入位置越界\n");
        free(new_node);  // 释放动态分配的内存
        return;
    }

    new_node->next = temp->next;
    temp->next = new_node;
}

// 删除单链表中的节点
// 参数1 传入单链表头结点指针
// 参数2 要删除的位置
void Delet_List(struct list* head, int pos)
{
    if (head->next == NULL)
    {
        printf("链表为空,无法删除\n");
        return;
    }

    struct list* temp = head;
    for (int i = 0; i < pos - 1 && temp->next != NULL; i++)
    {
        temp = temp->next;
    }

    if (temp->next == NULL)
    {
        printf("删除失败,删除位置越界\n");
        return;
    }

    struct list* dele = temp->next;
    temp->next = dele->next;
    free(dele);  // 释放删除节点的内存
}

// 遍历单链表
// 参数1 单链表头结点指针
void PrintfList(struct list* head)
{
    if (head->next == NULL)
    {
        printf("链表为空\n");
        return 0;
    }
    struct list* crunt = head->next; // 直接声明指针,无需动态分配内存
    for (crunt; crunt != NULL; crunt = crunt->next)
    {
        printf("%d\t", crunt->data);
    }
    printf("\n");
}

// 删除整个单链表
// 参数1 单链表 头指针
void DELET(struct list* head)
{
    // 定义工作指针current
    struct list* current = head->next;
    while (current != NULL)
    {
        // 保存当前的下一个指针域
        struct list* next = current->next;
        // 删除当前指针
        free(current);
        // 把前面保存的下一个指针 赋值给当前
        current = next;
    }
    // 删除完链表 也要把头指针置空
    head->next = NULL; 
}

int main()
{
    // 创建头结点
    struct list head;
    head.next = NULL;

    // 创建单链表
    CreatList(&head, 10);

    // 遍历单链表
    PrintfList(&head);

    // 在单链表中插入数值
    InsetList(&head, 3, 666);
    // 遍历单链表
    PrintfList(&head);

    // 在单链表中插入数值
    InsetList(&head, 1, 777);
    // 遍历单链表
    PrintfList(&head);

    // 删除节点
    Delet_List(&head, 16);
    // 遍历单链表
    PrintfList(&head);

    // 释放整个链表
    DELET(&head);
    PrintfList(&head);


    return 0;
}

结果

1       2       3       4       5       6       7       8       9       10
1       2       666     3       4       5       6       7       8       9       10
777     1       2       666     3       4       5       6       7       8       9       10
删除失败,删除位置越界
777     1       2       666     3       4       5       6       7       8       9       10
链表为空

基于C语言单链表的图书管理系统

要求: 输入1 添加书本 ,输入2根据ID删除书本,输入3根据ID 查看书本,输入4查看全部

图书管理系统是对链表的添加删除查找等功能的使用,在学习完链表后可以自己尝试做一个图书管理系统。

图书管理系统完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

// 定义书的链表结构
typedef struct BOOK
{
    char name[100];
    char author[100];
    int price;
    int id;
    struct BOOK* next;
} BOOK;

int main(void)
{
    // 创建书的头结点
    BOOK head;
    head.next = NULL;
    BOOK* tail = &head;

    while (1)
    {
        printf("***********************************************\n");
        printf("1,添加书本\t2, 删除书本\t3, 查看\t4, 查看全部\n");
        printf("请输入选项: \t");
        int chose = 0;
        scanf_s("%d", &chose);
        
        if (chose == 1)
        {
            // 创建新书
            BOOK* book = (BOOK*)malloc(sizeof(BOOK));
           
            // 给这本书赋值
            printf("请输入书名名字:\t ");
            scanf("%s", book->name);

            printf("\n请输入作者:\t ");
            scanf("%s", book->author);

            printf("\n请输入ID:\t ");
            scanf("%d", &book->id);

            printf("\n请输入价格:\t ");
            scanf("%d", &book->price);

            // 把这本书与前面的节点连接起来
            tail->next = book;
            // 移动尾结点到这本书的位置
            tail = book;
            // 置空尾结点便于后续操作
            tail->next = NULL;
        }
        else if (chose == 2)
        {
            printf("请输入要删除的ID\n");
            int del = 0;        // 接收删除的ID
            scanf("%d", &del);

            BOOK* pre = &head; // 定义指向头的节点
            BOOK* temp;        // 定义工作节点
            int flog = 0;
            for (temp = pre->next; temp != NULL; pre = temp, temp = temp->next)
            {
                if (del == temp->id)
                {
                    flog = 1;
                    pre->next = temp->next;
                    free(temp);
                    break;
                }
            }
            if (flog == 0)
            {
                printf("ID 不存在\n");
            }
            else if (flog == 1)
            {
                printf("删除成功\n");
            }
                
        }
        else if (chose == 3)
        {
            printf("请输入要查看的ID\n");
            int look = 0;
            scanf("%d", &look);

            int flog1 = 0;
            for (BOOK* temp = head.next; temp != NULL; temp = temp->next) 
            {
                if (look == temp->id)
                {
                    flog1 = 1;
                    printf("书名:%s\t\n作者:%s\t\nID:%d\t\n价格:%d\t\n", temp->name, temp->author, temp->id, temp->price);
                    break;
                }
            }
            if (flog1 == 0)
            {
                printf("查无次书\n");
            }
        }
        else if (chose == 4)
        {
            printf("书名\t作者\tID\t价格\n");
            for (BOOK* temp = head.next; temp != NULL; temp = temp->next)
            {
                printf("%s\t%s\t%d\t%d\n", temp->name, temp->author, temp->id, temp->price);
            }
        }
        else
            printf("输入错误\n");
    }

    return 0;
}

结果

***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     1
请输入书名名字:  数据结构

请输入作者:      张三

请输入ID:        1

请输入价格:      20
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     1
请输入书名名字:  算法

请输入作者:      李四

请输入ID:        2

请输入价格:      33
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     1
请输入书名名字:  C语言基础

请输入作者:      王五

请输入ID:        3

请输入价格:      44
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     4
书名    作者    ID      价格
数据结构        张三    1       20
算法    李四    2       33
C语言基础       王五    3       44
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     3
请输入要查看的ID
2
书名:算法
作者:李四
ID:2
价格:33
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     2
请输入要删除的ID
2
删除成功
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:     4
书名    作者    ID      价格
数据结构        张三    1       20
C语言基础       王五    3       44
***********************************************
1,添加书本      2, 删除书本     3, 查看 4, 查看全部
请输入选项:

上一篇:C语言数据结构线性表

下一篇:明日更新

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) C语言下的单链表,可以增加,删除,查找,销毁节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值