链表的相关知识

一、链表的概念

单链表:线性表的链接存储结构。

存储思想:用一组任意的存储单元存放线性表的元素。

存储特点:1.逻辑次序和物理次序不一定相同;2.元素之间的逻辑关系用指针表示。

![[Pasted image 20231215190148.png]]

存储包含两部分内容,如a1,保存有a1的元素和下一个元素的地址,而a4是最后一个元素,所以它的下一个元素的地址是空NULL。 从上图也可以注意到,链表存储数据是分散的、不连续的。

![[1702638042992 1.jpg]]

单链表由若干个结点构成,每个结点只有一个指针域。

单链表的结点结构:数据域(data) + 指针域(next)。数据域用于存储数据元素,指针域用于存储指向后继结点的地址。

程序:

typedef struct node
{
	数据类型 data;//数据域
	struct node *next;//指针域
}Node, *Link;
注:用typedef去声明一个结构体的时候,后面的Node和* Link就被声明为了类型,在后面定义的时候可以像这样去定义:
Node st 等价于 struct node st;
Link p 等价于 struct node * p;

如何申请一个结点?

p=(Link)malloc(sizeof(Node));sizeof(Node)算出Node结点的大小,得到该结构体需要多大的存储空间。

如何引用数据元素和指针域?

p->data; p->next;

存储结构的逻辑形式表示:

![[Pasted image 20231216150701.png]]

设置一个head指针指向链表的第一个结点的地址。

头指针(head):指向第一个结点的地址(指向头结点的地址)。

尾标志:最后一个结点的指针域,且为NULL。

头结点:在单链表的第一个有效元素结点之前设置一个类型相同的结点,以便空表和非空表处理统一。头结点的数据域没有作用。若头结点的指针域为NULL的话,则为空表;若头结点的指针域非空,则为非空表。

![[Pasted image 20231216151049.png]]

二、单链表的实现(要注意链表的边界条件:头和尾的特殊情况)

1、单链表的遍历操作

void display(Link head)
{
	Link p;
	p=head->next;
	while(p!=NULL)
	{
		printf("%d", p->data);
		p=p->next;//使p等于下一个元素的地址
	}
}

2、求单链表的元素个数

int length(Link head)
{
	Link p;
	int cnt=0;
	p=head->next;
	while(p)
	{
		p=p->next;
		cnt++;
	}
	return cnt;
}

3、单链表的查找

int query(Link head, datatype x)//datatype为x的数据类型
{
	Link p;
	p=head->next;
	while(p)
	{
		if(p->data==x)
		{
			return true;
		}
		p=p->next;
	}
	return false;
}

4、单链表的插入操作

![[Pasted image 20231216153832.png]]

上图中橙色虚线箭头的顺序是从左到右进行的,我们需要先将p->next的地址赋给node->next,再将node的地址赋给p-next。

算法描述:

1.工作指针p初始化;

2.查找第i-1个结点并使工作指针p指向该节点;

3.若查找不成功则返回false;否则:

3.1生成一个数据元素值为x的新结点s;
3.2将新结点s插入到结点p之后;
3.3返回true。
bool insert(Link head, int i, DataType x)//i是要插入的位置,希望插入到第i个位置
{
	p=head;//工作指针p先指向头结点
	int cnt=0;
	while(p!=NULL&&cnt<i-1)//查找第i-1个结点
	{
		p=p->next;
		cnt++;
	}
	//上面的循环结束后,p是第i-1个元素的地址
	if(p==NULL) return false;
	else
	{
		Link node;
		node=(Link)malloc(sizeof(Node));//创建需要插入的结点
		node->data=x;//给入数据组元素值
		node->next=p->next;
		p->next=node;
		//上两行代码的顺序不能够改变,先给新创建的结点的指针域赋上原来链表中第i个结点的地址;
		//再给第i-1个结点的指针域赋上新创建的结点的地址。
		return true;
	}
}

5、创建一个单链表——头插法

操作函数:Link newList(datatype a[ ], int n);

头插法:将待插入结点插在头结点head的后面。

如:

![[Pasted image 20231216212657.png]]

我们现在有一个数组a,想要创建一个单链表。

思路:

1.初始化头结点:head=(Link)malloc(sizeof(Node));//分配一块内存给头结点

head->next=NULL;//初始化头结点指向的下一个地址为空

2.插入第一个元素结点:node=(Link)malloc(sizeof(Node));//分配一块内存给第一个元素结点

node->data=a[0];//在数据域存入数据

node->next=head->next;//将终端地址(即NULL)赋给新创建的结点的指针域

head->next=node;//将新创建的结点的地址传给头结点的指针域

![[Pasted image 20231216214345.png]]

