线性表
线性表是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
前言
我们之前接触最多的线性表就是数组, 数组时存放相同元素的集合, 字符串如果是一个字符数组的话也算一个线性表;
逻辑上连续, 物理上不一定连续, 如果物理上连续就是顺序表, 物理上不连续就是链表
顺序表
1.概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
\1. 静态顺序表:使用定长数组存储。
2. 动态顺序表:使用动态开辟的数组存储。
// 顺序表的静态存储
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; // 定长数组
size_t size; // 有效数据的个数
}SeqList;
// 顺序表的动态存储
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t size ; // 有效数据个数
size_t capicity ; // 容量空间的大小
}SeqList;
动态顺序表与静态顺序表的区别:
动态顺序表 | 静态顺序表 |
---|---|
大小可变 | 定长, 大小不变 |
在堆上开辟空间 | 在栈上开辟空间 |
结构体大小不包含元素 | 结构体大小包含元素大小 |
2.顺序表的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int SLDataType;
typedef struct seqList {
SLDataType* _data;//需要动态开辟的数组
size_t _size; //有效元素的个数
size_t _capaticy; //当前可以存放的最大元素个数
}seqList;
//初始化
void InitSeqList(seqList* sl) {
sl->_data = NULL;
sl->_size = 0;
sl->_capaticy = 0;
}
//检查容量
void CheckCapacity(seqList* sl) {
//assert(sl);
if (sl == NULL)
return;
//如果元素个数和容量相同, 说明空间满了, 需要调整空间(扩容)
if (sl->_size == sl->_capaticy) {
int newCapacity = sl->_capaticy == 0 ? 1 : 2 * sl->_capaticy;
//开一个更大的空间, 拷贝已有的数据, 释放原有的空间
SLDataType* tmp = (SLDataType*)malloc(sizeof(SLDataType) * newCapacity);
memcpy(tmp, sl->_data, sizeof(SLDataType) * sl->_size);
free(sl->_data);
//另一种写法
//sl->_data = (SLDataType*)realloc(sl->_data, sizeof(SLDataType) * newCapacity);
//更新
sl->_data = tmp;
sl->_capaticy = newCapacity;
}
}
//尾插
void pushBack(seqList* sl, SLDataType val) {
//检查容量
CheckCapacity(sl);
sl->_data[sl->_size] = val;
++sl->_size;
}
//尾删
void popBack(seqList* sl) {
if (sl == NULL)
return;
if (sl->_size > 0)
sl->_size--;
}
//头插
// 一般不进行头插: 效率太低
//1.移动元素: [0, size)全部向后移动一个位置
// 方向: 不能从前向后移动, 会导致数据被覆盖
void pushFront(seqList* sl, SLDataType val) {
if (sl == NULL)
return;
//0.检查容量
CheckCapacity(sl);
//1.移动元素: 从前向后移动
size_t end = sl->_size;
while (end > 0) {
sl->_data[end] = sl->_data[end - 1];
--end;
}
//头插
sl->_data[0] = val;
//更新
sl->_size++;
}
//头删
//1.移动元素: [0, size)全部向前移动一个位置
//方向: 不能从后先前移动, 会导致数据被覆盖
void popFront(seqList* sl) {
if (sl == NULL || sl->_size == 0)
return;
CheckCapacity(sl);
int start = 1;
while (start < sl->_size) {
sl->_data[start - 1] = sl->_data[start];
++start;
}
sl->_size--;
}
void insert(seqList* sl, int pos, SLDataType val) {
if (sl == NULL)
return;
//0.检查容量
CheckCapacity(sl);
//有效的插入位置: [0, size]
if (pos >= 0 && pos <= sl->_size) {
size_t end = sl->_size;
//移动元素: [pos, size)
while (end > pos) {
sl->_data[end] = sl->_data[end - 1];
--end;
}
//插入
sl->_data[pos] = val;
//更新
sl->_size++;
}
}
void erase(seqList* sl, int pos) {
if (sl == NULL || sl->_size == 0)
return;
//0.检查容量
CheckCapacity(sl);
if (pos >= 0 && pos < sl->_size) {
//移动元素: (pos, size)
//从pos + 1开始从前向后以此移动元素
int start = pos + 1;
while (start < sl->_size) {
sl->_data[start - 1] = sl->_data[start];
--start;
}
--sl->_size;
}
}
int empty(seqList* sl) {
if (sl == NULL || sl->_size == 0)
return 1;
else
return 0;
}
int size(seqList* sl) {
if (sl == NULL)
return 0;
else
return sl->_size;
}
int find(seqList* sl, SLDataType val) {
for (size_t i = 0; i < sl->_size; ++i) {
if (sl->_data[i] == val)
return i;
}
return -1;
}
//打印
void printSeqList(seqList* sl) {
for (size_t i = 0; i < sl->_size; i++) {
printf("%d ", sl->_data[i]);
}
printf("\n");
}
void test() {
seqList sl;
InitSeqList(&sl);
pushBack(&sl, 1);
pushBack(&sl, 2);
pushBack(&sl, 3);
pushBack(&sl, 4);
pushBack(&sl, 5);
printSeqList(&sl);
popBack(&sl);
printSeqList(&sl);
popBack(&sl);
printSeqList(&sl);
popBack(&sl);
printSeqList(&sl);
popBack(&sl);
printSeqList(&sl);
}
int main() {
test();
return 0;
}
3.顺序表的特点
1.空间连续
2.支持随机访问
3.尾插, 尾删效率高
4.空间利用率高, 不容易造成内存碎片
5.其他位置插入, 删除效率底
6.增容代价大