概念以及分类
顺序表:是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
静态顺序表
#define N 100 typedef int SLDataType; typedef struct SeqList { SLDataType array[N]; // 定长数组 size_t size; // 有效数据的个数 }SeqList;
动态顺序表(重点掌握)
简单介绍(主体+接口函数)
#pragma once // 防止被重复包含 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int SLDateType; typedef struct SeqList { SLDateType* a; int size; //存储的有效数据 int capacity; // 存储数据的总容量 }SL; //接口函数 void SeqListInit(SL* ps); //初始化 void SeqListPrint(SL* ps); //顺序表的打印 void SeqListnewcapacity(SL* ps); //检查是否需要增容 void SeqListDestory(SL* ps); // 清除数据 void SeqListPopBack(SL* ps); // 尾插 void SeqListPushBack(SL* ps, SLDateType x); // 尾删 void SeqlistPushFront(SL * ps, SLDateType x); // 头插 void SeqListPopFront(SL* ps, SLDateType x); //头删 void SeqLisFind(SL* ps, SLDateType x); //查找元素 void SeqListInsert(SL* ps, int pos, SLDateType x); //任意位置插入 void SeqListErase(SL* ps, int pos, SLDateType x); //任意位置删除
首选需要说明的,这接口函数的命名风格是跟着STL走的,这样命名有助于C++的学习。
顺序表的主体由三部分组成:
1.插入的数据
2.插入数据的容量
3.该顺序表的最大容量
注意:
1.这里使用了typedef int SLDateType;,是为了后面方便对插入的数据类型进行修改
2.typedef struct SeqList ,是为了后面 用SL代替, 表示比较方便
初始化函数(SeqListInit(SL * ps))
void SeqListInit(SL* ps) //初始化 { ps->a = NULL; ps->size = ps->capacity = 0; }
初始化步骤很简单,就让数据容量都为0, 而传入的值都为NULL。
打印函数(void SeqListPrint(SL* ps))
void SeqListPrint(SL* ps) { for (int i = 0; i < ps->size; i++) { printf("%d\n", ps->a[i]); } }
这也很简单, 就用有个for循环, 挨着打印顺序表中的每个。
是否增容函数(SeqListnewcapacity(SL* ps))
void SeqListnewcapacity(SL* ps) { if (ps->size == ps->capacity) //扩容或者是开辟空间 { int newcapacity = ps->size == 0 ? 4 : ps->capacity * 2; SLDateType* tmp = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType)); //realloc(a, b) 若a = NULL,相当于开辟空间 // a != NULL 扩容 /*if (tmp == NULL) { printf("开辟空间失败"); exit(-1); }*/ assert(tmp != NULL); ps->a = tmp; ps->capacity = newcapacity; }
判断是否需要增容的条件是:
ps->size == ps->capacity
实际用的容量==顺序表最大容量
代表着顺序表已经没有多余读的空间,来存储下一个数据了
这里分两种情况:
1.如果ps->size = ps->capacity == 0;,那么让它等于4
2.如果ps->size = ps->capacity != 0;, 那么就 让它的最大容量加倍(当然这里你可以自己随便设置)
这两种情况的实现,这里使用了三目运算符
情况2:
这里使用了realloc函数,来进行增容。
realloc(a, b) 若a = NULL,相当于开辟空间
a != NULL 扩容
注意:
1.newcapacity * sizeof(SLDateType),需要扩容的空间大小 = 数据的个数 * 它的数据类型
2.开辟空间,自然要考虑到开辟空间失败的情况
清楚数据函数(SeqListDestory(SL* ps))
void SeqListDestory(SL* ps) { free(ps->a); ps->a = NULL; ps->capacity = ps->size == 0;
这里主要就使用free函数,对内存进行释放
ps->a = NULL;
ps->capacity = ps->size == 0;
尾插函数(SeqListPopBack(SL* ps))
void SeqListPushBack(SL* ps, SLDateType x) { SeqListnewcapacity(ps); ps->a[ps->size] = x; ps->size++;
尾插:就是在顺序表最后一个元素的后面插入一个元素
第一步:检查是否要增容
第二步:在顺序表后面插入元素 ,ps->size 表示此时顺序表中所存储的个数, ps->size - 1是顺序表中最后一个元素(数组下标), ps->size 自然就是最后一个元素的后面
第三步:插入了一个元素之后,自然就要让顺序表中存储的元素个数+1, ps->size + 1;
尾删函数(SeqListPushBack(SL* ps, SLDateType x); )
void SeqListPopBack(SL* ps) { /*if (ps->a > 0) { ps->size--; }*/ assert(ps->a > 0); ps->size--; }
尾删:从数组的最后一个元素开始删除
删除的前提自然是 ps->szie > 0;
这里有2种解决方法;
1:温柔的方式 ;用if语句来判断
2:粗暴的方式:用assert(),直接断言,如果不正确,直接就终止程序
注意要使用头文件(#inlucde<assert.h>)
删除方式:
通过让存贮的数据总数减少(ps->size–),从而达到从数组的最后一个元素开始删除的效果.
头插 (SeqlistPushFront(SL * ps, SLDateType x))
void SeqlistPushFront(SL* ps, SLDateType x) { SeqListnewcapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[0] = x; ps->size++; }
头插:在数组第一个元素的前面插入一个数据
主要思想:
前面插入一个数据,这里就涉及到一个挪移数据的过程,
也就是新插入的数据,就是顺序表数组中的第一个元素。, 那么就要挪出一个位置, 原来数组中每个元素向后面挪一位, 自然便能把数组的第一个位置空出来。(通过赋值操作,把元素的值赋值给下一位)
第一步:检查是否需要增容
第二步:插入数据并且开始挪移数据
end为数组元素的最后一位
这里让ps->a[end + 1] = ps->a[end];, 通过赋值操作,把元素的值赋值给下一位,然后end–, 继续挪动数据,这里自然是一个循环,while(end >= 0);
第三步::插入了一个元素之后,自然就要让顺序表中存储的元素个数+1, ps->size + 1;
头删函数(SeqListPopFront(SL* ps, SLDateType x); )
void SeqListPopFront(SL* ps, SLDateType x) { assert(ps->size > 0); int begin = 1; while (begin < ps->size) { ps->a[begin - 1] = ps->a[begin]; begin++; } ps->size--; }
头删:从数组的第一个元素开始删除
思想:(采用赋值删除的思想)
第二个值赋给第一个值, 第三个个值赋给第二个值,这样第一个元素自然就被消掉了,结果自然是每个元素向前移一位,也是通过赋值的操作
(挪移数据跟头插相似, 只是挪移的方向不一样)
第一步:删除元素,肯定要考虑越界问题 ps->size > 0
assert(ps->size > 0);
第二步:
beign 代表数组第一个元素的下标
通过赋值操作挪移数据,第二个值赋给第一个值, 第三个个值赋给第二个值,ps->a[begin - 1] = ps->a[begin];
begin++,挪动下一组数据
循环条件:while (begin < ps->size)
第三步:
删除了一个元素之后,自然就要让顺序表中存储的元素个数-1, ps->size - 1;
查找任意位置的函数( SeqLisFind(SL* ps, SLDateType x); )
void SeqLisFind(SL* ps, SLDateType x) { for(int i = 0; i < ps->size; i++) { if (ps->a[i] == x) { return i; } return -1; } }
查找元素,其实很简单
自然,
就是通过for循环和if语句, 与顺序表中的每个数据进行比较
找到的话, 就返回就这个数组的元素位置的下标
插入任意位置的函数(SeqListInsert(SL* ps, int pos, SLDateType x); )
oid SeqListInsert(SL* ps, int pos, SLDateType x) { assert(pos >= 0 && pos <= ps->size); int end = ps->size - 1; while (pos <= ps->size) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[pos] = x; ps->size++; }
第一步:自然要进行一个判断, 插入的位置肯定在这个数组中,不能够造成越界的非法操作,这里我还是用的assert函数来进行处理,当然你如果用if语句的话也是可以的。
第二步:里面插入了一个数据,自然要向后面挪移一个数据来腾出空间
end代表数组的最后一个元素
在pos位置之后的元素,都向后挪移一位
循环条件自然是:pos <= ps->size
while (pos <= ps->size)
{
ps->a[end + 1] = ps->a[end];
end–;
}
第三步:腾出的位置,赋值给插入的数据ps->a[pos] = x;
第四步:插入了一个元素之后,自然就要让顺序表中存储的元素个数+1, ps->size + 1;
运用:
如果pos = 0, 那么其实这就是头插
如果pos = ps->size, 那么这也就是尾插
任意位置删除的函数(SeqListErase(SL* ps, int pos, SLDateType x); )
void SeqListErase(SL* ps, int pos, SLDateType x) { assert(pos >= 0 && pos < ps->size); int begin = pos + 1; while (begin <= ps->size) { ps->a[begin - 1] = ps->a[begin + 1]; begin++; } ps->size--; }
第一步:
自然要进行一个判断, 删除的位置肯定在这个数组中,不能够造成越界的非法操作,这里我还是用的assert函数来进行处理,当然你如果用if语句的话也是可以的。
第二步:挪移数据,pos之后的元素向前挪移一个数据,通过赋值删除的方法。方法跟思路也是跟之前一样的。
while (begin <= ps->size)
{
ps->a[begin - 1] = ps->a[begin + 1];
begin++;
}
第三步:删除了一个元素之后,自然就要让顺序表中存储的元素个数-1, ps->size - 1;
运用:
1.同样的如果pos = 0,那么这就是头删
2,如果pos = ps->size - 1,那么这就是尾删
小总结
1.删除是向前挪移数据
2.插入式向后挪移数据
3.挪移数据的目的,是为了让数据在内存存储中是连续的
希望这能够对你们有所帮助,你们的关注与支持就是对我最大的鼓励