单链表的简单应用

目录

一、顺序表的问题及思考

二、链表的概念及结构

三、单链表的实现

3.1 增

3.1.1 尾插

3.1.2 头插

3.1.3 指定位置前插入

3.1.4 指定位置后插入

3.2 删

3.2.1 尾删

3.2.2 头删

3.2.3 指定位置删除

3.2.4 指定位置后删除

3.2.5 链表的销毁

3.3 查

3.4 改

四、完整代码



一、顺序表的问题及思考

1.1 

中间/头部的插⼊删除,时间复杂度为O(N)

1.2 

增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

1.3 

增容⼀般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为

100,满了以后增容到 200,我们再继续插⼊了5个数据,

后面没有数据插入了,那么就浪费了95个数据空间。

二、链表的概念及结构

是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链
接次序实现的 。
链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。
只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。
车厢是独立存在的,且每节车厢都有车门。想象⼀下这样的场景,假设每节车厢的车门都是锁上的
状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从车头走到车尾? 最简单
的做法:每节车厢里都放⼀把下⼀节车厢的钥匙。
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,称之为“结点/节点”
#include<stdio.h>

typedef int luck;//存储不同类型数据,重命名,这样后面就只用改这里的int
struct SListNOde
{
	luck data;//存储的数据
	SListNOde* next;//指向下一个节点的指针
};
节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。
为什么还需要指针变量来保存下⼀个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时才去申请⼀块节点的空间),我们需要通过指
针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。
首先,来完成结点申请空间的函数。
这里选择使用malloc函数(因为每个节点的都是相对独立存在的,不涉及到扩容的操作)
SLNO* SLNOIn()//节点申请空间
{
	SLNO* newnode = (SLNO*)malloc(sizeof(SLNO));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	return newnode;
}

不要忘了强制类型转换成节点类型

再来写一个打印每个节点数据的函数
void SLNOprint(SLNO* phead)
{
	SLNO* pres = phead;
	while (pres)//节点处不是空指针就有数据
	{
		printf("%d->", pres->data);
		pres = pres->next;
	}
	printf("NULL\n");
}

这里来简单做个测试

void test01()
{
	SLNO* n1 = SLNOIn();
	n1->data = 1;
	n1->next = NULL;
	SLNOprint(n1);
}

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

再多弄几个节点

void test02()
{
	SLNO* phead = NULL;
	SLNO* n1;
	SLNO* n2;
	SLNO* n3;
	SLNO* n4;
	n1 = SLNOIn();
	n2 = SLNOIn();
	n3 = SLNOIn();
	n4 = SLNOIn();
	phead = n1;//指向第一个节点的指针

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;

	SLNOprint(phead);
}

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

目前测试着是可行的,接下来实现单链表

三、单链表的实现

3.1 增

与顺序表类似,往链表中插入额外的数据

3.1.1 尾插

将一个新的节点插入到链表尾部。

(图中所列地址仅为方便展示而顺手写的)

那么首先我们需要找到尾节点

那么判断是否是尾节点的条是?

通过该图我们可以看出,尾节点中的指针是NULL

那么判断条件就是
 

SLNO* pres = phead;
while (pres->next)
	{
		pres = pres->next;//尾部节点是该节点保存的下个节点地址为NULL
	}

同时,因为需要在原链表中新增一个节点

所以需要进行空间申请

在这之后,空间申请成功后,这块空间的地址需要由原尾节点进行保存

依此,我们可以写出如下函数

void SLNOInsertback(SLNO* phead, luck X)
{

	SLNO* pres = pphead;//用来查找尾
    SLNO* newnode =  SLNOIn();
    newnode->data = X;
	newnode->next = NULL;

    //要从尾部进行插入,那就要找到尾部节点
	while (pres->next)
	{
		pres = pres->next;//尾部节点是该节点保存的下个节点地址为NULL
	}
	pres->next = newnode;//申请一个新节点
	

}

那么这里只是就链表中已经有数据的情况写出这个函数

如果拿到的链表是一个空链表,那么进行pres->next就肯定会报错,不能对NULL进行解应用

那就单独判断一下
  

无论是哪种情况,插入一个数据肯定都是要申请一个新节点的


