(3)数据结构与算法-伪链表

数组与动态数组

概念

数组:固定长度,空间连续,一次性申请好,为保证够用申请非常多的空间,会造成空间浪费!
动态数组:动态的增加或删除节点,也是重新申请更大的空间,会严重降低效率

链表由来

如果不想一次性申请太多空间,只申请一个,并保证原数据不变,怎么做?
数组访问各个元素的原理:下标运算(空间连续,直接首地址做加法)
由于后加的元素不是和原数组一起申请的,所以各元素地址不一定连续,就无法通过下标来进行访问,所以要通过前一个元素记录下一个元素的地址,这样就可以通过下标找到位置。
这样节点就有两个数据:一个装自身数据,一个装相邻节点的地址。
所以每个节点都是结构体类型。像这样只装下一个节点地址的就是单向链表

伪链表的创建

1.先创建链表结构体

struct Nobe
{
	int Num;
	struct Nobe *pNext;
};

2.在主函数中建立4个单独的节点

struct Nobe a = { 1, NULL },
			b = { 2, NULL },
			c = { 3, NULL },
			d = { 4, NULL }; 

此时的节点是NULL也就是说并未指向任何结构体,暂时还是单独的。
3.创建连接

	a.pNext = &b;
	b.pNext = &c;
	c.pNext = &d;

这样,单独的节点就连起了

操作伪链表

遍历链表

上面的操作仅仅是为了直观,易理解。实际的应用中却并非如此。
我们不可能增加一个元素就增加一个对象,而是要借助动态数组,动态添加删除空间,这样空间就会不停的有,而后再通过操作连接在一起就好了 。
实际中,都仅仅是链表头才会有变量,其他元素都是地址。然后通过链表头的变量访问其他元素。如下:

	printf("%d ",a.Num );
	 //用变量a中装的指针访问b的数据
	printf("%d ",a.pNext ->Num ); 
	//用b装的的指针访问c的数据
	printf("%d ",a.pNext ->pNext ->Num );  
	 //用c装的指针访问d的数据
	printf("%d ",a.pNext ->pNext ->pNext ->Num );

输出结果:
1 2 3 4

可以定义头指针,变成都是箭头式的:

	struct Nobe *Head = &a;
	printf("%d ", Head->Num);
	printf("%d ", Head->pNext ->Num); 
	printf("%d ", Head->pNext->pNext->Num); 
	printf("%d ", Head->pNext->pNext->pNext->Num);

但写这么多也很麻烦,这样就可以简化。
将下一个Head指向的pNext,指向当前Head

	struct Nobe *Head = &a;
	printf("%d ", Head->Num);
	//将下一个Head指向的pNext指向当前的Head
	Head = Head->pNext ;
	printf("%d ", Head->Num);  
	Head = Head->pNext;
	printf("%d ", Head->Num); 
	Head = Head->pNext;
	printf("%d ", Head->Num); 

发现都是相同的,这就可以用循环表示

void print(struct Nobe *Head)
{
	if (Head != NULL)
	{
		for (int i = 0; i < 4; i++)
		{
			printf("%d ", Head->Num);
			Head = Head->pNext;
		}
	}
}
//主函数调用
print(&a);

如上,相当于实参给形参初始化了,就用不着struct Nobe *Head = &a;
若到最后一个,Head就会被赋一个NULL,什么都没有,遍历条件就是Head != NULL

查找指定的节点
根据数据查找
//返回值 是结构体类型
struct Nobe * Find(struct Nobe *Head, int Data)
{
	//判断是否为NULL
	while (Head != NULL)
	{
		//有就返回Head
		if (Head->Num == Data)
			return Head;
		Head = Head->pNext;
	}
	//否则返回NULL
	return NULL;
}
//主函数调用
	print(&a);
	//用指针装Find的返回值
	struct Nobe *pFind = Find(&a, 3); 
	if (NULL == pFind)
		printf("没有此节点\n");
	else
		printf("查到此节点,%d\n",pFind->Num);
根据下标查找

根据下标查询,那条件就要从0开始计算,这里可以在输出函数中定义一个值为0的变量,if条件是判断下标与变量是否相等,每次循环中不断增加变量的值,知道找到要查的为止。如下:

