又到了 编故事 啊不讲故事的时间,还是那个菜鸡大学生。在高数和c语言的毒打之下,终于挺过了大一的上半学期。精通打退堂鼓这一门乐器的他知道,如果不继续往下学习。很可能他当程序员的理想就要夭折,遂,淦顺序表。
线性表是啥?
线性表(linear list)是n个具有 相同特性 的数据元素的有限序列。
常见的线性表有哪些?
顺序表、链表、栈、队列、字符串… 它们在之后的文章都会提到,今天我们主要讲顺序表。
注:线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
大概是这样:
简单来说就是一个高配的数组。
开工!
顺序表的本体(动态版本)
typedef int SLDataName;
typedef struct SeqList
{
SLDataName* arr; //指向动态开辟的数组
size_t size; //有效数据个数
size_t capacity; //容量
}SeqList;
int main()
{
SeqList s;
SeqListInit(&s);
}
//顺序表初始化
void SeqListInit(SeqList* psl)
{
psl->arr = (SeqList*)calloc(1, sizeof(SLDataName));
psl->capacity = 1;
psl->size = 0;
}
这里的capacity也可以写成0,但是我为了方便接下来的扩容就没有这么写。
//顺序表销毁
void SeqListDestory(SeqList* psl)
{
free(psl->arr);
psl->arr = NULL;
psl->capacity = 0;
psl->size = 0;
}
不销毁的话会存在内存泄漏的问题。(虽然程序结束操作系统会帮你自动回收,但是我们不能养成这种坏习惯)
free的时候编译器才会检查指针是否越界。这时候报的错一般都是越界的问题。
销毁的函数很好写,全部都清空就完事了。
//顺序表打印
void SeqListPrint(SeqList* psl)
{
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->arr[i]);
}
printf("\n");
}
遍历就好,轻松愉快。
//检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl)
{
assert(psl); //断言防止指针为空
if (psl->size == psl->capacity)
{
SLDataName* tmp =(SLDataName*) realloc(psl->arr,sizeof(SLDataName*) *(psl->capacity) * 2);
if (tmp != NULL)
{
psl->arr = tmp;
(psl->capacity) *= 2;
printf("Success!\n");
}
else
{
printf("Opps!\n"); //扩容失败
exit(-1) //结束程序
}
}
}
如果有效数据等于容量我们就可以考虑增容了。
这里是选择扩容到原来的两倍,当然你想扩多大就扩多大,数据结构在这个方面还是很宽松的。
//顺序表尾插
void SeqListPushback(SeqList* psl, SLDataName x)
{
assert(psl);
CheckCapacity(psl);
psl->arr[psl->size] = x;
psl->size++;
}
尾插还是很简单的,arr[psl->size]
正好是我们需要插入数字的位置,最后记得有效数据个数要加一就行。
//顺序表头插
void SeqListPushhead(SeqList* psl, SLDataName x)
{
assert(psl);
CheckCapacity(psl);
for (int i = psl->size; i > 0; i--)
{
psl->arr[i] = psl->arr[i - 1];
}
psl->size += 1;
psl->arr[0] = x;
}
只要将数据依次往后挪一位,最后在第一位插入数据,就完成了数据的头插。
//顺序表尾删
void SeqListPopback(SeqList* psl)
{
assert(psl);
if (psl->size == 0)
{
printf("顺序表为空\n");
return;
}
psl->size --;
}
有效数据减少就是删了。
什么,你说没删,你看的到吗?看不到,看不到就是删了。
反正增加数据的时候一覆盖死无对证。
注意顺序表为空的情况即可。
//顺序表头删
void SeqListPophead(SeqList* psl)
{
assert(psl);
if (psl->size == 0)
{
printf("顺序表为空\n");
return;
}
for (int i = 0; i <psl->size-1; i++)
{
psl->arr[i] = psl->arr[i + 1];
}
psl->size --;
}
将头插的思路倒过来加上尾删减去有效数据的操作就是头删这个缝合怪。
依旧注意顺序表不能为空。
//顺序表查找
int SeqListFind(SeqList* psl, SLDataName x)
{
for (int i = 0; i < psl->size; i++)
{
if (psl->arr[i] == x)
return i;
}
return -1;
}
遍历,找到返回下标,没找到返回-1。
简单粗暴,甚得人心。
//顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataName x)
{
// 暴力检查
assert(psl);
// 温和检查
if (pos > psl->size)
{
printf("pos 越界:%d\n", pos);
return;
}
CheckCapacity(psl);
for (int i = psl->size; i > pos; i--)
{
psl->arr[i] = psl->arr[i - 1];
}
psl->size ++;
psl->arr[pos] = x;
}
就是头插写法的推广版本,不多赘述。
还有一种写法:(i >= (int)pos)
循环操作写psl->arr[i+1] = psl->arr[i ];
注:由于pos的类型是size_t,要注意潜在的整形提升问题。
//顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
if (psl->size == 0)
{
printf("顺序表为空\n");
return;
}
for(int i = pos; i < psl->size-1; i++)
{
psl->arr[i] = psl->arr[i + 1];
}
psl->size--;
}
覆盖就好。
有了指定位置增删函数,我们就可以把它用到特殊的情况。
void SeqListPushBack(SeqList* psl, SLDataName x)
{
assert(psl);
SeqListInsert(psl, psl->size, x);
}
void SeqListPopBack(SeqList* psl)
{
assert(psl);
SeqListErase(psl, psl->size-1);
}
void SeqListPushFront(SeqList* psl, SLDataType x)
{
assert(psl);
SeqListInsert(psl, 0, x);
}
void SeqListPopFront(SeqList* psl)
{
assert(psl);
SeqListErase(psl, 0);
}
多画画图这些基本上就出来了。(菜鸡大学生如是说)