线性表 详讲及使用
在这里我要进行一下说明,在我们学习数据结构与算法之后,我们编程思维就不能仅仅局限于能够达成目标,而是在达成目标的基础上,代码的逻辑要相对直观清晰,时间复杂度和空间复杂度要相对低一些,这样,我们编写的代码才能算作是“合格”的代码
所谓的线性表,是一种 逻辑线性结构,即,一对一结构。
至于线性表的实现,可以用物理线性结构(即数组)来实现,也可以用物理非线性结构(即链表)实现。
至于数组的方法,我们就不进行展示了,因为我们既然是学习数据结构与算法,那么,我们就要掌握更加实用,适应性更强的方法来实现,能够满足所有的数据的类型的使用,并且许多函数运行起来时间复杂度和空间复杂度要相对低。
数组对于信息的删除和插入而言实现起来时间复杂度比较高。当然,数组实现起来还有其他的弊端,这里就不进行更多的解释了,我们直接进入正题。
首先,我们来编写我们自己编写的头文件——"mec.h":
#ifndef _MEC_H_
#define _MEC_H_
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
#endif
因为这是本人数据结构与算法分类的前几篇,所以对这里的代码进行一下讲解:
我们上述的操作相当于使用了JAVA编程语言中的boolean类型,它的作用是表明有些变量或者函数功能的使用成功与否,若是对这里的解释还有点疑惑的话,请继续浏览本博文,在下方的代码的编写中同学们就会有更深的理解
那么,我们要用链表方式实现线性表的话,现在就要先定义一个存储数据的结构体:
typedef struct MEC_LINEAR {
void **data; //这里的成员类型我们在底下进行详细的解释
int capacity; //这个结构体成员的作用是存储我们所构造的结构体的
int count;
}MEC_LINEAR;
这里的data类型是void *的原因是:
我们要用数组来存储用户定义的类型的数据的地址,而void类型是通用类型(定义后强转即可),但我们要存储的是数据的首地址(这样能够满足所有用户类型数据的存储),所以应该是void 类型,但是我们不能只存储仅仅一个用户数据,要用数组存储所有的用户数据(注意,这里的数组每个单元存储的是每个用户数据的首地址),所以data的类型是void **
结构体我们构建好了,那么我们现在来编写初始化线性表函数:
因为我们的结构体虽然构建好了,但是里面的指针我们还是不能直接使用,以免使得我们造成使用“野指针”
现在我们来思考下该函数的返回值:
因为我们要初始化的是结构体,里面的指针变量如果不规范,那么我们就不能进行初始化,以免造成错误,所以,这里就会出现初始化成功还是失败两种结果,所以,这里就用到了我们上面所定义的类型——boolean。
至于参数,那一定是结构体的指针和链表存储我们所要存储的数据的个数(即容量),这样才能将改变结构体内的值返回给主函数。
所以,相关代码如下:
boolean initMecLinear(MEC_LINEAR **head, int capacity) {
MEC_LINEAR *result;
if (NULL == head || NULL != *head) { //这里是防止传过来的是空指针(我们无法对空指针所指向的空间赋值)和防止该指针所指向的空间已被赋值(再次赋值会造成“内存泄漏”,VC和DEVC不会提示,但是会永久性使得之前的空间无法找到,浪费计算机的储存空间)
return FALSE; //这里的返回会终止这个函数,并提醒主函数,初始化失败了
}
result = (MEC_LINEAR *) calloc(sizeof(MEC_LINEAR), 1); //这里的calloc()函数在<malloc.h>头文件中,而且我们一旦使用就一定要记得释放
result->capacity = capacity;
result->count = 0;
result->data = (void **) calloc(sizeof(void *), capacity);
*head = result;
return TRUE; //这里的返回值提示主函数,初始化成功了
}
这里对calloc()进行解释:存在于<malloc.h>头文件中,用途:
在内存的动态存储区中分配n个长度为size的连续空间,并将所申请的空间全赋值为0,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
因为我们使用了calloc()函数,我们就一定要记得用free()函数释放它所定义的空间,所以,我们来编写销毁线性表函数:
销毁的话,不牵扯成功与失败,因为即使传递的是空指针,我们就不用进行销毁的操作了,所以直接return即可所以返回值是void类型。
而至于参数,因为我们要对于结构体进行修改,并且将修改后的值返回到主函数中,所以,参数 只用传递线性表的首地址即可。
void destoryMecLinear(MEC_LINEAR **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->data);
free(*head);
*head = NULL;
}
在free()使用时我们一定要注意,给谁calloc()或者malloc(),就要给谁free()。
因为我们要对于定义的线性表空间进行操作,就一定要知道线性表的大小,所以,我们现在来编写判断线性表容量的函数:
关于参数:因为我们只需知道线性表的容量而不需要对其内的数据进行改变,所以只需传递MEC_LINEAR *即可,而我们不能改变该指针所指向空间的值,所以,我们要在类型前加上const,表示只能读取不能修改。
相关代码如下:
int getMecLinearCapacity(const MEC_LINEAR *head) {
if (NULL == head) {
return -1;
}
return head->capacity;
}
既然知道了容量,那么对于数据进行增添和删除就要知道存储的元素个数,所以我们再来编写判断有效元素个数的函数:
int getLinearElementsCount(const MEC_LINEAR *head) {
if (NULL == head) {
return -1;
}
return head->count;
}
现在我们来编写一个通过下标访问指定元素的函数:
int getLinearElementsCount(const MEC_LINEAR *head) {
if (NULL == head) {
return -1;
}
return head->count;
}
以上就是我们对线性表进行操作的基本函数,那么,我们现在来总结一下:
mecLinear.h:
#ifndef _MEC_LINEAR_H_
#define _MEC_LINEAR_H_
#include "mec.h"
typedef struct MEC_LINEAR {
void **data;
int capacity;
int count;
}MEC_LINEAR;
boolean initMecLinear(MEC_LINEAR **head, int capacity);
void destoryMecLinear(MEC_LINEAR **head);
int getMecLinearCapacity(const MEC_LINEAR *head);
int getLinearElementsCount(const MEC_LINEAR *head);
void *linearElementAt(const MEC_LINEAR *head, const int index);
#endif
mecLinear.c:
#include <stdio.h>
#include <malloc.h>
#include "mec.h"
#include "mecLinear.h"
void *linearElementAt(const MEC_LINEAR *head, const int index) {
if (NULL == head || index < 0 || index >= head->count) {
return NULL;
}
return head->data[index];
}
int getLinearElementsCount(const MEC_LINEAR *head) {
if (NULL == head) {
return -1;
}
return head->count;
}
int getMecLinearCapacity(const MEC_LINEAR *head) {
if (NULL == head) {
return -1;
}
return head->capacity;
}
void destoryMecLinear(MEC_LINEAR **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->data);
free(*head);
*head = NULL;
}
boolean initMecLinear(MEC_LINEAR **head, int capacity) {
MEC_LINEAR *result;
if (NULL == head || NULL != *head) {
return FALSE;
}
result = (MEC_LINEAR *) calloc(sizeof(MEC_LINEAR), 1);
result->capacity = capacity;
result->count = 0;
result->data = (void **) calloc(sizeof(void *), capacity);
*head = result;
return TRUE;
}
现在我们来用这些工具函数来编写一个处理多项式问题的.c和.h文件:
首先,我们需要一个结构体来表示多项式的各项,现在来构建一个能表示多项式的各项的结构体:
typedef struct POLYNORMIAL {
double coefficient;
int exponent;
}POLYNORMIAL;
接下来,我们的操作也是十分基础的,就是:初始化,销毁,读取多项式指定项的信息,下面我们来实现一下:
1.初始化多项式函数:
boolean initPolynormial(POLY **polyHead, int capacity) {
return initMecLinear(polyHead, capacity); //这个函数我们在上面的mecLinear.h文件中声明过
}
2.销毁多项式函数:
void destoryPolynormial(POLY **polyHead) {
destoryMecLinear(polyHead); //这个函数我们在上面的mecLinear.h文件中声明过
}
3.读取多项有效长度函数:
这里来说明下为什么要编写这个函数:
因为我们要读取多项式某一项的信息,是要在合理范围内(即不能超过有效项数的范围),所以我们才来编写这个函数以防用户错误使用导致非法访问
int getPolyCount(const POLY *polyHead) {
return getLinearElementsCount(polyHead);
}
4.读取多项式指定项的信息函数:
POLYNORMIAL *polyAt(const POLY *polyHead, const int index) {
POLYNORMIAL *res = (POLYNORMIAL *) linearElementAt(polyHead, index);
return res;
}
那么,基本工具函数我们就处理好了,现在来总结下:
polynormail.h:
#ifndef _POLYNORMIAL_H_
#define _POLYNORMIAL_H_
#include "mec.h"
#include "mecLinear.h"
typedef MEC_LINEAR POLY;
typedef struct POLYNORMIAL {
double coefficient; //这一项的系数
int exponent; //这一项的质数
}POLYNORMIAL;
boolean initPolynormial(POLY **polyHead, int capacity);
void destoryPolynormial(POLY **polyHead);
int getPolyCount(const POLY *polyHead);
POLYNORMIAL *polyAt(const POLY *polyHead, const int index);
#endif
polynormail.c:
#include <stdio.h>
#include "mec.h"
#include "mecLinear.h"
#include "polynormial.h"
POLYNORMIAL *polyAt(const POLY *polyHead, const int index) {
POLYNORMIAL *res = (POLYNORMIAL *) linearElementAt(polyHead, index);
return res;
}
int getPolyCount(const POLY *polyHead) {
return getLinearElementsCount(polyHead);
}
void destoryPolynormial(POLY **polyHead) {
destoryMecLinear(polyHead);
}
boolean initPolynormial(POLY **polyHead, int capacity) {
return initMecLinear(polyHead, capacity);
}
现在我们来编写一个测试的文件来测试下我们上面所编写的所有内容:
demoPolynormail.c:
#include <stdio.h>
#include "mec.h"
#include "mecLinear.h"
#include "polynormial.h"
int main(void) {
POLY *polyOne = NULL;
POLY *polyTwo = NULL;
int oneCapacity;
initPolynormial(&polyOne, 30);
oneCapacity = getMecLinearCapacity(polyOne);
destoryPolynormial(&polyOne);
return 0;
}
在这里我们要注意一点:
对于同一个指针,譬如上面的POLY *polyOne,只能初始化一次,且销毁一次才能进行第二次初始化和销毁(对于同一个指针:初始化和销毁都不能连续出现两次以上),否则将会出现可怕的“内存泄漏”,因为我们的初始化和销毁的空间都是占用的“堆空间”(不是堆栈空间,相关知识在本人“数据结构与算法”专题的后续博文——《堆栈 的实现》中会进行详细对比以及讲解)VC和DEVC在编译时检测不出来,但是会永久性浪费内存空间。
剩下的类似于加减乘除操作都可以用我们上面所给出的函数,通过遍历多项式各项以实现目的,这里就不对这些简单问题进行详细的代码解释了。
以上就是我们这篇博文的所有内容了,可能刚学习编程语言不久的同学会有如此疑问:
这么多文件,该如何去编译呢?
这就需要我们自己学习:首先将mec.h、mecLinear.h、mecLinear.c、Polynormail.h、Polynormail.c以及demoPolynormail.c放在同一个文件夹下(因为我们之前调用很多自己编写的头文件式时路径写的是本目录下),再通过命令行窗口或者虚拟机来进行多文件联编。
在学习的过程中我们一定要耐下性子,戒骄戒躁,这样,在未来方能成大器!
这节课是我们数据结构与算法的第一节课,所以从现在起,我们要慢慢地改变自己代码的格式,以及代码的安排(比如多次出现的代码就要想到用一个函数来取代多行代码,这样我们的代码可读性强,并且要修改时也会十分方便)