【数据结构初阶】由浅入深学习链表

目录

前言

链表的概念及结构

 链表的分类

单链表的实现

接口实现

1.结构体

2.创建一个新结点

3.打印链表数据

4.尾插数据

5.尾删数据

6.头插数据

7.头删数据

8.任意位置删除

9.查找位置

10.pos之前插入

11.pos之后插入

12.释放内存

完整源码

总结


前言

在我们学习了顺序表之后,我们发现了顺序表有很多的不足之处,例如顺序表在空间不足之后需要扩容,扩容会对空间造成浪费,并且当需要头部或者中部操作数据时,顺序表必须进行挪动数据,挪动数据会造成浪费。

链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

链表就像一辆火车一样,一节车厢接着一节车厢,这每一节车厢都可以被看做是一个结点,每一个结点链接在一起就构成了链表。

链表中在每一个结点都是一个结构体,结构体中分为数据域和指着域,就是一个结点同时存储数据和下一个结点的地址,这样我们才能找到下一个结点。

 虽然我们在逻辑上看到一个结点是接着一个结点的,但是在物理上他们不一定是连续的,只是在结点中存储下一个结点的地址,所以才看上去是连续的。

 链表的分类

根据链表结构的不同,我们可以将链表分为八类,分别从以下三个方面进行区分。

1.带头或不带头

2.循环或不循环

3.双向或单向

我们这篇文章主要来探究不带头单向不循环链表,也就是我们最常见到的单链表,还有就是结构复杂但是效率更高的带头循环双向链表。

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结
,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向
循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而
简单了,后面我们代码实现了就知道了。

单链表的实现

接口实现

1.结构体

定义结构体来存储当前结点数据以及下一个结点的地址。

typedef int ElementType;

typedef struct SLTNode
{
	ElementType date;
	struct SLTNode* next;
}SLTNode;

2.创建一个新结点

使用malloc创建一个新结点,但是切记最后必须free掉。

SLTNode* BuyNewNode(ElementType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newNode->date = x;
	newNode->next = NULL;

	return newNode;
}

3.打印链表数据

直接顺序打印,直到遍历到空为止。

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->date);
		cur = cur->next;
	}
	printf("NULL\n");
}

4.尾插数据

当链表为空时,直接令头结点等于新结点就好,当链表不为空时,我们要通过遍历来找尾,当cur的next为空时找到尾,插入到尾的后边。

void SListPushBack(SLTNode** pphead, ElementType x)
{
	assert(pphead);
	SLTNode* newNode = BuyNewNode(x);

	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=NULL)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

5.尾删数据

当只有一个结点时,我们将这个结点释放掉就好了,如果有多个结点,还是先来找尾,但是切记要保存上一个结点的地址,我们free掉尾结点后,还要将尾的上一个结点的next置为空,否则就会造成野指针。

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next== NULL)
	{
		free(*pphead);
	}
	else
	{
		SLTNode* cur = *pphead, * prev = NULL;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

6.头插数据

只需将新结点的next指向原本的头结点,再将头结点变为新结点。

void SListPushFront(SLTNode** pphead, ElementType x)
{
	SLTNode* newNode = BuyNewNode(x);

	newNode->next = (*pphead);
	*pphead = newNode;
}

7.头删数据

保存头结点的next,free头结点,将新的头指向保存的next。

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

8.任意位置删除

当pos就是头结点时,只需头删就好了,如果pos不是头,就逐一向后遍历,要记录pos位置的前一个结点,方便删除pos位置结点。

void SListDel(SLTNode** pphead,SLTNode* pos)
{
	assert(pphead && (*pphead));
	assert(pos);
	if ((*pphead)==pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

9.查找位置

使用数据来查找,逐一向后遍历,如果相等,返回该结点。

SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
	assert(pphead && *pphead);
	SLTNode* cur = *pphead;
	while (cur->date != x)
	{
		cur = cur->next;
	}
	return cur;
}

10.pos之前插入

在pos位置之前插入必须要保存上一个结点prev,当查找到pos位置时,只需要将新结点的next指向pos,prev的next指向新结点。

void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	if ((*pphead)== pos)
	{
		newNode->next = (*pphead);
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead, * prve = NULL;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode->next = pos;
		cur->next = newNode;
	}
}

11.pos之后插入

直接插入,但是要记得先连接后边再连接前边。

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

12.释放内存

void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
	printf("内存释放完毕\n");
}

完整源码

slist.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
typedef int ElementType;

typedef struct SLTNode
{
	ElementType date;
	struct SLTNode* next;
}SLTNode;
//创建新节点
SLTNode* BuyNewNode(ElementType x);
//打印链表
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, ElementType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//头插
void SListPushFront(SLTNode** pphead, ElementType x);
//pos位置删除
void SListDel(SLTNode** pphead,SLTNode* pos);
//查找位置
SLTNode* SListFind(SLTNode** pphead, ElementType x);
//之前插入数据
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x);
//之后插入数据
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x);
//释放内存
void SListDestory(SLTNode** pphead);

slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"

SLTNode* BuyNewNode(ElementType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newNode->date = x;
	newNode->next = NULL;

	return newNode;
}
void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->date);
		cur = cur->next;
	}
	printf("NULL\n");
}
//尾插
void SListPushBack(SLTNode** pphead, ElementType x)
{
	assert(pphead);
	SLTNode* newNode = BuyNewNode(x);

	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=NULL)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next== NULL)
	{
		free(*pphead);
	}
	else
	{
		SLTNode* cur = *pphead, * prev = NULL;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

//头插

void SListPushFront(SLTNode** pphead, ElementType x)
{
	SLTNode* newNode = BuyNewNode(x);

	newNode->next = (*pphead);
	*pphead = newNode;
}

//头删

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}


//在任意位置删除

void SListDel(SLTNode** pphead,SLTNode* pos)
{
	assert(pphead && (*pphead));
	assert(pos);
	if ((*pphead)==pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

//查找位置
SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
	assert(pphead && *pphead);
	SLTNode* cur = *pphead;
	while (cur->date != x)
	{
		cur = cur->next;
	}
	return cur;
}
//pos之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	if ((*pphead)== pos)
	{
		newNode->next = (*pphead);
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead, * prve = NULL;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode->next = pos;
		cur->next = newNode;
	}
}


//pos之后插入

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

//释放内存
void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
	printf("内存释放完毕\n");
}

总结

今天主要学习了单链表的原理以及实现,希望可以帮到大家。

猜你喜欢

转载自blog.csdn.net/weixin_61654591/article/details/129461118