顺序表的特点:要求数据从第一个位置开始放并且连续存储
顺序表缺陷:
1,空间不够需要增容,但增容是要付出代价的(比如增容的程度较小时,顺序表所在的内存区域后面还有足够的空间,可以原地扩容,但增容的程度较大时,顺序表所在内存区域可能就没有足够的空间了,这是就需要异地扩容,即换一个空间足够的内存区域,这是的代价就相对较大,因为需要将表中的每一个元素都复制过去)。
2,为避免溢出,需要在增加元素的过程中对其进行扩容,为避免频繁扩容,基本每次扩容都是将容量扩大二倍,这就可能导致一定的空间浪费。
3,顺序表要求数据从开始位置连续存储,那么想要在顺序表头部或者中部插入删除数据就需要挪动数据,效率不高。
顺序表的头文件seqlist.h(一般在头文件中声明,在源文件中实现,对于创建顺序表的结构,属于声明部分,即说明我所要建立的线性表中包含哪些结构、内容,因此这部分一般放在头文件中)
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//#define N 10000
//typedef int SLDataType;
//
静态顺序表
//typedef struct SeqList
//{
// SLDataType a[N];
// int size; // 表示数组中存储了多少个数据
//}SL;
//
接口函数 -- 命名风格是跟着STL走的,建议大家也跟着我们上课走,方便后续学习STL
//void SeqListInit(SL* ps);
//
静态特点:如果满了就不让插入 缺点:给多少的合适呢?这个很难确定
N给小了不够用,N给大了浪费
//void SeqListPushBack(SL* ps, SLDataType x);
//void SeqListPopBack(SL* ps);
//void SeqListPushFront(SL* ps, SLDataType x);
//void SeqListPopFront(SL* ps);
...
typedef int SLDataType;
// 动态顺序表
typedef struct SeqList//定义一个SeqList类型的结构体变量struct SeqList,并将其名称定义为SL
{
SLDataType* a;//定义一个指向该结构体的指针a,该指针实际上是指向顺序表中的某一个元素,因此该指针的类型
//就需要是顺序表中的元素类型,亦即SLDatatype类型
int size; // 表示数组中存储了多少个数据
int capacity; // 数组实际能存数据的空间容量是多大
}SL;
//即定义一个表示顺序表的结构体变量时,内部要包括指向该结构体的指针,顺序表的容量和实际存储数据的数量
//头文件中都是声明,源文件中都是具体的实现,所以对于顺序表结构体的建立才放在头文件中
// 接口函数 -- 命名风格是跟着STL走的,建议大家也跟着我们上课走,方便后续学习STL
void SeqListPrint(SL* ps);//SL* ps代表指向结构体SL的指针变量,亦即结构体SL类型的指针变量
void SeqListInit(SL* ps);
void SeqListDestory(SL* ps);
void SeqListCheckCapacity(SL* ps);
void SeqListPushBack(SL* ps, SLDataType x);
void SeqListPopBack(SL* ps);
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);
// ...
// 找到了返回x位置下标,没有找打返回-1
int SeqListFind(SL* ps, SLDataType x);
// 指定pos下标位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x);
// 删除pos位置的数据
void SeqListErase(SL* ps, int pos);
顺序表的源文件,本文件中包含的是在头文件中所定义函数的具体实现
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"//引用我定义的头文件seqlist.h
void SeqListPrint(SL* ps)//打印顺序表,函数需要的参数即指向该结构体的指针,而结构体中拥有函数所需要的一切信息,那么我也就不需要引用其它变量了,以下定义的这些函数都同理
{
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListInit(SL* ps)//对于顺序表的初始化
{
ps->a = NULL;
ps->size = ps->capacity = 0;//将结构体中的一切都置为空,即将指针置为空指针,将数值置为0
}
void SeqListDestory(SL* ps)//顺序表的销毁
{
free(ps->a);//将顺序表结构体中的指针指向的空间(并不改变指针的值)即将这部分空间返还给系统,指针还是原来的指针
ps->a = NULL;
ps->capacity = ps->size = 0;//将指针和数值置为空
}
void SeqListCheckCapacity(SL* ps)//检测顺序表的空间
{
// 如果没有空间或者空间不足,那么我们就扩容
if (ps->size == ps->capacity)//如果顺序表的容量等于顺序表中存储元素的个数
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//经典的a?b:c格式,将该式的运算结果赋值给整型变量newcapacity
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//tmp即tmporary,临时的,定义一个该顺序表中变量类型的指针变量tmp,利用realloc函数重新分配一块空间,该空间的地址是顺序表
//的地址,空间大小是单个元素所占字节数的newcapacity倍
if (tmp == NULL)//利用malloc函数重新分配空间后,指向分配过后的区域的指针仍为空,那么说明申请空间失败,需要在程序中说明一下
{
printf("realloc fail\n");
exit(-1);//exit() 结束当前进程/当前程序,在整个程序中,只要调用 exit ,就结束。exit(1)表示进程正常退出.返回 1;exit(0)表示进程非正常退出.返回 0.
}
ps->a = tmp;//将指针tmp所指向的区域赋给指针a
ps->capacity = newcapacity;//将代表新空间容量的值赋给原先的代表容量的变量capacity
}
}
void SeqListPushBack(SL* ps, SLDataType x)//顺序表尾插函数(头插和尾插都涉及到特定的元素,因此要比其他的函数多引用一个变量,即SLDatatype类型的变量x,这个x不包括在原结构体中,因此需要额外引用,相比之下,头删
//和尾删虽然都涉及元素的变动,但所有变量仍局限在原结构体的范围之内,因此仍只需要引用原结构体即可)
{
SeqListCheckCapacity(ps);//直接引用上面定义的顺序表空间检测函数,保证本函数不会受空间不足的影响
ps->a[ps->size] = x;//size即数组的容量,a[size]即现有数组最后一个元素再后一个,将这个位置赋值为x
ps->size++;//赋值完毕后容量加一
}
void SeqListPopBack(SL* ps)//顺序表尾删函数
{
// 温柔处理方式
//if (ps->size > 0)
//{
// //ps->a[ps->size - 1] = 0;
// ps->size--;
//}
// 暴力处理方式
assert(ps->size > 0);//assert是断言函数,只有函数括号内的式子成立时,assert下面的程序才能正常运行
ps->size--;
}
void SeqListPushFront(SL* ps, SLDataType x)//顺序表前插函数
{
SeqListCheckCapacity(ps);//只要涉及到顺序表的增加,都要先保证空间
// 挪动数据
int end = ps->size - 1;//此时end的值等于顺序表最后一个元素的下表
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];//将该元素向后移动一位
--end;
}//最后一个循环,当end=0时,整个顺序表的第一个元素也被向后移动了一位,亦即整个顺序表全体都向后移动了一位,第一个位置就被空了出来,便于插入元素
ps->a[0] = x;
ps->size++;//这里要强调size加一的原因时因为前面定义的顺序表打印函数是以size的大小为依据来打印内容的
}
void SeqListPopFront(SL* ps)//顺序表头删函数
{
assert(ps->size > 0);
// 挪动数据
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];//从前往后,依次将除第一个元素外的每一个元素都向前移动一位
++begin;
}
ps->size--;
}
函数的源文件(前面的两个文件做好了准备工作,源文件则利用这些实现我的目的)
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"//引用我定义的头文件,这里不需要引用源文件seqlist.c,因为对于其中函数的调用无需额外说明,只需要在程序中直接使用即可
void TestSeqList1()
{
SL sl;//这条语句的作用与int a一样,即定义SL型结构体变量sl
SeqListInit(&sl);//初始化
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPushBack(&sl, 5);//分别尾插1,2,3,4,5
SeqListPrint(&sl);//打印出该顺序表
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);
SeqListPopBack(&sl);//分别尾删五个元素
SeqListPrint(&sl);//打印出顺序表
SeqListPushBack(&sl, 10);
SeqListPushBack(&sl, 20);//再次尾插两个元素
SeqListPrint(&sl);//打印出顺序表
SeqListDestory(&sl);//将该顺序表置空
}
void TestSeqList2()
{
SL sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPushBack(&sl, 5);
SeqListPrint(&sl);
SeqListPushFront(&sl, 10);
SeqListPushFront(&sl, 20);
SeqListPushFront(&sl, 30);
SeqListPushFront(&sl, 40);
SeqListPrint(&sl);
SeqListPopFront(&sl);
SeqListPopFront(&sl);
SeqListPrint(&sl);
//SeqListDestory(&sl);
}
int main()
{
//TestSeqList1();
TestSeqList2();
return 0;
}