v76.链表

1.可变数组的缺陷

因为每当有需求的时候,都要去重新申请一个额外的空间然后copy,这样的缺陷:1copy需要时间;2可能会无法再次申请空间。

2.链表(linked list)

每个单元称作做一个结点(node),每个结点包含一个值和一个指针,其中的指针会指向下一个结点。其中head时开始指向链表的东西。
在这里插入图片描述先来个代码展示

  • 链表的分类

在这里插入图片描述
单链表单向导航;双链表双向导航;循环列表 最后的结点指向第一个结点。

单链表:
在这里插入图片描述在这里插入图片描述

结点不需要按照顺序储存内存中。每个节点包含两个部分,一个用来储存实际数据,另一个储存下一个节点的地址(也就是指针)。这样就可以实现从第一个节点访问第二个节点,第二个节点访问第三个节点,只能是单向的,以此类推…那么如何访问第一个节点呢?需要一个head指针,里面存放第一个节点的地址,可以访问第一个节点。虽然链表在内存中是分散的,但是由于链表的链接部分,他们相互连接在一起。链表是列表的链接表示形式,但不是顺序的

  • 链表 vs 数组

假设需要储存四个数字,那么可以使用数组或者链表。
在这里插入图片描述
使用数组的方式,他们在内存中储存的位置是连续的。可以认为数组是列表的顺序表示

  • 单链表的创建–启蒙

在这里插入图片描述
自引用结构(structure),即里面包含 指向一个相同结构类型的指针 的结构体。

  • 一个结点包含两种不同的类型,data部分link部分,那么使用结构就可以将这两种类型连接在一起:
struct node{
	int data;
	struct node* link;
};

第一个成员变量是data部分,数据类型不重要,int\float\char…均可;
第二个成员变量是指针类型,struct node表示的是它指向的数据类型。因为每个结点本就属于struct node类型,指针要指向下一个节点,实际上就是:要指向一个struct node类型的变量。所以设置为struct node* link;link就表示指向下一个结构体结点的指针。结点只不过是一个自引用结构…

需要注意的是,单链表只能有一个指针,数据可以有多个:
在这里插入图片描述

  • 创建单链表的第一个节点

----至于什么时候用*head 什么时候用head的思考:
在一开始初始化指针head的时候,使用struct node* head,表示声明了一个struct node* 类型的变量:即一种指向struct node结构类型的指针。至此这个指针类型的变量名字就叫head,你为它分配内存甚至说你去改变它的值都是要用head = ...。使用*head表示取指针所指的东西!*head = ...。当然要表示函数参数表中的形式参数是要求传入指针,需要用*head表示。
—关于malloc函数:
分配内存所需的内存空间,并且返回一个指针 ,指向已分配内存的变量地址

#include <stdio.h>//c语言标准输入输出
#include <stdlib.h>//引用malloc函数为结构体节点创建内存

struct node {
	int data;
	struct node* link;
};

int main()
{
	struct  node* head = NULL;//head是指向 第一个 结构体节点的指针,通过这个指针,既可以访问data部分也可访问link部分
	//能不能把head看做一个结构体类型的呢?
	head = (struct node*)malloc(sizeof(struct node));//虽然有等号,但这不是赋值?malloc分配内存,帮助我们创建一个节点。这个操作后,节点的地址被返回到头指针head中
	//通过头指针head访问第一个节点,并尝试初始化
	head->data = 45;
	head->link = NULL;//这里也是一样,虽然link是指针类型,但是普通访问指针变量的时候只写名字link即可
	
	//打印第一个节点的data
	printf("%d",head->data);//使用头指针来访问第一个节点,因为访问这个结点只有一个路线:通过头指针
	return 0 ;
}

在每次为结点创建内存,那就必须得有一个指针来储存该节点的地址。因为如果没有这样的指针,那就无法访问结点。

在这里插入图片描述

  • 创建完整单链表

链表基本上必须由三个节点组成
下面通过程序实现下面的一个链表:
head头指针储存第一个节点的地址,第一个节点的link部分储存第二个节点的地址…最后的节点link部分指向零地址,也就是NULL。
在这里插入图片描述

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

struct node{
	int data;
	struct node* link;//见到这种反应一定要想到是指向结构类型的指针!!
};

int main()
{
	struct node* head;
	head = (struct node*)malloc(sizeof(struct node));//malloc函数返回的地址就是1000,节点一的地址
	//有了地址之后就可以访问数据
	head->data = 45;
	head->link = NULL;
}