void SLNOInsertback(SLNO* phead, luck X)
{
	
      SLNO* pres = phead;//用来查找尾
      SLNO* newnode =  SLNOIn();//申请一个新节点
      newnode->data = X;
	  newnode->next = NULL;

     //如果拿到的链表是一个空链表,那么进行pres->next就肯定会报错,不能对NULL进行解应用
	 
	 //那就单独判断一下
	
	 //如果是这种情况,那么就直接申请一个新节点就好了
	
	if (pres == NULL)
	{
       phead = SLNOIn();
	   return;
	}

	 //要从尾部进行插入,那就要找到尾部节点
	while (pres->next)
	{
		pres = pres->next;//尾部节点是该节点保存的下个节点地址为NULL
	}
	pres->next = newnode;
}

测试一下

void test03()
{
	SLNO* phead = NULL;
	SLNOInsertback(phead,3);
	SLNOprint(phead);
}
int main()
{
	test03();
	return 0;
}

这里可以看到出现了问题,调试看下是个什么情况

这里可以看到

newnode的值改变了,但是phead的值没有改变

即只改变了形参的值,没有改变实参的值

说明这里是传值调用,我们需要用传址调用

这里phead自身是个指针变量,那么要存放一级指针变量的地址,就要使用二级指针接收。

对应的函数也需要做出改变。

void SLNOInsertback(SLNO** pphead, luck X)
{
	assert(pphead);
	SLNO* pres = *pphead;//用来查找尾
	SLNO* newnode =  SLNOIn();
	newnode->data = X;
	newnode->next = NULL;

	//如果拿到的链表是一个空链表,那么进行pres->next就肯定会报错,不能对NULL进行解应用

	//那就单独判断一下

	//如果是这种情况,那么就直接申请一个新节点就好了

	if (pres == NULL)
	{
	   *pphead = newnode;
		return;
	}

	//要从尾部进行插入,那就要找到尾部节点
	while (pres->next)
	{
		pres = pres->next;//尾部节点是该节点保存的下个节点地址为NULL
	}
	pres->next = newnode;
}
void test03()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);
}
int main()
{
	test03();
	return 0;
}

这会儿可以看到,是成功输出出来了

3.1.2 头插

即把数据从链表的头部进行插入

插入的这个新结点作为链表的头部节点

(图中所列地址仅为方便展示而顺手写的)

如图,插入该节点后,首节点就是这个新插入的节点,这个节点的指针保存的就是原首节点的地址

//头部数据插入
void SLNOInsertfront(SLNO** pphead, luck X)
{
	assert(pphead);
	SLNO* pres = *pphead;//保存头节点的位置
	SLNO* newnode = SLNOIn();//申请空间
    newnode->data = X;
	newnode->next = pres;
	*pphead = newnode;
}

在这里就不用判断要插入数据的链表为不为空了,因为是直接从头部插入,不需要查找节点

测试一下

void test04()
{
	SLNO* phead = NULL;
	SLNOInsertfront(&phead, 3);
	SLNOInsertfront(&phead, 4);
	SLNOInsertfront(&phead, 5);
	SLNOInsertfront(&phead, 6);
	SLNOprint(phead);
}
int main()
{
	test04();
	return 0;
}

是可行的

3.1.3 指定位置前插入

在某一个指定位置前插入,首先需要找到该指定位置,该位置的前一节点的指向新插入的节点,

原指向节点为新插入位置所指向的节点

(图中所列地址仅为方便展示而顺手写的)

void SLNOInsertbefore(SLNO** pphead, SLNO* pos, luck X)
{
	assert(pphead && pos);//要插入的位置肯定不能为空
	SLNO* pres = *pphead;
	SLNO* newnode = SLNOIn();//申请新节点
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找插入位置前的那个节点
	SLNO* temp = pres->next;//保存插入位置前节点指向的下一个节点的指针
	newnode->data = X;
	newnode->next = temp;//原指向节点为新插入位置所指向的节点
	pres->next = newnode;//让插入为位置前节点的指向变更为插入的节点
}

这里,由于是链表,pos是一个指针,我们需要找到我们想要插入数据的位置就要先将对应的地址找

出来,这里再来一个find函数

SLNO* Find(SLNO* phead, luck num)
{
	SLNO* pres = phead;
	while (num != pres->data && pres != NULL)
	{
		pres = pres->next;
	}
	if (pres == NULL)
	{
		return NULL;
	}
	return pres;
}

