文章目录
前言
大家都知道要学好编程,数据结构是十分重要的一门课,而线性表是一种在实际中广泛使用的数据结构,那么线性表是什么呢,线性表是n个具有相同特性(数据类型)的数据元素的有限序列,常见的线性表有:顺序表、链表、栈、队列、字符串…但不要误会的是线性表在逻辑上是线性结构,也就是一条连续的直线,但在物理结构上不一定是连续的,所以线性表在物理上存储时,通常以数组和链式结构形式存储,下面我们就来学习一下线性表中的顺序表吧。
一、什么是顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改
二、顺序表的分类
1.静态顺序表:使用定长数组存储
代码如下(示例):
//静态顺序表
struct SArrayList
{
DataType array[MAX];//存储数据的数组
int size; // 有效的数据的个数
};
2.动态顺序表:使用动态开辟的数组存储
代码如下(示例):
//动态顺序表
struct DArrayList
{
DataType* pointer;//指向动态开辟的数组
int size;//有效数据的个数
int capacity;//最大容量
};
3.动静态顺序表的区别
- 从能存储最大容量的角度分析:静态顺序表的容量大小依靠于我们数组的大小,数组定义多大,我们容量就多大。而动态顺序表的最大容量是由capacity这个字段来标志的。
- 从存储数据的方式分析:静态顺序表是靠数组存储数据的,而动态顺序表是凭借指针来存储数据。
4.动静态顺序表的相同点
- 它们存储的有效数据的个数都是可以直接凭借size字段获取的,因此它们的初始化是一样的,是一开始都是空表状态,所以对于顺序表一开始的初始化一定要保证size为0。
5.动态顺序表比静态顺序表好在哪些方面
- 我们知道动态顺序表的存储数据是靠指针来存储的,那么这里就涉及到开辟空间大小的问题,所以才有了capacity这个字段,这个字段主要控制的是指针开辟的最大容量,如果说存储的数据过多,甚至超过了capacity,那么就需要扩大capacity,因而动态顺序表相比静态顺序表增添了扩容的功能,这样就弥补了静态顺序表的缺陷:1. 存储数据少,但开辟的数组容量大造成的空间内存浪费问题。2. 存储数据多,但开辟的数组容量要比存储的数据小的多,造成许多数据存不上的问题。所以我认为动态顺序表比静态顺序表好在有扩容的方面,存储数据多的话呢,就扩容,少的话,就存储,这样极大的减少了空间浪费问题。
三、(动态)顺序表的实现
1.动态顺序表的结构体
//动态顺序表
typedef struct SeqList
{
SLDateType* array;
int size;
int capacity;
}SeqList;
2.对数据的管理(增,删,查,改)完整代码
- SeqList.h
//SeqList.h
#pragma once
#include<stdio.h>
#include<iostream>
#include<assert.h>
#include<stdlib.h>
using namespace std;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* array; // 动态开辟数组
int size;//有效数据个数
int capacity;//最大容量
}SeqList;
//初始化
void SeqListInit(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
- SeqList.cpp
//SeqList.cpp
#include"SeqList.h"
//初始化
void SeqListInit(SeqList* psl)
{
psl->array = NULL;
psl->size = 0;
psl->capacity = 0;
}
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
//增容
int newCapacity = (psl->capacity == 0) ? 4 : (2 * psl->capacity);
SLDataType* tmp = NULL;
tmp = (SLDataType*)realloc(psl->array,sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
psl->capacity = newCapacity;
psl->array = tmp;
}
}
// 顺序表尾插 时间复杂度O(1)
void SeqListPushBack(SeqList* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
//尾插
psl->array[psl->size] = x;
psl->size++;
}
// 顺序表打印 时间复杂度O(N)
void SeqListPrint(SeqList* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
cout << psl->array[i] << " ";
}
}
// 顺序表尾删 时间复杂度O(1)
void SeqListPopBack(SeqList* psl)
{
assert(psl);
if (psl->size == 0)
{
cout << "已经是空表" << endl;
return;
}
psl->size--;
}
// 顺序表头插 时间复杂度O(N)
void SeqListPushFront(SeqList* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
for (int i = psl->size - 1; i >= 0; i--)
{
psl->array[i + 1] = psl->array[i];
}
psl->array[0] = x;
psl->size++;
}
// 顺序表头删 时间复杂度O(N)
void SeqListPopFront(SeqList* psl)
{
assert(psl);
if (psl->size == 0)
{
cout << "已经是空表" << endl;
return;
}
for (int i = 1; i < psl->size; i++)
{
psl->array[i - 1] = psl->array[i];
}
psl->size--;
}
// 顺序表查找 时间复杂度O(N)
int SeqListFind(SeqList* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->array[i] == x)
{
return i;
}
}
return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos <= psl->size);
CheckCapacity(psl);
//注意:这里i要用size_t类型,如果用int型,在比较i>=pos的时候会把int型转化为size_t类型再进行比较
for (size_t i = psl->size - 1; i >= pos; i--)
{
psl->array[i + 1] = psl->array[i];
}
psl->array[pos] = x;
psl->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos <= psl->size-1);
if (psl->size == 0)
{
cout << "已经是空表" << endl;
return;
}
for (size_t i = pos + 1; i < psl->size; i++)
{
psl->array[i - 1] = psl->array[i];
}
psl->size--;
}
// 顺序表销毁
void SeqListDestory(SeqList* psl)
{
assert(psl);
free(psl->array);
psl->capacity = 0;
psl->size = 0;
}
- test.cpp
//test.cpp
#include"SeqList.h"
void SeqListTest1()
{
SeqList sl;
SeqListInit(&sl);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushFront(&sl, 7);
SeqListPushFront(&sl, 7);
SeqListPushFront(&sl, 7);
SeqListInsert(&sl, 2, 9);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListErase(&sl, 2);
SeqListPrint(&sl);
SeqListDestory(&sl);
free(&sl);
SeqListPrint(&sl);
}
int main()
{
SeqListTest1();
return 0;
}
四、顺序表存在的问题
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
五、总结
学完顺序表我们发现,虽然顺序表对于存取数据较快,但是同时也存在着许多问题,例如中间头部的插入删除时间复杂度较高,扩容的时候需要申请新的空间,拷贝数据,释放旧空间,这会造成大量的消耗,而且扩容一般是呈2倍的增长,会有一定的空间浪费,那么就没有更好的数据结构能够解决这些问题了吗,答案是有的,它就是我们数据结构中的链表,我会在下次博客中揭晓链表的结构与顺序表相比其优缺点。