文章目录
目录
前言
在我们生活当中我们所用的QQ,vx,它们所展示的界面(如群的名字,人的昵称)这些就是一组数据,这些数据都被保存在内存当中,界面是去内存当中将这些数据拿出来然后展示在这个界面上,所以它的底层可以用顺序表,或者链表来进行存储这些数据。
一、顺序表是什么?
顺序表是在计算机内存中以数组的形式保存的线性表,顺序表是简单的一种线性结构,逻辑上相邻的数据在计算机内的存储位置也是相邻的,可以快速定位第几个元素,中间不允许有空值,插入、删除时需要移动大量元素。
顺序表:线性表采用顺序存储的方式存储就称之为顺序表,顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中,所以一般用数组来存储。
二、顺序表的分类
1.静态顺序表(使用定长(长度是固定的)数组存储)
让我们先来看一看顺序表的静态存储结构是怎样的,上代码:
#define MAX_SIZE 100 //方便修改定长数组长度的大小,增强程序可维护性
typedef int SLDataType;//方便替换数据类型,增加程序可维护性
typedef struct SeqList
{
SLDataType array[MAX_SIZE];//定长数组
size_t size;//有效的数据个数
}SL;//重新命名这个数据类型为SL
2.动态顺序表(使用动态开辟的数组存储)
让我们先来看一看顺序表的动态存储结构是怎样的,上代码:
#define MAX_SIZE 10
typedef int SLDataType;
typedef struct SeqList
{
SLDataType *a; //指向动态开辟的数组
int size;//有效数据个数
int capacity;//容量空间的大小
}SL;
三、静态顺序表的增删查改
1.静态结构初始化
void InitSeqList(SL *psl)//传入顺序表结构体对象的地址,用指针接收
{
memset(psl->a, 0, MAX_SIZE*sizeof(SLDataType)); //利用memset函数初始化定长数组,这里也可以通过for循环来初始化数组
psl->size = 0; //置有效数为0
}
2.头插元素
void HeadInsert(SL *psl,int x) // x表达的是要头插的数
{
if (psl->size >= MAX_SIZE)//如果有效数据超出了最大容量,则终止程序
{
printf("SeqList is full!");
assert(psl->size < MAX_SIZE);//断言,如果里面条件为假,就终止程序
}
else
{
int end = psl->size - 1;//end为最后一个有效数据的下标
do
{
psl->a[end+1] = psl->a[end]; //从后往前依次往后挪
end--; //一直到下标为0
} while (end>=0);
psl->a[0] = x;
psl->size++;
}
}
补充:
3.尾插元素
void FinalInsert(SL *psl, int x)
{
if (psl->size >= MAX_SIZE)
{
printf("SeqList is full!");
assert(psl->size < MAX_SIZE);
}
else
{
psl->a[psl->size] = x; //将要插入的x放到下标为psl->size上,也就是末尾的有效数据加1的位置上
psl->size++;
}
}
4.自测头插尾插
我们要养成勤测代码的好习惯,避免写好后出现一大堆的错误
测试很成功!目前代码并没有问题
5.头删元素与尾删元素
void HeadDelete(SL *psl)
{
assert(psl->size > 0);//如果不成立直接报错
int start = 1; //下标访问
while (start < psl->size)
{
psl->a[start - 1] = psl->a[start]; //迭代过程
start++; //迭代条件
}
psl->size--;
}
void FinalDelete(SL *psl)
{
assert(psl->size > 0);
psl->size--; //通过直接控制有效数来删除
}
代码测试:
运行结果:
测试正确,并没有问题(要测试多次,防止偶然性的发生)
6.定位插入
void PosInsert(SL *psl, int pos, int x)//pos为插入的位置
{
assert(psl->size < MAX_SIZE);
int end = psl->size - 1;
do{
psl->a[end+1] = psl->a[end];//迭代过程
end--;
} while (end>=pos);迭代条件
psl->a[pos] = x;//插入位置
psl->size++;
}
分析图:
end首先是作为最后一个有效数据的下标,将最后一个有效数据往后移(前提是后面有容量),end--,重复以上过程,一直到pos = end时候,我们选定的位置就空出来了 ,再进行赋值。
7.定位删除
void PosDelete(SL *psl,int pos)
{
assert(psl->size > 0); //断言如果不成立直接报错
int start = pos + 1; //下标引用 pos为删除的位置
while (start <= psl->size - 1) //迭代条件
{
psl->a[start - 1] = psl->a[start]; //迭代过程
start++;
}
psl->size--;
}
分析图:
pos是要删除的位置,这时候我们可以把pos后面的数据拿到pos这里来,把pos原来的值进行覆盖,这样pos后面的位置就空出来了(也就是I这个长方形图),因为是顺序存储 ,然后再把后面的数据往前移(变成图II)一直到start 小于psl->size - 1结束(因为psl->size -1是最后一个数据的下标)
8.对静态顺序表的总结
我们发现对于静态顺序表,它的容量是一个定值,很容易造成空间浪费严重的问题,过大浪费,过小又保存不了,这时候为了避免这个问题,用动态顺序表就大大提高了空间的利用率,减少了空间的浪费。
四、动态顺序表的增删查改
1.动态顺序表的初始化
void InitSeqList(SL *psl)
{
psl->a = NULL; //用指针来指向那片动态开辟的数组,先进行初始化为0
psl->size = 0;//有效数据,与静态顺序表无异
psl->capacity = 0; //容量,相当于静态顺序表中的MAX_SIZE,只不过这个是动态的可以变化
}
2.尾插元素
void FinalInsert(SL *psl, int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败!");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
psl->a[psl->size] = x;
psl->size++;
}
因为容量一开始是0,所以一开始必须给这个动态数组分配空间,如果空间为0,那么就要分配给20个空间给这个动态数组,如果有空间,空间又被装满了,那么则分配上次空间的2倍作为新的空间,如果分配成功,则让psl->a维护那片动态开辟的空间,容量换为新的容量,最后再进行尾插操作(与静态顺序表类似)。若没有分配成功,则要输出分配失败,并退出程序。
3.头插元素
void HeadInsert(SL *psl,int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
int end = psl->size - 1;
do
{
psl->a[end+1] = psl->a[end];
end--;
} while (end>=0);
psl->a[0] = x;
psl->size++;
}
与尾插类似先判断容量是不是够用,然后就跟静态顺序表中的头插思路一样
4.展示元素
void PrintSeqList(SL *psl)
{
for (int i = 0; i < psl->size; i++)//用有效数据控制
{
printf("%d ", psl->a[i]);
}
}
5.尾删元素
void FinalDelete(SL *psl)
{
assert(psl->size > 0);
psl->size--;
}
6.头删元素
void HeadDelete(SL *psl)
{
assert(psl->size > 0);
int start = 1;
while (start < psl->size)
{
psl->a[start - 1] = psl->a[start];
start++;
}
psl->size--;
}
7.删除指定元素
void PosDelete(SL *psl,int pos)
{
assert(psl->size > 0);
int start = pos + 1;
while (start <= psl->size - 1)
{
psl->a[start - 1] = psl->a[start];
start++;
}
psl->size--;
}
8.插入指定元素
void PosInsert(SL *psl, int pos, int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败!");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
int end = psl->size - 1;
do{
psl->a[end+1] = psl->a[end];
end--;
} while (end>=pos);
psl->a[pos] = x;
psl->size++;
}
原理相同,先判断要不要开辟空间然后后面的代码就跟顺序表差不多
五、代码分布
1.SeqLIst.h
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<malloc.h>
#define MAX_SIZE 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType *a;
int size;
int capacity;
}SL;
void InitSeqList(SL *psl);
//头插,尾插,头删,尾删
void HeadInsert(SL *psl,int x);
void FinalInsert(SL *psl,int x);
void HeadDelete(SL *psl);
void FinalDelete(SL *psl);
void PrintSeqList(SL *psl);
void PosInsert(SL *psl, int pos, int x);
void PosDelete(SL *psl, int pos);
2.SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
void InitSeqList(SL *psl)
{
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void HeadInsert(SL *psl,int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = (SLDataType *)realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
int end = psl->size - 1;
do
{
psl->a[end+1] = psl->a[end];
end--;
} while (end>=0);
psl->a[0] = x;
psl->size++;
}
void FinalInsert(SL *psl, int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = (SLDataType *)realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败!");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
psl->a[psl->size] = x;
psl->size++;
}
void HeadDelete(SL *psl)
{
assert(psl->size > 0);
int start = 1;
while (start < psl->size)
{
psl->a[start - 1] = psl->a[start];
start++;
}
psl->size--;
}
void FinalDelete(SL *psl)
{
assert(psl->size > 0);
psl->size--;
}
void PosInsert(SL *psl, int pos, int x)
{
if (psl->size == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 20 : psl->capacity * 2;
SLDataType *temp = (SLDataType *)realloc(psl->a, newcapacity*sizeof(SLDataType));
if (temp == NULL)
{
printf("分配失败!");
exit(-1);
}
psl->a = temp;
psl->capacity = newcapacity;
}
int end = psl->size - 1;
do{
psl->a[end+1] = psl->a[end];
end--;
} while (end>=pos);
psl->a[pos] = x;
psl->size++;
}
void PosDelete(SL *psl,int pos)
{
assert(psl->size > 0);
int start = pos + 1;
while (start <= psl->size - 1)
{
psl->a[start - 1] = psl->a[start];
start++;
}
psl->size--;
}
void PrintSeqList(SL *psl)
{
for (int i = 0; i < psl->size; i++)
{
printf("%d", psl->a[i]);
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
void menu()
{
printf("\n**********************************\n");
printf("**********************************\n");
printf("***********0.退出程序*************\n");
printf("***********1.头插*****************\n");
printf("***********2.尾插*****************\n");
printf("***********3.头删*****************\n");
printf("***********4.尾删*****************\n");
printf("***********5.定位插***************\n");
printf("***********6.定位删***************\n");
printf("**********************************\n");
printf("**********************************\n");
printf("**********************************\n");
}
void SeqListTest01()
{
SL sl;
InitSeqList(&sl);
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出程序!\n");
break;
case 1:
{
int x = 0;
printf("请输入要头插的数字>");
scanf("%d", &x);
HeadInsert(&sl, x);
PrintSeqList(&sl);
break;
}
case 2:
{
int x = 0;
printf("请输入要尾插的数字>");
scanf("%d", &x);
FinalInsert(&sl, x);
PrintSeqList(&sl);
break;
}
case 3:
{
printf("成功删除\n");
HeadDelete(&sl);
PrintSeqList(&sl);
break;
}
case 4:
{
printf("成功删除\n");
FinalDelete(&sl);
PrintSeqList(&sl);
break;
}
case 5:
{
int x = 0;
int pos = 0;
printf("请输入想要插入的数字>");
scanf("%d", &x);
printf("请输入想要插入的位置的下标>");
getchar();
scanf("%d", &pos);
PosInsert(&sl, pos, x);
PrintSeqList(&sl);
break;
}
case 6:
{
int pos = 0;
printf("请输入想要删除的位置的下标>");
scanf("%d", &pos);
printf("成功删除\n");
PosDelete(&sl, pos);
PrintSeqList(&sl);
break;
}
}
} while (input);
}
int main()
{
SeqListTest01();
return 0;
}
代码测试
六、静态顺序表与动态顺序表的类比
细心的网友们都发现了,动态顺序表其实跟静态顺序表上的代码大致上一样的,但是开辟的方式却有所不同,一个是动态开辟的用了realloc一个是静态开辟的就是用的数组,让我们来比较一下他们两个的优缺点吧
扩容方法:动态开辟一块新的空间(一般是为原空间的两倍),将原空间的数据拷贝到新的空间,然后让指针指向新的空间。
优缺点比较
静态顺序表创建空间时为静态开辟,没用realloc函数,代码简单,不存在内存泄露问题
动态顺序表,动态开辟空间,使用灵活,减少了空间浪费。