一、线性表
1.定义: 由n(n≥0)个数据特性相同的元素构成的有限序列,称为线性表。
2.线性表的特点:
线性表是n个数据元素的有限序列,其中n个数据是相同数据类型的。
线性表中元素的个数n(n≥0)定义为线性表的长度,当n=0时称之为空表。
对于非空的线性表或线性结构,其特点是:
(1)存在唯一的一个被称作“第一个”的数据元素;
(2)存在唯一的一个被称作“最后一个”的数据元素;
(3)除第一个元素外,结构中的每个数据元素均只有一个前驱;
(4)除最后一个元素外,结构中的每个数据元素均只有一个后继。
3.线性表 ADT
ADT List
{数据对象:D={ailai∈ ElemSet,i=1,2, ... ,n,n≥0}数据关系:S={<ai-1,ai>|ai-1,ai ∈ D, i=2, ... ,n}基本操作:InitList (&L)操作结果:构造一个空的线性表L。DestroyList (&L)初始条件:线性表L已存在。操作结果:销毁线性表L。
} ADT List
二、顺序表
1.定义: 用一组连续的内存单元依次存储线性表的各个元素,也就是说,逻辑上相邻的元素,实际的物理存储空间也是连续的。
2.基本操作:
(1)顺序表-初始化
#define MAXSIZE 100
typedef int ElemType;typedef struct
{ElemType data [MAXSIZE];int length;
} SeqList;void initList (SeqList *L)
{L->length = 0;
}
(2)顺序表-在尾部添加元素
int appendElem(SeqList *L, ElemType e)
{if (L->length>=MAXSIZE){printf("顺序表已满\n");return 0;}L->data[L->length] = e;L->length++;return 1;
}
(3)顺序表-插入元素
int insertElem(SeqList *L, int pos, ElemType e)
{if (pos <= L->length){for (int i = L->length-1; i >= pos-1; i -- ){L->data[i+1] = L->data[i];}L->data[pos-1] = e;L->length++;}return 1;
}
(4)顺序表-删除元素
int deleteElem(SeqList *L, int pos, ElemType *e)
{*e = L->data [pos-1] ;if (pos < L->length){for (int i = pos; i < L->length; i++){L->data[i-1] = L->data[i];}}L->length --;return 1;
}
(5)顺序表-遍历
void listElem (SeqList *L)
{for (int i = 0; i < L->length; i++){printf("%d ", L->data[i]) ;}printf("\n");
}
(6)顺序表-查找
int findElem(SeqList *L, ElemType e)
{for (int i = 0; i < L->length; i++){if (L->data[i] == e){return i + 1;}}return 0;
}
(7)顺序表-动态分配内存地址初始化
typedef struct
{ElemType *data;int length;
} SeqList;SeqList* initList ()
{SeqList *L = (SeqList*)malloc(sizeof(SeqList));L->data = (ElemType*) malloc(sizeof (ElemType)*MAXSIZE);L->length = 0;return L;
}
3.缺点:
顺序表作为基于连续内存空间的数据结构,不管是静态还是动态实现方式,都存在插入删除效率低、扩容有额外开销等缺点。
(1)插入与删除操作效率低:顺序表要保持元素的连续存储特性,当在表头或表中间位置执行插入操作时,得把目标位置及之后的所有元素依次后移,才能腾出空间;删除这类位置的元素时,又要把删除位置之后的元素依次前移来填补空缺。这些操作都要移动大量元素,时间复杂度为 O (n),数据量越大,操作耗时越明显。例如在有 1000 个元素的顺序表第 10 位插入元素,就需要移动后续 991 个元素。
(2)扩容操作存在额外开销:静态顺序表的大小固定,一旦元素数量达到预设上限就无法继续插入;动态顺序表虽可扩容,但过程繁琐。通常要先申请一块更大的新内存,接着将旧空间中的所有数据复制到新空间,最后释放旧空间。而且若扩容策略不合理,比如每次只扩容 1 个单位,插入 n 个元素可能产生 O (n²) 的时间成本;即便采用翻倍扩容的推荐策略,扩容时的数据拷贝操作依旧会带来短暂的性能损耗。
(3)空间利用率不佳:静态顺序表容易出现空间浪费或不足的问题,预设空间过大,多余部分会闲置;预设过小又会提前达到存储上限,无法新增元素。动态顺序表也有缺陷,扩容后若后续删除了大量元素,多出来的空闲空间往往不能自动释放,只能手动处理(部分编程语言),这就造成了内存的无效占用。比如动态顺序表扩容到 200 个存储单元后,若元素减少到 50 个,剩余 150 个单元的空间就被浪费了。
(4)依赖连续内存空间:顺序表需要一块完整连续的内存区域存储数据。当数据量较大时,系统中可能难以找到适配的连续内存块,即便此时系统整体剩余内存总量足够,也无法满足顺序表的存储需求,从而限制了其存储大规模数据的灵活性。
(5)易引发内存相关问题:在 C/C++ 等需要手动管理内存的语言中,动态顺序表若使用后忘记释放内存,会造成内存泄漏;同时扩容后旧空间若释放不当,还可能产生内存碎片。这些问题不仅浪费系统资源,还可能导致程序运行异常,增加了开发中的内存管理难度。