1 线性表基础
线性表是n(n >= 0)个同类型的数据元素的有限序列,逻辑结构为:L = (a1,a2,…,ai-1,ai,…,an)。
其中,L是线性表的名称,ai是组成该线性表的数据元素,i是线性表的元素序号,n是线性表的表长,当n为0时,该线性表为空表。
a1为线性表的头元素,an为尾元素。元素间的线性关系体现在相邻元素之间的顺序关系上,例如,ai-1是ai的直接前驱,ai+1是ai的直接后继。除头元素外,线性表所有元素有且只有一个直接前驱。同理,除了尾元素外,线性表的所有元素有且只有一个直接后继。
2 线性表之顺序表
线性表的顺序存储结构指的是用一组连续的内存单元依次存储线性表中的各个数据元素,采用这种形式的线性表称为顺序表。
顺序表的精华在于可以通过计算公式快速得到ai元素的存储地址,从而实现时间复杂度为O(1)的元素的快速存取,记loc(ai)为ai的存储地址,有loc(ai) = loc(a1) + (i -1) x d。其中d为一个元素所占的存储单元数。
3 顺序表的代码实现
3.1 定义顺序表结构体
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#define LIST_INIT_SIZE 100 //经验值。。。
#define INCREAMENT 10
//指明结构类型
typedef int ElemType;
//定义顺序表结构体
typedef struct SqList
{
ElemType* elem;
int length;
int list_size;
}SqList, *Ptr;
//起个外号。。。
typedef Ptr SqListPtr;
3.2 定义操作状态的枚举类型Status
//Status是表示操作状态的枚举类型
typedef enum Status
{
success,fail,fatal,range_error
}Status;
3.3 实现线性表的基本操作
//初始化线性表,时间复杂度O(1)
Status List_Init(SqListPtr L)
{
Status s = success;
L->list_size = LIST_INIT_SIZE;
L->length = 0;
L->elem = (ElemType*)malloc(sizeof(ElemType) * L->list_size);
if (L->elem == NULL)
s = fatal;
return s;
}
//按位置查找,O(1)
Status List_Retrival(SqListPtr L, int pos, ElemType* elem)
{
Status s = range_error; //找不到,越界错误
if (L) //判断线性表是否存在
{
if ((pos - 1) >= 0 && (pos - 1) < L->length) //C语言实际存储,第一个可用位置是0,所以-1.。。。
{
*elem = L->elem[pos - 1];
s = success;
}
}
else
s = fatal; //线性表不存在,致命错误
return s;
}
//按值查找, O(n)
Status List_Locate(SqListPtr L, ElemType elem, int* pos)
{
Status s = range_error;
if (L)
{
for (int i = 0; i < L->length; i++)
{
if (elem == L->elem[i])
{
*pos = i + 1;
s = success;
break;
}
}
}
else
s = fatal;
return s;
}
//插入操作,O(n)
Status List_Insert(SqListPtr L, int pos, ElemType elem)
{
Status s = range_error;
if ((pos - 1) >= 0 && (pos - 1) < L->length) //判断插入位置是否有效
{
if (L && L->length < L->list_size) //判断是否有有剩余空间
{
for (int i = L->length - 1; i >= (pos - 1); i--) //将插入位置后的元素往后挪
{
L->elem[i + 1] = L->elem[i];
}
L->elem[pos - 1] = elem; //将elem插入到pos-1位置上
L->length++; //顺序表长度+1
s = success;
}
}
else
s = fail; //插入位置无效,插入失败
return s;
}
//删除操作,O(n)
Status List_Delete(SqListPtr L, int pos)
{
Status s = range_error;
if ((pos - 1) >= 0 && (pos - 1) < L->length) //判断pos是否有效
{
if (L && L->length > 0)
{
for (int i = pos; i < L->length; i++)
{
L->elem[i - 1] = L->elem[i]; //数据往前挪
L->length--;
s = success;
}
}
}
else
s = fail;
return s;
}
//销毁操作
void List_Destroy(SqListPtr L)
{
if (L->elem) //当线性表的elem不为NULL时才能进行销毁操作
{
free(L->elem);
L->elem = NULL;
}
L->length = 0;
}
//清空操作
void List_Clear(SqListPtr L)
{
L->length = 0;
}
//判空操作
bool List_Empty(SqListPtr L)
{
return L->length == 0;
}
//求直接前驱操作,O(1)
Status List_Prior(SqListPtr L, int pos, ElemType* elem)
{
Status s = range_error;
if (L)
{
if ((pos - 1) > 0 && (pos - 1) < L->length) //头元素没有直接前驱
{
*elem = L->elem[(pos - 1) - 1];
s = success;
}
}
else
s = fatal;
return s;
}
//求直接后继操作,O(1)
Status List_Next(SqListPtr L, int pos, ElemType* elem)
{
Status s = range_error;
if (L)
{
if ((pos - 1) >= 0 && (pos - 1) < L->length - 1) //尾元素没有直接后继
{
*elem = L->elem[pos];
s = success;
}
}
else
s = fatal;
return s;
}
//求顺序表长度操作
int List_Size(SqListPtr L)
{
return L->length;
}
4 顺序表的优缺点总结
优点:
(1)实现简单。
(2)不需要为表示结点之间的逻辑关系而增加额外的存储开销。
(3)快速存取元素(O(1))。
缺点:
(1)插入、删除操作效率低(O(n)),平均需要移动一半的元素。
(2)需要预先分配空间,倘若预先分配的空间不合理,容易发生空间的浪费或者数据溢出。