测试一下

void test03()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOInsertbefore(&phead, find, 10);
	SLNOprint(phead);

	 find = Find(phead, 7);
	SLNOInsertbefore(&phead, find, 9);
	SLNOprint(phead);
}

这次测试没有问题,我们再来看一组测试


void test04()
{
	SLNO* phead = NULL;
	SLNOInsertfront(&phead, 3);
	SLNOInsertfront(&phead, 4);
	SLNOInsertfront(&phead, 5);
	SLNOInsertfront(&phead, 6);
	SLNOInsertbefore(&phead, phead, 10);
	SLNOprint(phead);
}


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

可以看到代码异常退出了

这里调试看看

通过调试可以看出,这里的错误是对空指针解应用了

回到代码的逻辑

想在头节点前插入一个节点,那么pos就是直接传的头节点phead,在代码的判定逻辑中,在

循环判定这个位置就不能找到pos。

我们发现这里的插入实际上就是前文所写的头插,那我们在进行插入时进行判断,如果是这种

情况调用一下头插函数就好了

void SLNOInsertbefore(SLNO** pphead, SLNO* pos, luck X)
{
	assert(pphead && pos);//要插入的位置肯定不能为空
	SLNO* pres = *pphead;
	if (pos == pres)
	{
		SLNOInsertfront(pphead, X);
		return;
	}
	SLNO* newnode = SLNOIn();//申请新节点
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找插入位置前的那个节点
	SLNO* temp = pres->next;//保存插入位置前节点指向的下一个节点的指针
	newnode->data = X;
	newnode->next = temp;//原指向节点为新插入位置所指向的节点
	pres->next = newnode;//让插入为位置前节点的指向变更为插入的节点
}

在测试一下

void test04()
{
	SLNO* phead = NULL;
	SLNOInsertfront(&phead, 3);
	SLNOInsertfront(&phead, 4);
	SLNOInsertfront(&phead, 5);
	SLNOInsertfront(&phead, 6);
	SLNOInsertbefore(&phead, phead, 1);
	SLNOprint(phead);
}


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

这样就解决了。

3.1.4 指定位置后插入

所插入位置的节点指针更改为指向这个插入的节点,原指向位置由插入的新节点指向

(图中所列地址仅为方便展示而顺手写的)

//插入指定位置后
void SLNOInsertafter(SLNO** pphead, SLNO* pos, luck X)
{
	assert(*pphead && pos);
	SLNO* pres = *pphead;
	SLNO* newnode = SLNOIn();
	while (pres != pos)
	{
		pres = pres->next;
	}//找到插入节点
	SLNO* temp = pres->next;//保存插入原指向位置
	newnode->data = X;
	newnode->next = temp;
	pres->next = newnode;
}

 测试一下

void test05()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOInsertafter(&phead, find, 10);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOInsertafter(&phead, find, 9);
	SLNOprint(phead);

	SLNOInsertafter(&phead, phead, 1);
	SLNOprint(phead);
}


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

可以看到这里没有问题

3.2 删

将一个节点删除,实际上就是将该节点所申请的空间释放掉。

3.2.1 尾删

从链表的尾部进行删除

 将尾部节点释放后,指向尾部节点的这个节点现在指向需要变更为NULL

(图中所列地址仅为方便展示而顺手写的)

void SLNOdelback(SLNO** pphead)
{
	assert(*pphead && pphead);//要进行删除操作,链表肯定不能为空
	SLNO* pres = *pphead;
	while (pres->next->next != NULL)
	{
		pres = pres->next;//找尾节点之前的那个节点
	}
	free(pres->next->next);//释放尾节点
	pres->next->next = NULL;//将指向尾节点的节点指向置空
	pres->next = NULL;
}
void test06()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);
}

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

这里的测试没有问题


void test06()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

}

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

这里看到出现了错误

前面几次函数都能正常运行

可见问题是出现在最后依次删除

void test06()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	/*SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);*/

	SLNOdelback(&phead);
	SLNOprint(phead);

}

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

进行一下调试

这里可以看到错误是对空指针解应用,说明该函数在对链表仅剩一个节点时存在漏洞,这里进行一

下改进

