C语言单链表

目录

始 

打印 

开辟节点

尾插 

头插

尾删

头删

 查找

指定位置前插入节点

指定位置后插入

指定位置删除 

删除pos之后的节点

销毁


始 

特征:物理结构不一定连续,逻辑结构一定连续物理结构:地址不是连续的,

逻辑结构:通过某几个地址能够找到一些参数,并把连接起来。

这些玩意儿叫节点(多半是结构体类型), 节点里面会放一些数据,以及 下一个节点的地址

节点:分为两部分,数据(可以放很多也可以很少,具体看需求)以及下一个节点的地址 

打印 

        首先,创建一个结构体类型的指针,赋值为NULL(表示为空链表),在插入几个数据之后打印。

        我们直接传递的是这个变量,这个变量里面存的是地址,由于我们不需要修改实参的数据,所以可以直接使用变量访问。

        创建一个临时变量存放p里面的地址,用 cp 访问,每次进来打印里面的数据 a ,再将下一位的地址赋给 p ,上来判断是否为 NULL

也可以不用创建临时变量不会影响实参的。


void SLNprint(SLN* p)//打印链表里的数据
{
	assert(p);
	
	while (p)
	{
		printf("%d->", p->a);
		p = p->Next;
	}
}

开辟节点

用malloc开辟一个节点的空间,判断是否为空,最后是返回开辟成功的地址

SLN* SLNinvent(void)
{
	SLN* iplist = (SLN*)malloc(sizeof(SLN));
	if (iplist == NULL)
	{
		perror("malloc");
		exit(EOF);
	}
	return iplist;

}

尾插 

大致可以把尾插分两部分开辟节点,和找到尾部

开辟节点后给节点赋值上你要插入的值,将下个节点的指针置NULL

第二部分:

        创建一个临时节点变量,用来存放头结点的的地址

        判断头链表是否是空链表(人话:头结点是否是空指针)

        else遍历链表,直到遇见某个节点的 Next 是NULL,此时的slntail 变量放的是最后一个节点的地址(一个结构体空间的起始地址)。再将这个节点里面的Next 赋值为刚刚开辟的节点地址。

void SLNpushback(SLN** phead,int a)
{
	assert(phead);
	
	SLN* list = SLNinvent();
	list->a = a;
	list->Next = NULL;
	
	
	SLN* slntail = *phead;
	
	if (*phead == NULL)
	{
		*phead = list;
	}
	else
	{
		while (slntail->Next)//plist->间接访问Next
		{
			slntail = slntail->Next;
		}
		slntail->Next = list;
	}

}

头插

头插还是比较简单一点

        函数进来和尾插一样都需要先判断有没有节点,没有就需要申请一块空间,将地址直接赋给 *phead 即可,*phead 才是保存节点的,phead 放的是 plist 的地址。

        else (表示不为空链表),我们需要将原头结点的地址放入刚才开辟的空的 Next 里面,再将*phead 赋值为新的节点的地址。

void SLNpushfront(SLN** phead, int a)//头插
{
	assert(phead);

	SLN* list = SLNinvent();
	list->a = a;
	list->Next = NULL;
	SLN* slnfront = *phead;
	if (*phead == NULL)
	{
		*phead = list;
	}
	else
	{
		list->Next = slnfront;
		*phead = list;
	}

}

尾删

两种情况:
        1、如果只有一个节点只需要释放该节点,然后将节点地址置空

        2、则需要找到最后一个节点,当我们的判断语句为空的时候,表示 Tail 指向的是最后一节点的地址,Tail 指向最后一个节点的操作是 ,当 Tail 为上一个节点的地址时,进入循环,然后将 Tail此时为最后一个节点的上一个节点的地址) 的地址赋给 Last ,Last就表示上一个节点的地址,然后将 Tail 变成最后一个节点地址

        最后将Taill释放,taill 置为空 (图里没写代码里补充),再将上一个节点里面的Next置为空。尾删结束

错误更改:SLN* Last = 的后面应该是 *phead

void SLNdetback(SLN** phead)//尾删
{
	assert(phead && *phead);//表示plist的地址不能为空,
							//plist里面不能没有节点
	SLN* Tail = *phead;
	SLN* Last = *phead;

	if ((*phead)->Next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	
	else
	{
		while (Tail->Next)
		{
			Last = Tail;
			Tail = Tail->Next;
		}
		free(Tail);
		Tail = NULL;
		Last->Next = NULL;

	}
}

头删

头删也有两种情况:

        1、只一有个节点,我们直接释放然后将地址置为空指针

        2、多个,先拷贝一份头节点的地址,然后将头节点赋值为Next 的地址(把第二节点当做头节点了),然后释放拷贝的原头节点,最将他置为空

void SLNdetfront(SLN** phead)//头删
{
	assert(phead && *phead);
	
	if ((*phead)->Next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		SLN* cphead = *phead;
		(*phead) = (*phead)->Next;
		free(cphead);
		cphead = NULL;
	}
}

代码二、

可以不需要上面的if语句直接在断言后使用就行

 查找

        由于查找不需要操作实参所以直接传节点就行。x 为查找的要求(由于这里的 x 实际是 Int 类型所以下面的if判断语句在某些情况下可能不能用)

        while 节点的遍历,直到遇见NULL停止,所以这里其实还能改一下(代码放下面),遍历都没有怎么可以直接打印找不到并返回空,接收find的变量再来个 if 判断不能为空。

在使用find函数返回的地址时应该判断是否为NULL

SLN* SLNfind(SLN* phead,SLNtype x)
{
	assert(phead);
	while (phead->Next)
	{
		if (phead->a == x)
			return phead;
		phead = phead->Next;
	}
	perror("not find\a");
	return phead;
}

纠错:while 的判断语句时有问题的,

他查不到最后一个节点,当我们把最后一个节点的地址赋给 phead 时(phead=phead->Next),此时上来判断,最后一个节点里的 Next 确实为NULL,不会进入循环也就查不了,应改为 while(phead),当 phead= 最后一个节点的地址时p 判断不为NULL,执行 if 判断语句,找到最后一个数据,直接返回了。

SLN* SLNfind(SLN* phead,SLNtype x)//查找
{
	assert(phead);
	while (phead)
	{
		if (phead->a == x)
			return phead;
		phead = phead->Next;
	}
	printf("找不到\n");
	return phead;
}

指定位置前插入节点

指定位置前依然要考虑头节点的问题。

如果是头节点直接调用头插函数

        如果不是那么,需要涉及到 pos 的上个节点和下个节点,我们需要先把下一个节点的地址给到Pos->Next ,然后 while 循环找到上一个节点,判断为 cpphead(拷贝了一份头节点地址用来找到 pos 上一个节点并且不会修改 plist 里面的头节点,),cpphead->Next 不等于pos,此时的 cpphead 的地址就是上一个节点的地址,操作他,cpphead->Next赋值为新节点的地址。

void SLTInsert(SLN** pphead, SLN* pos, SLNtype x)
{
	assert(pphead && *pphead && pos);
	
	if ((*pphead) == pos)
	{
		SLNpushfront(pphead,x);
	}
	else
	{
		SLN* cpphead = *pphead;
		SLN* Anewcode = SLNinvent();
		Anewcode->a = x;
		Anewcode->Next = pos;
	while ((cpphead)->Next != pos)
		{
			(cpphead) = (cpphead)->Next;
		}
		cpphead->Next = Anewcode;
	}
	
}

指定位置后插入

        很简单奥,创建一个新的节点(建议在插入输入时在创建节点函数里面直接插入 x 我懒得改了,事实证明那样简单一点),创建完节点后,我们copy 一份 pos->Next 的地址将它赋给新节点,再将 newcode 的地址赋给 pos->Next.

给出了两种可行的方案,第二种要方便一点,节省了一丢丢空间和速度

第二种的逻辑是,将pos下一个节点的地址给新节点(newcode)的Next,再将newcode的地址给pos当中的next。

void SLTInsertAfter(SLN* pos, SLNtype x)
{
	assert(pos);
	SLN* newcode = SLNinvent();
	newcode->a = x;
	//SLN* cpos = pos->Next;
	//pos->Next = newcode;
	//newcode ->Next= cpos;
	newcode->Next = pos->Next;
	pos->Next = newcode;
}

指定位置删除 

        只有一个节点的话直接头删

        cpphead的目的是找到pos的上一个节点

        多个:先拷贝一份头结点,用拷贝的头节点找到 pos 的上一个节点,然后将 pos 下个节点的地址放入 pos 的上一个节点的 Next 中,cpphead->Next(pos的上一个节点中的Next) = pos->Next下一个节点的地址),最后释放掉 pos 和置为空

void SLTErase(SLN** pphead, SLN* pos)
{
	assert(pphead && *pphead && pos);
	if ((*pphead) == pos)
	{
		SLNdetfront(pphead);
	}
	else
	{
		SLN* cpphead = *pphead;
		while (cpphead->Next != pos)
		{
			cpphead = cpphead->Next;
		}
		cpphead->Next = pos->Next;
		free(pos);
		pos = NULL;


	}
}

删除pos之后的节点

        将 pos 的下一个节点的地址先拿出来,然后然后将 pos Next (pos->Next) pos 的下一个节点中的 Next (pos->Next->Next)连接起来,此时 pos 的下一个节点的地址就变成后面第二个结节的地址 (pos->Next中放的就是后面第二个节点地址),代码是:pos->Next = Cpos->Next ,Cpos->Next 解析:因为 Cpos 是后一个节点的地址,那么Cpos->Next 就是后两个的地址,赋值完后再释放点Cpos就完成了。

void SLTEraseAfter(SLN* pos)//删除pos之后的一个几点
{
	assert(pos&&pos->Next);
	SLN* Cpos = pos->Next;
	pos->Next = Cpos->Next;
	free(Cpos);
	Cpos = NULL;

}

销毁

拷贝一份头节点,其实 SLN* cpphead  = *pphead 可以赋值为空指针反正也只是一个初始化,在循环里还会赋头节点的地址的。

        在循环里,先拷贝头节点,然后将头节点变成下一个节点,然后释放的头节点的地址,再将它置为NULL,此时,*pphead 等于第二节点的地址,上来判断是否为空,这里用*pphead 而不用*pphead->Next的原因是 ,*pphead 来到最后一个节点时如果是*pphead->Next 那么循环就进不去,也就不会销毁最后一个节点

void SListDesTroy(SLN ** pphead)
{
	assert(pphead && *pphead);
	SLN* cpphead = *pphead;
	while (*pphead)
	{
		cpphead = *pphead;
		*pphead = (*pphead)->Next;
		free(cpphead);
		cpphead = NULL;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值