每天学习亿点点系列——重温单链表

Posted 变秃变强 呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每天学习亿点点系列——重温单链表相关的知识,希望对你有一定的参考价值。

头文件以及测试部分的代码

SList.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<stdlib.h>
typedef int SDataType;
typedef struct Node
{
	SDataType data;
	struct Node* next;
}Node;


/*void SListinit(Node** phead);*/  //单链表不需要这个接口
void SListPushBack(Node** phead, SDataType x);
void SListPopBack(Node** phead);
void SListPrint(Node* phead);
void SListPushFront(Node** phead, SDataType x);
void SListPopFront(Node** phead);
Node* SListFind(Node* phead, SDataType x);
void SListPushBefore(Node** phead, Node* pos, SDataType x);//在任意位置之前插入
void SListPushAfter(Node** phead, Node* pos, SDataType x);//在任意位置之后插入
void SListPop(Node** phead, Node* pos);//删除查找的节点
void SListPopBefore(Node** phead, Node* pos);//删除任意位置之后的节点
void SListPopAfter(Node** phead, Node* pos);//删除任意位置之前的节点
bool SListEmpty(Node* phead);
void SListDestory(Node** phead);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void test()
{
	/*Node a;*///这种方式是写不出来的,之前在写stack就深有体会
	Node* a=NULL;
	//SListinit(&a);//单链表不要这个接口
	SListPushBack(&a, 1);
	SListPushBack(&a, 2);
	SListPopBack(&a);
	SListPopBack(&a);
	SListPushFront(&a, 1);
	SListPushFront(&a, 0);
	SListPopFront(&a);
	SListPopFront(&a);
	SListPushBack(&a, 1);
	SListPushBack(&a, 2);
	Node* pos = SListFind(a, 2);
	SListPushBefore(&a, pos, 3);
	pos = SListFind(a, 1);
	SListPushBefore(&a, pos, 0);  //复用,相当于头插
	pos = SListFind(a, 0);
	SListPushAfter(&a, pos, 66);
	SListPop(&a, pos);//复用,相当于头删
	pos = SListFind(a, 1);
	SListPopAfter(&a, pos);
	SListPopBefore(&a, pos);//复用,相当于头删
	SListPopAfter(&a, pos);//复用,相当于尾删
	/*printf("%p\\n", SListFind(a, 2));*/
	/*SListPopFront(&a);  判断191行的断言*/
	/*SListPopBack(&a);*/  //测试129行的断言的
	//SListDestory(&a);
	SListPrint(a);
}


int main()
{
	test();
	return 0;
}

单链表各个接口的实现

1.尾插

void SListPushBack(Node** phead, SDataType x)
{
	assert(phead);
	Node* tail = *phead;
	//没有节点
	if ((*phead == NULL))
	{
		*phead = BuyNode(phead, x);
		(*phead)->next = NULL;
	}
	//有节点
	else
	{
		while (tail->next)    //采用遍历的方法找尾节点,同时这儿要注意while()里面的写法
		{
			tail = tail->next;
		}
		Node* newnode = BuyNode(phead, x);
		tail->next = newnode;
		newnode->next = NULL;
	}
}

2.尾删

void SListPopBack(Node** phead)
{
	assert(phead);
	assert(*phead);
	/*assert(!SListEmpty(phead));*/
	//只有一个节点的情况
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个节点的情况
	else
	{
		Node* tail = *phead;
		while (tail->next)
		{
			tail = tail->next;
		}
		Node* tailpre = *phead;
		while (tailpre->next != tail)
		{
			tailpre = tailpre->next;
		}
		tailpre->next = tail->next;
		free(tail);
		tail = NULL;
	}
}

3.打印

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

4.头插

void SListPushFront(Node** phead, SDataType x)
{
	assert(phead);
	//链表中没有节点的情况
	if (*phead == NULL)
	{
		Node* newnode = BuyNode(phead, x);
		newnode->next = NULL;
		*phead = newnode;
	}
	//链表中有节点的情况
	else
	{
		Node* newnode = BuyNode(phead, x);
		newnode->next = *phead;
		*phead = newnode;
	}
}

5.头删

void SListPopFront(Node** phead)
{
	assert(phead);
	assert(*phead);
	//一个节点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个节点
	else
	{
		Node* newhead = (*phead)->next;
		free(*phead);
		*phead = newhead;
	}
}

6.查找

Node* SListFind(Node* phead, SDataType x)
{
	assert(phead);
	Node* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
}

7.在任意位置之前插入

void SListPushBefore(Node** phead, Node* pos, SDataType x)
{
	assert(phead);
	//在第一个节点之前插入的情况
	if (pos==*phead)
	{
		Node* newnode = BuyNode(phead, x);
		newnode->next = *phead;
		*phead = newnode;
	}
	//在非第一个节点之前插入的情况,这时就要记录你要插入节点的前一个节点了
	else
	{
		Node* pospre = *phead;
		while (pospre->next != pos)
		{
			pospre = pospre->next;
		}
		Node* newnode = BuyNode(phead, x);
		pospre->next = newnode;
		newnode->next = pos;
	}
}

