(二)栈、队列、链表 —— 3. 链表

这篇博客介绍了数组与链表的区别,强调了链表在插入操作上的优势。通过C语言展示了如何使用指针和malloc函数实现链表,详细解释了指针的概念和用法。接着,讲解了链表节点的结构,并给出了创建链表和在链表中插入元素的代码示例。最后,展示了在已有序链表中插入新元素的具体步骤和代码实现。
摘要由CSDN通过智能技术生成

3. 链表

3.1 基本概念

数组的局限性:
插入数值时,需要数字依次往后挪一位。操作耽误时间。

链表的优势:
插入数值,时间快很多。



3.2 链表的实现

如何实现链表呢?

在 C 语言中可以使用指针和动态分配内存函数 malloc 来实现。


3.2.1 回顾指针

指针的定义
int a;
int *p;

第一行是定义一个整型变量 a。
第二行定义了一个整型指针变量 p。


指针的作用

存储一个地址。确切地说是存储一个内存空间的地址。

严格地说这里的指针 p 只能存储“一个存放整数的内存空间”的地址。

整型指针 p 如何才能存储整型变量 a 的地址呢? 如下:

p = &a;

&叫取地址符。这样整型指针 p 就获得了(存储了)整型变量 a 的地址。

可以形象地理解整型指针 p 指向了整型变量 a。


p 指向了 a 之后,有什么用呢?

用处就是可以用指针 p 来操作变量 a 了。比如,可以通过操作指针 p 来输出变量 a 的值,如下:

#include <stdio.h>

int main() {
    int a=10;
    int *p;//定义个指针p
    p = &a;//指针p获取变量a的地址
    printf("%d", *p);//输出指针p所指向的内存中的值

    getchar();getchar();
    return 0;
}

其中, ∗ p *p p 中的*号叫做间接运算符,作用是取得指针 p 所指向的内存中的值。


在 C 语言中*号有三个用途,分别是:

  1. 乘号,用做乘法运算,例如 5*6。
  2. 申明一个指针,在定义指针变量时使用,例如 int *p;。
  3. 间接运算符,取得指针所指向的内存中的值,例如 printf("%d",*p);。

在程序中存储一个整数10,除了使用int a;这种方式在内存中申请一块区域来存储,还有另外一种动态存储方法:

malloc(4);

malloc函数的作用就是从内存中申请分配指定字节大小的内存空间。

上面这行代码就申请了 4 个字节。如果不知道 int 类型是 4 个字节的,还可以使用 sizeof(int)获取 int 类型所占用的字节数,如下:

malloc(sizeof(int));

已经成功地从内存中申请了 4 个字节的空间来准备存放一个整数,可是如何来对这个空间进行操作呢?

这里就需要用一个指针来指向这个空间,即存储这个空间的首地址。

int *p;
(int *)malloc(sizeof(int));

需要注意,malloc 函数的返回类型是 void * 类型。void * 表示未确定类型的指针。在 C 和 C++中,void * 类型可以强制转换为任何其他类型的指针。

上面代码中我们将其强制转化为整型指针,以便告诉计算机这里的 4 个字节作为一个整体用来存放整数。


指针就是用来存储内存地址的,为什么要分不同类型的指针呢?

因为指针变量存储的是一个内存空间的首地址(第一个字节的地址),但是这个空间占用了多少个字节,用来存储什么类型的数,则是由指针的类型来标明的。这样系统才知道应该取多少个连续内存作为一个数据。


可以通过指针 p 对刚才申请的 4 个字节的空间进行操作了,例如向这个空间中存入整数 10,如下:

*p = 10;

完整代码如下:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p;//定义一个指针p
    p = (int *) malloc(sizeof(int));//指针p获取动态分配的内存空间地址
    *p=10;//向指针p所指向的内存空间中存入10
    printf("%d", *p);//输出指针p所指向的内存中的值

    getchar();getchar();
    return 0;
}

为什么要用这么复杂的办法来存储数据呢?

因为之前的方法,必须预先准确地知道所需变量的个数,也就是说必须定义出所有的变量。

有了 malloc 函数,便可以在程序运行的过程中根据实际情况来申请空间。


3.2.2 链表的结点

每一个结点都由两个部分组成。左边的部分用来存放具体的数值,那么用一个整型变量就可以;

右边的部分需要存储下一个结点的地址,可以用指针来实现(也称为后继指针)。

这里定义一个结构体类型来存储这个结点,如下:

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指向这个结点
p = (struct node *)malloc(sizeof(struct node));

分别设置新创建的这个结点的左半部分和右半部分:

scanf("%d", &a);
p->data = a;
p->next = NULL;//设置当前结点的后继指针指向空,也就是当前结点的下一个结点为空

其中,符号“->” 叫做结构体指针运算符,也是用来访问结构体内部成员的。

因为此处 p 是一个指针,所以不能使用.号访问内部成员,而要使用->。

下面来设置头指针并设置新创建结点的*next 指向空。头指针的作用是方便以后从头遍历整个链表:

if (head == NULL) {
	head = p;//如果这是第一个创建的结点,则将头指针指向这个结点
} else {
	q->next = p;//如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点
}

如果这是第一个创建的结点,则将头指针指向这个结点。

如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点。

最后要将指针 q 也指向当前结点,因为待会儿临时指针 p 将会指向新创建的结点。

q=p;

完整代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//这里创建一个结构体用来表示链表的结点类型
struct node {
	int data;
	struct node *next;
};

int main() {
	struct node *head, *p, *q, *t;
	int i, n, a;
	scanf("%d", &n);
	head = NULL;//头指针初始为空
	for (i=1; i<=n; i++) //循环读入n个数
	{
		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\n", t->data);
		t = t->next;//继续下一个结点
	}

	getchar();getchar();
	return 0;
}

输入数据:
9
2 3 5 8 9 10 18 26 32

返回值:
2 3 5 8 9 10 18 26 32


接下来需要往链表中插入 6,操作如下:

首先用一个临时指针 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 <stdio.h>
#include <string.h>
#include <stdlib.h>

//这里创建一个结构体用来表示链表的结点类型
struct node {
	int data;
	struct node *next;
};

int main() {
	struct node *head, *p, *q, *t;
	int i, n, a;
	scanf("%d", &n);
	head = NULL;//头指针初始为空
	for (i=1; i<=n; i++) //循环读入n个数
	{
		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\n", t->data);
		t = t->next;//继续下一个结点
	}

	getchar();getchar();
	return 0;
}

输入数据:
9
2 3 5 8 9 10 18 26 32
6

返回值:
2 3 5 6 8 9 10 18 26 32





参考

《啊哈!算法》 —— 第2章 栈、队列、链表

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值