引入
我们来学学在数据结构中学到的第一个也是最简单最常用结构,线性表....中的顺序表吧!先回顾一下线性表,线性表(list):零个或多个元素的有限序列。这里注意无论是顺序表还是链表,他们在逻辑上都是线性的,但在物理上却不一定哦,今天我们要介绍的顺序表,无论是在逻辑上还是物理上都是线性的.
话不多说让我们回归主题,顺序表就可以理解为下面这个图。
大家有没有觉得这个图怎么这么像数组呢,没错我们可以利用数组来实现它。那我们有两个选择,要么用很大的数组来当线性表,这就是一个静态的顺序表了,还有一种做法,在c语言中我们可以利用malloc或者ralloc来申请空间来实现,具体要使用那个我们之后慢慢说。这两种方法各自有各自的优点,我之前在我的链表的博客中已经提到了,这里就不过多介绍了。
静态顺序表
先来讲讲静态顺序表吧!这里我就用C++来实现咯。
首先我们得考虑一件事情,我们开辟的数组应该放在main函数外面成为全局变量,还是放在main里面作为局部变量呢?
如果你有幸看过鹏哥的函数栈帧,那就应该能很好理解应该放在哪里呢,没看过也没关系,让我来简单介绍一下,全局变量存放在静态数据区,局部变量放在栈区中。很明显静态区里面的空间要大很多。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
//定义一个非常大的数组
int arr[N]
//用n来记录元素个数
int n;
定义好了这玩意,又要进行我们的增删查改了,这个就是数组的插入等问题,应该对于很多有一定基础同学来说是很简单了,我这里就不一一赘述了。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];//一个足够大的数组
int n;//数组的元素个数
//打印
void print()
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
//尾插
void push_back(int d)
{
a[n] = d;
n++;
}
//头插
void push_front(int d)
{
for (int i = n; i > 0; i--)
a[i] = a[i - 1];
a[0] = d;
n++;
}
//头删
void pop_front()
{
for (int i = 0; i < n; i++)
a[i] = a[i + 1];
n--;
}
//尾删
void pop_back()
{
n--;
}
//查找
int find(int d)
{
for (int i = 0; i < n; i++)
if (a[i] == d)
return i;
return -1;
}
//删除指定元素
void erase(int pos)
{
for (int i = pos; i < n; i++)
a[i] = a[i + 1];
n--;
}
//任意位置插入
void insert(int pos ,int d)
{
for (int i = n; i > pos; i--)
a[i] = a[i - 1];
a[pos] = d;
n++;
}
int main()
{
return 0;
}
动态顺序表
接下来就讲讲重头戏吧,用C语言来实现动态顺序表。
首先我们得创建一个结构体来实现.
#include<stdio.h>
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;//存储元素
int size;//元素个数
int capacity;//容积
}SeqList;
先来看看我们要实现的函数
// 对数据的管理:增删查改
void SeqListInit(SeqList* ps);
void SeqListDestroy(SeqList* ps);
void SeqListPrint(SeqList* ps);
void SeqListPushBack(SeqList* ps, SLDateType x);
void SeqListPushFront(SeqList* ps, SLDateType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
初始化
我们单单创建结构体时,如果我们不对它进行初始化,里面的size和capacity都是随机值。所以进行初始化是很有必要的。初始化就将这两个值以及a指针指向NULL
void SeqListUnit(SeqList * ps)
{
ps->a = NULL;
ps->capacity = ps->size = 0;
}
尾插
要实现尾插,其实就是在数组的末尾插入新数据,然后将size加1。注意此时size是始终指向空元素的即下一个元素,我可能说的有点抽象那让我们看看图吧!
其实单单这个几行代码就能实现
void SeqListPushBack(SeqLis* ps,SLDateType x)
{
ps->a[size++] = x;
}
但当我们的顺序表中没有元素时我们需要开辟空间来存储数据,就要考虑使用malloc,realloc函数了,realloc其实就是用来扩容的函数。一般我们扩容都是阔原来的内存的2倍,
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
这样用三目操作符能简单实现这个问题。
SeqList* tmp = (SeqList*)realloc(ps->a, newcapacity * sizeof(SLDateType));
这里要用tmp指针先暂时接收创建出来的内存空间,以防realloc申请失败,会造成内存泄漏,以后在公司中出现这种问题,造成的后果可不是你能想象的哦!
我们实现插入时可以都要进行扩容,所以我们把检查是否扩容分装成一个函数如下图
void SeqlistCheckCapacity(SeqList* ps)
{
if (ps->capacity == ps->size)
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SeqList* tmp = (SeqList*)realloc(ps->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL)
{
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
所以尾插就很好的实现了
void SeqListPushBack(SeqList* ps, SLDateType x)
{
assert(ps);
SeqlistCheckCapacity(ps);
ps->a[ps->size++] = x;
}
头插
头插相对于尾插显得要麻烦一点,我们得把所有元素向后移动一位再将要插入的元素插入,这里要十分注意,就是我们应该要从后往前移动。
任意位置插入
这个如果实现了头插就很容易了,将0改成我们要插入的位置的小标,我这里也就不啰嗦了哈。
尾删
尾删应该算是很简单的吧,只需要将元素个数减减就可以了。
头插
头插也需要将元素挪一挪。
删除指定元素
这对于你来说也是小菜一碟了。
不过要注意的是要检查数组的个数不能等于0
查找
之前都会了那这个就是小意思了。
销毁
销毁数组创建的内存也是一个很有必要的事情,我们不能只借不还啊,申请了内存,也要记得还回来。
完结撒花!