数据结构初阶——单链表

上一篇为各位介绍了顺序表, 那么这一篇就来介绍另一种数据结构:单链表。

单链表只是链表的一种形式,后面还会介绍其它的形式。


目录

1. 单链表的基本结构

2. 增加数据

        2.1 尾插

        2.2 头插

3. 删除数据

        3.1 尾删

扫描二维码关注公众号,回复: 14175969 查看本文章

        3.2 头删

4. 查找

5. 修改

6. 随机插入

7. 随机删除

8. 其它随机插入删除

        8.1 在随机位置后插入

        8.2 在随机位置后删除

结尾总结


1. 单链表的基本结构

首先我们先思考一下:
(1 )中间/头部的插入删除,时间复杂度为O(N)。
(1)增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
(2)增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
为了解决这些问题,就有了链表结构。
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

和顺序表一样,我们都会用到结构体,data为要存放的数据,next则存放下一个内存块的地址。

先申请四块空间为大家解释一下链表的结构,给每个结构体变量的data赋值,再依次把下一个结构体变量地址存放的每个next变量中,最后一个内存块的next指向一个空指针。

每个变量都是指针也是地址,所以逻辑上是可以连续的,由上一个就可以访问到这一个,这一个就可以访问到下一个,直到访问的下一个为空指针就停止。

写到这里就可以测试一下,顺便写一个打印函数,之后也是会用到的。

 此时phead指向这里,也是cur。

       phead不是NULL,就打印phead->data再把phead->next赋给phead,也就移动到了下一个位置,以此类推,直到访问到NULL就停止,这里打印一个箭头的样子也是为了形象,最后再打印一个NULL。 


2. 增加数据

        2.1 尾插

增加数据的时候就要考虑一下如何去传参

像把" 5 "插入到链表的尾部 ,这样传参会不会成功呢,一旦我这样说,那这里肯定是有问题的。

       这个函数返回值是void类型,不需要返回,传入plist和5,plist就是就是n1的地址,使用一级指针接收,当我们要操作的时候,phead形参只是plist实参的临时拷贝,而且函数返回类型还是void类型,调用完函数没有值被改变,这样就达不到插入数据的效果,所以就要思考一下,既然我们想要改变这个链表,也就是想要改变传入的plist,这样我们就得到了一个思路,想要改变一级指针就要传入二级指针。

传入一级指针的地址,使用二级指针来接收。

       改变值的问题解决了,接下来就是插入,插入的是一个新的内存块,就需要malloc申请一块新的内存,我们定义为newnode,这样我们就直接封装成一个新的函数,因为后面也会用到。

虽然说是一块空间,但在链表中有一个名字为节点,这里最后需要找到尾节点。

最后再想一个问题,如果一开始就是一个空链表呢?

所以这里一开始就不用assert来判断了,毕竟空链表也不是一个错误。那么空链表只有一个NULL那就比较方便了,直接把newnode赋给*pphead就可以了。

 来几个数测试一下。

 看到没有什么问题,那尾插就写完了。

        2.2 头插

开始时,还是先申请一块空间

如果开始是空链表,也是没有问题的,直接把newnode变成新的头节点。

再来几个数测试一下。


3. 删除数据

        3.1 尾删

 那我们再尾删3次

 就剩下一个节点了,那再删一次就变成空链表了。

看到最后的返回值,代码挂掉了。

当链表中只有一个值的时候,tail->next直接为NULL,就不会进入循环,free(tail),那最后tailPrev->next就是对空指针访问,那自然会报错。

所以要判断链表中是有一个节点还是多个节点。

所以打印函数也不要去assert判断,空链表也打印出来看看,当然有需要也可以加,决定权在自己手里。

        3.2 头删

 回到刚才插入的地方,这回再头删测试一下,看看能不能回到最初的链表。

也是没有问题的,再换一个思路

 

同样的效果 。

还要注意的是(*pphead)->next ,这个小括号一定不能省略,如果不注意会出现操作符优先级引发的错误。


4. 查找


5. 修改

修改也是建立在查找的基础上的,只有找到了才能修改

pos就是找到的ret


6. 随机插入

上一个函数把最后的节点修改了,在这个节点之前插入。 

那再来想一想,如果pos指向的是头节点呢,想找到prev->next为pos,那根本就找不到,所以头插的情况要单独写。

把头节点改了,再头插。


7. 随机删除


8. 其它随机插入删除

        8.1 在随机位置后插入

        8.2 在随机位置后删除

 


结尾总结

关于链表的内容就介绍到这里,链表也是一种存放数据的形式,当然后面也会对单链表进行优化,想要变得好看一点可以写一个菜单,下面就是所有的代码。

SList.h文件

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

typedef int SLDataType;

typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SLTNode;

//打印
void SListPrint(SLTNode* phead);

//申请新的内存
SLTNode* BuySListNode(SLDataType x);

//尾插/头插/尾删/头删
void SListPushBack(SLTNode** pphead, SLDataType x);
void SListPushFront(SLTNode** pphead, SLDataType x);
void SListPopBack(SLTNode** pphead);
void SListPopFront(SLTNode** pphead);

//查找
SLTNode* SListFind(SLTNode* phead, SLDataType x);
//修改
void SListModify(SLTNode** pphead, SLTNode* pos, SLDataType x);
//随机插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLDataType x);
//随机删除
void SListErase(SLTNode** pphead, SLTNode* pos);

//在pos位置后插入
void SListInsertAfter(SLTNode* pos, SLDataType x);
//在pos位置后插入
void SListEraseAfter(SLTNode* pos);

SList.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "SList.h"

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

SLTNode* BuySListNode(SLDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

void SListPushBack(SLTNode** pphead, SLDataType x)
{
	SLTNode* newnode = BuySListNode(x);

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

}

void SListPushFront(SLTNode** pphead, SLDataType x)
{
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);//判断一下,如果是空链表就不能删了

	if ((*pphead)->next == NULL)
	{
		(*pphead) = NULL;
	}
	else
	{
		//SLTNode* tail, * tailPrev;
		//tail = *pphead;
		//tailPrev = NULL;
		//while (tail->next != NULL)
		//{
		//	tailPrev = tail;
		//	tail = tail->next;
		//}
		//free(tail);
		//tailPrev->next = NULL;
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	//SLTNode* next = *pphead;
	//*pphead = (*pphead)->next;
	//free(next);
	//next = NULL;

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

SLTNode* SListFind(SLTNode* phead, SLDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void SListModify(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
	assert(pos);
	SLTNode* cur = *pphead;
	while (cur != pos)
	{
		cur = cur->next;
	}
	cur->data = x;

}

void SListInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SListPushFront(pphead, x);	
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}

}

void SListInsertAfter(SLTNode* pos, SLDataType x)
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);

	if (pos->next == NULL)
	{
		return;
	}
	SLTNode* del = pos->next;
	pos->next = NULL;
	free(del);
	del = NULL;
}

猜你喜欢

转载自blog.csdn.net/m0_64607843/article/details/124530805