C语言程序设计学习笔记:P14-链表


一、可变数组

1.1 可变数组

在C语言中,数组都是固定大小的。尽管C99可以使用变量作为数组大小,但是在运行过程中我们无法改变数组大小。因此,在实际应用中如果我们不知道有多少数据需要存放,传统方法就是尽量弄个大的数组,但是大的也有可能不够。因此,需要一个可以自己变大小的数组,这就是可变数组。

要实现可变数组需要满足以下特点:

• 可增长
• 可知道当前有多大
• 可以访问其中的单元

我们可以实现一个函数库:

Array array_create(int init_size);           //创建一个数组
void array_free(Array *a);                   //释放这个数组的空间
int array_size(const Array *a);              //数组现在有多少个单元可用
int* array_at(Array *a, int index);          //访问数组某个位置的元素
void array_inflate(Array *a, int nore_size); //让数组增大

我们首先来实现创建数组和释放数组功能:
array.h

#pragma once
typedef struct {
    
    
	int *array;
	int size;
}Array;

Array array_create(int init_size);
void array_free(Array *a);
int array_size(const Array *a);
int* array_at(Array *a, int index);
void array_inflate(Array *a, int more_size);

array.c

#include "array.h"
#include <stdio.h>
 
Array array_create(int init_size)
{
    
    
	Array a;
	a.size = init_size;
	a.array = (int*)malloc(sizeof(int)*a.size);
	return a; 
}

void array_free(Array *a)
{
    
    
	free(a->array);
	a->array = NULL;;
	a->size = 0;
}


1.2 可变数组的数据访问

这里我们实现获取数组大小和数组单元的数据
array.c

#include "array.h"
#include <stdio.h>
#include <stdlib.h>
 
Array array_create(int init_size)
{
    
    
	Array a;
	a.size = init_size;
	a.array = (int*)malloc(sizeof(int)*a.size);
	return a; 
}

void array_free(Array *a)
{
    
    
	free(a->array);
	a->array = NULL;;
	a->size = 0;
}

//可能随着代码升级,不能直接通过a.size得到大小
//封装,将a的size保护起来
int array_size(const Array *a)
{
    
    
	return a->size;
}

//返回指针比较好,我们可以对其赋值
int* array_at(Array *a, int index)
{
    
    
	return &(a->array[index]);
}

void array_inflate(Array *a, int more_size);

1.3 可变数组的自动增长

要实现自动增长,需要考虑两个问题;

1、什么时候需要增长?
  可以考虑在访问数组越界时进行增长
2、一次增长多大?
  一次不要只增长1个int,可以设置一个Block,一个Block里面包含若干个int。
  当访问越界时,根据越界多少来决定增长多少个Block。

我们这里设置一个Block包含20个int,根据访问数组下标越界的程度来决定增长多少个Block。

#include "array.h"
#include <stdio.h>
#include <stdlib.h>
 const int BLOCK_SIZE = 20;

Array array_create(int init_size)
{
    
    
	Array a;
	a.size = init_size;
	a.array = (int*)malloc(sizeof(int)*a.size);
	return a; 
}

void array_free(Array *a)
{
    
    
	free(a->array);
	a->array = NULL;;
	a->size = 0;
}

//可能随着代码升级,不能直接通过a.size得到大小
//封装,将a的size保护起来
int array_size(const Array *a)
{
    
    
	return a->size;
}