void SLNOdelback(SLNO** pphead)
{
	assert(*pphead && pphead);//要进行删除操作,链表肯定不能为空
	SLNO* pres = *pphead;
	if (pres->next == NULL)
	{
		free(pres);
		*pphead = NULL;
		return;
	}//判断是不是头节点
	while (pres->next->next != NULL)
	{
		pres = pres->next;//找尾节点之前的那个节点
	}
	free(pres->next->next);//释放尾节点
	pres->next->next = NULL;//将指向尾节点的节点指向置空
	pres->next = NULL;
}
void test06()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

}

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

这样就可以了

3.2.2 头删

从链表的头部进行删除

首节点指向的节点成为新的首节点

(图中所列地址仅为方便展示而顺手写的)


void test07()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);


}

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

可以看到是可行的

3.2.3 指定位置删除

删除指定位置

删除后,该位置前的节点指向该位置之后的节点

(图中所列地址仅为方便展示而顺手写的)

void SLNOdel(SLNO** pphead, SLNO* pos)
{
	
	assert(pphead && *pphead);//要删除的链表不能为空
	assert(pos);//要删除的位置不能为空
	SLNO* pres = *pphead;
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找删除位置的上一个节点
	pres->next = pos->next;//让pos的前一个节点指向pos的后一个节点
	free(pos);
	pos = NULL;
}
void test08()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOdel(&phead, find);
	SLNOprint(phead);
}


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

这里测试着是没啥问题

测试一下首节点


void test08()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	SLNOdel(&phead, phead);
	SLNOprint(phead);
}


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

代码异常退出了,调试看看

对空指针解应用了

优化一下针对首节点这种情况

//任意位置删除
void SLNOdel(SLNO** pphead, SLNO* pos)
{
	
	assert(pphead && *pphead);//要删除的链表不能为空
	assert(pos);//要删除的位置不能为空
	SLNO* pres = *pphead;
	if (pres == pos)
	{
		*pphead = pres->next;//让首节点指向的节点成为新的首节点
		free(pres);
		pres = NULL;
		return;
	}//判断是不是首节点
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找删除位置的上一个节点
	pres->next = pos->next;//让pos的前一个节点指向pos的后一个节点
	free(pos);
	pos = NULL;
}

void test08()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	SLNOdel(&phead, phead);
	SLNOprint(phead);
}


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

这样就可行了

3.2.4 指定位置后删除

将指定位置后的那个节点删除

指定位置指向被删除节点所指向的节点

(图中所列地址仅为方便展示而顺手写的)

pos不能是个空指针,且pos后的这个节点不能为空

void SLNOdelafter(SLNO** pphead, SLNO* pos)//任意位置后删除
{
	assert(pphead && *pphead);
	SLNO* pres = *pphead;
	while (pres != pos)
	{
		pres = pres->next;
	}//找到pos
	SLNO* temp = pres->next->next;//pos后的节点所指向节点
	free(pres->next);//释放pos后的一个节点
	pres->next = temp;//pos指向被删除的节点所指向的节点
}

void test10()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);
	SLNO* find = Find(phead, 4);

	SLNOdelafter(&phead, find);
	SLNOprint(phead);

	SLNOdelafter(&phead, phead);
	SLNOprint(phead);
	
	//find = Find(phead, 7);
	//SLNOdelafter(&phead, find);
   //	SLNOprint(phead);

}

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

3.2.5 链表的销毁

链表是动态申请来的空间,那肯定就会有释放

将所有节点都释放,链表就被销毁了

在进行一个节点的释放的时候,要先将该节点指向的下一个节点的地址先保存起来

void SLNODestory(SLNO** pphead)
{
	assert(pphead && *pphead);
	SLNO* pres = *pphead;
	while (pres)
	{
		SLNO* temp = pres->next;//保存下一节点的地址
		free(pres);
		pres = NULL;//释放
		pres = temp;//再到下个节点
	}
	*pphead = NULL;//最后再让头节点置空
	return;
}
void test09()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNODestory(&phead);
	SLNOprint(phead);
}


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

可以看到也是可行的

3.3 查

在指定位置插入删除的函数中已经说过了,这里就不再赘述

SLNO* Find(SLNO* phead, luck num)
{
	SLNO* pres = phead;
	while (num != pres->data && pres != NULL)
	{
		pres = pres->next;
	}
	if (pres == NULL)
	{
		return NULL;
	}
	return pres;
}

3.4 改

