1、顺序表的概念
顺序表是用一组地址连续的存储单元依次存储线性表数据元素的存储结构。顺序表具有随机存取的特点,因此通常使用数组来描述顺序表的存储结构。
2、顺序表的存储结构
2.1静态存储
使用静态分配内存空间时,数组大小一旦确定就无法改变。
//静态存储结构 #define N 10 //定义数组最大长度 typedef int SLDataType; typedef struct SeqList { SLDataType a[N]; //静态数组存放数据 int size; //当前存储的有效数据个数 }SL;
2.2动态存储
使用动态分配内存空间时,数据大小可以根据需求改变。
//动态存储结构 typedef int SLDataType; typedef struct SeqList { SLDataType* a; //在初始化顺序表的时候动态分配内存空间 int size; //当前存储的有效数据个数 int capacity; //顺序表的容量 }SL;
3、顺序表基本操作的实现
3.1初始化顺序表
为顺序表分配一个4*4B大小的数组空间,使a指向这段空间的基地址。将顺序表的容量设为4,有效元素个数设为0。
//初始化顺序表 void SLInit(SL* psl) { psl->a = (SLDataType*)malloc(sizeof(SLDataType) * 4); //使用malloc开辟4*4B大小的空间 if (psl->a == NULL) //判断是否开辟成功,如果开辟失败返回错误原因 { perror("malloc fail"); return; } psl->size = 0; //当前顺序表0个元素 psl->capacity = 4; //当前顺序表容量大小为4 }
3.2销毁顺序表
将指向顺序表的指针释放置空,再将顺序表的容量、有效元素个数全部置为0。
//销毁顺序表 void SLDestroy(SL* psl) { free(psl->a); psl->a = NULL; psl->size = 0; psl->capacity = 0; }
3.3打印顺序表
与打印数组元素方式相同。
//打印顺序表 void SLPrint(SL* psl) { for (int i = 0; i < psl->size; i++) { printf("%d ", psl->a[i]); } printf("\n"); }
3.4尾插法
在顺序表尾部插入数据,直接插入即可。
//尾插法 void SLPushBack(SL* psl, SLDataType x) { psl->a[psl->size] = x; psl->size++; }
上面代码似乎很合理,但是当我们插入超过4个以上数据,程序就会报错。
因为在初始化顺序表的时候只申请了4个SLDataType大小的空间,当插入第5个数据时,顺序表容量不够,就会出现错误。所以在插入之前需要检查一下顺序表的容量,如果顺序表容量不够,则扩容,扩大到原来顺序表容量的二倍。检查顺序表容量的代码如下:
//检查顺序表容量 void SLCheckCapacity(SL* psl) { if (psl->size == psl->capacity) { SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * psl->capacity * 2); if (tmp == NULL) //如果申请空间失败,返回失败原因 { perror("realloc fail"); return; } psl->a = tmp; psl->capacity *= 2; } }
优化后的尾插法代码如下:
//尾插法 void SLPushBack(SL* psl, SLDataType x) { SLCheckCapacity(psl); psl->a[psl->size] = x; psl->size++; }
使用尾插法插入数据,运行结果如下:
3.5头插法
在顺序表头部插入数据,在插入之前同样需要检查顺序表的容量,而且需要将表中原有的数据后移一个位置。注意从最后一个数据开始移动。
//头插法 void SLPushFront(SL* psl, SLDataType x) { SLCheckCapacity(psl); int end = psl->size - 1; //此时end为顺序表中最后一个元素的下标 while (end >= 0) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[0] = x; psl->size++; }
使用头插法插入数据,运行结果如下:
3.6 尾删法
删除顺序表中最后一个元素,可以把最后一个元素置为0,然后顺序表有效元素个数减1;也可以直接有效元素个数减1。
//尾删法 void SLPopBack(SL* psl) { psl->a[psl->size - 1] = 0; psl->size--; }
但这种写法有隐患。当删除次数超过顺序表中有效元素个数,此时再插入数据就会出现错误。如图,顺序表中有5个元素,但删除了6次,当再插入3个元素时,只成功插入2个元素。
所以在每次删除元素的时候要判断顺序表是否已经为空,若为空则不能继续删除。
//尾删法 void SLPopBack(SL* psl) { //暴力检查,不满足条件直接报错 assert(psl->size > 0); //温柔检查,不满足条件直接返回 if (psl->size == 0) return; psl->a[psl->size - 1] = 0; psl->size--; }
运行结果如下:
3.7头删法
删除顺序表中第一个元素,删除之前需要判断顺序表是否为空。若不为空,第2个元素覆盖第1个元素,第3个元素覆盖第2个元素……依次类推,最后将有效元素个数减1。
//头删法 void SLPopFront(SL* psl) { assert(psl->size > 0); int start = 0; while (start < psl->size - 1) { psl->a[start] = psl->a[start + 1]; start++; } psl->size--; }
运行结果如下,当顺序表为空删除时会终止程序。
3.8顺序表的插入
在顺序表的指定位置插入指定数据。首先找到指定位置,然后把指定位置及后面的数据全部后移一位,最后将指定数据放入指定位置,有效数据元素个数加1。注意,插入数据之前需要检查顺序表的容量,如果顺序表已经满了需要扩容后再插入数据。
//顺序表的插入 void SLInsert(SL* psl, int pos, SLDataType x) { assert(0 <= pos && pos <= psl->size); //判断插入位置是否合法 SLCheckCapacity(psl); int end = psl->size - 1; while (end >= pos) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[pos] = x; psl->size++; }
运行结果如下:
上面顺序表的尾插法和头插法其实是该方法的特例,可以套用这个函数来实现尾插法和头插法。
//尾插法 void SLPushBack(SL* psl, SLDataType x) { SLInsert(psl, psl->size, x); } //头插法 void SLPushFront(SL* psl, SLDataType x) { SLInsert(psl, 0, x); }
3.9顺序表的删除
在顺序表的指定位置删除指定数据。首先找到指定位置,然后把指定位置及后面的数据全部前移一位,有效数据元素个数减1。注意,删除数据时需要判断顺序表是否为空。
//顺序表的删除 void SLErase(SL* psl, int pos) { assert(psl->size > 0); assert(0 <= pos && pos <= psl->size); int start = pos; while (start < psl->size - 1) { psl->a[start] = psl->a[start + 1]; start++; } psl->size--; }
运行结果如下:
上面顺序表的尾删法和头删法其实是该方法的特例,可以套用这个函数来实现尾删法和头删法。
//尾删法 void SLPopBack(SL* psl) { SLErase(psl, psl->size - 1); } //头删法 void SLPopFront(SL* psl) { SLErase(psl, 0); }
3.10顺序表的查找
按值查找,如果找到,则返回该元素的下标;如果未找到,则返回-1。
//顺序表的查找 int SLFind(SL* psl, SLDataType x) { for (int i = 0; i < psl->size; i++) { if (psl->a[i] == x) return i; } return - 1; }
运行结果如下:
3.11顺序表的修改
修改指定位置元素的值,直接修改即可。
//顺序表的修改 void SLModify(SL* psl, int pos, SLDataType x) { assert(0 <= pos && pos <= psl->size); psl->a[pos - 1] = x; }
运行结果如下: