【数据结构】详解单链表(C语言实现)

目录

一、引言

1.1 为什么我们需要链表

1.2 单链表的概念与结构

二、单链表的实现

2.1 战前准备

2.2 特定位置节点的插入

2.2.1 节点的创建

2.2.2 头插

2.2.3 尾插

2.3 特定位置节点的删除

2.3.1 头删

2.3.2 尾删

2.3.2.1 尾删思路一

2.3.2.2 尾删思路二

2.4 打印单链表

2.5 在指定位置插入或删除节点

2.5.1 寻找特定数据

2.5.2 插入

2.5.2.1 在指定位置前插入节点

2.5.2.2 在指定位置后插入节点

2.5.3 删除

2.5.3.1 删除指定位置的节点

2.5.3.2 删除指定位置后的节点

2.6 销毁单链表

三、小结

3.1 顺序表与单链表的对比

3.2 结语

四、源代码

4.1 main.c

4.2 SinLinkedList.c

4.3 SinLinkedList.h


一、引言

1.1 为什么我们需要链表

        在学习链表之前,我们已经学过了顺序表,我们知道顺序表是为了弥补数组的缺陷而设计出来的,相应的,链表是为了弥补顺序表的缺陷而设计出来的。

        我们来了解一下顺序表的缺陷:

        1、空间不够了需要增容,增容是要付出代价的。

        

         如上图所示明明同样是realloc,但是两次realloc返回的地址不一样。这是因为realloc扩容有两种方式:第一种为原地扩容,即第一次扩容,这是当arr后有足够的内存可以申请用来存放数据是会执行的扩容方式。第二种为异地扩容,即第二次扩容,当arr后没有足够的内存时,会重新在另一处地址为起点申请足够的空间,再将原空间的的数据拷贝到新空间后将原空间释放,最后返回新空间的首地址。在第二种的过程中,拷贝数据是会造成消耗的

        2、避免频繁扩容,我们在顺序表满了后基本都是按倍数扩容,可能就会导致一定的空间浪费。

        3、顺序表要求数据从开始位置连续存储,那么我们在头部或者中间位置插入删除数据,就需要挪动数据,效率不高。

        针对顺序表的缺陷,就设计出了链表。

        而我们今天讲解的是链表中的单链表。

1.2 单链表的概念与结构

        概念:单链表是一种链式存取数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

        结构:单链表(Singly Linked List)是一种常用的数据结构,它由若干个节点组成,每个节点包含两部分:数据域指针域。数据域用于存储数据,而指针域则用于指向下一个节点的地址。单链表中每个节点只有一个指针域,指向下一个节点,最后一个节点的指针域指向 NULL,表示链表的结尾。


二、单链表的实现

2.1 战前准备

        在开始设计接口前,我们先需要完成对单链表的单位——节点的定义

//单链表存贮的数据类型
typedef int SLLDataType;

//创建单链表的节点
typedef struct SLLNode
{
	SLLDataType data;

	//用来存贮下一个节点的地址,用于后续来操作链表
	struct SLLNode* next;
}Node;

2.2 特定位置节点的插入

2.2.1 节点的创建

        在对链表进行节点的插入前,我们一定需要有可用于插入的节点,插入可以分为头插和尾插还有在指定位置插入多种逻辑,而创建节点就只有一种逻辑,一份相同代码不应在程序内多次出现,所以我们应封装一个函数来实现节点的创建。

//创造一个新的结点,并完成赋值操作
static Node* SLLNodeCreat(const SLLDataType val)
{
	Node* newnode = (Node*)malloc(sizeof(Node));
	if (newnode == NULL)
	{
		exit(-1);
	}

	//对新节点进行赋值
	newnode->data = val;
	newnode->next = NULL;

	//返回新节点的地址,用于后续访问
	return newnode;
}

        static可以避免该函数被其他文件误用。

2.2.2 头插

        单链表的头插相对简单,无论单链表在插入前是否有节点,都是一个逻辑。

void SLLPushFront(Node** pplist, const SLLDataType val)
{
	assert(pplist);
	assert(*pplist);

	Node* newnode = SLLNodeCreat(val);

	newnode->next = *pplist;
	*pplist = newnode;
}

        一定要先执行第一步,再执行第二步,如果反过来就会失去原本pplist所指向的地址。