找到要修改的位置进行修改就好了

void SLNOmodify(SLNO** pphead, SLNO* pos, luck X)
{
	assert(pphead && *pphead);
	assert(pos);
	SLNO* pres = *pphead;
	while (pres != pos)
	{
		pres = pres->next;
	}
	pres->data = X;
}

void test11()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOmodify(&phead, find, 2);
	SLNOprint(phead);

}


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

四、完整代码

#pragma once

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

//Seqlist.h

typedef int luck;//存储不同类型数据,重命名,这样后面就只用改这里的int
struct SListNOde
{
	luck data;//存储的数据
	struct SListNOde* next;//指向下一个节点的指针
};

typedef struct SListNOde  SLNO;


void SLNOprint(SLNO* phead);//打印每个节点处的数据

SLNO* SLNOIn();//初始化节点

void SLNOInsertback(SLNO** pphead, luck X);//单链表的尾插

void SLNOInsertfront(SLNO** pphead, luck X);//单链表的头插

void SLNOInsertbefore(SLNO** pphead, SLNO* pos, luck X);//任意位置前插入

SLNO* Find(SLNO* phead, luck num);//这是查找函数

void SLNOInsertafter(SLNO** pphead, SLNO* pos, luck X);//任意位置后插入

void SLNOdelback(SLNO** pphead);//尾删

void SLNOdelfront(SLNO** pphead);//头删

void SLNOdel(SLNO** pphead, SLNO* pos);//任意位置删除

void SLNOdelafter(SLNO** pphead, SLNO* pos);//任意位置后删除

void SLNODestory(SLNO** pphead);//链表的销毁

void SLNOmodify(SLNO** pphead, SLNO* pos, luck X);//链表的修改

 

#include"Seqlist.h"

//Seqlist.c

//打印每个节点处的数据

void SLNOprint(SLNO* phead)
{
	SLNO* pres = phead;
	while (pres)//节点处不是空指针就有数据
	{
		printf("%d->", pres->data);
		pres = pres->next;
	}
	printf("NULL\n");
}

SLNO* SLNOIn()//节点申请空间
{
	SLNO* newnode = (SLNO*)malloc(sizeof(SLNO));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	return newnode;
}

//尾插数据
void SLNOInsertback(SLNO** pphead, luck X)
{
	assert(pphead);
	SLNO* pres = *pphead;//用来查找尾
	SLNO* newnode = SLNOIn();
	newnode->data = X;
	newnode->next = NULL;

	//如果拿到的链表是一个空链表,那么进行pres->next就肯定会报错,不能对NULL进行解应用

	//那就单独判断一下

	//如果是这种情况,那么就直接申请一个新节点就好了

	if (pres == NULL)
	{
		*pphead = newnode;
		return;
	}

	//要从尾部进行插入,那就要找到尾部节点
	while (pres->next)
	{
		pres = pres->next;//尾部节点是该节点保存的下个节点地址为NULL
	}
	pres->next = newnode;
}

//头部数据插入
void SLNOInsertfront(SLNO** pphead, luck X)
{
	assert(pphead);
	SLNO* pres = *pphead;//保存头节点的位置
	SLNO* newnode = SLNOIn();//申请空间
	newnode->data = X;
	newnode->next = pres;
	*pphead = newnode;
}

//查找节点位置
SLNO* Find(SLNO* phead, luck num)
{
	SLNO* pres = phead;
	while (pres != NULL && num != pres->data  )
	{
		pres = pres->next;//找到要查找数据的地址
	}
	if (pres == NULL)
	{
		return NULL;//为空了说明链表中没有要查找的数据
	}
	return pres;
}

//插入指定位置前
void SLNOInsertbefore(SLNO** pphead, SLNO* pos, luck X)
{
	assert(pphead && pos && *pphead);//要插入的位置肯定不能为空
	SLNO* pres = *pphead;
	if (pos == pres)
	{
		SLNOInsertfront(pphead, X);
		return;
	}//如果是在首节点处插入就直接调用头插函数了
	SLNO* newnode = SLNOIn();//申请新节点
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找插入位置前的那个节点
	SLNO* temp = pres->next;//保存插入位置前节点指向的下一个节点的指针
	newnode->data = X;
	newnode->next = temp;//原指向节点为新插入位置所指向的节点
	pres->next = newnode;//让插入为位置前节点的指向变更为插入的节点
}