//返回指针比较好,我们可以对其赋值
int* array_at(Array *a, int index)
{
    
    
	if (index >= a->size) {
    
    
		array_inflate(a, (index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
	}
	return &(a->array[index]);
}

void array_inflate(Array *a, int more_size)
{
    
    
	int *p = (int*)malloc(sizeof(int)*(a->size + more_size));
	int i;
	for (i = 0; i < a->size; i++) {
    
    
		p[i] = a->array[i];
	}
	free(a->array);
	a->array = p;
	a->size += more_size;
}

设置链表增长程度的代码详情如下图所示:
在这里插入图片描述

二、链表

2.1 可变数组的缺陷

可变数组有一些很大的缺陷:每次都需要申请足够大的空间以存放所有数据,同时需要进行拷贝。

• 拷贝很花时间
  • 随着数组增长,比如增长到10000个,拷贝就很花时间
• 明明有空间,但是无法申请内存
  • 数组前面和后面的内存都不够,但是加起来够

无法申请内存的情况如下图所示。虽然在计算机上有很大的内存,但是当我们使用一些资源受限的单片机时,可能只有16KB内存,这时候就很容易发生这种情况。
在这里插入图片描述


因此,如果我们可以不去申请N+Block那么大的内存,而是申请一块Block大小的内存,然后将它们链起来,那么我们即可以不用拷贝,又可以充分利用内存中的角角落落。
在这里插入图片描述

2.2 链表

链表的结构如下图所示。链表由多个节点组成,每个节点由数据和指针构成,指针指向下个结点的数据的地址。其中最后一个节点的指针需要表示后面没有东西了。
在这里插入图片描述

我们首先将结点和链表定义出来,同时写出功能函数的原型。
node.h

#pragma once

typedef struct _node {
    
    
	int value;
	//不要用Node *next,因为编译器这时候还不知道Node
	struct _node *next;
} Node;

typedef struct _list {
    
    
	Node* head; //链表的头结点
	Node* tail; //链表的尾结点
}List;

void add(List* pList, int number);  //添加一个结点
void print(List *pList);  //打印链表中所有的值
int search(List *pList, int number);  //搜索某个值是否在链表中
void del(List *pList, int number); //删除链表中的特定元素
void clear(List *pList);  //清空整个链表

2.3 链表的函数

接着写出向链表中添加一个结点的代码。
node.c

void add(List* pList, int number)
{
    
    
	Node *p = (Node*)malloc(sizeof(Node));
	p->value = number;
	p->next = NULL;

	if (pList->head) {
    
    
		pList->tail->next = p; //tail的下一个结点是p
		pList->tail = p;       //将tail移动到p
	}
	else
	{
    
    
		pList->head = p;  //空链表,首次创建结点
		pList->tail = p;
	}
}

2.4 链表的搜索

为了验证是否成功添加了一个结点,我们写一个打印所有结点数据的函数。

void print(List *pList)
{
    
    
	Node *p;
	for (p = pList->head; p; p = p->next) {
    
    
		printf("%d\t", p->value);
	}
	printf("\n");
}

现在我们要搜索特定的值是否在链表中,思路和上面打印所有节点数据的函数类似,只是多了一个判断。

int search(List *pList, int number)
{
    
    
	Node *p;
	int isFound = 0;
	for (p = pList->head; p; p = p->next) {
    
    
		if (p->value == number) {
    
    
			isFound = 1;
			break;
		}
	}
	return isFound;
}

2.5 链表的删除

如果我想删除链表中的某个结,需要以下步骤:

1、定义两个结点p与q,分别代表当前结点与前面一个结点
2、令当前结点p为链表头结点,前面一个节点q为NULL
3、判断当前结点p是否为空。
  3.1、如果不为空,则判断是否等于要找的那个number。
    3.1.1、如果相等,则判断q是否存在。
      3.1.1.1、如果q存在,则直接q->next=p->next,同时释放当前结点。
      3.1.1.2、如果q不存在,说明头结点就是这个number,让链表头结点为p->next
    3.1.2、如果不相等,q=p,p=p->next,转移到下一个结点进行检查
  3.2、如果为空,则说明是个空链表

大概流程如下图所示:
在这里插入图片描述


代码如下所示:

void del(List *pList, int number)
{
    
    
	Node *q;
	Node *p;
	for (q = NULL, p = pList->head; p; q = p, p = p->next) {
    
    
		if (p->value == number) {
    
    
			if (q) {
    
    
				q->next = p->next;
			}
			else {
    
    
				pList->head = p->next;
			}
			free(p);
			break;
		}
	}
}

2.6 清除链表

清除整个链表需要两个结点,一个记录当前要清除的结点,一个记录下一个结点所在位置。

void clear(List *pList)
{
    
    
	Node* p;
	Node* q;
	for (p = pList->head; p; p = q) {
    
    
		q = p->next;
		free(p);
	}
}

2.7 总代码与测试

总体代码如下:
node.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "node.h"
#include <stdio.h>
#include <stdlib.h>

typedef struct _node {
    
    
	int value;
	//不要用Node *next,因为编译器这时候还不知道Node
	struct _node *next;
} Node;

typedef struct _list {
    
    
	Node* head;
	Node* tail;
}List;

void add(List* pList, int number);
void print(List *pList);
int search(List *pList, int number);
void del(List *pList, int number);
void clear(List *pList);

node.c

#define _CRT_SECURE_NO_WARNINGS
#include "node.h"
#include <stdio.h>
#include <stdlib.h>

void add(List* pList, int number)
{
    
    
	Node *p = (Node*)malloc(sizeof(Node));
	p->value = number;
	p->next = NULL;

	if (pList->head) {
    
    
		pList->tail->next = p;
		pList->tail = p;
	}
	else
	{
    
    
		pList->head = p;
		pList->tail = p;
	}
}

void print(List *pList)
{
    
    
	Node *p;
	for (p = pList->head; p; p = p->next) {
    
    
		printf("%d\t", p->value);
	}
	printf("\n");
}

int search(List *pList, int number)
{
    
    
	Node *p;
	int isFound = 0;
	for (p = pList->head; p; p = p->next) {
    
    
		if (p->value == number) {
    
    
			isFound = 1;
			break;
		}
	}
	return isFound;
}

void del(List *pList, int number)
{
    
    
	Node *q;
	Node *p;
	for (q = NULL, p = pList->head; p; q = p, p = p->next) {
    
    
		if (p->value == number) {
    
    
			if (q) {
    
    
				q->next = p->next;
			}
			else {
    
    
				pList->head = p->next;
			}
			free(p);
			break;
		}
	}
}

void clear(List *pList)
{
    
    
	Node* p;
	Node* q;
	for (p = pList->head; p; p = q) {
    
    
		q = p->next;
		free(p);
	}
}

主函数

#define _CRT_SECURE_NO_WARNINGS
#include "node.h"


int main(int argc, char const *argv[])
{
    
    
	//1、定义一个链表
	List *pList = (List*)malloc(sizeof(List));
	pList->head = pList->tail = NULL;

	//2、向链表中添加结点
	int number;
	printf("请输入需要存入链表的数字,输入-1结束\n");
	do {
    
    
		scanf("%d", &number);
		if (number != -1) {
    
    
			add(pList, number);
		}
	}while(number != -1);

	//3、打印链表中所有数据
	printf("你的链表中的数据有以下这些:\n");
	print(pList);

	//4、查询链表中是否有特定数据
	printf("请输入你想查询的数字是否在链表中:");
	scanf("%d", &number);
	if (search(pList, number)) {
    
    
		printf("%d在链表中\n", number);
	}
	else {
    
    
		printf("%d不在链表中\n", number);
	}

	//5、删除特定数据,并打印删除后的结果
	printf("请输入你想删除的数据:");
	scanf("%d", &number);
	del(pList, number);
	printf("删除%d后链表数据如下:\n", number);
	print(pList);

	//6、清空整个链表
	printf("最后,我们清空整个链表\n");


	return 0;
}

我们运行,并测试每一个功能,可以看出结果正确。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/InnerPeaceHQ/article/details/121779916
今日推荐