单链表的简单应用

news2024/11/17 8:17:32

目录

一、顺序表的问题及思考

二、链表的概念及结构

三、单链表的实现

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python爬虫使用需要注意什么?应用前景如何?

Python爬虫很多人都听说过&#xff0c;它是一种用于从网页上获取信息的程序&#xff0c;它可以自动浏览网页、提取数据并进行处理。技术在使用Python爬虫时需要注意一些重要的事项&#xff0c;同时本文也会跟大家介绍一下爬虫的应用前景。 第一个注意事项就是使用Python爬虫时…

HCIP-OSPF综合实验

一实验拓扑图 二.实验要求 1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&…

【JavaEE多线程】线程安全、锁机制及线程间通信

目录 线程安全线程安全问题的原因 synchronized 关键字-监视器锁monitor locksynchronized的特性互斥刷新内存可重入 synchronized使用范例 volatilevolatile能保证内存可见性volatile不保证原子性synchronized 也能保证内存可见性 wait 和 notifywait()方法notify()方法notify…

【CBB系列】EtherCAT硬件技术总结及其从站硬件设计

EtherCAT硬件技术总结及其从站硬件设计 EtherCAT硬件技术简介基于LAN9252的EtherCAT从站硬件设计LAN9252总览电源、时钟与复位主机总线(PDI/SPI)与MIII2C接口与硬配置引脚LED控制器与PORT总结作者按:最近在《硬件十万个为什么-开发流程篇》中看到了共用基础模块(Common bui…

最前沿・量子退火建模方法(2) : Domain wall encoding讲解和python实现

前言 上篇讲的subQUBO属于方法论&#xff0c;这次讲个通过编码量子比特的方式&#xff0c;同样的约束条件&#xff0c;不同的编码&#xff0c;所需的量子比特数是不同的。有的编码方式&#xff0c;很节省量子比特。比如&#xff0c;这次要讲的Domain wall encoding。 一、Doma…

利用AQS(AbstractQueuedSynchronizer)实现一个线程同步器

目录 1. 前言 2. 什么是同步器 3. 同步器实现思路 Semaphore(信号量) 4. 代码实现 4.1. 创建互斥锁类 4.2 编写静态内部类&#xff0c;继承AQS 4.3 内部类实现AQS钩子函数 4.3 封装lock&#xff0c;unlock方法 4.4. 测试 5. 总结 本文章源码仓库&#xff1a;Conc…

ros仿真启动小龟

1.启动RosMaster&#xff08;管理Ros中各个节点的“大管家”&#xff0c;每次启动Ros时需要首先启动RosMaster&#xff09; roscorefangfang-inspiron-5580:~/ros2/download/rosdistro$ roscore ... logging to /home/fang/.ros/log/6ec2d790-fe1d-11ee-aba8-1c1bb5cdec7c/ros…

基于SSM+Jsp+Mysql的电子商城系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

JAVA在线代码生成器 | 2024.04.20| 修复CDN问题+推出JDK8/11分支+修复大写下划线转驼峰问题

Description 项目介绍 Based on SpringBoot2Freemarker #基于SpringBoot2和Freemarker的代码生成平台 For reducing the repetitive CRUD work #以解放双手为目的&#xff0c;减少大量的重复CRUD工作 Support mysql, oracle and pgsql #支持MySQL、Oracle、PgSQL三大主流数据库…

解决Error in writing header file of the driver

在源代码里面更新了一批常规的内容&#xff0c;编译的时候遇到一个error&#xff0c;一大片都是红的。XXX是项目名称。 Description Resource Path Location Type Generator: ERROR: Error in writing header file of the driver XXX Cpu Processor Expert Problem 表面意思是…

docker灵活部署mysql

博客简要 用docker部署mysql,并将数据库映射到主机上&#xff0c;并增加远端访问mysql数据库 当你使用Docker运行MySQL时&#xff0c;并且希望将MySQL数据库的数据存储在宿主机&#xff08;也就是运行Docker的主机&#xff09;上的特定路径&#xff0c;你需要在启动容器时通过…

现代农业AI智能化升级之路:机器学习在现代农业领域的现状与未来发展

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

ppt技巧:​如何将两个PPT幻灯片文件合并成一个?

第一种方式&#xff1a;复制粘贴幻灯片 1. 打开第一个PPT幻灯片文件&#xff0c;确保你已经熟悉该文件的内容和布局。 2. 打开第二个PPT幻灯片文件&#xff0c;浏览其中的所有幻灯片&#xff0c;选择你想要合并到第一个文件中的幻灯片。 3. 使用快捷键CtrlC&#xff08;Wind…

python_列表和元组

介绍 列表&#xff08;List&#xff09;和元组&#xff08;Tuple&#xff09;是Python中两种不同的数据结构&#xff0c;它们都可以用来存储一系列的元素。下面是它们的主要特点和区别&#xff1a; 列表&#xff08;List&#xff09; 可变性&#xff1a;列表是可变的&…

Redis:报错Creating Server TCP listening socket *:6379: bind: No error

错误&#xff1a; window下启动redis服务报错&#xff1a; Creating Server TCP listening socket *:6379: bind: No error 原因&#xff1a; 端口6379已被绑定&#xff0c;应该是因为上次未关闭服务 解决&#xff1a; ①依次输入命令&#xff1a; redis-cli.exe &#xff08…

SpringBoot基于JavaWeb的菜鸟驿站快递管理系统ssm

前端&#xff1a;vue.jsElementUI 编程语言: java 框架&#xff1a; ssm/springboot 详细技术&#xff1a;springboot springbootvueMYSQLMAVEN 数据库: mysql5.7 数据库工具&#xff1a;Navicat/SQLyog都可以 ide工具&#xff1a;IDEA 或者eclipse 对菜鸟驿站快递管理系统设计…

MySql安装(Linux)

一、清除原来的mysql环境 在前期建议使用root用户来进行操作&#xff0c;使用 su -来切换成root用户&#xff0c;但是如果老是提示认证失败&#xff0c;那么有可能我们的root密码并没有被设置&#xff0c; 我们可以先设置root的密码 sudo passwd root 然后就可以切换了。 …

Window中Jenkins部署asp/net core web主要配置

代码如下 D: cd D:\tempjenkins\src\ --git工作目录 dotnet restore -s "https://nuget.cdn.azure.cn/v3/index.json" --nuget dotnet build dotnet publish -c release -o %publishPath% --发布路径

分布式锁实现方案-基于zookeeper的分布式锁实现(原理与代码)

目录 一、基于zookeeper的分布式锁 1.1 基于Zookeeper实现分布式锁的原理 1.1.1 分布式锁特性说明 1.1.1.1 特点分析 1.1.1.2 本质 1.1.2 Zookeeper 分布式锁实现原理 1.1.2.1 Zookeeper临时顺序节点特性 1.1.2.2 Zookeeper满足分布式锁基本要求 1.1.2.3 Watcher机制 …

‘language‘不能作为表名或字段名

今天写一个C#访问Access的程序&#xff0c;拼接SQL语句时一直出错&#xff0c; string sql "insert into dllinfos (dllname,dllfilename,type,functions,harm,repairmethod,issys, paths, ishorse, language, version, company) values (" textBox1.Text ",…