//插入指定位置后
void SLNOInsertafter(SLNO** pphead, SLNO* pos, luck X)
{
	assert(*pphead && pos);
	SLNO* pres = *pphead;
	SLNO* newnode = SLNOIn();
	while (pres != pos)
	{
		pres = pres->next;
	}//找到插入节点
	SLNO* temp = pres->next;//保存插入原指向位置
	newnode->data = X;
	newnode->next = temp;
	pres->next = newnode;
}

//尾删
void SLNOdelback(SLNO** pphead)
{
	assert(*pphead && pphead);//要进行删除操作,链表肯定不能为空
	SLNO* pres = *pphead;
	if (pres->next == NULL)
	{
		free(pres);
		*pphead = NULL;
		return;
	}//判断是不是头节点
	while (pres->next->next != NULL)
	{
		pres = pres->next;//找尾节点之前的那个节点
	}
	free(pres->next->next);//释放尾节点
	pres->next->next = NULL;//将指向尾节点的节点指向置空
	pres->next = NULL;
}

//头删
void SLNOdelfront(SLNO** pphead)
{
	assert(pphead && *pphead);//要删除链表不能为空
	SLNO* pres = *pphead;
	*pphead = pres->next;//让首节点指向的节点成为新的节点
	free(pres);//释放原首节点
	pres = NULL;
}

//任意位置删除
void SLNOdel(SLNO** pphead, SLNO* pos)
{
	
	assert(pphead && *pphead);//要删除的链表不能为空
	assert(pos);//要删除的位置不能为空
	SLNO* pres = *pphead;
	if (pres == pos)
	{
		*pphead = pres->next;//让首节点指向的节点成为新的首节点
		free(pres);
		pres = NULL;
		return;
	}//判断是不是首节点
	while (pres->next != pos)
	{
		pres = pres->next;
	}//找删除位置的上一个节点
	pres->next = pos->next;//让pos的前一个节点指向pos的后一个节点
	free(pos);
	pos = NULL;
}

//指定位置后删除
void SLNOdelafter(SLNO** pphead, SLNO* pos)
{
	assert(pphead && *pphead);
	assert(pos->next);
	SLNO* pres = *pphead;
	while (pres != pos)
	{
		pres = pres->next;
	}//找到pos
	SLNO* temp = pres->next->next;//pos后的节点所指向节点
	free(pres->next);//释放pos后的一个节点
	pres->next = temp;//pos指向被删除的节点所指向的节点
}

//链表的销毁
void SLNODestory(SLNO** pphead)
{
	assert(pphead && *pphead);
	SLNO* pres = *pphead;
	while (pres)
	{
		SLNO* temp = pres->next;//保存下一节点的地址
		free(pres);
		pres = NULL;//释放
		pres = temp;//再到下个节点
	}
	*pphead = NULL;//最后再让头节点置空
	return;
}

void SLNOmodify(SLNO** pphead, SLNO* pos, luck X)
{
	assert(pphead && *pphead);
	assert(pos);
	SLNO* pres = *pphead;
	while (pres != pos)
	{
		pres = pres->next;
	}
	pres->data = X;
}

 

#include"Seqlist.h"

//test.c

void test01()
{
	SLNO* n1 = SLNOIn();
	n1->data = 1;
	n1->next = NULL;
	SLNOprint(n1);
}
void test02()
{
	SLNO* phead = NULL;
	SLNO* n1;
	SLNO* n2;
	SLNO* n3;
	SLNO* n4;
	n1 = SLNOIn();
	n2 = SLNOIn();
	n3 = SLNOIn();
	n4 = SLNOIn();
	phead = n1;//指向第一个节点的指针

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;

	SLNOprint(phead);
}

void test03()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOInsertbefore(&phead, find, 10);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOInsertbefore(&phead, find, 9);
	SLNOprint(phead);
}

void test04()
{
	SLNO* phead = NULL;
	SLNOInsertbefore(&phead, phead, 1);

	//SLNOInsertfront(&phead, 3);
	//SLNOInsertfront(&phead, 4);
	//SLNOInsertfront(&phead, 5);
	//SLNOInsertfront(&phead, 6);
	SLNOprint(phead);
}


