你的点赞是对我最大的鼓励!!!
目录
3.1.1包含必要的头文件,以及对顺序表中元素类型进行重命名。
1.顺序表
1.1线性表
线性表是n个具有相同特征的数据元素的有限序列。常见的线性表:顺序表,链表,栈,队列,字符串等等……
线性表在逻辑上是线性结构,也就是说是一条连续的直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表的分类
2.1顺序表和数组的区别
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等功能。
2.2分类
静态顺序表:使用定长数组存储元素。存在很大缺陷,不够灵活,空间给少了不够用,给多了造成空间浪费。
动态顺序表:可自动增容,使用realloc等函数对内存进行动态管理,按需分配空间。
realloc函数是对已有空间扩容,顺序表用
malloc函数是申请新的空间,链表用
3.动态顺序表的实现
采用头文件.h与.cpp文件分离的方法
如图:
3.1 Sqlist.h头文件的讲解
Sqlist.h源码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<iostream>
using namespace std;
typedef int SLdatatype;
struct Seqlist
{
SLdatatype* arr;
int size;
int capacity;
};
typedef struct Seqlist SL;
void init(SL* ps);//动态顺序表的初始化
void destory(SL* ps);//动态顺序表的销毁
void pushfront(SL* ps, SLdatatype x);//动态顺序表的头插
void pushback(SL* ps, SLdatatype x);//动态顺序表的尾插
void popback(SL* ps);//动态顺序表的尾删
void popfront(SL* ps);//动态顺序表的头删
void expand(SL* ps);//动态顺序表的扩容
void SLinsert(SL* ps, int pos, SLdatatype x);//在顺序表指定位置插入元素
void SLErase(SL* ps, int pos);//在顺序表指定位置删除元素
int find(SL* ps, SLdatatype x);//在顺序表中查找元素
void print(SL* ps);//打印顺序表
3.1.1包含必要的头文件,以及对顺序表中元素类型进行重命名。
3.1.2 对结构体的讲解
struct Seqlist
{
SLdatatype* arr;
int size;
int capacity;
};
typedef struct Seqlist SL;
首先,我们要定义一个arr指针,用来存储空间,刚定义好后arr没有空间,我们用realloc函数就是对arr来分配空间。
int size;
表示当前顺序表中有几个元素。
int capacity;
表示当前顺序表的容量。
当size与capacity相等时,相当于顺序表满了,此时需要用realloc来扩容
3.2 Sqlist.cpp头文件的讲解
Sqlist.cpp源码
#include"Sqlist.h"
void expand(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcap = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLdatatype* tmp = (SLdatatype*)realloc(ps->arr,2 * newcap * sizeof(SLdatatype));
if (tmp == NULL)
{
perror("realloc");
return;
}
ps->capacity = newcap;
ps->arr = tmp;
}
}
void init(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void destory(SL* ps)
{
free(ps);
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
void pushfront(SL* ps, SLdatatype x)
{
expand(ps);
for (int i = ps->size; i-1 >= 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
void pushback(SL* ps, SLdatatype x)
{
expand(ps);
ps->arr[ps->size++] = x;
}
void popback(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
void popfront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i+1 < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
void SLinsert(SL* ps, int pos, SLdatatype x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
expand(ps);
for (int i = ps->size; i-1>= pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(ps->size);
if (pos >= ps->size || pos < 0)
{
printf("pos is error\n");
}
for (int i = pos; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
int find(SL* ps, SLdatatype x)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
void print(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
cout << ps->arr[i] << ' ';
}
cout << endl;
}
3.2.1 init()函数的讲解,初始化顺序表
声明:
void init(SL* ps);
定义:
void init(SL* ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
初始化函数中要将arr初始化为空指针NULL,并且把容量和元素个数都赋值为0。
3.2.2 expand()函数的讲解,对顺序表扩容函数
我们对顺序表扩容,一般以二倍来扩容,以capacity的二倍来扩容。经过数学可推导出二倍扩容是最高效的,空间利用最合理。
声明:
void expand(SL* ps);
定义:
void expand(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcap = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLdatatype* tmp = (SLdatatype*)realloc(ps->arr,2 * newcap * sizeof(SLdatatype));
if (tmp == NULL)
{
perror("realloc");
return;
}
ps->capacity = newcap;
ps->arr = tmp;
}
}
(1).首先要判断顺序表是否已经满了,判断size与capacity是否一样即可。
(2).当最开始时,size与capicty都是0,所以当我们要插入元素时,我们要扩容,因为要以capacity的二倍来扩容,而capacity此时为0,0乘任何数都是0,所以我们此时要引入一个新变量newcap,用三目表达式,当capacity为0时,newcap为2,当capacity不为0是,newcap=2*capacity。
(3).因为realloc是对空间的扩容,realloc 会在原有内存的后面继续扩容,当扩容失败时,就会导致我们之前存储的数据全部丢失,所以我们又要引入一个指针变量 SLdatatype* tmp 先存储下扩容后的空间,然后在判断realloc 是否为NULL,当为NULL是,意味着扩容失败,报错然后返回空。当扩容成功,我们再把tmp赋值给arr,这样即使扩容失败也不会丢失arr内的数据。
3.2.3 pushfront()函数讲解(头插)
声明:
void pushfront(SL* ps, SLdatatype x);
定义:
void pushfront(SL* ps, SLdatatype x)
{
expand(ps);
for (int i = ps->size; i-1 >= 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
(1).首先要对顺序表进行判断,是否需要扩容,我们直接调用expand()函数,expand()函数内部会判断是否需要增容,如果size==capacity就扩容,否则就什么也不做。
(2).头插,就是在顺序表最前面插入一个数字,想象一下如果数组中你会怎么做? 所以我们可以把顺序表看作一个数组了,不就是要把全部元素向后整体挪动一位吗?
序号为执行的先后顺序,先从顺序表最后一个开始挪动,一直到第一个,就会使的第一个空出来。
(3).把x赋值给顺序表第一个位置,然后再将size++。
3.2.4 pushback()尾插
声明:
void pushback(SL* ps, SLdatatype x);
定义:
void pushback(SL* ps, SLdatatype x)
{
expand(ps);
ps->arr[ps->size++] = x;
}
我们会发现,尾插更加简单,直接按数组来。
(1). 由于size此时指向最后一个元素之后的空位置,所以直接把 x 赋值给arr[size],然后再将size++,等价于 arr[size++]=x;
3.2.5 popback()尾删
声明:
void popback(SL* ps);
定义:
void popback(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
(1).首先断言,要想删除元素,顺序表中必须要有元素,且顺序表不能为空指针。assert(ps)如果ps为NULL,则报错;assert(ps->size)如果size为0,则报错。
(2).通过两个断言后,顺序表一定有元素且不为空指针,则只需将size--即可,因为当我们插入元素时,新的元素会覆盖旧的元素,无需再将尾删的这个元素赋值为0或者其他值。
3.2.6 popfront() 头删
声明:
void popfront(SL* ps);
定义:
void popfront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i+1 < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
(1).首先,老规矩,我们要进行断言,要想删除元素,顺序表中必须要有元素,且顺序表不能为空指针,与尾删道理一样。
(2).想要删除顺序表中第一个元素,再次联想一下数组中,在数组中你会怎么做?不就是将整体向前挪动一位吗,最前面的一位就会被挤出去,就达到了删除第一个元素的目的了。
(3).遍历完顺序表后,一定要记得将size--。
3.2.7 SLinsert() 在指定位置插入元素
声明:
void SLinsert(SL* ps, int pos, SLdatatype x);
定义:
void SLinsert(SL* ps, int pos, SLdatatype x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
expand(ps);
for (int i = ps->size; i-1>= pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
(1).先判断顺序表是否为空指针,在判断pos(要插入的位置下标)是否大于等于0 并且 小于等于size。
(2).再判断是否需要扩容,我们直接调用expand()函数,expand()函数内部会判断是否需要增容,如果size==capacity就扩容,否则就什么也不做。
(3).我们想要在pos位置插入元素,则只需将pos及pos之后的元素,整体向后挪动一位,将pos位置空出来即可。
(4).然后将pos位置赋值为x,记得要将size++。
3.2.8 SLErase() 删除指定位置元素
声明:
void SLErase(SL* ps, int pos);
定义:
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(ps->size);
if (pos >= ps->size||pos<0)
{
printf("pos is error\n");
}
for (int i = pos; i < ps->size; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
(1).只要涉及到删除元素,就要判断顺序表是否为空,并且还要判断顺序表是否为空指针。
(2).在判断pos是否合法,pos必须大于等于0,并且小于size。
(3).要移除pos,只需将pos之后的整体向前移动一位。
(4).最后将size--。
3.2.9 find() 查找元素
声明:
int find(SL* ps, SLdatatype x);
定义:
int find(SL* ps, SLdatatype x)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
(1).先判断顺序表是否为空,或者为空指针。
(2).从0到size遍历顺序表即可,等于x就返回下标,如果循环结束还没有返回,则返回-1。
3.2.10 print() 打印顺序表
声明:
void print(SL* ps);
定义:
void print(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
cout << ps->arr[i] << ' ';
}
cout << endl;
}
(1).只需从头到尾遍历顺序表即可。
3.3 test.cpp 的讲解
包括了三个函数
(1).主函数
(2).测试函数
(3).菜单函数
test.cpp源码
#include"Sqlist.h"
SL ps;
void menu()
{
printf("****************************\n");
printf("***1.pushfront 2.pushback***\n");
printf("***3.popback 4.popfront***\n");
printf("***5.SLinsert 6.SLErase***\n");
printf("***7.find 0.exit***\n");
printf("***8.print ***\n");
printf("****************************\n");
}
void test()
{
int op;
int n;
SLdatatype x;
int pos;
do
{
menu();
printf("input option\n");
cin >> op;
switch (op)
{
case 1:
printf("input pushfront number\n");
cin >> n;
printf("input member\n");
for (int i = 0; i < n; i++)
{
cin >> x;
pushfront(&ps, x);
}
break;
case 2:
printf("input pushback number\n");
cin >> n;
printf("input member\n");
for (int i = 0; i < n; i++)
{
cin >> x;
pushback(&ps, x);
}
break;
case 3:
popback(&ps);
break;
case 4:
popfront(&ps);
break;
case 5:
printf("input pos\n"); cin >> pos;
printf("input x\n"); cin >> x;
SLinsert(&ps, pos, x);
break;
case 6:
printf("input pos\n"); cin >> pos;
SLErase(&ps, pos);
break;
case 7:
printf("input x\n"); cin >> x;
find(&ps, x);
break;
case 8:
print(&ps);
break;
case 0:
break;
default:
printf("error option\n");
break;
}
} while (op);
}
int main()
{
init(&ps);
test();
return 0;
}
测试:
总结:
怎么样?看了我的讲解是不是茅塞顿开了?
顺序表有以下注意点:
(1).每当删除元素时,一定要记得将size--。
(2).每当增加元素时,一定要记得将size++。
(3).删除元素时要先判断顺序表是否已经为空。
如果你喜欢我的博客,就点点小赞👍,谢谢各位大佬!!!