链表入门
数据结构中最基础且简单的部分就是线性结构的链表。数据结构之后的内容都能经常见到与链表操作相似的部分。理解和掌握链表,对于数据结构之后的学习至关重要。
始
数组,可以用于存放数据的有序的元素序列。,
一个数组的例子:
元素 | 11 | 5 | 34 |
---|---|---|---|
下标 | 0 | 1 | 2 |
特点如下:
- 可以存放的元素个数是固定的
- 存储空间连续,存储密度大
- 访问元素通过索引(下标)访问,时间复杂度为O(1)
可以看出,数组的空间和可以容纳的元素个数在数组创建之初就确定下来了。在确定元素个数时,利用数组无疑是十分高效便捷的。但是,当我们需要存储不定个数据,且数据类型又复杂多样时,又该如何处理呢?这时,就可以引申出链表这一概念,先来看看链表的特点:
- 长度不固定,可以按照需求增删
- 存储空间不连续,存储密度小
- 访问元素从表头开始,循环遍历到需要的元素,时间复杂度为O(n)
- 在特定元素之间增删元素不需要移动大部分元素,时间复杂度为O(1)
链表的实现
如何把一个个在物理上分散的数据联系起来呢?C中的结构体就能很好地实现链表。首先,结构体可以包含多个成员,每一个成员都可以是不同的数据类型,这样就可以储存多样化的数据。在结构体中再添加指向该结构体类型的指针,这样即可将同类型的结构体相连从而实现链表。
C语言实现:
#include <stdio.h>
#include <stdlib.h>
#define NULL 0
struct student//定义结构体
{
long num;
float score;//储存数据的成员
struct student *next;//指向同类结构体的指针
};
void main()
{
struct student a, b, c, *head, *p;//*head是头指针,存储链表的第一个元素的地址
a.num = 99101; a.score = 89.5;
b.num = 99103; b.score = 90;
c.num = 99107; c.score = 85;//对结点的 num 和 score 成员赋值
head = &a;//将结点 a 的起始地址赋给头指针 head
a.next = &b;//将结点 b 的起始地址赋给 a 结点的 next 成员
b.next = &c;
c.next = NULL;// c 结点的 next 成员不存放其他结点地址
p = head;//使 p 指针指向 a 结点
do
{
printf("%ld %5.1f\n", p->num, p->score);// 输出 p 指向的结点的数据
p = p->next;//使 p 指向下一结点
}while(p != NULL);//输出完 c 结点后 p 的值为 NULL
system("pause");
}
//源码出处:https://www.runoob.com/w3cnote/c-structures-intro.html,我对部分代码加以注释补充说明
处理动态链表所需要的函数
- malloc函数
void *malloc(size);
该函数的功能是在内存的动态存储区分配一片长度为size的空间,函数的返回值是这块被开辟出的空间的起始地址。若函数因为某些原因开辟空间失败,则会返回NULL
. - free函数
void free(void *p);
该函数功能为将指针p指向的内存区释放,使得该内存区可被其他变量使用
实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#define NULL 0
#define LEN sizeof(struct student)//定义结构体大小常数便于使用
struct student//定义结构体(节点)
{
long num;
float score;
struct student *next;
};
struct student *create()//创建链表
{
struct student *p1, *p2, *head;
int num;
float score;
int n = 0;//计数器,判断
head = NULL;
p1 = p2 = (struct student *)malloc(LEN);//向指针所指区域开辟空间
printf("please input num and score.\n");
scanf("%d,%f", &p1->num, &p1->score);//将值传入创建的节点内
while(p1->num != 0)
{
n ++;
if(n == 1)//首次循环时记录表头地址
head = p1;
else//之后则正常插入节点
p2->next = p1;
p2 = p1;
p1 = (struct student *)malloc(sizeof(struct student));
printf("please input num and score.\n");
scanf("%d,%f", &p1->num, &p1->score);
}
p2->next = NULL;
return head;
}
void printlist(struct student *head)//输出链表内容
{
struct student *p;
p = head;
if(head != NULL)
{
do
{
printf("num=%d score=%f\n", p->num, p->score);
p = p->next;
}while(p != NULL);
}
}
void main()
{
struct student *head;
head = create();
printlist(head);
system("pause");
}
链表之中还有具体细分的类别:
-
单链表:
- 仅包含指向下一节点的指针,意味着单链表只能单向遍历
-
双链表:
- 同时包含指向上一个节点的与下一个节点的指针,可以双向遍历
- 操作更复杂,但使用更便捷
-
循环链表:
- 由尾结点与头结点相连,形成类似环状结构
- 类似单链表,但情况更特殊