Bootstrap

数据结构——线性表(C语言)

一、相关介绍

  • 定义:线性表 (linear list) 是 数据结构 的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
  • 线性表的工具代码函数
    看到这里,千万不要被这么多的函数给吓住了。每个函数实现过程都很简单。在我的老师,大微易码“铁血教主”的改进下,此代码已经非常的简洁易懂
//线性表的初始化
boolean initLinear(LINEAR **head, int capacity);
//线性表的销毁
boolean destoryLinear(LINEAR **head);
//判断是否为空
boolean isLinearEmpty(const LINEAR *head);
//判断是否已满
boolean isLinearFull(const LINEAR *head);
//获取元素个数,也就是线性表的长度
int getLinearElementCount(const LINEAR *head);
//获取线性表的容量大小
int getLinearCapacity(const LINEAR *head);
//插入元素
boolean insertLinearElement(const LINEAR *head, const int index, const USER_TYPE data);
//追加元素
boolean appendLinearElement(const LINEAR *head, const int index, const USER_TYPE data);
//获取对应下标的元素
boolean getLinearElementAt(const LINEAR *head, const int index, USER_TYPE *data);
//删除元素
boolean removeLinearElement(LINEAR *head, const index);
//相等比较
int linearIndexOf(const LINEAR *head, USER_TYPE data, boolean (*eq)(USER_TYPE num1, USER_TYPE num2));

如果二维指针形参变量,以及函数指针不能理解,在我的其他博文里有相关的介绍,欢迎大家参考学习

二、函数原码以及相关讲解

  • 线性表的初始化:
    我们所写的线性表主要由两部分构成:1、表头。2、数据存储区。而初始化,主要就是创建表头,并且给数据区开辟用户指定大小的存储数据空间。接下来就让我通过原码给大家进行解释具体是如何实现初始化的。
boolean initLinear(LINEAR **head, int capacity) {

	if (NULL == head || *head != NULL || capacity <= 0) {
		return FALSE;
	}

	*head = (LINEAR*) calloc(sizeof(LINEAR), 1);
	(*head)->data = (USER_TYPE*) calloc(sizeof(USER_TYPE), capacity);
	(*head)->capacity = capacity;
	(*head)->count = 0;

	return TRUE;
}

LINEAR 是用来创建表头的结构体,capacity是用户指定的空间大小。第一个条件判断主要是用来判断传入数据的合法性,以及避免发生内存泄漏 (结合第一部分所给出的主函数进行理解内存泄漏,以及数据的合法性)。如果capacity小于零,就会发生歧义,因为空间大小不可能是小于等于零的。我们将表头写成结构体的形式,既方便数据处理,同时,通过指针可以节省很大的空间 (任何类型的指针,大小只有4B) 与此同时,通过calloc()函数分别给表头分配地址以及申请存储空间。

  • 线性表的销毁
    可能有读者就会好奇了,还没实现其他功能怎么就急着写销毁功能。在这里我要告诉大家,当写好一个函数功能之后尽可能的将其与之对应的功能写出来,这是一个良好的编程习惯。接下来让我们看看第二部分代码;
boolean destoryLinear(LINEAR **head) {

	if (NULL == head || NULL == *head) {
		return TRUE;
	}

	free((*head)->data);
	free(*head);
	*head = NULL;

	return TRUE;
}

同样的,我们先判断参数的合法性,之后释放前面所申请的空间,注意:释放空间时,首先要释放data的,然后再释放head的。因为data包含在head里面,所以要从里外,一层一层的进行释放。

  • 判断是否为空
    这个功能主要就是为了判断该线性表里面是否还存在存储的数据元素。代码量很少,但是想要很快理解,还是需要花费一定时间的。这里首先使用到了 短路运算 的思想。通过短路运算,对传入的参数进行了合法性的判断,如果传入的数据合理,才会接着判断是否为空。
boolean isLinearEmpty(const LINEAR *head) {

	return (NULL == head || head->count <= 0);
}
  • 判断是否已满
    用到的思想和为空判断完全一致,就不做过多解释。
boolean isLinearFull(const LINEAR *head) {

	return (NULL == head || head->count >= head->capacity);
}

  • 获取元素个数,也就是线性表的长度
    在结构体类型的表头head里面有一个count变量,每当存储进来一个元素,他都会自增,所以只需通过获取count的值就可以获取元素的个数,也就是线性表当前的长度。
int getLinearElementCount(const LINEAR *head) {
	
	if (NULL == head) {
		return NONE_ELEMENT;
	}

	return head->count;
}
  • 获取线性表容量大小
    该函数的功能就是为了获取用户在初始化时申请的总空间个数。在初始化时,我们将用户设置的空间个数存储在表头的capacity里面,所以只需获取表头的capacity变量的值即可。
int getLinearCapacity(const LINEAR *head) {

	if (NULL == head) {
		return NONE_ELEMENT;
	}

	return head->capacity;
}
  • 插入元素
    将元素插入到指定下标的位置里。同样的,刚开始先对传入参数的合法性进行判断,我相信如果你已经到了研究数据结构的这个层次,那么插入过程的实现代码肯定可以理解。切记,插入完成后一定要对count进行自增。
