1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。
上图为单链表
1.1顺序表定义
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
1.2顺序表实现
1.2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
静态的顺序表(定长数组实现)
动态的顺序表
// 顺序表的静态存储 #define N 100 //为了便于修改数据大小 typedef int SLDataType;//为了便于修改数据类型 typedef struct SeqList { SLDataType array[N]; // 定长数组 size_t size; // 有效数据的个数 }SeqList;
静态实现
// 顺序表的动态存储 typedef struct SeqList { SLDataType* array; // 指向动态开辟的数组 size_t size ; // 有效数据个数 size_t capicity ; // 容量空间的大小 }SeqList;
动态实现
1.2.2接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大
了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态
的分配空间大小,所以下面我们实现动态顺序表。
// 顺序表的动态存储 typedef struct SeqList { SLDataType* array; // 指向动态开辟的数组 size_t size ; // 有效数据个数 size_t capicity ; // 容量空间的大小 }SeqList; // 基本增删查改接口 // 顺序表初始化 void SeqListInit(SeqList* psl); // 顺序表销毁 void SeqListDestory(SeqList* psl); // 顺序表打印 void SeqListPrint(SeqList* psl); // 检查空间,如果满了,进行增容 void CheckCapacity(SeqList* psl); // 顺序表尾插 void SeqListPushBack(SeqList* psl, SLDataType x); // 顺序表尾删 void SeqListPopBack(SeqList* psl); // 顺序表头插 void SeqListPushFront(SeqList* psl, SLDataType x); // 顺序表头删 void SeqListPopFront(SeqList* psl); // 顺序表查找 intSeqListFind(SeqList*psl, SLDataType x); // 顺序表在pos位置插入x void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);// 顺序表删除pos位置的值 void SeqListErase(SeqList* psl, size_t pos);
1.初始化顺序表
void SeqListInit(SL* ps) { assert(ps); ps->a = NULL; ps->size = 0; ps->capacity = 0;//注意这里空间赋为0开辟空间翻倍要赋初值 }
由于下面的增删查改要多次使用检查空间是否足够,这里我们使用一个检查空间函数来解决这个问题。
void SeqListCheckCapacity(SL* ps) { int newcapicity = ps->capacity == 0 ? 4 : ps->capacity * 2; SQDataType* tmp = (SQDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(SQDataType)); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } else { ps->a = tmp; ps->capacity = newcapicity; } }
1.头插接口的实现
(后面的元素向后挪一个,从最后开始,因为从头开始会导致数据的覆盖)
void SeqListPushFront(SL* ps, SQDataType 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++; }
2.尾插的实现
void SeqListPushBack(SL* ps, SQDataType x) { SeqListCheckCapacity(ps); ps->a[ps->size] = x; ps->size++; }
3.头删与尾删
//尾删 void SeqListPopBack(SL* ps) { assert(ps->size > 0); ps->a[ps->size - 1] = 0; ps->size--; } //头删 void SeqListPopFront(SL* ps) { assert(ps->size > 0); int start = 1; while (start < ps->size) { ps->a[start - 1] = ps->a[start]; ++start; } ps->size--; }
4.随机位置的写删
void SeqListPopFront(SL* ps) { assert(ps->size > 0); int start = 1; while (start < ps->size) { ps->a[start - 1] = ps->a[start]; ++start; } ps->size--; SeqListErase(ps, 0); } void SeqListInsert(SL* ps, int pos, SQDataType x) { assert(pos <= ps->size); SeqListCheckCapacity(ps); int end = ps->size - 1; while (end >= pos) { ps->a[end + 1] = ps->a[end]; --end; } ps->a[pos] = x; ps->size++; }
这里我们发现以上的头插/删和尾插/删可以通过以上的随机位置读写完成,大大减少了代码量
于是代码简化成
//头删 void SeqListPopFront(SL* ps) { SeqListErase(ps, 0); } //尾删 void SeqListPopBack(SL* ps) { SeqListErase(ps, ps->size - 1); } //头插 void SeqListPushFront(SL* ps, SQDataType x) { SeqListInsert(ps, 0, x); } //尾插 void SeqListPushBack(SL* ps, SQDataType x) { SeqListInsert(ps, ps->size, x); }
5.其余接口实现
int SeqListFind(SL* ps, SQDataType x) { for (int i = 0; i < ps->size; ++i) { if (ps->a[i] == x) { return i; } } return -1; } void SeqListModity(SL* ps, int pos, SQDataType x) { assert(pos < ps->size); ps->a[pos] = x; } void SeqListPrint(SL* ps) { for (int i = 0; i < ps->size; ++i) { printf("%d ", ps->a[i]); } printf("\n"); }