数组与动态数组
概念
数组:固定长度,空间连续,一次性申请好,为保证够用申请非常多的空间,会造成空间浪费!
动态数组:动态的增加或删除节点,也是重新申请更大的空间,会严重降低效率
链表由来
如果不想一次性申请太多空间,只申请一个,并保证原数据不变,怎么做?
数组访问各个元素的原理:下标运算(空间连续,直接首地址做加法)
由于后加的元素不是和原数组一起申请的,所以各元素地址不一定连续,就无法通过下标来进行访问,所以要通过前一个元素记录下一个元素的地址,这样就可以通过下标找到位置。
这样节点就有两个数据:一个装自身数据,一个装相邻节点的地址。
所以每个节点都是结构体类型。像这样只装下一个节点地址的就是单向链表
伪链表的创建
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);//直接调用函数修改即可
通过对伪链表的了解,对于链表的学习肯定会非常容易的!!
下面就要开始介绍链表了,你准备好了么!