boolean insertLinearElement(LINEAR *head, const int index, const USER_TYPE data) {
	int i;

	if (NULL == head || isLinearFull(head) || index < 0 || index > head->count) {
		return FALSE;
	}

	for (i = head->count; i > index; i--) {
		head->data[i] = head->data[i - 1];
	}

	head->data[i] = data;
	++head->count;

	return TRUE;
}
  • 追加元素
    追加元素其实插入的一种特殊情况,所以我们只需将插入函数的功能拿来使用即可。相信大家看到这里已经完全可以体会到代码简洁性了,代码简洁并且功能强大。这仅仅是大微易码“铁血教主”能力的冰山一角。
boolean appendLinearElement(LINEAR *head, const USER_TYPE data) {
	return head != NULL && insertLinearElement(head, head->count, data);
}
  • 获取对应下标的元素
    这段代码虽然不长,但是其中的思想却是很厉害的。相信认真阅读的小伙伴已经发现了。我们是为了获取对应下标的元素,但为什么返回值类型用的是boolean类型呢?线性表存储的数据类型需要根据用户的使用情况而定,可能是int,也可能是float,所以如果贸然的直接将查找到的值返回,很容易在主函数中出现问题,所以我们就借助一个中间变量data,通过在主函数中对data进行处理就很容易避免一些麻烦的出现。这就再一次的体现出了我的老师“铁血教主”编程经验是多么的丰富。
boolean getLinearElementAt(const LINEAR *head, const int index, USER_TYPE *data) {
	if (NULL == head || index < 0 || index >= head->count) {
		return FALSE;
	}

	*data = head->data[index];

	return TRUE;
}
  • 删除元素
    其实这个函数我最早是打算将其拆成分成三个独立的函数来写,分别是头删法、尾删法、普通删除这三种但是自己有点懒,所以就写在一起,读者可以自己尝试一下。
    删除的过程其实就是一个覆盖的过程,把要删除的元素,用其后面的元素进行覆盖即可。切记:删除后要给count进行自减
boolean removeLinearElement(LINEAR *head, const index) {
	int i;

	if (NULL == head || index < 0 || index >= head->count) {
		return FALSE;
	}

	if (index == (head->count - 1)) {
		--head->count;
		return TRUE;
	}

	for (i = index; i < (head->count - 1); i++) {
		head->data[i] = head->data[i+1];
	}

	--head->count;

	return TRUE;
}
  • 相等比较
    这个函数,我认为是最强大的。他使用了未来将要书写的功能函数。使用未来!!!这种写法是多么令人畏惧,但同时又心生敬畏。在形参里面它使用了函数指针。这个函数规定了相等比较的规则,具体如何判断相等,可以根据未来用户的需求而定(其实这也就是Java里面抽象方法的思想)。具体如何比较就看个人的规定。
int linearIndexOf(const LINEAR *head, USER_TYPE data, boolean (*eq)(USER_TYPE num1, USER_TYPE num2)) {
	int index;

	if (NULL == head) {
		return NOT_FIND;
	}

	for (index = 0; index < head->count; index++) {
		if (TRUE == eq(data, head->data[index])) {
			return index;
		}
	}

	return NOT_FIND;
}

三、一个小例子来简单使用一下线性表。

线性表其实和我们平时用的数组很类似,他们都是线性(所谓的线性,其实是指逻辑线性,并不一定是物理线性)存储结构。

#include <stdio.h>

//下面三个头文件是自己定义的,在库里面找不到。我将其放到结尾
#include "linear .h"
#include "UserType.h"
#include "mec.0.0.2.h"

int main() {
//声明一个空的线性表
	LINEAR *head = NULL;
//进行初始化
	initLinear(&head, 10);
//插入一个元素	
	insertLinearElement(head, 0, 0);
//追加元素
	appendLinearElement(head, 1);
	appendLinearElement(head, 2);
	appendLinearElement(head, 3);
	removeLinearElement(head, 2);
//输出结果,这个功能要根据用户的使用情况来写。
	print(head);
//销毁线性表
	destoryLinear(&head);
	
	return 0;
}

四、该数据结构中,所需要用到的头文件资源(自己写的头文件)

  • linear .h
#ifndef _MEC_LINEAR_H_
#define _MEC_LINEAR_H_

#include "UserType.h"
#include "mec.0.0.2.h"

typedef struct LINEAR {
	USER_TYPE *data;
	int capacity;
	int count;
}LINEAR;

#define NONE_ELEMENT -1


boolean initLinear(LINEAR **head, int capacity);
boolean destoryLinear(LINEAR **head);
boolean isLinearEmpty(const LINEAR *head);
boolean isLinearFull(const LINEAR *head);
int getLinearElementCount(const LINEAR *head);
int getLinearCapacity(const LINEAR *head);
boolean insertLinearElement(LINEAR *head, const int index, const USER_TYPE data);
boolean appendLinearElement(LINEAR *head, const USER_TYPE data);
boolean removeLinearElement(LINEAR *head, const index);
boolean getLinearElementAt(const LINEAR *head, const int index, USER_TYPE *data);
int linearIndexOf(const LINEAR *head, USER_TYPE data, boolean (*eq)(USER_TYPE num1, USER_TYPE num2));
void print(const LINEAR *head);

#endif
  • UserType.h
#ifndef _MEC_USERTYPE_H
#define _MEC_USERTYPE_H

// User can chenage the int to any type

typedef int USER_TYPE;

#endif
  • mec.0.0.2.h
#ifndef _MEC_H_
#define _MEC_H_

typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
#define NOT_FIND -1

#endif
;