8.在任意位置之后插入

void SListPushAfter(Node** phead, Node* pos, SDataType x)
{
	assert(phead);
	//由于是在节点之后插入,所以没必要分类了
	Node* newnode = BuyNode(phead, x);
	newnode->next = pos->next;
	pos->next = newnode;
}

9.删除当前位置

void SListPop(Node** phead, Node* pos)
{
	assert(phead);
	assert(*phead);
	//删除第一个节点的情况
	if (pos == *phead)
	{	
//		free(*phead);
不能这样写,因为如果你这样写free掉*phead时,相当于把pos也释放掉了,因为你的pos
是通过find这个接口找到的,而这个接口也是二级指针接收,所以你在这个接口总释放
掉了*phead,也会有影响到find的那个接口。应该用一个指针提前记录好新的头节点
//		*phead =pos->next;
		Node* newhead = pos->next;
		free(*phead);
		*phead = newhead;
	}
	//删除的不是第一个节点时
	else
	{
		Node* pospre = *phead;
		while (pospre->next != pos)
		{
			pospre = pospre->next;
		}
		pospre->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

10.删除任意位置的前一个节点

void SListPopBefore(Node** phead, Node* pos)
{
	assert(phead);
	assert(*phead);
	assert((*phead)->next != NULL); //当链表中只有一个元素时,此时它之后也不能删除了,因为之后没有元素了。
    //当pos为第二个节点,也就是要删除的是第一个节点时,也就是头删,这儿也可以选择复用
	if (pos == (*phead)->next)
	{
		Node* newhead = (*phead)->next;
		free(*phead);
		*phead = newhead;
	}
	//其他情况
	else
	{
		Node* posprepre = *phead;
		while (posprepre->next->next != pos)
		{
			posprepre = posprepre->next;
		}
		posprepre->next = pos;
		free(posprepre->next);
		posprepre->next = NULL;
	}
}

11.删除任意位置的后一个节点

void SListPopAfter(Node** phead, Node* pos)
{
	assert(phead);
	assert(*phead);
	assert((*phead)->next != NULL);//当链表中只有一个元素时,此时它之后也不能删除了,因为之后没有元素了。
	Node* posnextnext = pos->next->next;
	free(pos->next);
	pos->next = posnextnext;
}

12.销毁

void SListDestory(Node** phead)
{
	assert(phead);
	assert(*phead);
	Node* cur = *phead;
	Node* next = NULL;
	while (cur)
	{
		next= cur->next;
		free(cur);
		cur = next;
	}
	printf("销毁成功\\n");
}

13.判空

bool SListEmpty(Node* phead)
{
	return phead == NULL;
}

注意点

1.单链表的实现方式里面不能用一级指针接收的方式来实现也不可能做的到

因为像这些地方你必须要解引用传过来的指针才能改变外面,而你类型又不同,所以这种方式不可能实现

这一点在栈的实现当中就深有体会,具体可以去看链接


2.单链表不需要初始化这个接口

单链表不需要初始化这个接口,而且你单链表本来就是由一个个插入的节点构成,为什么要初始化了,又初始化什么东西了 也没东西给你初始化呀。

3.尾节点如何解决的问题(悬疑问题)

尾节点一般大家写单链表的时候都是通过便利去找的,这样效率会比较低,但是不是只能这么实现了?我有一个想法,我们可以用结构体包裹的方法,定义一个单链表类型的结构体,这个结构体里面包含了一个头指针和尾指针,因为整个结构体其实就可以看成由这两个指针构成的,然后这两个指针又是节点类型的结构体,具体我还没实现,只是想法而已,如果实现了效率将大大提高

4. free时注意函数之间可能会有影响(悬疑问题)

如果这段代码你用我注释中的那种写法来写的话,你会发现当释放掉phead时,pos居然也释放掉了,我猜测具体原因可能是与Find那个函数之间存在一些影响,暂且把它做为悬疑问题,日后回过头来再看看。

5.很多接口都要注意分类讨论

这儿就不具体再说了,在上面的各个函数的实现里面,注释都写的很清楚。

以上是关于每天学习亿点点系列——重温单链表的主要内容,如果未能解决你的问题,请参考以下文章

每天学习亿点点系列——OJ203题:移除链表元素

每天进步一点点之带头节点单链表

每天学习亿点点 day20,21,22 : 排序算法总结

每天学习亿点点day 13: UE4 渲染pipeline

每天学习亿点点day 5,6: Actor, UobjectBase, component的源码剖析

每天学习亿点点day 16: 脚部IKBone的做法