-----继续建立第二个节点:

	struct node* current = malloc(sizeof(struct node));//创建一个新的头指针,并且指向了一个新的节点
	current->data = 98;
	current->link = NULL;
	
	head->link = current;//更新head->link指向的零地址,地址给到第一个节点,将第一个节点的链接部分的指针指向第二个节点
	return 0 ;

在这里插入图片描述

----继续建立第三个节点。可以像之前一样初始化一个指针current2、访问、交地址。

struct node* current2 = malloc(sizeof(struct node));//建立第三个字节
current2->data = 3;
current2->link = NULL;//正常建立数据和link
//建立节点之间的连接:
current->link/*第二个结点的link*/ = current2;//更新current->link指向的零地址,
//地址放到第二个节点的link,使第二个节点的link指向第三个节点。

这种方法的图形化:
在这里插入图片描述
但是这种方法是浪费内存的,实际上完全没有必要去额外使用指针。

  • 方法改进

在这里插入图片描述
这样,使用头指针就可以实现对链表中所有节点的访问。

#include <stdio.h>
#include <stdlib.h>
struct node {
	int data;
	struct node* link;
};
int main()
{
	//建立head指针
	struct node* head = malloc(sizeof(struct node));
	head->data = 45;
	head->link = NULL;
	//建立第二个节点
	struct node* current = malloc(sizeof(struct node));
	current->data = 98;
	current->link = NULL;
	//建立第一和第二结点的联系
	head->link = current;//此时current已经完成了任务,将第二个节点的地址传给了第一个节点的link,建立了连接
	//重复使用指针current
	current = (struct node*)malloc(sizeof(struct node));
	current->data = 3;
	current->link = NULL;
	//使用head完成对第三节点的建立连接
	head->link->link = current;
} 
  • 遍历链表

进行创建struct node*变量的时候,别忘记分配内存,不然会是储存空间为默认四字节的指针。
核心代码:

struct node* p =malloc(sizeof(struct node));
p = head;
while(p->link != NULL)
{
	p = p->link;
}

计数:

#include <stdio.h>
#include <stdlib.h>
int count_of_node(struct node* head);
void num_of_node(struct node* head);
struct node {
	int data;
	struct node* link;//见到这种反应一定要想到是指向结构类型的指针!!
};
int count1 = 0;
int count2 = 0;
int main()
{
	struct node* head;
	head = (struct node*)malloc(sizeof(struct node));//malloc函数返回的地址就是1000,节点一的地址
	//有了地址之后就可以访问数据
	head->data = 45;
	head->link = NULL;

	struct node* current1 = NULL;
	current1  = (struct node*)malloc(sizeof(struct node));
	current1->data = 9;
	current1->link = NULL;
	head->link = current1;
	
	current1 = NULL;
	current1 = (struct node*)malloc(sizeof(struct node));
	current1->data = 33;
	current1->link = NULL;
	head->link->link = current1;

	num_of_node(head);
	printf("\n建立的链表所包含的节点有%d个", count_of_node(head));
	free(current1);
	free(head);
}
int count_of_node(struct node* head)
{
	if (head->link)
	{
		count1++;
		count_of_node(head->link);//注意只能读到最后一个节点,并且并没有计入个数
	}
	return count1+1;
}
void num_of_node(struct node* head)
{
	if (head == NULL)
	{
		printf("链表中的节点个数为0");
	}
	struct node* p = head;
	while (p != NULL)
	{
		count2++;
		p = p->link;
	}
	printf("链表中的节点个数为%d", count2);
}

打印:

void Print_data(struct node* head)
{
	if (head == NULL)
		printf("链表中无数据");
	struct node* p = head;
	while (p != NULL)
	{
		printf("%d  ", p->data);
		p = p->link;
	}
}

主要通过改变指针所指向的link实现遍历。
在链表末尾插入新节点:

void Insert(struct node* head,int num)
{
	struct node* p = malloc(sizeof(struct node));
	p = head;
	if (p == NULL)
		printf("链表中无数据");
	while (p->link != NULL)//为了避免使用NULL进行赋值,首先进行检验,确保p->link不是NULL再更新它的值
	{
		p = p->link;
	}
	//创建新节点
	struct node* insert = malloc(sizeof(struct node));
	insert->data = num;
	insert->link = NULL;
	
	p->link = insert;
	free(insert);
}

对链表的一些操作,核心都是通过循环 + 改变指针变量所指的link,让循环的指针指向下一个节点。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值