本章我们学习链表。
(1)温故而知新。数据结构这块,我们前面已经学了队列和栈的操作了,并且也有简单的实例可供大家练习,不知道大家在学习和复习之中有没有对知识有新的理解;我的感触是颇多的,刚开始学习队列和链表的时候,敲程序很陌生,思路不连贯,但是思考清楚和多加练习之后,我对知识点就更加熟悉了,代码敲得也更加流畅,感觉爽了不少,但是还是需要继续努力学习。
(2)今天的内容会比以前难一些(不过也不难),在存储一大波数的时候,我们通常用的是数组,但有的时候数组显得不够灵活,比如在对一组数中按大小顺序插入其他数时……
(3)但是我们用链表的方法,就会以一种比较简单方式解决问题。
本章的学习目标:
(1)理解指针和动态分配内存函数malloc
(2)理解链表操作的原理
(3)能够使用链表解决简单的实际问题
有一串已经从小到打排好序的数2 3 5 8 9 10 18 26 32。现需要往这串数中插入6使其得到的新序列仍符合从小到大的顺序。
如何实现链表呢?
在C语言中可以使用指针和动态分配内存函数malloc来实现。
回想一下,我们想在程序中存储一个整数10,除了使用int a;这种方式在内存中申请一块区域来存储,还有另外一种动态储存方法。
malloc(4);
malloc函数的作用就是从内存中申请分配指定字节大小的内存空间。上面这行代码就申请了4个字节。如果你不知道int类型是4个字节的,还可以用sizeof(int)获取int类型所占用的字节数,如下:
malloc(sizeof(int));
现在你已经成功从内存中申请了4个字节的空间来准备存放一个整数,可是如何来对这个空间进行操作呢?这里我们就需要用一个指针来指向这个空间,即存储这个空间的首地址。
int *p;
p = (int *)malloc(sizeof(int));
需要注意,malloc函数的返回类型是void*类型。void星表示未确定类型的指针。在C和C++中,
void*
类型可以强制转换为任何其他类型的指针。上面我们将其强制转化为整型指针,以便告诉计算机这里的4个字节作为一个整体用来存放整数。
ok,现在我们可以通过指针p对刚才申请的4个字节的空间进行操作了,例如我们向这个空间中存入整数10,如下
*p = 10;
完整代码如下,注意当在程序中使用malloc函数时需要用到stdlib.h头文件。
#include
#include
int main()
{
int *p; //定义一个指针p
p = (int *)malloc(sizeof(int)); //指针p获取动态分配的内存空间地址
*p = 10; //向指针p所指向的内存空间存入10
printf("%d",*p); //输出指针p所指向的内存中的值
return 0;
}
运行结果是
10
每一个链表都由两部分组成。左边的部分用来存放具体的数值,右边的部分需要村粗下一个结点的地址,可以用指针来实现(也称为后继指针)。那么,左边用一个整型变量就可以。
struct node
{
int data;
struct node *next;
}
上面的代码中,我们定义了一个叫做node(结点)的结构体类型,这个结构体类型有两个成员。第一个是整型data,用来存储具体的数值;第二个成员是一个指针,用来村粗下一个结点的地址。因为下一个结点的类型也是struct node,所以这个指针的类型也必须是
struct node *
类型的指针。
首先我们需要一个头指针head指向链表的最开始。当链表还没有建立的时候头指针head为空(也可以理解为指向空结点)。
struct node *head;
head = NULL; //头指针初始为空
现在我们来创建第一个结点,并用临时指针p指向这个结点。
struct node *p;
//动态申请一个空间,用来存放一个结点,并用临时指针指向这个结点
p = (struct node *)malloc(sizeof(struct node));
接下来分别设置新创建的这个结点的左半部分和右半部分。
scanf("%d",&a);
p->data = a; //将数据存储到当前结点的data域中
p->next = NULL; //设置当前结点的后继指针向空,也就是当前结点的下一个结点为空
上面的代码中,->叫做结构体指针运算符,也是用来访问内部成员的。
因为p是一个指针,所以不能使用.号访问内部成员,而是使用->。
下面设置头指针和新创建的结点。
if (head == NULL)
head = p; //如果这是第一个创建的结点,则头指针指向这个结点
else
q->next = p; //如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点
最后要将指针q也指向当前结点,因为待会临时指针p将会指向新创建的结点。
q = p; //指针q也指向当前结点
#include
#include
//这里创建一个结构体用来表示链表的结点类型
struct node
{
int data;
struct node *next;
};
int main()
{
struct node *head, *p, *q, *t;
int i, n, a;
scanf("%d",&n);
head = NULL; //头指针初试为空
q = NULL;
//读取一串从小到大的数字并存入链表
for (i = 1; i <= n; i++)
{
scanf("%d",&a);
//申请一个动态空间,用来存放一个结点,并用临时指针p指向这个结点
p = (struct node *)malloc(sizeof(struct node)); //每次申请的都不同
p->data = a; //将数据存储到当前结点的data域中
p->next = NULL; //设置当前结点的后继指针向空,也就是当前结点的下一个结点为空
if (head == NULL)
head = p; //如果这是第一个创建的结点,则头指针指向这个结点
else
q->next = p; //如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点
q = p; //指针q也指向当前结点
}
//输出链表中的所有数
t = head;
while (t != NULL)
{
printf("%d ",t->data);
t = t->next; //继续下一个结点
}
return 0;
}
首先用一个临时指针t从链表的头部开始遍历。
t = head; //从链表头部开始遍历
等到指针t的下一个结点的值比6大的时候,将6插入到中间。即t ->next->data大于6时进行插入,代码如下
scanf("%d",&a); //读入待插入的数
while (t != NULL) //当没有到达链表尾部的时候循环
{
if (t->next->data > a) //如果当前结点的下一个结点的值大于待插入数,将数插入到中间
{
p = (struct node*)malloc(sizeof(struct node)); //动态申请一个空间,用来存放新增结点
p->data = a;
p->next = t->next; //新增结点的后继指针指向当前结点的后继指针
t->next = p; //当前结点的后继指针指向新增结点
break; //插入完毕,退出循环
}
t = t->next; //继续下一个结点
}
#include
#include
//这里创建一个结构体用来表示链表的结点类型
struct node
{
int data;
struct node *next;
};
int main()
{
struct node *head, *p, *q, *t;
int i, n, a;
scanf("%d",&n);
head = NULL; //头指针初试为空
q = NULL;
//读取一串从小到大的数字并存入链表
for (i = 1; i <= n; i++)
{
scanf("%d",&a);
//申请一个动态空间,用来存放一个结点,并用临时指针p指向这个结点
p = (struct node *)malloc(sizeof(struct node)); //每次申请的都不同
p->data = a; //将数据存储到当前结点的data域中
p->next = NULL; //设置当前结点的后继指针向空,也就是当前结点的下一个结点为空
if (head == NULL)
head = p; //如果这是第一个创建的结点,则头指针指向这个结点
else
q->next = p; //如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点
q = p; //指针q也指向当前结点
}
scanf("%d",&a); //读入待插入的数
t = head; //从链表头部开始遍历
while (t != NULL) //当没有到达链表尾部的时候循环
{
if (t->next->data > a) //如果当前结点的下一个结点的值大于待插入数,将数插入到中间
{
p = (struct node*)malloc(sizeof(struct node)); //动态申请一个空间,用来存放新增结点
p->data = a;
p->next = t->next; //新增结点的后继指针指向当前结点的后继指针
t->next = p; //当前结点的后继指针指向新增结点
break; //插入完毕,退出循环
}
t = t->next; //继续下一个结点
}
//输出链表中的所有数
t = head;
while (t != NULL)
{
printf("%d ",t->data);
t = t->next; //继续下一个结点
}
return 0;
}