C语言实现数据结构——单链表

本人数据结构编写规则

① 在函数中判断结构体指针是否为空时采用assert函数,而不是用if语句判断。
② 函数的命名规则遵循:操作+结构类型名 的规则。例如 InitSqList 与DestroySqList。
③ 严蔚敏老师一书中很多运用了C++的语法,而我们是用C语言来实现,因此编写规则与书上会有很多不同,但是思路是一样的。例如用malloc代替new,free代替delete,引用与指针的区别等。
④ 本文没有采用bool变量以及自定义的Status作为返回值来判断是否操作成功。

正文

线性表的特点是可以随意存取表中任意元素,但是在插入和删除时需要移动大量的元素效率低下,而且因为数组的长度相对固定的静态特性,当表中数据元素个数多且变化大时操作过程相对复杂,必然导致空间的浪费。链表是线性表的链式表示,用于弥补线性表的缺陷。但是链表本身也有缺陷,我们后面再说。

线性链式表,简称链表,特点是用一组任意的存储单元存储线性表的数据元素。因此为了表示当前元素和逻辑上的下一个元素之间的关系,除了存储本身的数据之外,还要存储一个指示其直接后继的信息。由这两个信息组成的单元叫做结点

通俗地讲,链表需要定义一个数据域和一个指针域,指针域用于存放逻辑上下一个元素的地址,即指向下一个元素。
C语言实现数据结构——单链表_第1张图片

函数实现

结构体定义

typedef int LLDataType;
typedef struct LinkListNode
{
	LLDataType data;
	struct LinkListNode* next;//存储下一个结点的地址
}LLNode;

其中定义LLDataType便于后续修改元素类型。data是数据域,next是指针域。此处我们只定义了一个next指针,而没有pre,指向上一个结点的指针,因此是单向链表。链表最后的结点指向NULL。

函数声明

//单链表不需要初始化
void PrintLinkList(LLNode** phead);		遍历打印链表
LLNode* CreateLinkNode(LLDataType i);	创建新的结点

void BackInsertLinkList(LLNode** pphead, LLDataType i);尾插
void BackDeleteLinkList(LLNode** pphead);尾删

void FrontInsertLinkList(LLNode** pphead, LLDataType i);头插
void FrontDeleteLinkList(LLNode** pphead);头删

LLNode* SearchLinkList_Address(LLNode** pphead, LLDataType i);
查找元素,返回的是该元素的地址
int SearchLinkList_Position(LLNode** pphead, LLDataType i);
查找元素,返回的是该元素的位置

void ModifyLinkList(LLNode** pphead, int pos, LLDataType i);
修改pos位置的元素

void InsertBeforeAddressLinkList(LLNode** pphead, 
LLNode* pos, LLDataType i);
在pos地址前插入

void InsertAfterAddressLinkList(LLNode* pos, LLDataType i);
在pos地址后插入

void DeleteAtLinkList(LLNode** pphead, LLNode* pos);
删除pos地址处的结点

void DeleteAfterLinkList(LLNode* pos);
删除pos地址处之后的一个结点

void DestoryLinkList(LLNode** pphead); 摧毁链表

注意:
本文是带头结点(也叫根结点)的单向链表,因此每次需要定义一个头结点指向NULL,又传参的时候某些函数需要传二级指针,因为要修改头结点(头指针)的指向以便链接之后的结点。

新建结点

LLNode* CreateLinkNode(LLDataType i)
{
	LLNode* newnode = (LLNode*)malloc(sizeof(LLNode));
	if (newnode == NULL)
	{
		printf("malloc失败!\n");
		exit(-1);
	}
	else
	{
		newnode->data = i;
		newnode->next = NULL;
	}
	return newnode;
}

在需要插入的时候定义一个newnode用于接收这个函数的返回值,之后用于插入操作。

打印链表

void PrintLinkList(LLNode** pphead)
{
	//不用assert,头结点可以没有下一个
	LLNode* cur = *pphead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
		//下一个结点的地址在next
	}
	printf("NULL\n");
}

一般是定义一个cur指针保存pphead,再通过cur来遍历。

尾插

