目录
1、数据结构中的线性表是什么?
为了解释这个概念,我摘用《数据结构与算法分析》一书中的概念,即:线性表是线性结构的抽象,线性结构的特点是结构中的元素之间存在一对一的线性关系。这种一对一的的关系主要指数据元素之间的位置关系,包括:
- 除第一个位置的数据元素以外,其他数据元素的位置的前面都只有一个数据元素;
- 除最后一个位置元素以外,其他数据元素位置的后面都只有一个元素(这里说的不是很严谨,后面会解释)。
从上述的解释中我们可以知道:线性表中的数据元素是一个接着一个的排列,其在逻辑上是线性结构,也就是说是一条连续的直线,但是在物理结构方面,其并不一定是连续的。因此可以把线性表想象为一种数据元素序列的数据结构。
常见的线性表有:顺序表、链表、栈、队列、字符串……。线性表在物理上的存储,通常以数组和链式结构的形式存储。本节主要讲解线性表中的顺序表相关知识。
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
顺序表示意图
2、顺序表
在计算机中,保存线性表最简单、最自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储。顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表分为静态顺序表和动态顺序表两种类型,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/b521a055e294a437736760488e236171.png)
1 | 2 | 3 | 4 | 5 | 6 |
静态顺序表,开辟定长数组空间,存储数据个数为有效固定值。
![](https://i-blog.csdnimg.cn/blog_migrate/28f95d7e400a2253ba797b3e2c89d2ed.png)
二者区别:静态顺序表只适用于确定知道需要多少数据的场景。当静态顺序表中的N相对于所需要存储的数据相差很大时,就会导致系统空间浪费,而且当需要存储的数据足够多时,会存在开辟的空间不够用的情况。为此,现实生活中,我们基本都是使用动态顺序表,根据需要动态分配空间大小,所以接下来,我将主要围绕动态顺序表作进一步讲解。
3、动态顺序表中数据的增删查改问题
3.1动态顺序表总体框架
#pragma once
#include<stdio.h>
#include<assert.h>
//assert用法,assert()括号中为真,则执行后面语句,如果为假,直接报错
//动态顺序表
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态数组指针
int size;//存储数据个数
int capacity;//动态开辟空间容量大小
}SL;
//初始化动态顺序表
void SLInit(SL*ps);
//打印顺序表
void SLPrint(SL*ps);
//尾插顺序表
void SLPushBack(SL*ps, SLDataType x);
//检查空间容量函数
void SLCheck(SL*ps);
//头插顺序表
void SLPushFront(SL*ps, SLDataType x);
//尾删顺序表
void SLPopBack(SL*ps);
//头删顺序表
void SLPopFront(SL*ps);
//顺序表在pos的位置插入x
void SLInsert(SL*ps,int pos, SLDataType x);
//顺序表在pos的位置删除x
void SLErase(SL*ps, int pos);
//顺序表销毁
void SLDestory(SL*ps);
//查找数组中的某个数,并返回其下标
int SLFind(SL*ps, SLDataType x);
//替换数组中的某个元素
void SLModify(SL*ps, int pos, SLDataType x);
3.2 初始化动态顺序表
//初始化动态顺序表
void SLInit(SL*ps)
{
assert(ps != NULL);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
3.3 顺序表打印函数
//打印顺序表
void SLPrint(SL*ps)
{
assert(ps != NULL);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
}
3.4 尾插顺序表
//检查空间容量函数
void SLCheck(SL*ps)
{
assert(ps != NULL);
//检查容量并扩容
if (ps->size == ps->capacity)
{
int newcapacity = 0;
newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//初始化为4个,后面以二倍数扩容
SLDataType*tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
return;//这里也可用exit(-1)代替
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
//尾插顺序表
void SLPushBack(SL*ps, SLDataType x)
{
assert(ps != NULL);
//首先检查容量并扩容
SLCheck(ps);
//扩容成功,添加数据
ps->a[ps->size] = x;
ps->size++;
}
3.5 头插顺序表
//检查空间容量函数
void SLCheck(SL*ps)
{
assert(ps != NULL);
//检查容量并扩容
if (ps->size == ps->capacity)
{
int newcapacity = 0;
newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//初始化为4个,后面以二倍数扩容
SLDataType*tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
return;//这里也可用exit(-1)代替
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
//头插顺序表
void SLPushFront(SL*ps, SLDataType x)
{
assert(ps != NULL);
//首先检查容量并扩容
SLCheck(ps);
//扩容成功,添加数据的同时向后挪动数据
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
3.6 尾删顺序表
//尾删顺序表
void SLPopBack(SL*ps)
{
//assert(ps != NULL);
//方法一:
//ps->size--;
//方法二:利用SLErase函数
SLErase(ps, ps->size - 1);
}
3.7 头删顺序表
//头删顺序表
void SLPopFront(SL*ps)
{
//assert(ps != NULL);
//方法一
//int begin = 1;
//while (begin < ps->size)
//{
// ps->a[begin - 1] = ps->a[begin];
// begin++;
//}
//ps->size--;
//方法二:利用SLErase函数
SLErase(ps, 0);
}
3.8 在指定位置(pos)插入数据x
//检查空间容量函数
void SLCheck(SL*ps)
{
assert(ps != NULL);
//检查容量并扩容
if (ps->size == ps->capacity)
{
int newcapacity = 0;
newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//初始化为4个,后面以二倍数扩容
SLDataType*tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
return;//这里也可用exit(-1)代替
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
//顺序表在pos的位置插入x
void SLInsert(SL*ps,int pos, SLDataType x)
{
assert(ps != NULL);
assert(pos >= 0 && pos <= ps->size);
//首先检查容量并扩容
SLCheck(ps);
//扩容成功,添加数据的同时向后挪动数据
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
3.9 在指定位置(pos)删除数据x
//顺序表在pos的位置删除x
void SLErase(SL*ps, int pos)
{
assert(ps != NULL);
assert(pos >= 0 && pos <= ps->size);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
3.10 查找顺序表中的某个数
//查找数组中的某个数,并返回其下标
int SLFind(SL*ps, SLDataType x)
{
assert(ps != NULL);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;//返回下标
}
}
return -1;
}
3.11 替换数组中的元素
//替换数组中的某个元素
void SLModify(SL*ps, int pos, SLDataType x)
{
assert(ps != NULL);
assert(pos >= 0 && pos <= ps->size);
ps->a[pos] = x;
}
3.12 顺序表销毁
//顺序表销毁
void SLDestory(SL*ps)
{
assert(ps != NULL);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
}
4、动态顺序表举例说明
4.1 初始化+尾插+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//尾插顺序表
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
//打印顺序表
SLPrint(&sl);
}
int main()
{
TestSeqList1();
return 0;
}
4.2 初始化+头插+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插顺序表
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
}
int main()
{
TestSeqList1();
return 0;
}
4.3 初始化+尾删+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插数据
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
printf("\n");
//尾删数据
SLPopBack(&sl);
SLPopBack(&sl);
//打印顺序表
SLPrint(&sl);
}
4.4 初始化+头删+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插数据
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
printf("\n");
//尾删数据
SLPopBack(&sl);
SLPopBack(&sl);
//打印顺序表
SLPrint(&sl);
printf("\n");
//头删数据
SLPopFront(&sl);
SLPopFront(&sl);
//打印顺序表
SLPrint(&sl);
}
4.5 初始化+在指定位置插入数据+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插数据
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
printf("\n");
//在某一位置插入数据
SLInsert(&sl, 1, 7);
SLInsert(&sl, 5, 10);
//打印顺序表
SLPrint(&sl);
printf("\n");
}
4.6 初始化+在指定位置删除数据+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插数据
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
printf("\n");
//在某一位置插入数据
SLInsert(&sl, 1, 7);
SLInsert(&sl, 5, 10);
//打印顺序表
SLPrint(&sl);
printf("\n");
//在某一位置删除数据
SLErase(&sl, 4);
SLPrint(&sl);
printf("\n");
}
4.7 初始化+查找数据并进行替换+打印数据
void TestSeqList1()
{
SL sl;
//初始化动态顺序表
SLInit(&sl);
//头插数据
SLPushFront(&sl, 1);
SLPushFront(&sl, 2);
SLPushFront(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
//打印顺序表
SLPrint(&sl);
printf("\n");
//在某一位置插入数据
SLInsert(&sl, 1, 7);
SLInsert(&sl, 5, 10);
//打印顺序表
SLPrint(&sl);
printf("\n");
//在某一位置删除数据
SLErase(&sl, 4);
SLPrint(&sl);
printf("\n");
int x = 0;
int y = 0;
printf("请输入你要替换的数以及替换后的数:");
scanf("%d%d", &x, &y);
int pos = SLFind(&sl, x);
if (pos != -1)
{
SLModify(&sl, pos, y);
SLPrint(&sl);
}
}
5、结语
动态顺序表和静态顺序表相比,能够对空间更加合理的使用,而且灵活性高,但也不能说动态顺序表就能用在任何场景,其也是有一定的缺陷。
例如:在头部或者中部插入删除数据时,我们会发现,需要变动的数组数据量很大,结合上一讲的复杂度,这里想表达的就是在头部/中部位置插入删除数据时,时间复杂度为O(N);另外增容需要申请新的空间,拷贝数据,释放旧空间,会有不小的损耗。
那如何才能解决动态线性表的缺陷呢?不用担心,使用链表即可解决上述存在的缺陷,下节我会为大家详细介绍数据结构中的链表相关知识,欢迎点赞,关注,支持!!!