C语言:单链表学习

文件:main.c

c 复制代码
#include "linkedList.h"

int main(int argc, char *argv[]) {
	// 创建头结点
	NODE *head = NULL;
	// 创建链表
	if (llist_create(&head, 666) < 0)
	{
		perror("链表创建失败!");
		return -1;
	}
	
	// 向链表插入数据
	llist_addTail(&head, 777);
	llist_addTail(&head, 888);
	llist_addTail(&head, 999); // 666 777 888 999
	
	llist_addHead(&head, 7777);
	llist_addHead(&head, 8888);
	llist_addHead(&head, 9999); // 9999 8888 7777 666 777 888 999
	
	llist_insert(&head, 888, 1024); // 9999 8888 7777 666 777 1024 888 999
	
	// 测试遍历数据
	llist_showAll(head);
	
	// 更新数据
	llist_update(head, 999, 99999);
	llist_showAll(head); // 9999	8888	7777	666	777	1024	888	99999
	
	// 删除数据
	llist_delete(&head, 777);
	llist_showAll(head); // 9999	  8888	 7777  666	1024 888 99999
	
	// 销毁链表
	llist_destroy(&head);
	
	return 0;
}

文件:linkedList

c 复制代码
#include "linkedList.h"

/**
 * @brief 创建一个节点(私有函数)
 *
 * @param head
 * @param data
 *
 * @return
 */
static NODE* __node_create(NODE **head, DATA data)
{
	// 创建一个新节点
	NODE *p = (NODE*)malloc(sizeof(NODE));
	// 校验是否创建成功
	if (!p) return NULL;
	
	// 初始化节点
	p->data = data; // 将外部插入的数据存储到当前节点的data成员
	p->next = NULL; // 节点默认指向NULL
	
	return p;
}

/**
 * @brief 链表的创建(没有指定头结点的前提下进行(本案例涉及的是无头节点))
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
int llist_create(NODE **head, DATA data)
{
	// 校验链表是否存在
	if (*head != NULL) return -1;
	
	// 创建一个新节点
	NODE *p = (NODE*)malloc(sizeof(NODE));
	// 校验是否创建成功
	if (!p) return -1;
	
	// 初始化节点
	p->data = data; // 将外部插入的数据存储到当前节点的data成员
	p->next = NULL; // 节点默认指向NULL
	
	// 将创建的新节点作为链表的头节点
	*head = p; // 就是让链表指针指向第一个节点
	
	return 0;
}

/**
 * @brief 向链表头部插入一个节点数据(头插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
int llist_addHead(NODE **head, DATA data)
{
	// 创建一个新节点
	NODE *p = (NODE*)malloc(sizeof(NODE));
	// 校验是否创建成功
	if (!p) return -1;
	
	// 初始化节点
	p->data = data;  // 存放插入的数据
	p->next = *head; // 让头指针指向新创建的节点p
	
	// 将当前的新节点作为头节点(更新头指针)
	*head = p;
	return 0;
}

/**
 * @brief 向链表尾部插入一个节点数据(尾插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
int llist_addTail(NODE **head, DATA data)
{
	// 创建一个新节点
	NODE *pNew = (NODE*)malloc(sizeof(NODE));
	// 校验是否创建成功
	if (!pNew) return -1;
	
	// 初始化节点
	pNew->data = data;
	pNew->next = NULL;
	
	// 接收链表
	NODE *p = *head, *q = NULL;
	
	// 如果*p不存在,说明这是一个空链表
	if (!p)
	{
		*head = pNew; // 更新头指针,让头指针指向pNew
		return 0;
	}
	
	// 如果是非空链表,使用循环查找尾结点
	// 指针尾速法遍历链表
	while (p)
	{
		q = p;      // q记录p偏移之前的位置
		p = p->next;// 指针偏移,得到尾结点
	}
	// 此时p指向NULL,q指向尾节点
	// 让原链表的尾节点指向pNew
	q->next = pNew;
	
	return 0;
}

/**
 * @brief 向链表指定位置插入一个节点数据(中间插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param pos 待插入位置数据
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
int llist_insert(NODE **head, DATA pos, DATA data)
{
	// 创建一个新节点
	// NODE *pNew = __node_create(head,data);
	NODE *pNew = (NODE*)malloc(sizeof(NODE));
	// 校验是否创建成功
	if (!pNew) return -1;
	
	// 初始化节点
	pNew->data = data;
	pNew->next = NULL;
	
	// 因为需要遍历链表得到前后两个位置,所以可以使用指针尾随法
	NODE *p = *head, *q = NULL;
	
	// 情景1:空链表
	if (!p)
	{
		*head = pNew; // 更新头指针
		return 0;
	}
	
	// 情景2:pos是头节点
	if (memcmp(&(p->data), &pos, sizeof(DATA)) == 0)
	{
		// 头插法
		pNew->next = *head;
		*head = pNew; // 更新头指针
		return 0;
	}
	
	// 情景3:pos是非头节点
	while (p)
	{
		// 校验是否是pos
		if (memcmp(&(p->data), &pos, sizeof(DATA)) == 0)
		{
			// 插入到目标节点pos前面
			pNew->next = p; // p就是pos对应的节点
			q->next = pNew; // q就是pos的上一个节点
			return 0;
		}
		q = p;
		p = p->next;
	}
	
	// 情景4:找不到pos的位置(使用尾插法,大家也可以选择不插入)
	q->next = pNew;
	
	return 0;
}

/**
 * @brief 遍历链表数据
 * @param head 待遍历的链表
 *
 * @return
 */
void llist_showAll(const NODE *head)
{
	// 首先使用一个指针变量接受形参
	const NODE *p = head;
	
	while (p)
	{
		printf("%d\t", p->data);
		p = p->next; // 类似与顺序表中的p++,实现相邻节点之间的指针偏移
	}
	printf("\n");
}

/**
 * @brief 根据data返回其对应的节点
 * @param head 待查找的链表
 * @param data 待查找的节点数据(相同数据只匹配第一个)
 * 
 * @return 成功返回节点指针,失败返回NULL
 */
NODE *llist_find(const NODE *head, DATA data)
{
	const NODE *p = head;
	
	while(p)
	{
		if (memcmp(&(p->data), &data, sizeof(DATA)) == 0)
		{
			return (NODE*)p;
		}
		p = p->next;
	}
	return NULL;
}

/**
 * @brief 更新链表节点数据old为newdata
 * @param head 待更新的链表
 * @param old 待更新节点的数据
 * @param newdata 节点需要更新的数据
 * 
 * @return 修改成功返回0,否则返回-1
 */
int llist_update(const NODE *head, DATA old, DATA newdata)
{
	NODE *p = NULL;
	
	// 找到old对应的NODE
	if (!(p = llist_find(head,old)))
	{
		return -1;
	}
	// 更新数据
	p->data = newdata;
	
	return 0;
}

/**
 * @brief 删除链表中节点值为data的节点
 * @param head 待删除节点链表
 * @param data 待删除节点数据
 * 
 * @return 删除成功返回0,否则返回-1
 */
int llist_delete(NODE **head, DATA data)
{
	NODE *p = *head, *q = NULL;
	
	// 情景1:空链表
	if (!p) return -1;
	
	// 情景2:头节点
	if (memcmp(&(p->data), &data, sizeof(DATA)) == 0)
	{
		*head = p->next;
		// 回收节点
		free(p);
		return 0;
	}
	
	// 情景3:非头节点(含尾节点)
	while(p){
		if (memcmp(&(p->data), &data, sizeof(DATA)) == 0)
		{
			q->next = p->next;// 让p的上一个节点的next指向p的下一个节点,实际上就是从链表中将p剔除
			free(p);
			return 0;
		}
		// 指针偏移
		q = p;
		p = p->next;
	}
	
	return -1;
}