void test05()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOInsertafter(&phead, find, 10);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOInsertafter(&phead, find, 9);
	SLNOprint(phead);

	SLNOInsertafter(&phead, phead, 1);
	SLNOprint(phead);
}

void test06()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

	SLNOdelback(&phead);
	SLNOprint(phead);

}

void test07()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);

	SLNOdelfront(&phead);
	SLNOprint(phead);


}

void test08()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	find = Find(phead, 7);
	SLNOdel(&phead, find);
	SLNOprint(phead);

	SLNOdel(&phead, phead);
	SLNOprint(phead);
}

void test09()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNODestory(&phead);
	SLNOprint(phead);
}

void test10()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);
	SLNO* find = Find(phead, 4);

	SLNOdelafter(&phead, find);
	SLNOprint(phead);

	SLNOdelafter(&phead, phead);
	SLNOprint(phead);
	
	//find = Find(phead, 7);
	//SLNOdelafter(&phead, find);
   //	SLNOprint(phead);

}
void test11()
{
	SLNO* phead = NULL;
	SLNOInsertback(&phead, 3);
	SLNOInsertback(&phead, 4);
	SLNOInsertback(&phead, 5);
	SLNOInsertback(&phead, 6);
	SLNOInsertback(&phead, 7);
	SLNOprint(phead);

	SLNO* find = Find(phead, 4);
	SLNOmodify(&phead, find, 2);
	SLNOprint(phead);

}


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/560822.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Godot4自学手册】第三十九节利用shader(着色器)给游戏添加一层雾气效果

今天&#xff0c;主要是利用shader给游戏给地宫场景添加一层雾气效果&#xff0c;增加一下气氛&#xff0c;先看一下效果&#xff1a; 一、新建ParallaxBackground根节点 新建场景&#xff0c;根节点选择ParallaxBackground&#xff0c;命名为Fog&#xff0c;然后将该场景保…

什么是AIoT?

什么是AIoT? AIoT&#xff0c;即人工智能物联网&#xff0c;是一种将人工智能&#xff08;AI&#xff09;技术与物联网&#xff08;IoT&#xff09;相结合的新型应用形态。它不仅实现了设备之间的互联互通&#xff0c;还赋予了它们更智能化的特性。AIoT的核心在于通过AI的数据…

prometheus+grafana可视化监控

prometheus监控 一、用二进制安装 1、安装Prometheus 打开官方网址:https://prometheus.io/download/ wget https://github.com/prometheus/prometheus/releases/download/v2.45.4/prometheus-2.45.4.linux-amd64.tar.gz下载完成后解压一下安装包 tar vxf prometheus-2.45.…

解锁棋盘之谜:探索N皇后问题的全方位解决策略【python 力扣51题】

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作 作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打怪升级之旅 python数据分析…

软考138-上午题-【软件工程】-软件评审

一、软件评审 通常&#xff0c;把“质量”理解为“用户满意程度”。为了使得用户满意&#xff0c;有以下两个必要条件&#xff1a; (1) 设计的规格说明书符合用户的要求&#xff0c;这称为设计质量。 (2) 程序按照设计规格说明所规定的情况正确执行&#xff0c;这称为程序质…

Docker安装教程,什么系统都有

下载Docker 如果你的系统是图形界面的&#xff0c;比如windows、mac、ubuntu等&#xff0c;到 Docker 官网下载 Docker Desktop。 官网链接: https://www.docker.com/products/docker-desktop/ 根据你的系统选择对应的安装包&#xff0c;然后下载&#xff0c;是不是特别简单&a…

Git TortoiseGit 安装使用详细教程

前言 Git 是一个免费的开源分布式版本控制系统&#xff0c;是用来保存工程源代码历史状态的命令行工具&#xff0c;旨在处理从小型到非常大型的项目&#xff0c;速度快、效率高。《请查阅Git详细说明》。TortoiseGit 是 Git 的 Windows Shell 界面工具&#xff0c;基于 Tortoi…

改进前后端交互实例

前后端交互实例(javaweb05)-CSDN博客 在这之前我假设大家都已经学完了IOC和DI 不明白的这里我也解释一下,首先是两个概念 1.控制反转:对象的创建控制权由程序自身转到外部(容器) 2.依赖注入:容器为程序提供运行时所依赖的资源 Bean对象:IOC容器中创建,关联的对象,称之为be…

