c语言_单链表

- 0.头文件

#define _CRT_SECURE_NO_DEPRECATE //scanf
#include<stdio.h>
#include<stdlib.h>//用于malloc建立结点

1.定义结构体

typedef int ElemType;//数据元素类型
typedef struct Node {//定义单链表结构类型
	ElemType data;//数据域
	struct Node* next;//指针域,指向结点的指针
}*List;

2.建立头结点和尾插入法

在这里插入图片描述
建立头结点,用尾插入法插入结点,再定义一个指针r,永远指向新建的结点p,前一个指针r的next一直指向后面新建的结点,然后自己定义链表长度,for循环依次输入数据。

如果是和我一样void定义函数的,有想过参数List &L为什么要加&吗?(本页最后解答)

void CreateList(List &L,int n) //List &L就相当于 Node* &L
{
	L = (Node*)malloc(sizeof(Node));//建立头结点,并使L指向头结点(数据域和指针域都是空的计数要从下一个结点计数)
	if (!L) {
		printf("存储失败");
		L->next = NULL;//初始化头地址
	}
	Node *r,  *p;
	r = L;//r指针指向头结点
	printf("请输入:\n");
	for (int i = 0; i<n; i++)//尾插入法
	{
		p = (Node*)malloc(sizeof(Node));
		scanf_s("%d",&p->data);//把输入的值给结点数据
		p->next = NULL;
		r->next = p;//前一个指针指向下一个结点
		r = p;//指向当前结点
	}
}

3.打印链表

p指针直接遍历链表,输出数据域即可。

void Print(List L)///打印输出

{
	Node* p;//定义一个新指针
	p = L->next;//指针指向L的第一个结点
	printf("链表为:");
	while (p)//当p不等于空
	{
		printf("%d ", p->data);//将输入的值放入数据域
		p = p ->next;//p指向下一个指针
	}
}

4.按内容查找

在这里插入图片描述
p指针遍历链表到p->next=null为止,while循环判断是不是需要的数据即可。

void Find(List  L, ElemType e)//按内容查找
{
	Node* p;
	p = L ;//p指针指向L的第一个指针
	int i=0;
	while (p && p->data!=e) {//当p不等于空,输入的值不等于想要查找的值,循环
		p = p->next;//指向下一个结点的指针域
		i++;//计数
	}
	if (p ==NULL) printf("查找错误");
	else printf("\n(1)按内容查找%d的是第%d位\n",e,i);
}

5.按位置查找

在这里插入图片描述
p指针遍历链表,当j=i-1时候,p指针指向第i个结点

void FindTwo(List L, int i)//按位置查找
{
	Node* p;
	p = L ;//p指向第一个结点
	int j = 0;
	while (p && j< i)//p不等于空并且当j=i-1时候p指针指向第i个结点
	{
		p = p->next;
		j++;
	}
	if (p == NULL) printf("查找错误");
	printf("\n(3)按位置查找的第%d位数据是:%d\n",j,p->data);
}

6.插入结点(头插入法)

在这里插入图片描述
先找到需要插入结点的前一个结点p,然后让新结点r指向原结点p->next(就是你想插入的位置的结点),再让原结点的前结点p连接新结点r。

void Insert(List L, int n, ElemType a) //插入(头插入法)
{
	Node* p,*r;
	p = L;//p指针指向头指针L
	int i=0;
	while (p && i<n-1)//当p不为零,i++向下查找当i=n-2时候,p指针指向第n-1项
	{                  //要是想要插在第i个位置需要在第n结点之前插入(也就是第n-1的后面)
		p = p->next;
		i++;
	}
	if (i != n-1)printf("插入错误");
	r= (Node*)malloc(sizeof(Node));//开辟新节点
	r->data = a;//将需要插入的值输入到新节点的数据域
	r->next = p->next;//因为原来的p->next(当前p的位置在n-1处)指向第n个结点就是L,所以新节点r->next指向的也是就是L
	p->next = r;//在n-1处的p->next指向新节点r
	printf("\n(2)在第%d位插入%d后\n", n,a);
}

