文章目录
1.线性表的本质
线性表是零个或多个数据元素的集合,其数据元素之间是有顺序的,个数是有限的,并且类型必须相同。
书上的定义:线性表是由n(n≥0)个数据特性相同的元素构成的有限序列。
线性表中,1)a0为第一个元素,只有一个后继;2)an为最后一个元素,只有一个前驱;3)其它元素ai,既有前驱,也有后继,线性表能够逐项访问和顺序存取。
2.线性表的相关操作
假设我们要实现一个图书信息管理系统,每种图书仅包括三部分信息:ISBN、书名和价格,那么这个系统应具备以下功能:
(1)查找:根据指定的ISBN或书名查找图书,并返回该书在表中的位置;
(2)插入:插入一种新的图书及信息;
(3)删除:删除一种图书及信息;
(4)修改:根据指定的ISBN或书名,修改其价格;
(5)排序:将书按价格由高到低进行排序;
(6)计数:统计表中书的数量。
实现上述功能,我们应首先根据图书表的特点将其抽象为一个线性表,将每本书作为线性表中的一个元素,然后可以采用适当的存储结构来表示该线性表,在此基础上设计完成相应的算法。
3.线性表的常用操作
1.创建线性表;
2.销毁线性表;
3.清空线性表;
4.将元素插入线性表;
5.将元素从线性表中删除;
6.获取线性表中某个元素的位置;
7.获取线性表的长度。
线性表的顺序存储结构,是指用一段地址连续的存储单元依次存储线性表的数据元素。在C语言中,我们可以使用动态分配的一维数组来实现顺序存储结构,其表示为:
typedef unsigned long long int TSeqListNode; //在64位机中,该变量以为8字节
typedef struct _tag_SeqList
{
int capacity; //线性表最大容量
int length; //当前线性表的长度
TSeqListNode* node; //动态地申请指针的空间
} TSeqList;
接着便是线性表各操作的讲解与代码实现。
3.1创建线性表
在创建线性表之前,为了避免误操作,我们要对数据进行封装,其操作为:
typedef void SeqList; //对数据进行封装,避免误操作
typedef void SeqListNode;
首先,我们先令线性表为空,若最大容量大于等于0,我们就通过 malloc
来动态申请内存空间,接着我们就初始化容量,长度,数据指针的指向,最后返回创建的线性表。操作如下:
SeqList* SeqList_Create(int capacity)
{
TSeqList* ret = NULL;
if (capacity >= 0) //若容量大于0,则可以动态地申请内存空间
{
ret = (TSeqList*)malloc(sizeof(TSeqList) + sizeof(TSeqListNode) * capacity);
}
if (ret != NULL)
{
ret -> capacity = capacity;
ret -> length = 0;
ret -> node = (TSeqListNode*)(ret + 1); //指向头后面的位置,指向sizeof(TSeqListNode) * capacity
}
return ret;
}
在动态申请内存空间时,我们申请了线性表指针和数据指针,在初始化数据时,我们将指针指向数据,所以就加了1。
3.2销毁线性表
销毁线性表的操作很简单,直接free就行,操作如下:
void SeqList_Destroy(SeqList* list)
{
free(list);
}
3.3清空线性表
清空线性表就是让创建好的线性表回到初始状态,其操作为:
void SeqList_Clear(SeqList* list)
{
TSeqList* sList = (TSeqList*)list;
if (sList != NULL)
{
sList -> length = 0;
}
}
3.4获取线性表长度
获取线性表的长度的方法就是直接返回创建的线性表的长度,其代码如下:
int SeqList_Length(SeqList* list)
{
TSeqList* sList = (TSeqList*)list;
int ret = -1;
if (sList != NULL)
{
ret = sList -> length;
}
return ret;
}
3.5获取线性表的容量
获取线性表容量的方法和获取长度的方法一样,代码如下:
int SeqList_Capacity(SeqList* list)
{
TSeqList* sList = (TSeqList*)list;
int ret = -1;
if (sList != NULL) //判断线性表是否合法
{
ret = sList -> capacity;
}
return ret;
}
3.6插入元素
线性表的插入元素操作,从道理上来说,和以前小学上体育课站队一样,当排好一列后,如果体育老师想让一个同学插进来,则会先叫原来位置及之后的同学向后挪动一个位置,然后让那个同学插进来,如图所示。
当我们在操作线性表时,插入一个元素的步骤如下:
(1)判断线性表是否合法;
(2)判断插入位置是否合法;
(3)把最后一个元素到插入位置的元素后移一个位置;
(4)将新元素插入;
(5)线性表长度加1.
操作如下:
int SeqList_Insert(SeqList* list, SeqListNode* node, int pos)
{
TSeqList* sList = (TSeqList*)list;
int ret = (sList != NULL);
int i = 0;
ret = ret && (sList -> length + 1 <= sList -> capacity);
ret = ret && (pos >= 0);
if (ret)
{
if (pos >= sList -> length)
{
pos = sList -> length;
}
for (i = sList -> length; i > pos; i--)
{
sList -> node[i] = sList -> node[i - 1];
}
sList -> node[i] = (TSeqListNode)node;
sList -> length++;
}
return ret;
}
3.7获取元素
获取一个元素的操作步骤为:
(1)判断线性表是否合法;
(2)判断位置是否合法;
(3)直接通过数组下标的方式获取元素。
SeqListNode* SeqList_Get(SeqList* list, int pos)
{
TSeqList* sList = (TSeqList*)list;
SeqListNode* ret = NULL;
if ((sList != NULL) && (pos >= 0) && (pos < sList -> length))
{
ret = (SeqListNode*)(sList -> node[pos]);
}
return ret;
}
3.8删除元素
接着插入元素的例子,如果体育老师又想把那个同学调到另一列去,那么就会先叫他出列,然后让其他同学先后挪动一个位置,如图所示。
在操作线性表时,步骤为:
(1)判断线性表是否合法;
(2)判断删除位置是否合法;
(3)将元素取出;
(4)将删除位置后的元素分别向前移动一个位置;
(5)线性表长度减1。
SeqListNode* SeqList_Delete(SeqList* list, int pos)
{
TSeqList* sList = (TSeqList*)list;
SeqListNode* ret = SeqList_Get(list, pos);
int i = 0;
if (ret != NULL) //返回值不为空,则线性表是合法的
{
for (i = pos + 1; i < sList -> length; i++)
{
sList -> node[i - 1] = sList -> node[i];
}
sList -> length--;
}
return ret;
}
4.测试
在写完了上述操作后,开始对其进行测试。
#include <stdio.h>
#include <stdlib.h>
#include "SeqList.h"
int main(int argc, char *argv[])
{
SeqList* list = SeqList_Create(5);
int i = 0;
int j = 1;
int k = 2;
int x = 3;
int y = 4;
int z = 5;
int index = 0; //循环变量
SeqList_Insert(list, &i, 0);
SeqList_Insert(list, &j, 0);
SeqList_Insert(list, &k, 0);
SeqList_Insert(list, &x, 0);
SeqList_Insert(list, &y, 0);
SeqList_Insert(list, &z, 0);
for (index = 0; index < SeqList_Length(list); index++)
{
int* p = (int*)SeqList_Get(list, index);
printf("%d\n", *p);
}
printf("\n");
while(SeqList_Length(list) > 0)
{
int* p = (int*)SeqList_Delete(list, 0);
printf("%d\n", *p);
}
SeqList_Destroy(list);
return 0;
}
我们在创建了线性表后,先插入5个元素到线性表中,然后在观察每个位置的元素是什么,接着通过删除操作,观察每一次删除的元素是什么,结果如下:
5.分析与总结
写完顺序存储结构后,我们来分析一下算法的效率:
(1)创建:O(1)
(2)销毁:O(1)
(3)清空:O(1)
(4)获取长度:O(1)
(5)获取容量:O(1)
(6)插入元素:最好情况下为O(1),最坏情况下为O(n)
(7)获取元素:O(1)
(8)删除元素:最好情况下为O(1),最坏情况下为O(n)
由此,我们可以总结出顺序存储结构的优点为:
1.无需为线性表中的逻辑关系增加额外的空间;
2.可以快速地获取表中合法位置的元素;
缺点为:
1.插入和删除操作需要移动大量的元素;
2.当线性表长度变化较大时难以确定存储空间的容量。