【wpf】ObservableCollection 跨线程报错问题

背景 ObservableCollection 我们之前介绍过他和List的区别。ObservableCollection 的好处在于&#xff0c;当集合发生变化时&#xff0c;能发送通知通知界面发生相应的更改。但是ObservableCollection 有个弊端。无法在非UI线程中访问。 要么就是通知失效了&#xff0c;要么就…

从0到1实现RPC | 接入Apollo配置中心

一、代码实现 添加依赖 添加apollo客户端的依赖和spring配置相关依赖 添加监听器 通过实现ApplicationContextAware接口&#xff0c;获取Spring上下文。 使用ApolloConfigChangeListener注解监听命名空间rpc-demo-provider.yaml和默认的application.properties。 监听逻辑…

前端页面助手 (vue)

快速开发页面&#xff08;图形化开发页面&#xff09; 自主编辑 然后自己也可以修改属性 最后导出页面即可 github地址 ;https://github.com/opentiny/tiny-engine

虹科Pico汽车示波器 | 免拆诊断案例 | 2016款保时捷911 GT3 RS车发动机异响

一、故障现象 一辆2016款保时捷911 GT3 RS车&#xff0c;搭载4.0 L水平对置发动机&#xff08;型号为MA176&#xff09;&#xff0c;累计行驶里程约为4.2万km。车主反映&#xff0c;1星期前上过赛道&#xff0c;现在发动机有“哒哒”异响。 二、故障诊断 接车后试车&#xff…

C++语言·类和对象(下)

1. 初始化列表 我们回忆上节写的MyQueue类&#xff0c;其中有两个栈类和一个int类型&#xff0c;栈类因为其特殊性&#xff0c;要开空间&#xff0c;所以我们必须手搓Stack类的构造函数。但是正常来说MyQueue自动生成的构造函数会调用自定义类型的默认构造函数&#xff0c;也就…

Proxy 代理

意图 为其它对象提供一种代理以控制这个对象的访问。 结构 Proxy保存一个引用使得代理可以访问实体&#xff1b;提供一个与Subject的接口相同的接口&#xff0c;使代理可以用来替代实体&#xff1b;控制实体的存取&#xff0c;并可能负责创建和删除它&#xff1b;其他功能依赖…

【leetcode面试经典150题】63. 删除链表的倒数第 N 个结点(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

现货白银保证金交易要先分析趋势

现货白银是保证金交易品种&#xff0c;买卖过程中可能会涉及数十倍的资金杠杆&#xff0c;所以它对投资者的分析水平和交易水平的要求都比较高&#xff0c;所以在进入这个市场之前&#xff0c;投资者需要先学习一些基本的分析方法&#xff0c;当中可以分为基本面和技术面两大流…

Vmware ---快捷键

Vi 文件名.c xrandr 查看分辨率 xrandr -s 分辨率 调你自己想要的分辨率 ctr shift 放大字体 ctr - 缩小字体 ctr alt t 打开控制台 cd caoshupei 进入曹树培文件夹 cd .. 退回上层文件夹 ls 列出生成的文件 ls -a 显示所有文件&#xff0c;包含隐藏的文件和文件…

2024-4-狼道

2024-4-狼道 2024-4-9 宋犀堃&#xff08;堃通坤&#xff0c;多用于人名&#xff09; fatux&#xff1a; 做人当如狗&#xff0c;和蔼可亲&#xff1b;做事当如狼&#xff0c;专注果决。 狼道 智慧生存的强者法则 走向卓越的成功之道 狼道&#xff0c;是追求卓越的野心&am…

第一个Python程序

1、python与java的区别 Python和Java是两种不同的编程语言&#xff0c;它们具有以下一些主要区别&#xff1a; 语法&#xff1a;Python的语法相对简洁和可读性高&#xff0c;使用缩进来表示代码块&#xff1b;而Java的语法更为严格&#xff0c;使用花括号来表示代码块。 类型…

后端-MySQL-week11 事务

事务 简介 操作 有两种方式&#xff0c;一种是设置为手动提交——不执行“commit”不进行变更&#xff1b;另一种是手动开启一个事务&#xff0c;用开启事务的代码&#xff08;SQL语句&#xff09;来创建一个需要“commit”才能进行变更的事务 1.第一种方式 2.第二种方式 四…