目录
一. 线性表
二.顺序表
1.静态顺序表与动态顺序表
2.动态顺序表的接口实现
2.1顺序表初始化
2.2判断是否需要扩容
2.3顺序表指定位置插入
2.4顺序表头插
2.5顺序表尾插
2.6顺序表指定位置删除
2.7 顺序表头删
2.8 顺序表尾删
2.9顺序表查找
2.10 顺序表修改
2.11 顺序表销毁
2.12顺序表打印
3. 顺序表的缺点
三.代码仓库
一. 线性表
1. 线性表(linear list)是n个具有相同特性的数据元素的有限序列。
2. 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
3. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二.顺序表
1.静态顺序表与动态顺序表
1. 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
2. 顺序表一般可以分为:
静态顺序表:使用定长数组存储元素。
#define N 10 typedef int SLDataType; typedef struct SeqList {SLDataType array[N]; //定长数组size_t size; //有效数据个数 }SeqList;
动态顺序表:使用动态开辟的数组存储。
typedef int SLDataType; typedef struct SeqList {SLDataType* array; //指向动态开辟的数组size_t size; //有效数据个数size_t capacity; //空间容量 }SeqList;
2.动态顺序表的接口实现
1. 静态顺序表只适用于确定知道需要存多少数据的场景。
2. 静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。
3. 所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
2.1顺序表初始化
将结构的每个成员初始化
void SeqListInit(SeqList* psl, size_t capacity) {assert(psl);psl->array = (SLDataType*)malloc(sizeof(SeqList) * capacity);if (psl->array == NULL){perror("malloc");return;}psl->size = 0;psl->capacity = capacity; }
2.2判断是否需要扩容
1.判断有效个数是否和容量相等
2.使用realloc进行扩容
void CheckCapacity(SeqList* psl) {assert(psl);if (psl->size == psl->capacity){SLDataType* tmp = (SLDataType*)realloc(psl->array, sizeof(SLDataType) * psl->capacity * 2);if (tmp == NULL){perror(realloc);return;}psl->array = tmp;psl->capacity *= 2;} }
2.3顺序表指定位置插入
1. 对pos进行范围判断。
1. 判断是否需要扩容。
2. 将pos后面的元素往后挪一位。
3. pos位置插入值。
4. 有效个数加一。
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) {assert(psl);assert(pos >= 0 && pos <= psl->size);CheckCapacity(psl);size_t cur = psl->size;while (cur != pos){psl->array[cur] = psl->array[cur - 1];cur--;}psl->array[pos] = x;psl->size++; }
2.4顺序表头插
方法1:
1. 判断是否需要扩容。
2. 将所有元素都往后移一位,留出第一个位置插入(如果头插之前没有元素,则直接插入即可)。
3. 有效个数加一。
void SeqListPushFront(SeqList* psl, SLDataType x) {assert(psl);CheckCapacity(psl);int tmp = psl->size;while (tmp > 0) //当本身为空时,就不走这个循环{psl->array[tmp] = psl->array[tmp - 1];tmp--;}psl->array[0] = x;psl->size++; }
方法2:复用SeqListInsert
void SeqListPushFront(SeqList* psl, SLDataType x) {SeqListInsert(psl, 0, x); }
2.5顺序表尾插
1. 插入前判断是否需要增容。
2. size作为下标正好是最后一个元素的后一位。
void SeqListPushBack(SeqList* psl, SLDataType x) {assert(psl);CheckCapacity(psl);psl->array[psl->size++] = x; }
方法2:复用SeqListInsert
void SeqListPushBack(SeqList* psl, SLDataType x) {assert(psl);SeqListInsert(psl, psl->size, x); }
2.6顺序表指定位置删除
1. 判断pos是否合法。
2. 将pos后面的元素往前覆盖一位。
3. 有效个数减一。
void SeqListErase(SeqList* psl, size_t pos) {assert(psl);assert(pos >= 0 && pos < psl->size);int cur = pos;while (cur < psl->size - 1){psl->array[cur] = psl->array[cur + 1];cur++;}psl->size--; }
2.7 顺序表头删
方法1:
1. 判断有效个数是否为0,为0不用删。
2. 将后面的与元素往前覆盖一位。
3. 有效元素个数减一。
void SeqListPopFront(SeqList* psl) {assert(psl);assert(psl->size);size_t cur = 0;while (cur < psl->size - 1){psl->array[cur] = psl->array[cur + 1];cur++;}psl->size--; }
方法2:复用SeqListErase
void SeqListPopFront(SeqList* psl) {assert(psl);SeqListErase(psl, 0); }
2.8 顺序表尾删
方法1:
1. 判断size,size如果为0就不能删。
2. 删除尾部元素直接将size减减即可。
void SeqListPopBack(SeqList* psl) {assert(psl);assert(psl->size);psl->size--; }
方法2:复用SeqListErase
void SeqListPopBack(SeqList* psl) {assert(psl);SeqListErase(psl, psl->size - 1); }
2.9顺序表查找
1. 遍历一遍进行比较。
2. 找到返回下标,否则返回-1。
int SeqListFind(SeqList* psl, SLDataType x) {assert(psl);for (size_t i = 0; i < psl->size; i++){if (psl->array[i] == x) return i;}return -1; }
2.10 顺序表修改
1. 对pos进行范围判断。
2. 将pos作为下标进行修改。
void SeqListModify(SeqList* psl, size_t pos, SLDataType x) {assert(psl);assert(pos >= 0 && pos < psl->size);psl->array[pos] = x; }
2.11 顺序表销毁
1. 销毁时需要释放空间,指针置空。
2. 有效个数和容量置0。
void SeqListDestory(SeqList* psl) {assert(psl);free(psl->array);psl->array = NULL;psl->capacity = 0;psl->size = 0; }
2.12顺序表打印
遍历数字打印
void SeqListPrint(SeqList* psl) {assert(psl);for (int i = 0; i < psl->size; i++) printf("%d ", psl->array[i]);printf("\n"); }
3. 顺序表的缺点
1. 中间/头部的插入删除,时间复杂度为O(N)。
2. 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
三.代码仓库
gitee/jimmywang16/learn_1