/**
 * @brief 销毁链表
 * @param head 待销毁的链表
 * 
 * @return 
 */
void llist_destroy(NODE **head)
{
	NODE *p = *head, *q = NULL;
	
	while (p)
	{
		q = p;
		p = p->next;
		free(q);
	}
	// 重置头指针
	*head = NULL;
}

文件:linkedList.h

c 复制代码
#ifndef LINKEDLIST_H
#define LINKEDLIST_H

// 引用相关头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 给数据设置一个别名,本次涉及的所有数据结构的存储数据的类型都是int,大家做项目的时候,可以替换成相应的结构体
typedef int DATA;

// 创建链表的节点结构体
typedef struct node
{
	DATA         data;     // 数据域:存放节点数据
	struct node *next;     // 指针域:指向下一个同类型节点
} NODE;

/**
 * @brief 链表的创建(没有指定头结点的前提下进行(本案例涉及的是无头节点))
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
extern int llist_create(NODE **head, DATA data);  

/**
 * @brief 向链表头部插入一个节点数据(头插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
extern int llist_addHead(NODE **head, DATA data);

/**
 * @brief 向链表尾部插入一个节点数据(尾插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
extern int llist_addTail(NODE **head, DATA data);

/**
 * @brief 向链表指定位置插入一个节点数据(中间插法)
 * @param head 待操作链表,如果是改变链表结构,使用二级指针
 * @param pos 待插入位置数据
 * @param data 待插入数据
 * @return 创建成功返回0,否则返回-1
 */
extern int llist_insert(NODE **head, DATA pos, DATA data);

/**
 * @brief 遍历链表数据
 * @param head 待遍历的链表
 * 
 * @return 
 */
extern void llist_showAll(const NODE *head);

/**
 * @brief 根据data返回其对应的节点
 * @param head 待查找的链表
 * @param data 待查找的节点数据(相同数据只匹配第一个)
 * 
 * @return 成功返回节点指针,失败返回NULL
 */
extern NODE *llist_find(const NODE *head, DATA data);

/**
 * @brief 更新链表节点数据old为newdata
 * @param head 待更新的链表
 * @param old 待更新节点的数据
 * @param newdata 节点需要更新的数据
 * 
 * @return 修改成功返回0,否则返回-1
 */
extern int llist_update(const NODE *head, DATA old, DATA newdata);

/**
 * @brief 删除链表中节点值为data的节点
 * @param head 待删除节点链表
 * @param data 待删除节点数据
 * 
 * @return 删除成功返回0,否则返回-1
 */
extern int llist_delete(NODE **head, DATA data);

/**
 * @brief 销毁链表
 * @param head 待销毁的链表
 * 
 * @return 
 */
extern void llist_destroy(NODE **head);


#endif
相关推荐
知识分享小能手1 小时前
Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)
前端·javascript·css·vue.js·学习·typescript·vue3
江影影影2 小时前
Spring Boot 2.6.0+ 循环依赖问题及解决方案
java·spring boot·后端
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [arm][lds]
linux·运维·arm开发·笔记·学习
Jonathan丶BNTang3 小时前
IntelliJ IDEA 2025.2 重磅发布
java·ide·intellij-idea
tanxiaomi3 小时前
学习分库分表的前置知识:高可用系统架构理论与实践
java·mysql·spring cloud·系统架构·springboot
晨非辰4 小时前
#C语言——刷题攻略:牛客编程入门训练(六):运算(三)-- 涉及 辗转相除法求最大公约数
c语言·开发语言·经验分享·学习·学习方法·visual studio
m0_741574754 小时前
tomcat
java·tomcat
Gloria_niki4 小时前
爬虫与数据分析结合案例学习总结
爬虫·学习·数据分析
跟着珅聪学java4 小时前
Redis 的使用场景
java
一颗正在价投韭菜5 小时前
《范仲淹传》读书笔记与摘要
笔记·学习·范仲淹