void BackInsertLinkList(LLNode** pphead, LLDataType i)
{
	assert(pphead);
	LLNode* newnode = CreateLinkNode(i);
	if (*pphead == NULL)//如果是空链表,直接插上去
	{
		*pphead = newnode;
	}
	else
	{
		//从头开始找尾结点
		LLNode* tail = *pphead;
		1 2 3 4 5
		while (tail->next != NULL)//这里区别于打印
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

pphead不能为空,因为pphead是目标结构体的地址,不能对一个空指针进行操作,因此要assert判断。定义一个尾指针来找最后一个结点。区别于打印,这里尾结点的下一个是空时就要停止之后插入,打印的判断条件是指针走到空便结束,要区分开来。

尾删

void BackDeleteLinkList(LLNode** pphead)
{
	assert(pphead);
	LLNode* tail = *pphead; 尾指针
	if (*pphead != NULL) 先判断链表是否为空
	{
		if ((*pphead)->next == NULL) 再判断是只有一个结点还是多个
		{
			free(*pphead);
			*pphead = NULL;
			return;
		}
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
		return;
	}
	else
	{
		printf("链表为空,删除失败\n");
		return;
	}
}

头插

void FrontInsertLinkList(LLNode** pphead, LLDataType i)
{
	LLNode* newnode = CreateLinkNode(i);
	//if (*pphead == NULL)
	//{
	//	*pphead = newnode;
	//}
	//else		不用这一步
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
}

这里不用判断链表是否为空,空链表照样插入。

头删

void FrontDeleteLinkList(LLNode** pphead)
{
	assert(pphead);
	LLNode* aim = *pphead;
	if (aim == NULL)
	{
		printf("表为空,删除失败\n");
		return;
	}
	else 
	{
		//if (aim->next == NULL) 一个结点
		//{
		//	free(aim);
		//	aim = NULL;
		//	*pphead = NULL;
		//}
		*pphead = aim->next;
		free(aim);
	}
}

这里同样不用判断是否只有一个结点,因为aim指向空的时候也能free aim。free(NULL)是合理的。

查找元素(返回地址)

LLNode* SearchLinkList_Address(LLNode** pphead, LLDataType i)
{
	assert(pphead);
	LLNode* cur = *pphead;
	while (cur != NULL)
	{
		if (cur->data == i)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

返回的是结点的地址,如果没找到就返回NULL。

查找元素(返回位置)不常用

int SearchLinkList_Position(LLNode** pphead, LLDataType i)
{
	assert(pphead);
	int pos = 0;
	LLNode* cur = *pphead;
	while (cur != NULL)
	{
		pos++; 进来先++一下
		if (cur->data == i)
		{
			return pos;
		}
		cur = cur->next;
	}
	pos = 0;      置零
	return pos;
}

找到了就返回在第几个位置,没找到就返回0

修改元素(通过地址)

void ModifyLinkList_Add(LLNode* pos, LLDataType i)
{
	assert(pos);
	pos->data = i;
}

这太简单了

修改元素(指定位置)不常用

void ModifyLinkList_Pos(LLNode** pphead, int pos, LLDataType i)
{
	assert(pphead);
	LLNode* cur = *pphead;
	if (pos > 0)
	{
		for (int i = 1; i < pos; i++)
		{
			if (cur == NULL)
			{
				printf("位置溢出,修改失败\n");
				return;
			}
			cur = cur->next;
		}
		cur->data = i;
	}
	else
	{
		printf("位置输入不合法\n");
		return;
	}
}

这也没啥好说的

在结点前插入(通过地址)

void InsertBeforeAddressLinkList(LLNode** pphead, LLNode* pos,
LLDataType i)
{
	assert(pphead && pos);
	if ((*pphead) == pos) 先判断是否头插 是的话直接复用头插函数
	{
		FrontInsertLinkList(pphead, i);
	}
	else
	{
		LLNode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		LLNode* newnode = CreateLinkNode(i);
		newnode->next = pos;
		pre->next = newnode;
	}
}

在结点后插入(通过地址)

void InsertAfterAddressLinkList(LLNode* pos, LLDataType i)
{
	assert(pos);
	LLNode* aim = (pos)->next;
	LLNode* newnode = CreateLinkNode(i);
	newnode->next = aim;
	pos->next = newnode;
}

删除结点(通过地址)

void DeleteAtLinkList(LLNode** pphead, LLNode* pos)
{
	assert(pphead && pos);
	if ((*pphead) == pos) 如果是第一个结点,同样复用头删函数
	{
		FrontDeleteLinkList(pphead);
	}
	else
	{
		LLNode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		pre->next = (pos)->next;
		free(pos);
		pos = NULL;
	}
}

删除pos结点之后的一个结点(通过地址)

void DeleteAfterLinkList(LLNode* pos)
{
	assert(pos);
	LLNode* aim = pos->next;
	if (aim != NULL)
	{
		pos->next = aim->next;
		free(aim);
		aim = NULL;
	}
}

摧毁链表

void DestoryLinkList(LLNode** pphead)
{
	assert(pphead);
	LLNode* cur = *pphead;
	while (cur != NULL)
	{
		LLNode* aim = cur->next;
		free(cur);
		cur = aim;
	}
	*pphead = NULL;
}

效果展示
C语言实现数据结构——单链表_第2张图片
C语言实现数据结构——单链表_第3张图片
结语
单链表是一种使用指针来存储值的数据结构,其改善了线性表的缺点,但是也存在不足。单链表只能以一个方向进行遍历。为了把一个值插入链表中,首先需要找到插入的位置,之后进行断链勾链
在使用单链表时,通过指定下标或位置插入值的情况比较少见,使用结点地址更多一些。且链表的应用场景一般是作为复杂数据结构的子结构,因此单链表是今后学习复杂数据结构的基础。

单链表是链式数据结构的开端。

(本篇完)

你可能感兴趣的:(C/C++,c语言,数据结构)