线性表
线性表(linear list)是具有相同特性的数据元素的有限列表,在实际中广泛使用的数据元素,常见的线性表有顺序表、链表、栈、队列、字符串…
线性表逻辑结构上是线性结构,但是物理结构上不一定是连续的,线性表物理结构上存储时,通常用数组和链式结构的形式来存储。
顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表的实现
顺序表可分为以下两种:
1.静态版本:使用定长数组存储。
2.动态版本:使用动态开辟的数组存储。结构体的申明
静态版本
//静态版本 #define N 100 typedef struct SeqList { SLDataType a[N]; //定长数组存储 int size; //记录数组中有效个数 }SL;
动态版本
//动态版本 typedef int SLDataType; typedef struct SeqList { SLDataType* array; //指向动态开辟的数组 int size; //数组中有效数据的个数 int capacity; //空间容量的大小 }SL;
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大
了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。接口函数(动态版本)
初始化
void SeqListInit(SL* ps) { ps->a = NULL; ps->size = 0; ps->capacity = 0; }
增容
void SeqListCheckCapacity(SL* ps) { if (ps->size == ps->capacity) { printf("增容\n"); int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; // 如果一开始容量为空就先分配四个空间,以后满了就容量就翻倍 SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } else { ps->a = tmp; ps->capacity = newcapacity; } } }
头插
void SeqListPushFront(SL* ps, SLDataType x) { SeqListCheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[0] = x; ps->size++; }
图解(画得不太好看)
一个易错点:在往后移动数据时,不能从左边开始,这样会覆盖掉后面的数据,应该从数组最后一个元素开始向后移动尾插
void SeqListPushBack(SL* s1, SQDateType x) { assert(s1); CheckSeqList(s1); s1->a[s1->size] = x; s1->size++; }
头删
void SeqListPopFront(SL* s1) { assert(s1); int i = 0; for (i = 0; i < s1->size-1; i++) { s1->a[i] = s1->a[i + 1]; } s1->size--; }
图解
一个易错点:在移动元素的时候,不可以从数组最后面开始往前移动,这样会使数组其他数据也被覆盖尾删
void SeqListPopBack(SL* s1) { assert(s1); s1->size--; }
从指定位置插入
void SeqListInsert(SL* s1, int pos, SQDateType x) { assert(s1); CheckSeqList(s1); int end = s1->size; if(pos > end) { return; } while (end > pos) { s1->a[end] = s1->a[end - 1]; end--; } s1->a[pos] = x; s1->size++; }
图解
从指定位置删除
void SeqListErase(SL* s1, int pos) { assert(s1); while (s1->size-1 > pos) { s1->a[pos] = s1->a[pos + 1]; pos++; } s1->size--; }
图解
易错点:移动元素时,不能从数组末尾开始移动,这样会使pos到末尾的元素都被覆盖,应该从pos后一位往前覆盖。查找
int SeqListFind(SL* s1, SQDateType x) { assert(s1); int i = 0; for (i = 0; i < s1->size; i++) { if (s1->a[i] == x) { return i; } } return -1; }
修改
void SeqListModify(SL* s1, int pos, SQDateType x) { assert(s1); //判断 if (s1->size == 0) { printf("有效个数为空\n"); return; } if (pos > s1->size) { printf("要修改位置超出有效个数\n"); return; } //修改 s1->a[pos] = x; }
展示函数
void PrintSeqList(SL* s1) { assert(s1); int i = 0; for (i = 0; i < s1->size; i++) { printf("%d\n", s1->a[i]); } }
内存销毁函数
void SeqListDestory(SL* s1) { assert(s1); free(s1->a); s1->a = NULL; s1->size = s1->capacity = 0; }
总结
优点
1.尾插和尾删时间复杂度为O(1)
2.支持随机访问缺点
1.空间不够需要增容,需要消耗一些性能,且存在一定的空间浪费(增容后。
2.头部和从指定位置插入删除的效率比较低,时间复杂度为O(n)。