【数据结构】顺序表
1.顺序表
1.1线性表
线性表(Linear List)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
到这有的同学可能不理解什么是物理结构和逻辑结构,简单来说:>
物理结构:数据存储在磁盘中的方式(数据结构在计算机中的表示),是实实在在存在的。
逻辑结构:数据与数据之间的关联关系,准确的说是数据元素之间的关联关系。这种结构是人为想象出来的,是为了方便理解某种结构,不是实实在在存在的。
图例
而顺序表就是线性表的一种。
1.2顺序表基本概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表一般可以分为
1.静态顺序表:使用定长数组存储元素
2.动态顺序表:使用动态开辟的数组存储
但是创建顺序表最好使用动态顺序表,原因下面讲
2.顺序表代码实现
2.1文件创建
首先创建三个文件
文件名 | 作用 |
---|---|
Test.c | 用来调用之后的增删改查等函数 |
SeqList.c | 函数功能的实现 |
SeqList.h | 存放顺序表增删改查等功能函数的声明(接口) |
2.2创建顺序表
//静态的非常不实用,最好改成动态的
typedef struct SeqList
{
SQDataType *a;//指向数组的指针即动态分配的数组,即声明了一个名为a的长度不确定的数组,也叫“动态数组”
int size; //标识有多少个有效数据
int capacity;//多了这个,空间大小
}SLT;
为毛不设置成如下图静态顺序表?
#define N 100
#include<stdio.h>
typedef int SQDataType;//换类型,换名字在这里换就行
typedef struct SeqList
{
SQDataType a[N];
int size;
}SLT;
因为静态表定长数组导致N定大了,空间开多了浪费,空间开少了不够用。换言之不实用,所以用动态顺序表,根据所需要动态分配空间大小,这样来看属于是方便许多了。
2.3顺序表的初始化
为毛需要初始化?
因为初始化,除了可以申请足够大小的物理空间之外,还为了方便后期使用表中的数据。
好,现在我们先在seqList.h里声明初始化函数SeqlistInit()
void SeqListInit(SLT* psl);//这是SeqList里面的声明
这里切记要传的是地址,各位指针那块学过,函数传参,形参是改变不了实参的.下面是在SeqList.c里的定义
void SeqListInit(SLT* psl)
{
psl->a = NULL;
psl->size = psl->capacity = 0;
}
这里复习一下->符号的意思
-> 是C语言和C++语言的一个运算符,叫做指向结构体成员运算符,用处是使用一个指向结构体或对象的指针访问其内成员。
2.4顺序表的销毁
void SeqListDestory(SLT* psl)
{
assert(psl);//此处为断言函数,进if循环前判断psl是否为空
//若psl为空,你就传不了东西下去了,也没必要销毁了
//所以最好有断言,断言出现在一定不能为空的前面,作为判断
//但是注意使用前包头文件#include<assert.h>
if (psl->a)//若指向的a不为空,就释放a指针
{
free(psl->a);//不释放可能会造成内存泄漏
//用free注意要包头文件#include<stdlib.h>
}
psl->size = psl->capacity = 0;//空间全部释放,置为0
psl->a = NULL;//谨防野指针
}
2.5顺序表的打印
在SeqList.h里声明
void SeqListPrint(SLT* psl);
在.c文件里写
void SeqListPrint(SLT* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", *(psl->a+i));//psl->a[i]
}
printf("\n");
}
2.6顺序表的尾插
在SeqList.h里面声明
void SeqListPushBack(SLT* psl,SQDataType x);//需要传顺序表的(结构体变量)地址和要插的数字尾插
所以就可以写代码了
void SeqListPushBack(SLT* psl, SQDataType x)
{
assert(psl);
psl->a[psl->size] = x;//原来的数最后一个下标为size-1,
//那我现在要尾插我就先把x放在下标为size的位置上
//然后再size++增容,也就是空间就大了一个
psl->size++;
}
但是这种情况下如果我们插入的有效数字size个数大于capacity时,不就相当于静态顺序表里数组越界访问了吗!所以我们需要在把x放到size前判断是否需要增容,这就是动态顺序表中的动态的一个含义。好到此代码应该没问题了把
void SeqListPushBack(SLT* psl, SQDataType x)
{
assert(psl);
if (psl->size == psl->capacity)
{
//一般来说为了避免频繁插入数据增容,当空间满了后
//一般一次2倍增容,2倍增容算比较好的解决方法了,倍数越大反而不是
//很好,官方库里面一般也是2倍,算是折中的办法了
//这里由于realloc的特性,size和capacity为0,下面realloc
//也会申请空间,所以我们需要给一个初始值,所以需要来个newcapacity
size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
psl->a = (SQDataType*)realloc(psl->a, newcapacity*sizeof(SQDataType));//防止开小了
psl->capacity = newcapacity ;
}
psl->a[psl->size] = x;//原来的数最后一个下标为size-1,
//那我现在要尾插我就先把x放在下标为size的位置上
//然后再size++增容,也就是空间就大了一个
psl->size++;
}
2.7顺序表的检验是否增容
我们在尾插头插等都需要检验是否增容,所以一不做二不休,直接创建个检验增容的函数来复用它
void SeqListCheckCapacity(SLT* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
psl->a = (SQDataType*)realloc(psl->a, newcapacity * sizeof(SQDataType));
psl->capacity = newcapacity;
}
}
2.8顺序表的尾删
在SeqList.h里声明
void SeeqListPopBack(SLT* psl, SQDataType x);//尾删,从后面删除
由于size小于capacity,不存在空间不够所以我直接删除下标为size-1的数据,不就好了
void SeqListPopBack(SLT* psl)
{
assert(psl);
SeqListCheckCapacity(psl);
psl ->size--;
}
下面的SeqListInsert函数可以在这里复用,这里先不看
void SeqListPushBack(SLT* psl, SQDataType x)
{
SeqListInsert(psl, psl->size, x);
}
因为在pos位插入一个数字,由于capacity时大于size的
所以我们可以在pos==size位进行插入,就等于尾插。
2.9顺序表的头插
在SeqList.h里声明
void SeqListPushFront(SLT* psl);//头插
SeqList.c里面定义
void SeqListPushFront(SLT* psl, SQDataType x)
{
assert(psl);
SeqListCheckCapacity(psl);
//psl->a[0] = x;
int end = psl->size - 1;
while(end>=0)
{
psl->a[end+1 ]= psl->a[end];
end--;
}
psl->a[0] = x;
psl->size++;
}
下面的SeqListInsert函数可以在这里复用,这里先不看
void SeqListPushFront(SLT* psl, SQDataType x)
{
SeqListInsert(psl, 0, x);
}
2.10顺序表的头删
在SeqList.h里声明
void SeeqListPopFront(SLT* psl);//头删
SeqList.c定义
void SeqListPopFront(SLT* psl)
{
assert(psl);
assert(psl->size > 0);//要考虑没有数字时
int begin = 1;
while (begin < psl->size)
{
psl->a[begin-1]=psl->a[begin++];
//begin++;
}
psl->size--;
}
2.11顺序表的查找
int SeqListFind(SLT* psl, SQDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
2.12顺序表在pos位置插入x
void SeqListInsert(SLT* psl, /*int*/size_t pos, SQDataType x);
void SeqListInsert(SLT* psl, /*int*/size_t pos, SQDataType x)
{
assert(psl);
assert(pos <=psl->size&&pos>=0);
SeqListCheckCapacity(psl);
//int end = psl->size - 1;
/*while(end>=pos)
{
psl->a[end + 1] = psl->a[end];
--end;
}*/
size_t end = psl->size ;
while (end > pos)
{
psl->a[end -1] = psl->a[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
这里要避免负数给到无符号,或者避免有符号数变成负数以后被整型提升提升或者强转成有符号,会造成严重的越界问题。
2.13顺序表删除pos位置的值
在SeqList.h里声明
void SeqListErase(SLT* psl, size_t pos);
在SeqList.c里面定义
void SeqListErase(SLT* 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--;
}
3.代码优化分析
1.“高内聚,低耦合”代码能复用的就复用,增加可读性。
2.注意void *realloc( void *memblock, size_t size );的定义,动态内存开辟是在free()处检查,当出现什么断点错误,通常都是你内存越界了,越界一般两种可能性,一种是你越界了,一种是你内存开小了。仔细检查发现,此动态表里realloc开辟的是4个字节大小,而我们需要的是四个int类型,即16个字节,那伯分之伯越界了,所以还需要乘SQDataType字节大小,这才是真正要开辟的大小。
3.realloc可能会释放旧空间,假如原来的内存后面还有足够多剩余内存的话,realloc的内存=原来的内存+剩余内存,realloc还是返回原来内存的地址;;假如原来的内存后面没有足够多剩余内存的话,realloc将申请新的内存,然后把原来的内存数据拷贝到新内存里,原来的内存将被free掉,realloc返回新内存的地址。(关于动态内存这篇作者写的不错博客链接)
4.代码链接
来点真实的:>
代码链接