目录
1、线性表的概念
概念:线性表(linear list)是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构, 常见的线性表:顺序表、链表、栈、队列、字符串......线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2、顺序表
2.1 概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表是把线性表中的所有元素按照其逻辑顺序依次存储到指定存储位置开始的一块连续的存储区域。这样,线性表的第 1 个元素的存储位置就是指定的存储位置,第 i 个元素的存储位置紧接着第 i - 1 个元素的存储位置的后面。如下图:
顺序表的特点如下:
(1)在顺序表中,各个元素的逻辑顺序跟物理顺序一致,第 i 项就存在第 i 个位置。
(2) 对顺序表中的所有元素,既可以顺序访问,也可以随机访问。
2.2 分类
顺序表一般可以分为:
(1) 静态顺序表:使用定长数组存储元素。//顺序表的静态存储 #define N 5 typedef int SLDataType; typedef struct SeqList { SLDataType array[N]; //固定数组长度 int size; //有效数据的个数 }SeqList;
静态顺序表就是平时所使用的数组,数组在申请内存的时候,必须指定数组长度,它所需要的内存在编译时分配,空间开辟的大小是固定的。有时候我们所需要的空间大小在程序运行的时候才知道,那么数组在编译时开辟空间的方式就不能满足了。
(2) 动态顺序表:使用动态开辟的数组存储。
typedef int SLDataType; typedef struct SeqList { SLDataType* array; //指向动态开辟的数组 int capacity; //容量空间的大小 int size; //有效数据的个数 }SeqList;
使用动态开辟的数组存储,数组的长度不是固定的,是可变的,空间开辟的大小不是固定的,比如:一开始,可以先开辟数组长度为 5 内存空间,,也就是可以存储 5 个数组元素,后面空间不够则增容,就不会造成空间浪费。
2.3 动态顺序表的基本操作
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致 N 定大了,空 间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间 大小,所以下面我们实现动态顺序表。
动态顺序表的基础操作如下图:
源码如下:
test.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#define CapacityInit 5 //数组初始的元素个数
typedef int SLDataType;
typedef struct list
{
SLDataType* array; //指向动态开辟的数组
int capacity; //容量空间的大小
int size; //有效数据的个数
}SL;
void SeqListInit(SL* ps);//初始化
void SeqListPrint(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);//头删
int SeqListFind(SL* ps, SLDataType x);//找数据 x 的下标,找到返回 x 的下标,没有找到返回 0
void SeqListInsert(SL* ps, int pos, SLDataType x);//在指定的 pos 下标位置插入
void SeqListErase(SL* ps, int pos);//删除 pos 位置的数据
void SeqListDestory(SL* ps);//销毁动态开辟的内存
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"test.h"
void SeqListInit(SL* ps)//初始化
{
ps->array = (SLDataType*)calloc(CapacityInit,sizeof(SLDataType));//动态开辟数组
if (ps->array == NULL)//判断是否开辟失败
{
printf("开辟失败\n");
return;
}
ps->capacity = CapacityInit; //初始数组空间大小
ps->size = 0; //初始有效数据个数
}
void SeqListPrint(SL* ps)//打印顺序表中的数据
{
if (ps->size == 0)//判断顺序表是否为空
{
printf("顺序表为空\n");
return;
}
int i = 0;
for (i = 0; i < ps->size; i++) //遍历顺序表打印有效数据
{
printf("%d ", ps->array[i]);
}
printf("\n");
}
void SeqListCheckCapacity(SL* ps)//检查容量
{
if (ps->size == ps->capacity) //判断有效数据是否等于数组空间的容量
{
SLDataType* temp = (SLDataType*)realloc(ps->array, (ps->capacity + 5) * sizeof(SLDataType));//有效数据等于数组空间容量,扩容
if (temp != NULL)//判断是否扩容成功
{
ps->array = temp;
ps->capacity += 5;
printf("增容成功\n");
}
else
{
printf("增容失败\n");
return;
}
}
}
void SeqListPushBack(SL* ps, SLDataType x)//尾插
{
SeqListCheckCapacity(ps); //检查数组的空间容量是否足够插入数据
ps->array[ps->size] = x; //插入数据
ps->size++; //有效数据个数++
}
void SeqListPopBack(SL* ps)//尾删
{
if (ps->size > 0) //判断有效个数是否大于 0,大于 0 把有效数据个数减减,把最后一个数据去掉
{
ps->size--;
}
//②
//assert(ps->size > 0);
//ps->size--;
}
void SeqListPushFront(SL* ps, SLDataType x)//头插
{
SeqListCheckCapacity(ps);//检查数组的空间容量是否足够插入数据
int end = ps->size;
while (end > 0)
{
ps->array[end] = ps->array[end-1];//将数据往后移,留出第一个位置出来进行头插
--end;
}
ps->array[end] = x; //头插
ps->size++; //有效数据个数++
}
void SeqListPopFront(SL* ps)//头删
{
if (ps->size > 0)//判断有效数据个数是否大于0。意思就是有数据才删,没有数据不进行操作
{
int begin = 0;
while (begin < ps->size - 1)//数据往前移覆盖掉第一个数据,达到删除的效果
{
ps->array[begin] = ps->array[begin + 1];
begin++;
}
ps->size--;//成功删掉一个数据,就把有效数据个数减减
}
//②
//assert(ps->size > 0);
//int begin = 0;
//while (begin < ps->size - 1)
//{
// ps->array[begin] = ps->array[begin + 1];
// begin++;
//}
//ps->size--;
}
int SeqListFind(SL* ps, SLDataType x)//找数据 x 的下标,找到返回 x 的下标,没有找到返回 0
{
int i = 0;
for (i = 0; i < ps->size; i++)//遍历数组
{
if (ps->array[i] == x)//找到该数据的下标,返回下标
{
return i;
}
}
if (ps->size == i)
{
return 0;
}
}
void SeqListInsert(SL* ps, int pos, SLDataType x)//在指定的 pos 下标位置插入
{
SeqListCheckCapacity(ps);//检查数组的空间容量是否足够插入数据
if (pos > ps->size || pos < 0)//顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,在大于 ps->size 的位置插入就不符合顺序表的规则了
{
printf("pos invalid\n");
return;
}
int end = ps->size;
while (end > pos)//将pos位置的数据往后移,留出 pos 位置的空间,插入新的数据
{
ps->array[end] = ps->array[end - 1];
--end;
}
ps->array[pos] = x;
ps->size++;
//②
//assert(pos <= ps->size&&pos>0);
//int end = ps->size;
//while (end > pos)
//{
// ps->array[end] = ps->array[end - 1];
// --end;
//}
//ps->array[pos] = x;
//ps->size++;
}
void SeqListErase(SL* ps, int pos)//删除 pos 位置的数据
{
if (pos < ps->size && pos >= 0)//pos的范围数 0 ~ ps->size 之间的数据,包括 0 但不包括 size
{
while (pos < ps->size-1)
{
ps->array[pos] = ps->array[pos + 1];
pos++;
}
ps->size--;
}
//②
//assert(pos < ps->size&& pos>=0);
//while (pos < ps->size)
//{
// ps->array[pos] = ps->array[pos + 1];
// pos++;
//}
//ps->size--;
}
void SeqListDestory(SL* ps)//销毁动态开辟的内存
{
free(ps->array);//使用完动态开辟的内存空间,就是释放掉,防止内存泄漏
ps->array = NULL;//将指向动态开辟内存的指针置NULL,防止野指针
ps->capacity = 0;
ps->size = 0;
}
main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"test.h"
void Menu()
{
printf("|------------------------------------------------|\n");
printf("| 动态顺序表的基础操作 |\n");
printf("|------------------------------------------------|\n");
printf("|-----1.尾插 2.尾删--------------------|\n");
printf("|-----3.头插 4.头删--------------------|\n");
printf("|-----5.指定位置插入 6.删除指定位置数据--------|\n");
printf("|-----7.打印 8.查找指定数据的下标------|\n");
printf("|-----0.退出 ------------------------------------|\n");
printf("|------------------------------------------------|\n");
printf("请输入你的选择:> ");
}
void test()
{
SL sl;
SeqListInit(&sl);//初始化
SLDataType value = 0;
int pos = 0;
int input = 0;
do
{
Menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入插入的数据:> ");
scanf("%d", &value);
SeqListPushBack(&sl, value);
printf("插入成功\n");
break;
case 2:
SeqListPopBack(&sl);
printf("删除成功\n");
break;
case 3:
printf("请输入插入的数据:> ");
scanf("%d", &value);
SeqListPushFront(&sl,value);
printf("插入成功\n");
break;
case 4:
SeqListPopFront(&sl);
printf("删除成功\n");
break;
case 5:
printf("请输入插入位置:> ");
scanf("%d", &pos);
printf("请输入插入的数据:> ");
scanf("%d", &value);
SeqListInsert(&sl, pos, value);
printf("插入成功\n");
break;
case 6:
printf("请输入删除的位置:> ");
scanf("%d", &pos);
SeqListErase(&sl, pos);
printf("删除成功\n");
break;
case 7:
SeqListPrint(&sl);
break;
case 8:
printf("请输入查找的数据:> ");
scanf("%d", &value);
int ret = SeqListFind(&sl, value);
if (ret)
{
printf("数据: %d 的下标为: %d\n", value, ret);
}
else
{
printf("无此数据\n");
}
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
2.4 顺序表的缺点
(1)中间/头部的插入删除,时间复杂度为O(N)。
(2)增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
(3)增容增太多,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间;增太少会造成频繁增容,频繁申请新空间,拷贝数据,释放旧空间。会有不小的消耗。