12·C语言---链表与状态机 笔记

本文深入探讨了链表的基础知识,包括单链表和双链表的实现。详细讲解了如何进行链表节点的插入(头插、尾插)、遍历、删除以及逆序操作。通过实例代码展示了这些操作的具体步骤,同时提到了Linux内核链表和状态机的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


提示:这只是笔记

一、链表的引入

1·1 先说说数组的缺陷:数组元素必须一致;数组元素个数一旦指定就不可更改。

1·2 结构体解决了数组的第一个缺陷,链表就是解决了数组的第二个缺陷。

1·3 时刻记住链表是用来解决数组的大小不能扩展问题。

二、单链表的实现

2·1 单链表的节点构成 : 有效数据 + 指针

2·2 定义的struct node 只是一个结构体,本身并没有变量生成,也不占内存。结构体的定义相当于为链表的节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。

2·3 堆内存的申请 与 使用
(1)链表的内存要求比较灵活,不能用栈,也不能用数据段(存储全局变量的)。只能用堆内存。
(2)使用堆内存来创建一个链表节点的步骤:第一,申请堆内存,大小为一个节点(包括检查申请结果是否正确);第二,清理申请到的堆内存;第三,把申请到的堆内存当作一个新节点;第四,填充新节点的有效数据和指针区域。

2·4 链表的头指针
(1)头指针并不是节点,而是一个普通的指针,只占4个字节。头指针的类型是struct node * 类型的,所以他才能指向链表的节点。
(2)一个链表的典型实现是:头指针指向链表的第一个节点,第一个节点中的指针指向第二个节点,第二个节点的指针指向第三个节点,这样一直推。形成了链表。
在这里插入图片描述
单链表实现代码:


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

// 构建一个链表节点,结构体打包
struct node
{
   

	int data;          //有效数据
	struct node *pNext; // 指向下一个节点的指针
};


/* 创建一个链表节点的函数
 返回值:结构体类型的指针,
 		指向本函数新创建的节点的首地址。
*/
struct node *cread_node(int Data)
{
   

	// 创建一个结构体指针p,
	// p指向的就是malloc申请的新节点首地址
	struct node *p = (struct node *)malloc(sizeof(struct node));

	if(NULL == p)
	{
   
		printf("malloc error!\n");
		return NULL;
	}
	// 清理申请到的堆内存
	memset(p,0,sizeof(struct node));

	// 填充节点
	p->data = Data; 
	p->pNext = NULL;   
//pNext 指向的应该是下一个节点的首地址,
//实际操作时将下一个节点malloc返回的指针赋值给pNext。

	return p;
}

int main(void)
{
   
	// 定义头指针
	struct node *pHeader = NULL;

	// 创建第一个节点,并且和头节点绑定
	pHeader = cread_node(1); 
	// cread_node函数的返回值(指针p)就是(第一个)一个节点的首地址
	
	// 创建第二个节点
	pHeader -> pNext = cread_node(2);

	// 创建第3个节点
	pHeader ->pNext -> pNext = cread_node(3);

	// 访问节点,拿出节点数据域的数据
	printf("node 1 data = %d\n",pHeader -> data );
	printf("node 2 data = %d\n",pHeader ->pNext -> data );
	printf("node 3 data = %d\n",pHeader -> pNext ->pNext -> data );


	return 0;
}

结果:
node 1 data = 1
node 2 data = 2
node 3 data = 3

注意点:
(1)因为是链表,所以在填充数据以及访问节点的时候 应该从头指针(pHeader)处入手。
(2)封装 创建新节点函数的关键点在于:函数的接口设计(函数传参和返回值的设计)。

三、 单链表的插入

3·1 单链表的尾部插入
//  头指针 -> 第一个节点 -> 第二个节点 -> 第三个节点......的方式。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


// 构建一个链表节点,结构体打包
struct node
{
   

	int data;          //有效数据
	struct node *pNext; // 指向下一个节点的指针
};


// 创建一个链表节点的函数
// 返回值:结构体类型的指针,指向本函数新创建的节点的首地址。
struct node *cread_node(int Data)
{
   

	// 创建一个结构体指针p,p指向的就是malloc申请的新节点首地址
	struct node *p = (struct node *)malloc(sizeof(struct node));

	if(NULL == p)
	{
   
		printf("malloc error!\n");
		return NULL;
	}
	// 清理申请到的堆内存
	memset(p,0,sizeof(struct node));

