0
点赞
收藏
分享

微信扫一扫

【数据结构】带你玩转单链表

彪悍的鼹鼠 2022-05-02 阅读 216

目录

前言

实现单链表:

1.节点结构

2.打印函数 void SListPrint(SListNote* phead)

3.开辟节点 SListNote*  BuySListData(SListDataType x);

4.单链表尾插void SListPushBack(SListNote** pphead, SListDataType x)

 5.单链表头插void SListPushFront(SListNote** pphead, SListDataType x)

 6.单链表尾删void SListPopBack(SListNote** pphead)

7.单链表头删void SListPopFront(SListNote** pphead)

8.单链表查找SListNote* SListFind(SListNote* phead, SListDataType x)

9.单链表在pos位置请插入 void SListInsertFront(SListNote**pphead,SListNote*pos, SListDataType x)

10.单链表在pos位置删除void SListErase(SListNote** pphead, SListNote* pos)

11.单链表在pos位置后插入void SListInsertAfter( SListNote* pos, SListDataType x)

 12单链表在pos位置后删除void SListEraseAfter(SListNote* pos)


 

前言

上一篇博客写了顺序表,我们都知道顺序表有两个优点1.物理空间连续,2.下标随机访问,也是因为它的两个优点让它产生了一下缺点:1.空间不够时需要扩容,而扩容呢有存在性能消耗;2.在头部插入数据、在某个位置插入数据、在某个位置删除数据它需要挪动数据,这就存在效率低的问题。解决这来两个问题我们可以用链表。

链表是在逻辑上连续在物理上又不连续的一种结构,它的逻辑上连续是通过指针来完成的。

链表又可分为:单向或者双向链表、带头或者不带头链表、循环或者非循环链表,这几种组合起来有8种结构:

常用的有:单向不带头不循环和双向带头循环链表下面我们一起看看单向不带头不循环链表,也就是单链表。

每一个单链表的结构称为节点,它有两部分组成:一是存储数据,二是存储下一个节点的地址。

如下图:

实现单链表:

1.节点结构

2.打印函数 void SListPrint(SListNote* phead)

        打印函数需要对链表做出更改,所以只需要传达链表的起始地址就行,我们知道打印整个链表避免不了遍历整个链表,那到什么时候结束呢? 知道这个我们要明确一点单链表的最后一个节点存储下一个节点的指针是为NULL的,那这样就好办了,当phead指针指向空的时候它就打印完了。另外我们也要考虑另外一种情况:链表原本就是空的,那我们打印也只能是NULL,所以开始打印是我们就要判断,当不为空时才开始遍历、打印,反之直接打印NULL。代码如下: 

//打印
void SListPrint(SListNote* phead)
{
	if (phead != NULL)
	{
		while (phead != NULL)
		{
			printf("%d ", phead->data);
			phead = phead->next;
		}
		printf("\n");
	}
	else
	{
		printf("NULL \n");
	}
}

3.开辟节点 SListNote*  BuySListData(SListDataType x);

        当需要插入数据是我们需要开辟节点,考虑到会不止一次开辟这里封装成函数 ,代码如下:

//开辟节点
SListNote* BuySListData(SListDataType x)
{
	SListNote* Newdata = (SListNote*)malloc(sizeof(SListNote));
	assert(Newdata);
	Newdata->data = x;
	Newdata->next = NULL;
	return Newdata;
}

4.单链表尾插void SListPushBack(SListNote** pphead, SListDataType x)

这里需要考虑两种情况:

1.链表为空:这种情况下链表为空,那我们需要将链表的起始位置更改,也就是将创建的节点的地址给链表,例如我创建一个结构体指针 SListNote* s1 = NULL;想要在它为空的情况下插入数据,那就要改变这个指针,所以传参是要传指针的地址,这样才能在函数里改动这个指针。

2.链表不为空,这种情况下我们需要找到链表的尾,然后在尾上插入,那该怎样找呢,我们前面讲了单链表最后一个节点里的指针变量是空,那这样的话也就好办了,找到这个节点将节点里的指针改为我们开辟节点的地址;

 代码:

//单链表尾插
void SListPushBack(SListNote** pphead, SListDataType x)
{
	assert(pphead);
	SListNote* Newdata = BuySListData(x);
	if (*pphead == NULL)
	{
		*pphead = Newdata;
	}
	else
	{
		//找尾
		SListNote* end = *pphead;
		while (end->next != NULL)
		{
			end = end->next;
		}

		end->next = Newdata;
	}
}

 5.单链表头插void SListPushFront(SListNote** pphead, SListDataType x)

也可以考虑两种情况:

1.链表空,这里和尾插链表为空相同,也就不在赘述了;

2.链表不为空,那我们需要将链表的头改为新开辟的节点,然后将新开辟节点里的指针指向之前的头部;

