单链表的实现

引例

        顺序表的容量变化,对于增容,我们一般是以两倍的数量进行成倍增长,这样可能会出现空间的浪费,在这里,我们就要介绍一种新的线性表----链表。链表的逻辑结构是线性的,但它的物理空间是非线性的。

链表就像火车车厢,每节车厢都是独立的,火车是由一个一个车厢组成的,而链表是由一个一个节点组成的。

链表的组成

一.节点的组成

1.数据

2.指向下一个节点的地址的指针

我们利用结构体来定义链表

struct SListNode
{
	int data;
	struct SListNode* next;
};

通过有一个结构体指针来指向下一个链表的节点。

于是,我们来尝试创建几个节点

SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2; 
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;

通过malloc函数创建node指针作为节点,然后,我们再通过结构体指针next将节点连接起来。

node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;

为了更直观地调试代码,这里我们写一个Print函数来将链表的值打印出来

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");	
}
SLTNode* plist = node1;
SLTPrint(plist);

如果链表为空则会打印"NULL"。

这样就将我们给节点所赋的值打印出来了。

二.链表的具体实现

对于链表的具体实现,我们要实现以下几个函数

SLTNode* SLTBuyNode(SLTDataType x);
void SLTPrint(SLTNode* phead);//定义形参phead执行链表的第一个节点
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

1.节点创建函数

节点的创建,我们需要返回一个指向节点的指针,所以函数形式为SLTNode*:

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)//防止创建失败
	{
		perror("malloc fail!");
		return;
	}
	newnode->data = x;//新节点中的值
	newnode->next = NULL;//创建新节点后的值为空
	return newnode;
}

2.尾插函数

尾插函数要注意的是,传到函数的实参应该是指针的函数,因为这里我们想要让形参影响形参,所以我们不传值,而是传地址(这里的地址指的是指针的地址,而不是值的地址)。

void SLTPushBack(SLTNode** pphead, SLTDataType x)//phead为头节点
{
	assert(pphead);//防止传来的指针的地址为空
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)//如果传来的指针为空
	{
		*pphead = newnode;//则直接将传来的指针指向newnode的值
	}
	else
	{
		SLTNode* ptail = *pphead;//定义ptail来进行指针的指向变化
		while (ptail->next)
		{
			ptail = ptail->next;//ptail往后走
		}
		ptail->next = newnode;//此时ptail指向的就是尾节点,于是将新节点给这个尾节点
	}
}

3.头插函数

头插则较为简单,先将要插入的节点与原来的头节点相连,再将newnode设为新的节点即可。

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;//直接传指针
	*pphead = newnode;//将newnode设为新的头节点
}

4.尾删函数

尾删函数要求头节点与头节点的地址都不为空,并且要分为两种情况,链表只有一节点和链表有多节点,避免ptail变为野指针。

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)//如果链表只有一个节点
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;//prev指向ptail
			ptail = ptail->next;
		}
			free(ptail);
			ptail == NULL;
			prev->next = NULL;
		
	}
}

5.头删函数

头删函数就要注意不能上来直接删除头节点,要先将头节点的下一个节点单独存起来

void SLTPopFront(SLTNode** pphead)//不能上来直接删头节点
{
	//链表不为空
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

6.查找函数

查找函数不需要形参改变实参,所以不需要传头节点

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;

	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}//pcur == NULL
	return NULL;//如果想要再遍历,则只让pcur改变,不改变phead
}

SLTNode* Find = SLTFind(plist, 3);
if (Find == NULL)
{
	printf("没有找到!");
}
else
{
	printf("找到了!");
}

7.在指定位置前插入数据函数

在指定位置前我们就要考虑第一个位置,又因为第一个位置前插入数据就是头插函数,所以这里我们直接调用即可。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//需要利用头插函数,此处需要用到头节点
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* prev = *pphead;
	//pos == *pphead,说明是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead,x);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;//遍历数组
		}
		newnode->next = pos;
		prev->next = newnode;//prev找到新节点newnode,与newnode连接
	}
}

8.在指定位置之后插入数据

在指定位置后插入数据相对简单,因为不需要找前一个节点了,也不需要遍历数组了。

void SLTInsertAfter(SLTNode* pos, SLTDataType x)//不需要遍历数组了,所以不需要头节点
{
	assert(pos);

	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;//注意节点连接顺序
	pos->next = newnode;
}

9.删除pos节点

此处仍然要考虑删除第一个节点的情况。

void SLTInsertAfter(SLTNode* pos, SLTDataType x)//不需要遍历数组了,所以不需要头节点
{
	assert(pos);

	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;//注意节点连接顺序
	pos->next = newnode;
}

10.删除pos之后的节点

此处要定义一个中间变量,防止pos->next指向的节点改变。

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//pos后的节点为空不需要删除
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

11.销毁链表

销毁链表需要头节点向后遍历,所以需要用到头节点的二级指针。

void SListDestroy(SLTNode** pphead)
{
	assert(pphead  && *pphead);

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

这样,单链表的实现就完成了

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值