	// 填充节点
	p->data = Data; 
	p->pNext = NULL;   
//pNext 指向的应该是下一个节点的首地址,实际操作时将下一个节点malloc返回的指针赋值给pNext。

	return p;
}

// 尾部插入 函数
void insert_tail(struct node *pH,struct node *new)
{
   
	// 两步完成尾部插入
	// 第一步,先找到链表最后一个节点
	struct node *p = pH;
	while (NULL != p->pNext)
	{
   
		p = p->pNext; // 往后走一个节点
	}

	// 第二步,将新节点插入到最后一个节点尾部
	p->pNext = new;

}

int main(void)
{
   
	// 定义头指针
	struct node *pHeader = cread_node(1);// pHeader 与第一个节点绑定
	insert_tail(pHeader,cread_node(23)); // 将第二个节点插入第一个节点尾部
	insert_tail(pHeader,cread_node(356));// 将第3个节点插入第2个节点尾部
	insert_tail(pHeader,cread_node(467));// 将第4个节点插入第3个节点尾部


	// 访问节点,拿出节点数据域的数据
	printf("node 1 data = %d\n",pHeader -> data );
	printf("node 2 data = %d\n",pHeader ->pNext -> data );
	printf("node 3 data = %d\n",pHeader -> pNext ->pNext -> data );
	printf("node 4 data = %d\n",pHeader->pNext->pNext->pNext->data );

	return 0;
}


结果:
node 1 data = 1
node 2 data = 23
node 3 data = 356
node 4 data = 467


链表还有一种用法,就是把头指针指向第一个节点作为头节点使用。

头节点的特点是:第一,紧跟在头指针后面;第二,头节点的数据是空的(有时是存储的整个链表的节点数),指针部分指向下一节点,也就是第一节点。

如此看来,头节点与其他节点确实不同。所以我们在创建一个链表是添加节点的方法也不同。头节点在创建头指针的时候一并创建并且和头指针关联起来;后面指针存储数据的节点用节点添加函数(intsert_tail)进行添加。

总结来说,没有头结点对第一个结点的操作大多和中间结点不太一样,在第一个元素之前插入或删除第一个元素,每个操作都要考虑特殊情况,有头结点的话就不必考虑那么多了,还不容易出现代码错误。空表和非空表的处理也统一了,有头节点空表head->next==NULL,没有头节点空表,head->NULL,可以很明显的看出来,带头节点的与后面其他节点的格式类似 -----------------------这是借鉴的某一位大神的总结

// 头指针 -> 头节点 -> 第一个节点 -> 第二个节点.....的方式。

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


// 构建一个链表节点,结构体打包
struct node
{
   

	int data;          //有效数据
	struct node *pNext; // 指向下一个节点的指针
};


// 创建一个链表节点的函数
// 返回值:结构体类型的指针,指向本函数新创建的节点的首地址。
struct node *cread_node(int Data)
{
   

	// 创建一个结构体指针p,p指向的就是malloc申请的新节点首地址
	struct node *p = (struct node *)malloc(sizeof(struct node));

	if(NULL == p)
	{
   
		printf("malloc error!\n");
		return NULL;
	}
	// 清理申请到的堆内存
	memset(p,0,sizeof(struct node));

	// 填充节点
	p->data = Data; 
	p->pNext = NULL;   
//pNext 指向的应该是下一个节点的首地址,实际操作时将下一个节点malloc返回的指针赋值给pNext。

	return p;
}



// 尾部插入 函数
// 并且计算添加了新的节点后节点总数,把节点总数写进头节点中。

/* 思路:头指针遍历,直到走到原来的最后一个节点。
原来最后一个节点里面的pNext是NULL,
现在我们只要将它改成new就行了。添加了之后新节点就变成了最后一个。
*/

void  insert_tail(struct node *pH,struct node *new)
{
   
	int node_sum_num = 0;
	// 两步完成尾部插入
	// 第一步,先找到链表最后一个节点
	struct node *p = pH;
	while (NULL != p->pNext)
	{
   

		p = p->pNext; // 往后走一个节点
		node_sum_num ++;
	}

	// 第二步,将新节点插入到最后一个节点尾部
	p->pNext = new;
	pH -> data = node_sum_num + 1; // 这个1表示头节点

}


int main(void)
{
   
	// 定义头指针 he 绑定头节点
	struct node *pHeader = cread_node(-1);

	insert_tail(pHeader,cread_node(1)); // 将第1个节点插入头节点尾部
	insert_tail(pHeader,cread_node(356));// 将第2个节点插入第1个节点尾部
	insert_tail(pHeader,cread_node(467
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值