Bootstrap

【嵌入式开发之数据结构】单链表的链式存储结构、结点描述以及单链表创建的实现

线性表的链式存储结构

将线性表L=(a_{0},a_{1},...,a_{n-1})中各个元素分布在存储器的不同存储块,称为结点,通过地址或指针建立元素之间的联系:

datanext

结点的data域存放数据元素a_{i},而next域是一个指针,指向a_{i}的直接后继a_{i+1}的结点。

 线性表的链式存储结点描述

结点类型描述:

typedef struct node
{
    data_t data; //结点的数据域//
    struct node *next; //结点的后继指针域//
}listnode, *linklist;

设p指向链表中结点a_{i}

获取a_{i},写作:p->data;

而取 a_{i+1},写作:p->next->data;

若指针p的值为NULL,则它不指向任何结点,此时获取p->data或者p->next->data会报错,怎么解决这个问题?

有两个解决办法,其中之一:

listnode A; //A在栈上
linklist p = &A;

在这个情况下,如果要给结点赋值,有两种写法,第一种是:

A.data = value;

第二种方法是:

p->data = value;

 这种方法定义的A的地址在栈上,随着程序的运行跳出函数或者循环,这个值会被销毁,所占用的内存会被释放,无法做到自己控制,因此,我们要考虑将A放在堆上,通过动态内存的方式管理。

可调用C语言中malloc()函数向系统申请结点的存储空间:

linklist p;
p = (linklist)malloc(sizeof(listnode));

创建一个类型为linklist的结点,且该结点的地址已存入指针变量p中:

此时,给结点赋值的方法是:

p->data = value;

建立单链表

依次读入表L=(a_{0},a_{1},...,a_{n-1})中的每个元素a_{i}(假设为整型),若a_{i}\neq -1(-1为结束符),则为a_{i}创建一个结点,然后插入表尾,最后返回链表的头结点指针H。

L=(2,4,8,-1),则建表过程如下:

链表的结构是动态形成的,在算法运行之前,表结构是不存在的。

单链表创建的实现

一、创建一个.h头文件linklist.h

typedef int data_t;

typedef struct node {
	data_t data;
	struct node *next;
}linknode, *linklist;

//创建头结点
linklist list_create();

//从链表尾部插入新增的数据
int list_tail_insert(linklist P, data_t value);

//输出链表
int list_show(linklist P);

在头文件中,用typedef关键字为数据类型int(可根据需要调整)定义一个新的名字data_t。typedef struct node linknode; //将结构体类型struct node重命名为linknode
typedef struct node *linklist; //将结构体类型struct node *重命名为linklist

声明三个函数:分别实现链表的创建,数据的尾部插入和输出。

二、创建一个.c文件linklist.c

在这个文件中实现链表的创建、尾部插入和输出三个功能。

1、链表创建的实现函数
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"

//实现list的创建
linklist list_create() {
	//声明一个linklist指针
	linklist P;

	//给指针分配内存空间,如果分配失败,直接返回
	if ((P = (linklist)malloc(sizeof(linknode))) == NULL) {
		printf("malloc failed.\n");
		return P;
	}

	//赋值,data=0,头结点的next指针为NULL
	P->data = 0;
	P->next = NULL;

	//返回指针P
	return P;
}

定义一个结构体指针P,并用malloc为其分配内存,再对数据和指针域赋值,分别是0和NULL,最后返回这个指针P。 

2、尾部插入的实现函数
//实现尾部插入
int list_tail_insert(linklist P, data_t value) {
	//判断传入的P是否为NULL,如为NULL直接返回
	if (P == NULL) {
		printf("P is NULL.\n");
		return -1;
	}
	
	//第一步:创建新结点,并完成赋值
	//声明两个指针p,q
	linklist q;
	linklist p;

	//给node指针q分配内存空间
	if ((q = (linklist)malloc(sizeof(linknode))) == NULL) {
		printf("malloc failed.\n");
		return -1;
	}

	//将传入的value赋值给q指向的data,并将指针P指向的next指向q
	q->data = value;
	q->next = NULL;

	//第二步:移动指针p,直到指向链表P的最后一个结点
    //将P的头指针赋值给p,并将p不断后移至所指向的next为NULL
	p = P;
	while (p->next != NULL) {
		p = p->next;
	}

	//第三步:将指针q赋值给p指向的next,完成插入
	p->next = q;

	return 0;
}

首先对传入的链表指针P进行判断,如果是NULL,则直接返回。

声明两个linklist指针,分别为p和q,并对q指针用malloc分配空间,然后将传入的value付给q指针所指向的data,并给q指针指向的next赋值为NULL。

因为我们要实现的功能是从尾部插入,所以首先要找到尾部结点,尾部结点的next值为NULL,所以只需要用一个指针p,从P的头指针位置,一直往后移动,移动到p->next为NULL即可。

最后将指针q赋值给p->next,实现尾部插入。

3、链表输出的实现函数
//实现链表的输出
int list_show(linklist P) {
	linklist p;

	if (P == NULL) {
		printf("P is NULL.\n");
		return -1;
	}

	p = P;
	while (p->next != NULL) {
		printf("%d ", p->next->data);
		p = p->next;
	}
	puts("");

	return 0;
}

单链表的输出,其实就是从头指针开始,通过next存储的地址,一直往后遍历,知道next的值为NULL,表明链表结束,依次输出p->next->data即可。

三、创建一个.c文件test.c 

这个文件主要作用是调用linklist.c中的各个函数,测试其功能是否满足需求。

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

int main(int argc, const char *argv[])
{
	linklist P;
	int value;
	
	//调用链表头结点创建函数
	P = list_create();

	//调用scanf函数获取用户输入的值
	//调用list_tail_insert函数将用户输入的值插入
	printf("input value(input '-1' quit):");
	while (1) {
		scanf("%d", &value);
		if (value == -1) 
			break;
		list_tail_insert(P, value);
		printf("input value:");
	}

	//调用list_show函数显示链表
	list_show(P);

	return 0;
}

首先调用list_create()函数,创建一个结点,这个结点在链表中被当为头结点。

通过一个while循环,在循环中调用scanf函数实现用户的多次输入,当用户输入-1时跳出循环,通过调用list_tail_insert()函数,将用户输入的数值value插入到链表尾部。

最后,调用list_show()函数输出最终的链表。

;