一、顺序表介绍
- 概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。
- 动态顺序表:使用动态开辟的数组存储。
- 要求:
顺序表要求存储的数据是从0开始,依次连续存储,中间不能有空的。
- 接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
- 注:
本文将创建SeqList.h、SeqList.c、Test.c三个文件夹,分别用于声明,定义,具体实现
正文开始:
二、准备工作
1、创建顺序表
- SeqList.h文件:
//创建顺序表 typedef int SLDataType; //确保以后想存其它类型的时候方便改动,本文以存放整型数据为例 typedef struct SeqList { SLDataType* a; //动态开辟数组 int size; //存储顺序表中有效数据个数 int capacity;//存储空间个数-->记录最大容量 }SeqList;
2、初始化顺序表
- SeqList.h文件:
//初始化顺序表 void SeqListInit(SeqList* psl);
- SeqList.c文件:
//初始化通讯录 void SeqListInit(SeqList* psl) { assert(psl); psl->a = NULL; psl->size = 0; psl->capacity = 0; }
3、检测是否需要扩容
- SeqList.h文件:
//检测是否需要扩容 void SeqListCheckCapacity(SeqList* psl);
- SeqList.c文件:
//检测是否需要扩容 void SeqListCheckCapacity(SeqList* psl) { assert(psl); //如果满了,就要扩容 if (psl->size == psl->capacity) { size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2; //防止原始capacity的容量本身为0,导致后续扩容仍为0 SLDataType* tmp = realloc(psl->a, sizeof(SLDataType) * newCapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } else { psl->a = tmp; psl->capacity = (int)newCapacity; } } }
- 注意:
当realloc的指针为空时,此时realloc就相当于malloc
4、销毁顺序表
- SeqList.h文件:
//销毁顺序表 void SeqListDestroy(SeqList* psl);
- SeqList.c文件:
//销毁顺序表 void SeqListDestroy(SeqList* psl) { assert(psl); free(psl->a); psl->a = NULL; psl->capacity = psl->size = 0; }
5、打印顺序表
- 思想:
只需for循环依次打印即可
- SeqList.h文件:
//打印顺序表 void SeqListPrint(SeqList* psl);
- SeqList.c文件:
//打印顺序表 void SeqListPrint(SeqList* psl) { assert(psl); for (int i = 0; i < psl->size; i++) { printf("%d ",psl->a[i]); } printf("\n"); }
三、四大功能
1、增加数据
头插
- 思想:
比尾插稍微复杂一点,头插就是把最后一个数字往后挪,再把前一个数字往后挪,以此类推,直到把第一个位置给它空出来,但是前提是得确保空间足够,不够就得扩容
- SeqList.h文件:
//头插 void SeqListPushFront(SeqList* psl, SLDataType x);
- SeqList.c文件:
//头插 void SeqListPushFront(SeqList* psl, SLDataType x) { assert(psl); SeqListCheckCapacity(psl); //检测容量 int end = psl->size - 1; while (end >= 0) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[0] = x; psl->size++; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //一定要加上&,因为形参的改变不会影响实参,要传地址 //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //头插2个数字 SeqListPushFront(&s, 0); SeqListPushFront(&s, -1); SeqListPrint(&s); //头插2次后打印 return 0; }
- 效果如下:
尾插
- 思想:
其实数组a的下标size就是最后一个数据的下一个位置,尾插只需要在ps->size处插入一个数据即可,不过前提是要先检查容量是否满
- SeqList.h文件:
//尾插 void SeqListPushBack(SeqList* psl, SLDataType x);
- SeqList.c文件:
//尾插 void SeqListPushBack(SeqList* psl, SLDataType x) { assert(psl); SeqListCheckCapacity(psl); //检测容量 psl->a[psl->size] = x; psl->size++; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //一定要加上&,因为形参的改变不会影响实参,要传地址 //尾插5个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPushBack(&s, 5); SeqListPrint(&s); //打印 return 0; }
- 效果如下:
指定下标插入
- 思想:
其实指定位置插入的思想同上文的尾插是很相似的,但是要确保这个指定插入的位置在有效数据size范围内,不然就会出现越界,就不是顺序表了,
- SeqList.h文件:
//在pos位置插入x void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
- SeqList.c文件:
//在pos位置插入x void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) { assert(psl); //暴力检查 /*assert(pos <= psl->size && psl);*/ //温和检查 if (pos > psl->size) { printf("pos 越界:%d\n", (int)pos); return; } SeqListCheckCapacity(psl); //检测容量 int end = psl->size - 1; while (end >= (int)pos) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[pos] = x; psl->size++; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //指定下标插入2个数字 SeqListInsert(&s, 10, 100); SeqListInsert(&s, 1, 20); SeqListPrint(&s); //插入成功后打印 SeqListDestroy(&s); return 0; }
- 效果如下:
- 注意:
当我们实现完指定下标插入时,我们发现当pos=size时,实现的就是尾插
//尾插 void SeqListPushBack(SeqList* psl, SLDataType x) { assert(psl); //法一: /*SeqListCheckCapacity(psl); //检测容量 psl->a[psl->size] = x; psl->size++;*/ //法二: SeqListInsert(psl, psl->size, x); }
类似的,当pos=0时,实现的就是头插,如下:
//头插 void SeqListPushFront(SeqList* psl, SLDataType x) { assert(psl); //法一: /*SeqListCheckCapacity(psl); //检测容量 int end = psl->size - 1; while (end >= 0) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[0] = x; psl->size++;*/ //法二: SeqListInsert(psl, 0, x); }
2、删除数据
头删
- 思想:
头删不需要考虑扩容,头删需要把第二个数字往前挪,再把下一个数字往前挪,以此类推,注意,再删数据时要确保有效数据size恒>=0,以保证后续能够正常加数据。
- SeqList.h文件:
//头删 void SeqListPopFront(SeqList* psl);
- SeqList.c文件:
//头删 void SeqListPopFront(SeqList* psl) { assert(psl); if (psl->size > 0) { int begin = 1; while (begin < psl->size) { psl->a[begin - 1] = psl->a[begin]; begin++; } psl->size--; } }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //尾插10个数字 for (int i = 1; i <= 10; i++) { SeqListPushBack(&s, i); } SeqListPrint(&s); //尾插10个数字后打印 //头删12次数据 for (int i = 1; i <= 12; i++) { SeqListPopFront(&s); } SeqListPrint(&s); //头删12次后打印 //头插5个数字 for (int i = -5; i <= -1; i++) { SeqListPushFront(&s, i); } SeqListPrint(&s); //头插5次后打印 return 0; }
- 效果如下:
尾删
- 思想:
这里size就是统计开辟的数组中有效数据的个数,只需要把有效数据的个数-1,打印的时候自然会把原本最后一个数据给删掉,但是要注意,如果删除次数过多,有效数据size可能会变成负的,为了避免这一现象,只需要确保在size>0时再减减即可,同样为了防止传入空指针,只需要assert断言即可。
- SeqList.h文件:
//尾删 void SeqListPopBack(SeqList* psl);
- SeqList.c文件:
//尾删 void SeqListPopBack(SeqList* psl) { assert(psl); if (psl->size > 0) { psl->size--; } }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //一定要加上&,因为形参的改变不会影响实参,要传地址 //尾插5个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPushBack(&s, 5); SeqListPrint(&s); //尾插5次后打印 //尾删6个数字 SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPrint(&s); //尾删6次后打印 //再尾插2个数字 SeqListPushBack(&s, 6); SeqListPushBack(&s, 7); SeqListPrint(&s); //再尾插2次打印 return 0; }
- 效果如下:
指定下标删除
- 思想:
其实思想并不复杂,首先要断言不是空指针,其次要确保下标pos<size,不能=size,因为下标size的值是空的,并非有效数据,若=size则删除一个无意义的数,接下来就跟头删类似了,把pos后一个位置挪到前一个,再换下一个,依次类推
- SeqList.h文件:
//删除pos位置的数据 void SeqListErase(SeqList* psl, size_t pos);
- SeqList.c文件:
//删除pos位置的数据 void SeqListErase(SeqList* psl, size_t pos) { assert(psl); assert(pos < psl->size); size_t begin = pos + 1; while (begin < psl->size) { psl->a[begin - 1] = psl->a[begin]; ++begin; } psl->size--; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //删除2个指定下标的数字 SeqListErase(&s, 3);//下标3 SeqListErase(&s, 1);//下标1 SeqListPrint(&s); //删除后打印 return 0; }
- 效果如下:
- 注意:
当pos=size-1时,删除的就是最后一个数字,实现的就是尾删,所以尾删可以这样写:
//尾删 void SeqListPopBack(SeqList* psl) { assert(psl); /*if (psl->size > 0) { psl->size--; }*/ SeqListErase(psl, psl->size - 1); }
当pos=0时,删除的就是第一个数字,实现的就是头删,所以头删也可以这样解决
//头删 void SeqListPopFront(SeqList* psl) { assert(psl); //法一: /*if (psl->size > 0) { int begin = 1; while (begin < psl->size) { psl->a[begin - 1] = psl->a[begin]; begin++; } psl->size--; }*/ //法二:指定下标删除法 SeqListErase(psl, 0); }
3、查找数据
- 思想:
遍历数组即可。
- SeqList.h文件:
//查找指定数字 int SeqListFind(SeqList* psl, SLDataType x);
- SeqList.c文件:
//查找指定数字 int SeqListFind(SeqList* psl, SLDataType x) { assert(psl); for (int i = 0; i < psl->size; i++) { if (psl->a[i] == x) { return i; } } return -1; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 int pos = SeqListFind(&s, 3); if (pos != -1) printf("找到了,下标是:%d", pos); else printf("找不到\n"); return 0; }
- 效果如下:
4、修改数据
- 思想:
只需要把指定下标的数字进行修改即可,前提是修改的数字是有效数据
- SeqList.h文件:
//修改指定下标数字 void SeqListModify(SeqList* psl, size_t pos, SLDataType x);
- SeqList.c文件:
//修改指定下标数字 void SeqListModify(SeqList* psl, size_t pos, SLDataType x) { assert(psl); assert(pos < psl->size); psl->a[pos] = x; }
- Test.c文件:
int main() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 SeqListModify(&s, 1, 5); SeqListPrint(&s); //修改后打印 int pos = SeqListFind(&s, 3); if (pos != -1) { SeqListModify(&s, pos, 5000); SeqListPrint(&s); //查找再修改后打印 } return 0; }
- 效果如下:
四、总代码
1、SeqList.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> //创建顺序表 typedef int SLDataType; //确保以后想存其它类型的时候方便改动,本文以存放整型数据为例 typedef struct SeqList { SLDataType* a; //动态开辟数组 int size; //存储顺序表中有效数据个数 int capacity;//存储空间个数-->记录最大容量 }SeqList; //初始化顺序表 void SeqListInit(SeqList* psl); //检测是否需要扩容 void SeqListCheckCapacity(SeqList* psl); //打印顺序表 void SeqListPrint(SeqList* psl); //销毁顺序表 void SeqListDestroy(SeqList* psl); //尾插 void SeqListPushBack(SeqList* psl, SLDataType x); //尾删 void SeqListPopBack(SeqList* psl); //头插 void SeqListPushFront(SeqList* psl, SLDataType x); //头删 void SeqListPopFront(SeqList* psl); //在pos位置插入x void SeqListInsert(SeqList* psl, size_t pos, SLDataType x); //删除pos位置的数据 void SeqListErase(SeqList* psl, size_t pos); //查找指定数字 int SeqListFind(SeqList* psl, SLDataType x); //修改指定下标数字 void SeqListModify(SeqList* psl, size_t pos, SLDataType x);
2、SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" //初始化通讯录 void SeqListInit(SeqList* psl) { assert(psl); psl->a = NULL; psl->size = 0; psl->capacity = 0; } //检测是否需要扩容 void SeqListCheckCapacity(SeqList* psl) { assert(psl); //如果满了,就要扩容 if (psl->size == psl->capacity) { size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2; //防止原始capacity的容量本身为0,导致后续扩容仍为0 SLDataType* tmp = realloc(psl->a, sizeof(SLDataType) * newCapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } else { psl->a = tmp; psl->capacity = (int)newCapacity; } } } //打印顺序表 void SeqListPrint(SeqList* psl) { assert(psl); for (int i = 0; i < psl->size; i++) { printf("%d ", psl->a[i]); } printf("\n"); } //销毁顺序表 void SeqListDestroy(SeqList* psl) { assert(psl); free(psl->a); psl->a = NULL; psl->capacity = psl->size = 0; } //尾插 void SeqListPushBack(SeqList* psl, SLDataType x) { assert(psl); //法一: /*SeqListCheckCapacity(psl); //检测容量 psl->a[psl->size] = x; psl->size++;*/ //法二:指定下标插入 SeqListInsert(psl, psl->size, x); } //尾删 void SeqListPopBack(SeqList* psl) { assert(psl); /*if (psl->size > 0) { psl->size--; }*/ SeqListErase(psl, psl->size - 1); } //头插 void SeqListPushFront(SeqList* psl, SLDataType x) { assert(psl); //法一: /*SeqListCheckCapacity(psl); //检测容量 int end = psl->size - 1; while (end >= 0) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[0] = x; psl->size++;*/ //法二:指定下标插入法 SeqListInsert(psl, 0, x); } //头删 void SeqListPopFront(SeqList* psl) { assert(psl); //法一: /*if (psl->size > 0) { int begin = 1; while (begin < psl->size) { psl->a[begin - 1] = psl->a[begin]; begin++; } psl->size--; }*/ //法二:指定下标删除法 SeqListErase(psl, 0); } //在pos位置插入x void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) { assert(psl); //暴力检查 /*assert(pos <= psl->size && psl);*/ //温和检查 if (pos > psl->size) { printf("pos 越界:%d\n", (int)pos); return; } SeqListCheckCapacity(psl); //检测容量 int end = psl->size - 1; while (end >= (int)pos) { psl->a[end + 1] = psl->a[end]; end--; } psl->a[pos] = x; psl->size++; } //删除pos位置的数据 void SeqListErase(SeqList* psl, size_t pos) { assert(psl); assert(pos < psl->size); size_t begin = pos + 1; while (begin < psl->size) { psl->a[begin - 1] = psl->a[begin]; ++begin; } psl->size--; } //查找指定数字 int SeqListFind(SeqList* psl, SLDataType x) { assert(psl); for (int i = 0; i < psl->size; i++) { if (psl->a[i] == x) { return i; } } return -1; } //修改指定下标数字 void SeqListModify(SeqList* psl, size_t pos, SLDataType x) { assert(psl); assert(pos < psl->size); psl->a[pos] = x; }
3、Test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" void test1() { SeqList s; SeqListInit(&s); //一定要加上&,因为形参的改变不会影响实参,要传地址 //尾插5个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPushBack(&s, 5); SeqListPrint(&s); //尾插5次后打印 //尾删6个数字 SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPopBack(&s); SeqListPrint(&s); //尾删6次后打印 //再尾插2个数字 SeqListPushBack(&s, 6); SeqListPushBack(&s, 7); SeqListPrint(&s); //再尾插2次打印 } void TestSeqList2() { SeqList s; SeqListInit(&s); //一定要加上&,因为形参的改变不会影响实参,要传地址 //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //头插2个数字 SeqListPushFront(&s, 0); SeqListPushFront(&s, -1); SeqListPrint(&s); //头插2次后打印 } void TestSeqList3() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //头删 SeqListPopFront(&s); SeqListPopFront(&s); SeqListPopFront(&s); SeqListPopFront(&s); SeqListPopFront(&s); SeqListPopFront(&s); SeqListPrint(&s); //头删6次后打印 //头插2个数字 SeqListPushFront(&s, 0); SeqListPushFront(&s, -1); SeqListPrint(&s); //头插2次后打印 } void TestSeqList4() { SeqList s; SeqListInit(&s); //尾插10个数字 for (int i = 1; i <= 10; i++) { SeqListPushBack(&s, i); } SeqListPrint(&s); //尾插10个数字后打印 //头删12次数据 for (int i = 1; i <= 12; i++) { SeqListPopFront(&s); } SeqListPrint(&s); //头删12次后打印 //头插5个数字 for (int i = -5; i <= -1; i++) { SeqListPushFront(&s, i); } SeqListPrint(&s); //头插5次后打印 } void TestSeqList5() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 //指定下标插入2个数字 SeqListInsert(&s, 10, 100); SeqListInsert(&s, 1, 20); SeqListInsert(&s, 5, 50); SeqListPrint(&s); //指定插入2次后打印 SeqListDestroy(&s); } int main() { SeqList s; SeqListInit(&s); //先尾插4个数字 SeqListPushBack(&s, 1); SeqListPushBack(&s, 2); SeqListPushBack(&s, 3); SeqListPushBack(&s, 4); SeqListPrint(&s); //尾插4次后打印 SeqListModify(&s, 1, 5); SeqListPrint(&s); //修改后打印 int pos = SeqListFind(&s, 3); if (pos != -1) { SeqListModify(&s, pos, 5000); SeqListPrint(&s); //查找再修改后打印 } return 0; }
版权归原作者 三分苦 所有, 如有侵权,请联系我们删除。