//输出函数
struct Nobe * Find(struct Nobe *Head, int FindIndex)
{
	int iNum = 0; //下标从0开始,就初始化为0
	while (Head != NULL)
	{
		//下标与要查的下标相等就打印
		if (iNum == FindIndex)
			return Head;
		iNum++; //每次循环都增加1,直到找到要查的
		Head = Head->pNext;
	}
	return NULL;
}
//主函数输出
	struct Nobe *pFind = Find(&a, 2); 
	if (NULL == pFind)
		printf("没有此节点\n");
	else
		printf("查到此节点,%d\n",pFind->Num);
重复数据的查找

以上都是一个数据,若要有几个相同的数据查找时该怎么办呢?
1.统计指定数据的数量
这是一个功能,所以可以单独封装在一个函数里
1)返回值为次数(int型)
2)判断条件是,要查的值是否和链表中的值相等,相等即有一个,又相等即有两个,所以是累加。

int Count(struct Nobe *Head,int Data)
{
	int iCount = 0;//初始没有,就为0
	while (Head != NULL)
	{
		if (Head->Num == Data)
		{
			iCount++; //有一次就自加1
		}
		Head = Head->pNext;
	}
	return iCount;
}
//主函数调用
	//输出结果
	printf("总共的个数:%d\n",Count(&a, 2));

上面只是记录了数,可不知道具体的位置,就要通过下面的方式来实现。
2.记录具体的节点
要得出多个节点的值,这就要借助返回值返回多个数据
返回多个数据:
1)在函数里malloc一个数组(一定不要是栈区的,如果是的出了函数就没了)
2)通过参数,传递一个数组首地址(方便)

下面用传参来进行:
1)申请结构体数组
假设4个都是要传的,这种就有可能造成空间不够的问题,所以可以用动态数组来进行操作,这里简化不用。

	//申请一个结构体数组
	struct Nobe *Arr[4] = {NULL};   //初始为NULL

2)传参
需要传数组参数,要查的值,还有结构体指针
数组参数,直接可以将struct Nobe *Arr[4]这个类型传进来就可以,或者也可以写成二级指针struct Nobe ** Arr.

int Local(struct Nobe *Head,struct Nobe *Arr[4],int Data)

3)遍历链表

	while (Head == NULL)
		Head = Head->pNext;

4)判断元素与要查的值是否相同,相同则记录下标,存在Arr数组中

		int Index = 0; //记录下标
//判断是否相等,相等就存
		if (Data == Head->Num ) 
			Arr[Index++] = Head;
//主函数调用
		Local(&a, Arr, 2);
		//循环遍历数组
		for (int i = 0; Arr[i] != NULL&&i<4; i++)
			printf("查找:%d ", Arr[i]->Num);

在首部增加节点

在首部增加节点,只需要再申请一个对象,让结构体指针指向第一个节点即可。
注意调用的时候,首地址已改变。

	//在头添加
	struct Nobe AddHead = { 12 ,NULL};
	//将pNext指向第一个的首地址,就是头添加了	
	AddHead.pNext = &a;  
	//现在的首节点是AddHead
	print(&AddHead);
在尾部添加节点

与在首部添加节点同理,只需要申请一个结构体指针,将d结构体指针指向新申请的即可。

//在尾部添加
	struct Nobe AddLast = {13,NULL};
	d.pNext = &AddLast;

注意:别忘了增加输出的节点数。

在中间添加

上一结构体指针指向新结构体,新结构体指向下一个结构体即可。道理都与之前的一样。
如下:

	//在中间添加
	struct Nobe AddMid = { 14, &c };
	b.pNext = &AddMid;

删除

与添加的逻辑基本一样,只要将要删除的上一结构体指针重新指向要删除的下一结构体即可。
如下:

	//删除AddMid
	b.pNext = &c;

或者

	//删除AddMid
	//指向它指向的下一个也可以
	b.pNext = &AddMid.pNext; 

删除头,删除尾道理也是一样。

修改

找到要修改的节点直接修改即可。

	//修改b
	b.Data = 23;

像伪链表,每个节点都有名字还好修改,增加,删除。正常的链表都是没有名字的,需要先找到位置再进行相应的操作。
1)找位置

struct Nobe * Find(struct Nobe *Head, int Num,int ChangeNum)
{
	while (Head != NULL)
	{
		if (Head->Data == Num)
		{
			Head->Data = ChangeNum;
			return Head;
		}
		Head = Head->pNext;
	}
	return NULL;
}

2)修改

Find(&AddHead, 4, 6);//直接调用函数修改即可

通过对伪链表的了解,对于链表的学习肯定会非常容易的!!
下面就要开始介绍链表了,你准备好了么!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐鑫本鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值