2.2.3 尾插

        尾插相对于比较麻烦,麻烦在于它有着两种逻辑,在不同情况下需要不同处理。

        当链表内原本有节点时,运用尾插的逻辑;当链表内没有节点时,就是头插的逻辑了。

void SLLPushBack(Node** pplist,const SLLDataType val)
{
	assert(pplist);

	Node* newnode = SLLNodeCreat(val);

	//分情况讨论
	//当链表内没有数据时就相当于头插,要特殊处理
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}

	//当链表内有数据时要先找到链表的末尾
	else
	{
		Node* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

        要做到尾插,我们就需要找到链表的末尾。我们就可以定义一个节点指针来通过遍历来寻找链表的末尾。但是遍历什么时候停止呢?我们就要抓住末尾的特征:最后一个节点的next == NULL,我们就可以以这个特征来结束循环。

        找到尾后的插入操作就大同小异了。因为newnode在创建的时候next就已经设置为NULL了,就不需要对其进行操作了。

2.3 特定位置节点的删除

2.3.1 头删

        头删的逻辑相对简单,与头插相似。

        头删在进行处理时同样就只有一个逻辑,无需特殊处理。

void SLLPopFront(Node** pplist)
{
	assert(pplist);
	assert(*pplist);

	Node* next = (*pplist)->next;

	free(*pplist);
	*pplist = next;
}

        在进行删除前,我们需要定义一个指针next来记录第二个节点的位置,这样才不会丢失后面的节点。

2.3.2 尾删

        尾删和尾插有着一样的性质,在不同的情况下有着尾删和头删两种逻辑。

2.3.2.1 尾删思路一

        但是尾删有点特别,因为在删除时,我们需要对倒数第二个节点的next进行改变,但是因为单链表的缺陷,我们无法从后一个节点找到前一个节点,所以我们需要引入一个新指针prev来记录倒数第二个节点的位置

void SLLPopBack(Node** pplist)
{
	assert(pplist);
	assert(*pplist != NULL);

	//当链表内没有数据时相当于头删,要特殊处理
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		//因为单链表的缺陷,从后一个节点无法找到前一个节点
		//想要对前一个节点的next进行修改,就要记录上一个节点的地址
		Node* tail = *pplist;
		Node* prev = NULL;

		//寻找链表的最后的一个节点
		while (tail->next != NULL)
		{
			//记录上一个节点的地址
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

        第二步操作将tail设为NULL是为安全考虑,其实没有这一步完全可行。

2.3.2.2 尾删思路二

        尾删除了上述思路外还有一种思路:

void SLLPopBack(Node** pplist)
{
	assert(pplist);
	assert(*pplist != NULL);

	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		Node* tail = *pplist;

		//通过一下访问两个节点可以做到不用记录链表末尾的前一个节点
		//tail最终停在了倒数第二个节点
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

         这种思路没有引入指针prev,而是通过一下读取两个节点,将tail停在了倒数第二个节点,而非倒数第一个节点。

        两种思路都是可行的,可以依照自己的理解来选择。

2.4 打印单链表

        我们可以通过循环来打印单链表,通过cur来遍历,当cur==NULL时便会停止,如果单链表内没有数据,即cur==plist==NULL则不会进入循环打印数据。

void SLLPrint(const Node* plist)
{
	//保留assert就不能在单链表内没有数据时打印数据
	//assert(plist);

	const Node* curr = plist;

	//直到指向链表的末端才停下
	while (curr != NULL)
	{
		printf("%d->", curr->data);
		curr = curr->next;
	}
	printf("NULL\n");
}

2.5 在指定位置插入或删除节点

2.5.1 寻找特定数据

        单链表和顺序表不同,单链表各个节点的地址不一定时连续的,想要对单链表完成指定位置的插入和删除,就必须知道目标节点的位置。

Node* SLLFind(Node* plist, const SLLDataType val)
{
	assert(plist);

	//从一个节点开始找
	Node* pos = plist;

	while (pos)
	{
		if (pos->data == val)
		{
			//找到了就返回该节点的地址
			return pos;
		}
		pos = pos->next;
	}

	//没找到就返回空指针
	return NULL;
}

        如果找到了,则返回目标节点的下标,若没找到就返回空指针。

        如果想要查找重复元素,可以采用迭代(循环)的方法。

    Node* pos = SLLFind(plist, 10);
    while (pos != NULL)
    {
        //......
        //你想要进行的操作
        pos = SLLFind(plist, 10);
    }

2.5.2 插入

2.5.2.1 在指定位置前插入节点
void SLLInsertBefore(Node** pplist, Node* pos, const SLLDataType val)
{
	assert(pplist);
	assert(pos);

	Node* newnode = SLLNodeCreat(val);

	//头插需要特殊处理
	if (*pplist == pos)
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
	else
	{
		//找到目标节点的前一个节点
		Node* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		newnode->next = pos;
		prev->next = newnode;
	}
}

        在指定位置前插入会包含头插的情况,所以我们必须对头插的情况特殊处理。

        并且它有着与尾插类似的窘境,我们必须要找到指定位置的前一个节点,然后改变其的next。我们同样需要prev来记录目标节点上个节点的位置,然后完成插入操作。

        当prev->next==pos时,就说明prev已经到达了目标节点的上一个节点。

2.5.2.2 在指定位置后插入节点

        单链表的缺陷限制了单链表从后往前寻找数据,但从前往后寻找数据并没有困难,所以在指定位置后插入节点比在指定位置前插入节点要轻松许多。

void SLLInsertAfter(Node* pos, const SLLDataType val)
{
	assert(pos);

	Node* newnode = SLLNodeCreat(val);

	newnode->next = pos->next;
	pos->next = newnode;
}

2.5.3 删除

2.5.3.1 删除指定位置的节点
void SLLErase(Node** pplist, Node* pos)
{
	assert(pplist);
	assert(*pplist);

	//头删特殊处理
	if (*pplist == pos)
	{
		*pplist = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{
		Node* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

        删除指定位置的节点同样需要对头删进行特殊处理,也同样需要借助prev指针来记录上一个节点的地址。

2.5.3.2 删除指定位置后的节点

        删除指定位置后的节点也同样相比删除指定位置的节点要轻松许多。

void SLLEraseAfter(Node* pos)
{
	assert(pos);

	//保证指定位置的后面有数据可删
	assert(pos->next);

	//记录pos->next的位置,用来销毁空间
	Node* next = pos->next;
	pos->next = next->next;
	free(next);
	next = NULL;
}

        相比于另外两种指定位置的插入和删除,这里更推荐在指定位置后插入,在指定位置后删除,这两种更简单也更合理,符合单链表的特点。

2.6 销毁单链表

        销毁单链表也需要我们从第一个节点开始,通过遍历找到每一个节点的地址后再释放空间。在这个过程中,我们同样需要记录将被销毁节点的下一个节点,用于后续节点的销毁。

void SLLDestory(Node** pplist)
{
	assert(pplist);
	assert(*pplist);

	Node* curr = *pplist;
	
	//记录下一个节点的地址,防止一个节点销毁后找不到下一个节点
	Node* next = NULL;
	while (curr)
	{
		next = curr->next;
		free(curr);
		curr = next;
	}

	//将plist设为空,指示该链表已经没有数据
	*pplist = NULL;
}

        在所有节点被销毁完毕后,将plist设为NULL,代表着这个单链表已经没有节点了。


三、小结

3.1 顺序表与单链表的对比

顺序表单链表
优点缺点优点缺点
支持随机访问。空间不够了需要扩容,扩容是有消耗。按需申请空间,不用了就释放空间(更合理的使用了空间)。每个一个数据,都要存一个指针去链接后面数据节点。不支持随机访问(用下标直接访问第i个)。
头部或者中间位置的插入删除,需要挪动数据,挪动数据也是有消耗的。头部中间插入删除数据,不需要挪动数据。
为了避免频繁扩容,一次一般都是按倍数扩容,可能存在一定空间浪费。不存空间浪费。

        注:有些算法是需要结构支持随机访问的,比如:二分查找和优化的快速排序,等等。

        所以,顺序表和单链表各有其的好处,我们两种都需要学习,而不是单一的学习一种。

3.2 结语

        今天我们讲解的是链表中的单链表,并且是没有哨兵位的单链表。但是我们可以发现,单链表也有很大的缺陷,如不能从后往前访问,这就需要我们来学习双向链表来解决这个问题。数据结构需要不断的学习和练习,理论要和实践并存。

        以上为我个人对单链表的认识,如有错误还请各位指正!


四、源代码

4.1 main.c

#include "SinLinkedList.h"

static void SLLTest1()
{
	Node* plist = NULL;
	SLLPushBack(&plist, 1);
	SLLPushBack(&plist, 2);
	SLLPushBack(&plist, 3);
	SLLPushBack(&plist, 4);
	SLLPushBack(&plist, 5);
	SLLPrint(plist);
	SLLPopBack(&plist);
	SLLPopBack(&plist);
	SLLPopBack(&plist);
	SLLPopBack(&plist);
	//SLLPopBack(&plist);
	//SLLPopBack(&plist);
	//SLLPopBack(&plist);
	//SLLPopBack(&plist);
	//SLLPopBack(&plist);
	SLLPushBack(&plist, 10);
	SLLPushBack(&plist, 20);
	SLLPrint(plist);
}

static void SLLTest2()
{
	Node* plist = NULL;
	SLLPushFront(&plist, 80);
	SLLPushFront(&plist, 100);
	SLLPushFront(&plist, 120);
	SLLPushFront(&plist, 170);
	SLLPrint(plist);
	SLLPushBack(&plist, 1);
	SLLPushBack(&plist, 2);
	SLLPushBack(&plist, 3);
	SLLPushBack(&plist, 4);
	SLLPushBack(&plist, 5);
	SLLPrint(plist);
	SLLPushFront(&plist, 10);
	SLLPushFront(&plist, 20);
	SLLPushFront(&plist, 30);
	SLLPushFront(&plist, 40);
	SLLPushFront(&plist, 50);
	SLLPrint(plist);
}
static void SLLTest3()
{
	Node* plist = NULL;
	SLLPushFront(&plist, 10);
	SLLPushFront(&plist, 20);
	SLLPushFront(&plist, 30);
	SLLPushFront(&plist, 40);
	SLLPrint(plist);
	SLLPopBack(&plist);
	//SLLPopBack(&plist);
	SLLPopBack(&plist);
	SLLPrint(plist);
	SLLPushFront(&plist, 50);
	SLLPushFront(&plist, 60);
	SLLPushFront(&plist, 70);
	SLLPushFront(&plist, 80);
	SLLPrint(plist);
	SLLPopFront(&plist);
	SLLPopFront(&plist);
	SLLPopFront(&plist);
	SLLPrint(plist);
	SLLPopFront(&plist);
	//SLLPopFront(&plist);
	SLLPopFront(&plist);
	SLLPopFront(&plist);
	SLLPushBack(&plist, 1);
	SLLPushBack(&plist, 2);
	SLLPushBack(&plist, 3);
	SLLPrint(plist);
}

static void SLLTest4()
{
	Node* plist = NULL;
	SLLPushFront(&plist, 10);
	SLLPushFront(&plist, 20);
	SLLPushFront(&plist, 30);
	SLLPushFront(&plist, 40);
	SLLPrint(plist);
	Node* pos = SLLFind(plist, 10);
	if (pos)
	{
		printf("找到了,地址为%p\n", pos);
	}
	else
	{
		printf("没找到\n");
	}
	pos = SLLFind(plist, 70);
	if (pos)
	{
		printf("找到了,地址为%p\n", pos);
	}
	else
	{
		printf("没找到\n");
	}
}

static void SLLTest5()
{
	Node* plist = NULL;
	SLLPushBack(&plist, 10);
	SLLPushBack(&plist, 20);
	SLLPushBack(&plist, 30);
	SLLPushBack(&plist, 40);
	SLLPrint(plist);
	Node* pos = SLLFind(plist, 40);
	SLLInsertBefore(&plist, pos, 35);
	SLLPrint(plist);
	pos = SLLFind(plist, 10);
	SLLInsertBefore(&plist, pos, 5);
	SLLPrint(plist);
	//pos = SLLFind(plist, 100);
	//SLLInsertBefore(&plist, pos, 95);
	pos = SLLFind(plist, 10);
	SLLInsertAfter(pos, 15);
	SLLPrint(plist);
	pos = SLLFind(plist, 40);
	SLLInsertAfter(pos, 45);
	SLLPrint(plist);
}

static void SLLTest6()
{
	Node* plist = NULL;
	SLLPushBack(&plist, 10);
	SLLPushBack(&plist, 20);
	SLLPushBack(&plist, 30);
	SLLPushBack(&plist, 40);
	SLLPrint(plist);
	Node* pos = SLLFind(plist, 40);
	SLLErase(&plist, pos);
	SLLPrint(plist);
	pos = SLLFind(plist, 10);
	SLLErase(&plist, pos);
	SLLPrint(plist);
	SLLPushBack(&plist, 70);
	SLLPushBack(&plist, 80);
	SLLPushBack(&plist, 90);
	SLLPushBack(&plist, 100);
	SLLPrint(plist);
	pos = SLLFind(plist, 20);
	SLLEraseAfter(pos);
	SLLPrint(plist);
	//pos = SLLFind(plist, 90);
	//SLLEraseAfter(pos);
	//SLLPrint(plist);
	//pos = SLLFind(plist, 100);
	//SLLEraseAfter(pos);
	SLLPrint(plist);
}

static void SLLTest7()
{
	Node* plist = NULL;
	SLLPushBack(&plist, 10);
	SLLPushBack(&plist, 20);
	SLLPushBack(&plist, 30);
	SLLPushBack(&plist, 40);
	SLLPrint(plist);
	SLLDestory(&plist);
	SLLPopBack(&plist);

	//Node* pos = SLLFind(plist, 10);
	//while (pos != NULL)
	//{
	//	//......
	//	//你想要进行的操作
	//	pos = SLLFind(plist, 10);
	//}
}
int main()
{
	//SLLTest1();
	//SLLTest2();
	//SLLTest3();
	//SLLTest4();
	//SLLTest5();
	SLLTest6();
	//SLLTest7();
	return 0;
}

4.2 SinLinkedList.c

#include "SinLinkedList.h"

void SLLPrint(const Node* plist)
{
	//保留assert就不能在单链表内没有数据时打印数据
	//assert(plist);

	const Node* curr = plist;

	//直到指向链表的末端才停下
	while (curr != NULL)
	{
		printf("%d->", curr->data);
		curr = curr->next;
	}
	printf("NULL\n");
}

//创造一个新的结点,并完成赋值操作
static Node* SLLNodeCreat(const SLLDataType val)
{
	Node* newnode = (Node*)malloc(sizeof(Node));
	if (newnode == NULL)
	{
		exit(-1);
	}

	//对新节点进行赋值
	newnode->data = val;
	newnode->next = NULL;

	//返回新节点的地址,用于后续访问
	return newnode;
}

void SLLPushBack(Node** pplist,const SLLDataType val)
{
	assert(pplist);

	Node* newnode = SLLNodeCreat(val);

	//分情况讨论
	//当链表内没有数据时就相当于头插,要特殊处理
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}

	//当链表内有数据时要先找到链表的末尾
	else
	{
		Node* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

void SLLPopBack(Node** pplist)
{
	assert(pplist);
	assert(*pplist != NULL);

	//当链表内没有数据时相当于头删,要特殊处理
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		//因为单链表的缺陷,从后一个节点无法找到前一个节点
		//想要对前一个节点的next进行修改,就要记录上一个节点的地址
		Node* tail = *pplist;
		Node* prev = NULL;

		//寻找链表的最后的一个节点
		while (tail->next != NULL)
		{
			//记录上一个节点的地址
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

//void SLLPopBack(Node** pplist)
//{
//	assert(pplist);
//	assert(*pplist != NULL);
//
//	if ((*pplist)->next == NULL)
//	{
//		free(*pplist);
//		*pplist = NULL;
//	}
//	else
//	{
//		Node* tail = *pplist;
//
//		//通过一下访问两个节点可以做到不用记录链表末尾的前一个节点
//		//tail最终停在了倒数第二个节点
//		while (tail->next->next != NULL)
//		{
//			tail = tail->next;
//		}
//
//		free(tail->next);
//		tail->next = NULL;
//	}
//}

void SLLPushFront(Node** pplist, const SLLDataType val)
{
	assert(pplist);
	assert(*pplist);

	Node* newnode = SLLNodeCreat(val);

	newnode->next = *pplist;
	*pplist = newnode;
}

void SLLPopFront(Node** pplist)
{
	assert(pplist);
	assert(*pplist);

	Node* next = (*pplist)->next;

	free(*pplist);
	*pplist = next;
}

Node* SLLFind(Node* plist, const SLLDataType val)
{
	assert(plist);

	//从一个节点开始找
	Node* pos = plist;

	while (pos)
	{
		if (pos->data == val)
		{
			//找到了就返回该节点的地址
			return pos;
		}
		pos = pos->next;
	}

	//没找到就返回空指针
	return NULL;
}

void SLLInsertBefore(Node** pplist, Node* pos, const SLLDataType val)
{
	assert(pplist);
	assert(pos);

	Node* newnode = SLLNodeCreat(val);

	//头插需要特殊处理
	if (*pplist == pos)
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
	else
	{
		//找到目标节点的前一个节点
		Node* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		newnode->next = pos;
		prev->next = newnode;
	}
}

void SLLInsertAfter(Node* pos, const SLLDataType val)
{
	assert(pos);

	Node* newnode = SLLNodeCreat(val);

	newnode->next = pos->next;
	pos->next = newnode;
}

void SLLErase(Node** pplist, Node* pos)
{
	assert(pplist);
	assert(*pplist);

	//头删特殊处理
	if (*pplist == pos)
	{
		*pplist = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{
		Node* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SLLEraseAfter(Node* pos)
{
	assert(pos);

	//保证指定位置的后面有数据可删
	assert(pos->next);

	//记录pos->next的位置,用来销毁空间
	Node* next = pos->next;
	pos->next = next->next;
	free(next);
	next = NULL;
}

void SLLDestory(Node** pplist)
{
	assert(pplist);
	assert(*pplist);

	Node* curr = *pplist;
	
	//记录下一个节点的地址,防止一个节点销毁后找不到下一个节点
	Node* next = NULL;
	while (curr)
	{
		next = curr->next;
		free(curr);
		curr = next;
	}

	//将plist设为空,指示该链表已经没有数据
	*pplist = NULL;
}

4.3 SinLinkedList.h

#pragma once

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

//单链表存贮的数据类型
typedef int SLLDataType;

//创建单链表的节点
typedef struct SLLNode
{
	SLLDataType data;

	//用来存贮下一个节点的地址,用于后续来操作链表
	struct SLLNode* next;
}Node;

//依此打印单链表内的数据
extern void SLLPrint(const Node* plist);

//从单链表的尾部开始插入数据
extern void SLLPushBack(Node** pplist, const SLLDataType val);

//在单链表的尾部开始删除数据
extern void SLLPopBack(Node** pplist);

//从单链表的头部开始插入数据
extern void SLLPushFront(Node** pplist, const SLLDataType val);

//从单链表的头部开始删除数据
extern void SLLPopFront(Node** pplist);

//从单链表中查找数据
extern Node* SLLFind(Node* plist, const SLLDataType val);

//在指定位置的前面插入数据
extern void SLLInsertBefore(Node** pplist, Node* pos, const SLLDataType val);
 
//在指定位置后面插入数据
//相较于在前面插入数据,在后面插入数据更符合单链表的特性,也更加简单
extern void SLLInsertAfter(Node* pos, const SLLDataType val);

//删除指定数据
extern void SLLErase(Node** pplist, Node* pos);
 
//删除指定位置后面的数据
extern void SLLEraseAfter(Node* pos);

//销毁单链表
extern void SLLDestory(Node** pplist);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值