线性结构
线性结构的特点是:在数据元素的非空有限集中,(1)存在唯一一个被称作“第一个”的数据元素;(2)存在唯一一个被称作“最后一个”的数据元素;(3)除第一个之外,集合中的每个元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。
线性表
线性表是最常用且最简单的一种数据结构。一个线性表是n个数据元素的有限序列。线性表中元素的个数n(n≥0)定义为线性表的长度,n=0时称为空表。
线性表是一个相当灵活的数据结构,它的长度可以根据需要增长或缩短,即对线性表的数据元素不仅可以进行访问,还可以进行插入和删除等。
线性表的顺序表示和实现
线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素。
global.h
#ifndef GLOBAL_H
#define GLOBAL_H
#include<stdio.h>
#define OK 1 // 通过
#define ERROR 0 // 错误
#define OVERFLOW -2 // 堆栈上溢
typedef int Status; // 函数类型,其值为状态码
typedef int ElemType; // 抽象数据类型
#endif // !GLOBAL_H
存储结构
由于线性表的长度可变,且所需最大存储空间随问题不同而不同,在C语言中可用动态分配的一维数组,如下描述。
#define LIST_INIT_SIZE 10 // 线性表存储空间初始分配量
#define LISTINCREMRNT 10 // 线性表存储空间的分配增量
// 线性表的动态分配顺序存储结构
typedef struct SqList
{
ElemType* elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量
}SqList;
上述定义中,数组指针elem指示线性表的基地址,length指示线性表的当前长度。顺序表的初始化操作就是为顺序表分配一个预定义大小的数组空间,并将线性表的当前长度设为“0”。listsize指示顺序表当前分配的存储空间大小,一旦因插入元素而空间不足时,可进行再分配,即增加一个大小为存储LISTINCREMRNT个数据元素的空间。
Status InitList_Sq(SqList* L)
{
// 构造一个空的线性表L
L->elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!(L->elem))
exit(OVERFLOW);// 存储分配失败
L->length = 0;// 空表长度为0
L->listsize = LIST_INIT_SIZE;// 初始存储容量
return OK;
}// InitList_Sq
插入和删除
一般情况下,在第i(1≤i≤n)个元素之前插入一个元素时,需将第n至第i(共n-i+1)个元素向后移动一个位置。
// 在顺序线性表L中第i个位置之前插入新的元素e
Status ListInsert_Sq(SqList* L, int i, ElemType e)
{
// 在顺序线性表L中第i个位置之前插入新的元素e
// i的合法值为1<=i<=ListLength_Sq(L)+1
if (i<1 || i>L->length + 1)
return ERROR;// i值不合法
if (L->length >= L->listsize) {// 当前存储空间已满,增加分配
ElemType* newbase = (ElemType*)
realloc(L->elem, (L->listsize + LISTINCREMRNT) * sizeof(ElemType));
if (!newbase)// 存储分配失败
exit(OVERFLOW);
L->elem = newbase;// 新基址
L->listsize += LISTINCREMRNT;// 增加存储容量
}
ElemType* q = &(L->elem[i - 1]), * p = NULL;// q为插入位置
for (p = &(L->elem[L->length - 1]); p >= q; --p)
* (p + 1) = *p;// 插入位置及之后的元素后移
*q = e;// 插入e
++(L->length);// 表长增加1
return OK;
}// ListInsert_Sq
一般情况下,删除第i(1≤i≤n)个元素时需将从第i+1至第n(共n-i)个元素依次向前移动一个位置。
// 在顺序线性表L中删除第i个元素,并用指针e返回
Status ListDelete_Sq(SqList* L, int i, ElemType* e)
{
// 在顺序线性表L中删除第i个元素,并用指针e返回
// i的合法值为1<=i<=ListLength_Sq(L)
if (i<1 || i>L->length)
return ERROR;// i值不合法
ElemType* p = &(L->elem[i - 1]), * q = NULL;// p为被删除元素的位置
*e = *p;// 被删除元素的值赋给*e
q = L->elem + L->length - 1;// 表尾元素的位置
for (++p; p <= q; ++p)
* (p - 1) = *p;// 被删除元素之前的元素前移
--(L->length);// 表长减一
return OK;
}// ListDelete_Sq
测试
测试代码:
int main() {
SqList* L = (SqList*)malloc(sizeof(SqList));
InitList_Sq(L);
int i;
for (i = 0; i < 15; i++) {
ListInsert_Sq(L, 1, i);
}
PrintList_Sq(L);
ElemType* e = (ElemType*)malloc(sizeof(ElemType));
ListDelete_Sq(L, 5, e);
printf("%d is del\n", *e);
PrintList_Sq(L);
return 0;
}
结果如下:
算法分析
在顺序存储结构的线性表中某个位置插入或者删除一个数据元素时,其时间主要耗费在移动元素上(换句话说,移动元素的操作为预估算法时间复杂度的基本操作),而移动元素的个数取决于插入或删除的元素的位置。
假设pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个元素时所需移动元素次数的期望(平均次数)为:
假设qi是删除第i个元素的概率,则在长度为n的线性表中删除一个元素时所需移动元素次数的期望(平均次数)为:
不失一般性,假定在线性表的任何位置上插入或删除元素都是等概率的,即
由上面两式可见,在顺序存储结构的线性表中插入或者删除一个数据元素,平均移动表中一半元素。若表长为n,则算法ListInsert_Sq和ListDelete_Sq的时间复杂度为O(n)。