3.插入后续的元素结点(以第i个元素为例):nodei=(Link)malloc(sizeof(Node));//分配一块内存给下一个元素结点

nodei->data=a[i];//存入数据

nodei-next=head->next;//将head指针域存储的地址传递给新创建的指针域

head->next=nodei;//将新创建的结点的地址传给head的指针域。

![[Pasted image 20231216214402.png]]

所以从上面我们插入新结点的方式来看,可以发现,最后我们所创建的单链表中,结点数据的排列顺序和我们原本在数组中的排列顺序是相反的。

![[Pasted image 20231216215222.png]]

而且由于插入元素结点的步骤都是相似的,且只需要改变i的大小,所以我们可以使用循环的方式去创建这个单链表。

Link newList(Datatype a[], int n)
{
	Link head, node;
	head=(Link)malloc(sieof(Node));
	head->next=NULL;
	//以上步骤为创建头结点并初始化
	for(i=0;i<n;i++)
	{
		node=(Link)malloc(sizeof(Node));
		node->data=a[i];
		node->next=head->next;
		head->next=node;
	}
	return head;
}

6、创建一个单链表——尾插法

操作函数:Link newList(Datatype a[ ], int n)

尾插法:将待插入的结点插在终端结点的后面。

1.初始化头结点:head=(Link)malloc(sizeof(Node));

head->next=NULL;

rear=head;//这里和头插法不同,我们需要创建一个表达尾部的指针,且rear等于head

2.插入第一个元素结点:node=(Link)malloc(sizeof(Node));

node->data=a[0];

rear->next=node;

rear=node;

3.插入后续元素(以第i个元素为例):node=(Link)malloc(sizeof(Node));

node->data=a[i];

rear->next=node;

rear=node;

4.最后一个结点的插入(与头插法不同之处,重要):

rear->next=NULL;

尾插法完成后,单链表中的数据的顺序与我们原来数组中数据的顺序就是一样的了。

Link nweList(Datatype a[], int n)
{
	Link head, node, rear;
	head->next=NULL;
	rear=head;
	for(i=0;i<n;i++)
	{
		node=(Link)malloc(sizeof(Node));
		node->data=a[i];
		//也可以在这里加一条:node->next=NULL,这样最后的rear->next=NULL就不用写了
		rear->next=node;
		rear=node;
	}
	rear->next=NULL;
	return head;
}

7、单链表结点的删除

操作函数:bool deleteNode(Link head, Datatype x);

思路:

1.初始化:p=head->next;

q=head;//初始化后p和q就是一前一后的关系

2.移动指针找到需要删除的位置:q=p;

p=p->next;

3.删除:q->next=p->next;

free ( p );

![[Pasted image 20231216223350.png]]

注:删除时需要分析如果要删的表是空表怎么办?在查找过程中,如果发现待删除的表是空表(即下图左边两种情况都是空表),则提前返回next (head=NULL可能是动态内存分配失败)

![[Pasted image 20231216223843.png]]

bool delete(Link head, Datatype x)
{
	if(head==NULL||head->next==NULL) return false;
	p=head->next;
	q=head;
	while(p!=NULL)
	{
		if(p->data==x)
		{
			q->next=p-next;
			free(p);
			return true;
		}
		else
		{
			q=p;
			p=p->next;
		}
	}
	return false;
}

8、单链表的释放

操作函数:void clearLink(Link head);//会将头结点也删掉

将单链表中的所有结点的存储空间释放。

![[Pasted image 20231216224721.png]]

算法描述:q=head;

head=head->next;

free(q);

循环退出的条件为:head == NULL,然后free(head);

三、循环链表

(单)循环链表:将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表。

![[Pasted image 20231217195156.png]]

空循环链表:

![[Pasted image 20231217195411.png]]

非空循环链表:

![[Pasted image 20231217195426.png]]

循环链表的特点:循环链表没有明显的尾端。所以要防止死循环的出现,比如可以以p!=head或p->next!=head作为循环条件。

循环链表的操作与单链表的操作都是相似的,除了退出循环的条件不同外。

四、双向链表

如果要找到p的前一个元素的话,使用循环链表的时间复杂度较高,所以可以使用双向链表来完成。

![[Pasted image 20231217195846.png]]

在单链表的基础上增加一个prior域来指向前一个结点。

双向链表:在单链表的每个结点中再设置一个指向其前驱结点的指针域。

结点结构:

![[Pasted image 20231217200104.png]]

虽然双向链表对于有关前驱问题的时间处理更快,但是占用的内存也更多,结构也更加复杂。

参考:懒猫老师-C语言-链表(单链表,循环链表)_哔哩哔哩_bilibili
  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值