线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
简单来说,线性表就是在逻辑上呈一条直线的形式,用于存放n个具有相同特性的数据的结构。
顺序表(sequence list)是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为: 1. 静态顺序表:使用定长数组存储元素。 2.动态顺序表:使用动态开辟的数组进行存储,在需要时可以进行扩容。
1)静态顺序表
#define MAXSIZE 7 //数组最大长度
typedef int SeqListType; //定义顺序表数据类型,容易做修改
typedef struct SeqList
{
SeqListType data[MAXSIZE];//定长数组
int size; //有效数据个数
};
2)动态顺序表
typedef int SeqListType; //定义顺序表数据类型,容易做修改
typedef struct SeqList
{
SeqListType* data;//指向动态开辟的数组
int size; //有效数据个数
int capacity;//容量
}SL;
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空 间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
动态顺序表要求:
a.插入数据,空间不够了要增容
b.要求数据是依次存储的
缺陷:
a.空间不够进行增容;增容会付出一定的性能消耗,其次可能存在一定的空间浪费,
b.在头部或者中部左右的插入效率低,时间复杂度 o(N)
解决方法:
a.空间上,按需给空间
b.不要求物理空间上的连续,就不需要移动数据
1)结构定义:动态顺序表的结构中由三部分组成:其一是一个指向动态开辟数组的指针;其二是有效数据的个数,便于对数组中的元素进行定位;其三是容量,便于在数组空间进行判定在不够时进行扩容。
typedef int SeqListType; //定义顺序表数据类型,容易做修改
typedef struct SeqList
{
SeqListType* data;//指向动态开辟的数组
int size; //有效数据个数
int capacity;//容量
}SL;
2)初始化:动态顺序表的初始化主要就是开辟一个动态数组用于存放数据,并对有效数据个数与容量赋值。
//初始化函数
void SLInit(SL* p) {
p->data = (SeqListType*)calloc(sizeof(SeqListType), MAXSIZE);//动态内存开辟,MAXSIZE为自定义的数值
if (p->data == NULL) {
perror("calloc");
return;
}
p->size = 0;
p->capacity = MAXSIZE;
}
3)尾插:将目标元素插入在顺序表中的尾部。注意每次插入元素都要进行容量判断,判断顺序表时需要进行扩容。
//扩容判定
void CheckCapacity(SL* p) {
if (p->capacity == p->size) {
SeqListType* tmp = (SeqListType*)realloc(p->data,2*sizeof(SeqListType)*p->capacity);//开辟更大空间的动态内存
if (p->data == NULL) {
perror("realloc");
return;
}
p->capacity *= 2;//增加容量
p->data = tmp;//让指针指向新的动态内存区域
}
}
//尾插
void SLPushBack(SL* p,SeqListType x) {
//判定是否需要扩容,在需要时要进行扩容
CheckCapacity(p);
p->data[p->size] = x;//将最后一个位置赋值为插入的元素
p->size++;//有效数据个数加一
}
4)头插:将目标元素插入在顺序表起始位置。在顺序表中,插入到起始位置,需要将当前起始位置及之后的所有元素都向后移一位,为目标元素腾出空间。
//头插
void SLPushFront(SL* p,SeqListType x) {
CheckCapacity(p);
//将顺序表中的所有元素向后移动一位
for (int i = p->size; i > 0;i--) {
p->data[i] = p->data[i - 1];
}
//将首元素位置的数据令为插入数据
p->data[0] = x;
//有效数据个数加一
p->size++;
}
5)尾删:删除顺序表尾部的元素。可以直接将有效数据减一,最后一个元素虽然依然存在但用户无法进行访问。
//尾删
void SLPopBack(SL* p) {
//顺序表中有元素才可以删除
assert(p->size > 0);
//有效数据个数减一,最后一个元素就访问不到了
p->size--;
}
6)头删:删除顺序表首位元素,需要将顺序表中的所有元素向前移动一位。
//头删
void SLPopFront(SL* p) {
assert(p->size > 0);
//将顺序表中的所有元素向前移动一位
for (int i = 0; i < p->size - 1; i++) {
p->data[i] = p->data[i + 1];
}
p->size--;
}
7)按位置插入:定位目标所在位置,并将目标位置及其之后的元素都往后移动一位。注意:数组下标是从零开始的,要在传输前或传输后对目标位置进行处理将目标位置减一,此处我在传输前就对目标位置进行了减一,所以采用正常情况进行处理。
//按位置插入
void SLInsert(SL* p,int pos,SeqListType x) {
//插入位置在顺序表中存在
assert(pos <= p->size);
//扩容判定
CheckCapacity(p);
//将目标位置及之后的元素都向后移动一位
int end = p->size - 1;
while (end >= pos) {
p->data[end + 1] = p->data[end];
end--;
}
//为目标位置赋值
p->data[pos] = x;
p->size++;
}
8)按位置删除:锁定目标位置,将该位置之后的元素都向前移动一位,对该位置的值进行覆盖。
//按位置删除
void SLErase(SL* p,int pos) {
assert(pos < p->size);
//将目标位置之后的数据都向前移动一位
int start = pos;
while (start < p->size - 1) {
p->data[start] = p->data[start + 1];
++start;
}
p->size--;
}
9)按位置查找:直接对目标位置进行定位,将目标位置的值返回。
//按位置查找
SeqListType SLFindByLocation(SL* p,int pos) {
assert(pos < p->size);
//返回该位置元素
return p->data[pos];
}
10)按值查找:从起始位置开始对目标值进行判定,将满足目标的首个位置返回。
//按值查找
int SLFindByValue(SL* p,SeqListType x) {
//在整个顺序表中对该值所处位置进行查找,如有多个位置,则只会显示最前面的一个位置
for (int i = 0; i < p->size;i++) {
if (p->data[i] == x) {
return i;
}
}
return -1;
}
11)打印:使用循环将顺序表中的元素全都打印在显示窗口中。
//打印
void SLPrintf(SL* p) {
//将顺序表中左右元素打印出来
for (int i = 0; i < p->size;i++) {
printf("%d ", p->data[i]);
}
printf("\n");
}
12)修改:直接根据有效数据个数对目标数据进行定位,然后对其值进行修改。
//修改
void SLModify(SL* p,int pos,SeqListType x) {
assert(pos < p->size);
//对指定位置的元素的值进行覆盖
p->data[pos] = x;
}
13)销毁:将动态开辟的内存空间进行释放。
//销毁
void SLDestroy(SL* p) {
//将动态内存释放
free(p);
p = NULL;
}
14)保存:这样设计表的数据完整的保存在一个文本文件中,功能需要用到文件操作的相关函数。
//保存
void SLSave(SL* p) {
//打开一个文件进行写入
FILE* f = fopen("SeqList.txt","wb");
if (f == NULL) {
perror("fopen");
return;
}
//将顺序表数据都写入其中
for (int i = 0; i < p->size;i++) {
fwrite(p->data+i,sizeof(SeqListType),1,f);
}
//关闭文件
fclose(f);
f = NULL;
}
1、SeqList.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#define MAXSIZE 2
typedef int SeqListType; //定义顺序表数据类型,容易做修改
typedef struct SeqList
{
SeqListType* data;//指向动态开辟的数组
int size; //有效数据个数
int capacity;//容量
}SL;
//初始化
void SLInit(SL* p);
//尾插
void SLPushBack(SL* p, SeqListType x);
//头插
void SLPushFront(SL* p, SeqListType x);
//尾删
void SLPopBack(SL* p);
//头删
void SLPopFront(SL* p);
//插入
void SLInsert(SL* p, int pos, SeqListType x);
//按位置删除
void SLErase(SL* p, int pos);
//按位置查找
SeqListType SLFindByLocation(SL* p, int pos);
//按值查找
int SLFindByValue(SL* p, SeqListType x);
//打印
void SLPrintf(SL* p);
//修改
void SLModify(SL* p, int pos, SeqListType x);
//保存
void SLSave(SL* p);
//销毁
void SLDestroy(SL* p);
2、SeqList.c
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
//初始化函数
void SLInit(SL* p) {
p->data = (SeqListType*)calloc(sizeof(SeqListType), MAXSIZE);//动态内存开辟,MAXSIZE为自定义的数值
if (p->data == NULL) {
perror("calloc");
return;
}
p->size = 0;
p->capacity = MAXSIZE;
}
//扩容判定
void CheckCapacity(SL* p) {
if (p->capacity == p->size) {
SeqListType* tmp = (SeqListType*)realloc(p->data,2*sizeof(SeqListType)*p->capacity);//开辟更大空间的动态内存
if (p->data == NULL) {
perror("realloc");
return;
}
p->capacity *= 2;//增加容量
p->data = tmp;//让指针指向新的动态内存区域
}
}
//尾插
void SLPushBack(SL* p,SeqListType x) {
//判定是否需要扩容,在需要时要进行扩容
CheckCapacity(p);
p->data[p->size] = x;//将最后一个位置赋值为插入的元素
p->size++;//有效数据个数加一
}
//头插
void SLPushFront(SL* p,SeqListType x) {
CheckCapacity(p);
//将顺序表中的所有元素向后移动一位
for (int i = p->size; i > 0;i--) {
p->data[i] = p->data[i - 1];
}
//将首元素位置的数据令为插入数据
p->data[0] = x;
//有效数据个数加一
p->size++;
}
//尾删
void SLPopBack(SL* p) {
//顺序表中有元素才可以删除
assert(p->size > 0);
//有效数据个数减一,最后一个元素就访问不到了
p->size--;
}
//头删
void SLPopFront(SL* p) {
assert(p->size > 0);
//将顺序表中的所有元素向前移动一位
for (int i = 0; i < p->size - 1; i++) {
p->data[i] = p->data[i + 1];
}
p->size--;
}
//按位置插入
void SLInsert(SL* p,int pos,SeqListType x) {
//插入位置在顺序表中存在
assert(pos <= p->size);
//扩容判定
CheckCapacity(p);
//将目标位置及之后的元素都向后移动一位
int end = p->size - 1;
while (end >= pos) {
p->data[end + 1] = p->data[end];
end--;
}
//为目标位置赋值
p->data[pos] = x;
p->size++;
}
//按位置删除
void SLErase(SL* p,int pos) {
assert(pos < p->size);
//将目标位置之后的数据都向前移动一位
int start = pos;
while (start < p->size - 1) {
p->data[start] = p->data[start + 1];
++start;
}
p->size--;
}
//按位置查找
SeqListType SLFindByLocation(SL* p,int pos) {
assert(pos < p->size);
//返回该位置元素
return p->data[pos];
}
//按值查找
int SLFindByValue(SL* p,SeqListType x) {
//在整个顺序表中对该值所处位置进行查找,如有多个位置,则只会显示最前面的一个位置
for (int i = 0; i < p->size;i++) {
if (p->data[i] == x) {
return i;
}
}
return -1;
}
//打印
void SLPrintf(SL* p) {
//将顺序表中左右元素打印出来
for (int i = 0; i < p->size;i++) {
printf("%d ", p->data[i]);
}
printf("\n");
}
//修改
void SLModify(SL* p,int pos,SeqListType x) {
assert(pos < p->size);
//对指定位置的元素的值进行覆盖
p->data[pos] = x;
}
//销毁
void SLDestroy(SL* p) {
//将动态内存释放
free(p);
p = NULL;
}
//保存
void SLSave(SL* p) {
//打开一个文件进行写入
FILE* f = fopen("SeqList.txt","wb");
if (f == NULL) {
perror("fopen");
return;
}
//将顺序表数据都写入其中
for (int i = 0; i < p->size;i++) {
fwrite(p->data+i,sizeof(SeqListType),1,f);
}
//关闭文件
fclose(f);
f = NULL;
}
3、Test.c
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void menu() {
printf("*******************************\n");
printf("***1、头插 2、尾插 ***\n");
printf("***3、头删 4、尾删 ***\n");
printf("***5、打印 6、按位置查找***\n");
printf("***7、按值查找 8、修改 ***\n");
printf("***9、插入 10、删除 ***\n");
printf("***11、保存 12、销毁 ***\n");
printf("***-1、退出 ***\n");
printf("*******************************\n");
}
int main() {
SL s;
SLInit(&s);
int input = 0;
SeqListType x;
int pos;
SeqListType ret1;
int ret2;
do{
menu();
printf("请输入你想进行的操作:");
scanf("%d",&input);
switch (input) {
case 1:
printf("请输入你要插入的数据,以-1结束\n");
do {
scanf("%d", &x);
if (x != -1)
{
SLPushFront(&s, x);
}
} while (x != -1);
break;
case 2:
printf("请输入你要插入的数据,以-1结束\n");
do {
scanf("%d", &x);
if (x != -1)
{
SLPushBack(&s, x);
}
} while (x != -1);
break;
case 3:
SLPopFront(&s);
break;
case 4:
SLPopBack(&s);
break;
case 5:
SLPrintf(&s);
break;
case 6:
printf("请输入你想要查找的位置:");
scanf("%d",&pos);
ret1 = SLFindByLocation(&s, --pos);
printf("该处的值为:%d\n",ret1);
break;
case 7:
printf("请输入你想要查找的值:");
scanf("%d", &x);
ret2 = SLFindByValue(&s, x);
printf("该值的序号为:%d\n", ++ret2);
break;
case 8:
printf("请输入你想要修改的位置:");
scanf("%d", &pos);
printf("请输入修改后的值:");
scanf("%d",&x);
SLModify(&s, --pos,x);
break;
case 9:
printf("请输入你想要插入的位置:");
scanf("%d", &pos);
printf("请输入你想要插入的值:");
scanf("%d", &x);
SLInsert(&s, --pos, x);
break;
case 10:
printf("请输入你想要删除的位置:");
scanf("%d", &pos);
SLErase(&s, --pos);
break;
case 11:
SLSave(&s);
break;
case 12:
SLDestroy(&s);
break;
case -1:
break;
}
} while (input != -1);
return 0;
}