这是写给自己以后复习看的,把重要的记下来
目前我已经学完了栈和队列,对数据结构已经有了一定的理解,相比于C语言,我认为数据结构其实没有"新知识",它的真面目是运用C语言学的,数组,函数,指针,结构体,动态内存管理等知识来做一些结构,从而更好地解决一些问题。
一.什么是顺序表?
所谓顺序表,有点像数组,但不完全是
**定义:**一段物理地址连续的存储单元,依次存储数据的线性结构。
二.核心特征
特征就很符合线性规律:
-
物理连续:内存地址相邻,无空隙。
-
逻辑有序:存储顺序与逻辑顺序一致。
不论是物理上还是逻辑上,顺序表都是线性的。
顺序表也分为动态顺序表和静态顺序表,通常都是用动态,因为静态顺序表申请的空间是固定的,不能扩容,申请空间小了会溢出,申请多了又会浪费空间。
三.基本结构
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;//动态数组,用于储存数据
int size;//用来表示顺序表长度
int capacity;//空间大小
}SL;
基本结构就长这样了,其中的SLDataType是方便改数据类型的。
接下来就是重头戏了。
四.关于顺序表的操作
void SLInit(SL*ps);//顺序表初始化
void SLDestroy(SL* ps);//顺序表销毁
void SLpushback(SL* ps, SLDataType x);//顺序表尾插
void SLpushfront(SL* ps, SLDataType x);//顺序表头插
void SLpopback(SL* ps);//顺序表尾删
void SLpopfront(SL* ps);//顺序表头删
void SLInsert(SL* ps, int pos, SLDataType x);//顺序表指定位置插入
void SLErase(SL* ps, int pos);//顺序表指定位置删除
我写了这么多操作,说实话刚开始学还是有点痛苦的,有点懵逼,不过现在好多了。
一个个来介绍吗?我把对应的代写出来吧。
1.初始化
cpp
void SLInit(SL* ps)//初始化
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
很简单,没啥好说的。
2.销毁
cpp
void SLDestroy(SL* ps)//销毁
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
简单来说就是该置零的置零,该置空的置空。
3.尾插
cpp
void SLpushback(SL* ps, SLDataType x)//尾插
{
assert(ps);//检查空指针
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* temp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
if (temp == NULL)
{
perror("realloc fail");
return;
}
ps->arr = temp;
ps->capacity = newcapacity;
}
ps->arr[ps->size++] = x;
}
尾插是我感觉最难的步骤了,这个解决了就好理解多了。
我们先看最后一句:
cpp
ps->arr[ps->size++] = x;
这其实才是插入,前面的部分是检查容量外加扩容。这一步是先插入,再让size++;
重点是扩容部分。
cpp
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
这一步是判断初始容量,如果为0,就直接让新空间==4,如果不是0,那就2倍扩容(当然前提是if的条件------ps->size == ps->capacity)
cpp
SLDataType* temp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
if (temp == NULL)
{
perror("realloc fail");
return;
}
ps->arr = temp;
ps->capacity = newcapacity;
切记,刚才那个步骤只是改变了capacity的值,并没有实际开辟新空间,这一步就是配合newcapacity 来扩容arr的,这样就好理解了。
这样我们就知道了,尾插就是就是 容量检查(扩容)+插入(size++)。
这样就可以把前面那和检查扩容部分单独写成一个函数:
cpp
void SLCheckCapacity(SL*ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* temp = (SLDataType*)realloc(ps->arr, newcapacity * sizeof(SLDataType));
if (temp == NULL)
{
perror("realloc fail");
return;
}
ps->arr = temp;
ps->capacity = newcapacity;
}
}
这样代码就可以简化了,比如接下来的头插:
cpp
void SLpushfront(SL* ps, SLDataType x)//头插
{
assert(ps);
SLCheckCapacity(ps);//检查扩容部分写成函数
for (int i = ps->size;i > 0;i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
头插这个循环开始可能会很绕,其实我现在看到都觉得绕了点,它是让数据一个个后移,比如
arr[4]=arr[3],arr[3]=arr[2]......arr[1]=arr[0];
不太好讲,稍微想一下就知道在后移动了,这样吧arr[0]空出来,就可以插入进去了。
记住让size++.
4.尾删
cpp
void SLpopback(SL*ps)//尾删
{
assert(ps);
assert(ps->size);
--ps->size;
}
很简单,没啥好说的
5.头删
cpp
void SLpopfront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
for (int i = 0;i<ps->size-1;i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
头删也简单,细想一下就明白了。
6.指定位置插入
cpp
void SLInsert(SL* ps, int pos, SLDataType x)//指定位置插入
{
SLCheckCapacity(ps);
assert(pos >= 0 && pos <= ps->size);
for (int i = ps->size;i > pos;i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
通过循环把数据往后移动,让pos空出来,从而插入数据。
7.指定位置删除
cpp
void SLErase(SL* ps, int pos)//指定位置删除
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos;i < ps->size - 1;i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
比较简单,还行。
好了,总共就这样了,以后复习应该很容易看懂了