7.按内容删除

在这里插入图片描述
定义两个指针,第一个指针p根据内容查找指向需要删除的结点,第二个指针r指向需要删除的结点的前一个,然后让前一个结点直接指向 需要删除的结点 的后一个结点。

void Delete(List L, ElemType x)//按照内容删除 
{
	Node *r,  *p;
	p = L;
	r = L;//定义两个指针都指向L的第一个结点
	int i = 0;
	int j = 0; 
	while (p && p->data != x)//第一个指针指向查找的结点
	{
		p = p ->next;
		i++;
	}
	while (r && j < i-1 )//第二个指针指向查找的结点的前一个 
	{
		r = r->next;
		j++;
	}
	if (j != i-1) printf("删除错误");
	r->next =p->next;//前一个结点指针直接指向后一个结点
	printf("\n(4)按内容删除的位置为%d,值为%d的结点\n",i,x);
	free(p);//释放删除结点所占空间
}

8.按位置删除

和按内容删除差不多,就是直接找到需要删除的结点的前一个p,然后前一个结点p直接指向 需要删除的结点r 的后一个结点。

void DeleteTwo(List L, int n)//按位置删除
{
	Node* p,*r;
	p = L;//定义p指向头指针
	int i = 0;
	while (p && i < (n-1))//p不为空,i找到第n-1个结点
	{
		p = p->next;
		i++;
	}
	if (i != n - 1)printf("删除错误");
	r = p->next;//r指向第n个结点
	p->next = r->next;//在第n-1个结点直接指向n+1个结点
	printf("\n(5)按位置删除第%d个值为%d的结点\n",n,r->data);
	free(r);//释放删除结点所占空间
}

9.主函数

int main()
{
	List L;
	ElemType x=8;
	CreateList(L, 5);//长度为5的链表
	Print(L);//打印
	printf("\n");
	Find(L, 4);//按内容查找4为第几位
	Insert(L, 2, x);//插入位置为 2 值为 x的结点
	Print(L);
	printf("\n");
	FindTwo(L, 3);//查找第三位
	Delete(L,x);//删除刚才插入的值为x结点
	Print(L);//
	printf("\n");
	DeleteTwo(L, 2);//删除位置为2的结点
	Print(L);
	printf("\n");
}

10.关于&L:

List L就相当于Node*L,CreateList接收的是主函数里L的一份拷贝,当CreateList运行完时会销毁L,实参L并没有发生改变,这是为什么呢?
因为在单链表中的头结点(实际就是头指针),L就是指向头指针的指针,申请的结空间会泄露,因为L这个指针被当做值传参(以传值的形式作为参数的变量在函数体内被修改之后,出了函数体就会失效,准确的说这个变量没被修改)那如何解决呢?
要是想修改L的内容,就需要修改指向L的指针的内容,因为L本身就是指针,所以需要修改的是指向指针的指针内容也就是Node*&L,如果传的是Node &L也是不行的,因为&L也会被当做拷贝,只能是Node*&L(*&L:指向L的指针指向的内容)这就是引用传参,因为它是直接将L作为参数传入进去,不会对L进行复制,既然传入进去的是L,那么对L的修改肯定也是有效的。

11.我要说一下我编译时发现的自己知识的不足(p指针指向)

p=L->next和p=L是两个事情,第一个p指的是第一个结点,第二个p指的是头节点。
之前我总是把p=L->next当做p指向头指针的指针域,导致我在后面的操作中出现蝴蝶效应,从而百思不得其解,其实这种说法也对只不过在链表建成之前也就是还是结点的时候可以这么想,在已经建成的链表中就应该想的是指向下一个结点。

最后运行结果如下 在这里插入图片描述

二.内核链表 内核链表是一种链表,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、付费专栏及课程。

余额充值