这里不能发现1 2两种情况实质上是一样的,不管为不为空都需要改变头的位置,只是在链接下面的节点时有所不同,但是把第二种情况运用到第一种也可以,无非就是链接下一个节点时第一种情况链接的为空。 

 

代码如下:

//单链表头插
void SListPushFront(SListNote** pphead, SListDataType x)
{
	assert(pphead);
	SListNote* temp = *pphead;
	SListNote* ret = BuySListData(x);
	*pphead = ret;
	(*pphead)->next = temp;
}

 6.单链表尾删void SListPopBack(SListNote** pphead)

既然要删除那单链表就不能为空,在函数的开始我们就要判断;

正常情况下我们将尾删除,只需要将尾释放掉,然后将上一个节点里的指针置为空。但是当单链表接一个节点时这种方法并不适用。所以我们还是要分为两种情况:

1.只有一个节点,这种情况下:我们只需将头释放掉,然后置为空就好

2.多个节点,这种情况下:我们需要找到链表的尾,和链表的倒数第二个节点,将尾释放掉,将倒数第二个节点的指针置为空。

代码如下:

//单链表尾删
void SListPopBack(SListNote** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNote* end = *pphead;
		SListNote* preend = NULL;
		while (end->next != NULL)
		{
			preend = end;
			end = end->next;
		}
		preend->next = NULL;
		free(end);
		end = NULL;
	}
}

7.单链表头删void SListPopFront(SListNote** pphead)

同样的链表不能为空;

这里和头插类似,可分为两种情况,但是一个节点和多个节点,但是可以合并,记录头的下一个节点地址,释放头,将头再改为刚记录的地址。代码如下

//单链表头删
void SListPopFront(SListNote** pphead)
{
	assert(pphead);
	assert(*pphead);
	SListNote* begin = *pphead;
	*pphead = (*pphead)->next;
	free(begin);
	begin = NULL;
}

8.单链表查找SListNote* SListFind(SListNote* phead, SListDataType x)

既然都是查找了,链表当然不能为空啦。

这个比较简单节直接上代码了。

//单链表查找
SListNote* SListFind(SListNote* phead, SListDataType x)
{
	assert(phead);
	SListNote* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

9.单链表在pos位置请插入 void SListInsertFront(SListNote**pphead,SListNote*pos, SListDataType x)

同样的链表不能为空;

第一种情况:pos是头的位置,这种情况下和头插相同直接调用就行;

第二种情况:pos和头的位置不相同,这种情况下我们要找到pos前一个节点的为位置,插入一个节点,然后将这各节点与pos相连

 代码:

//单链表在pos位置前插入
void SListInsertFront(SListNote** pphead, SListNote* pos, SListDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SListNote* Newdata = BuySListData(x);
		SListNote* prepos = *pphead;
		while (prepos->next != pos)
		{
			prepos = prepos->next;
		}
		prepos->next = Newdata;
		Newdata->next = pos;
	}
}

10.单链表在pos位置删除void SListErase(SListNote** pphead, SListNote* pos)

同样的既然要删除,链表自然不能为空;

1.当pos的位置为头时,和头删情况相同,可以直接调用;

2.pos不为头时,我们需要找到pos前的节点,然后链接pos后的节点,释放pos位置的节点。

 代码:

//单链表在pos位置删除
void SListErase(SListNote** pphead, SListNote* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNote* prepos = *pphead;
		while (prepos->next != pos)
		{
			prepos = prepos->next;
		}
		prepos->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

11.单链表在pos位置后插入void SListInsertAfter( SListNote* pos, SListDataType x)

这里不存在该表头的更改也就不需要传指针的地址;

在pos后插入不需要找pos前的位置,直接将pos位置的指针指向新开辟的节点,将新开辟的节点的指针指向pos后的节点就行

代码:

//单链表在pos位置后插入
void SListInsertAfter( SListNote* pos, SListDataType x)
{
	assert(pos);
	SListNote* Newdata = BuySListData(x);
	SListNote* posafter = pos->next;
	pos->next = Newdata;
	Newdata->next = posafter;

}

 12单链表在pos位置后删除void SListEraseAfter(SListNote* pos)

与11相同这里也不需要传指针的地址;

这里要考虑只用一个节点的情况,如果只有一个节点也就不存在删除pos后一个节点了

第二种就是多个节点的情况:释放pos后的节点,让pos与下下一个节点相连。

void SListEraseAfter(SListNote* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return ;
	}
	SListNote* cur = pos->next;
		pos->next = pos->next->next;
		free(cur);
		cur = NULL;
}

到这里单链表就完成了,本篇博客也就到这里了,以上若有错误恳请指正,感激不尽^_^

举报

相关推荐

0 条评论