顺序表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储 。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
// 顺序表的静态存储
#define N 100
typedef int SLDataType;
typedef struct SeqList
{
SLDataType _arr[N]; // 定长数组
int _size; // 有效数据的个数
}SeqList;
// 顺序表的动静态存储
typedef int SeqListDataType;
typedef struct SeqList {
SeqListDataType* _arr;
int _size;
int _capacity;
}SeqList;
接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
SeqList.h文件
#pragma once //防止头文件重复包含
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SeqListDataType;//想要适应int,double等数据类型,就要用typedef
typedef struct SeqList {
SeqListDataType* _arr;
int _size;
int _capacity;
}SeqList;
//初始化
void SeqListInit(SeqList* ps, int capacity);
//检查容量
void SeqListCheckCapacity(SeqList* ps);
//尾插
void SeqListPushBack(SeqList* ps, SeqListDataType val);
//尾删
void SeqListPopBack(SeqList* ps);
//判空
int SeqListEmpty(SeqList* ps);
//头插
void SeqListPushFront(SeqList* ps, SeqListDataType val);
//头删
void SeqListPopFront(SeqList* ps);
//任意位置插入
void SeqListInsert(SeqList* ps, int pos, SeqListDataType val);
//任意位置删除
void SeqListErase(SeqList* ps, int pos);
//打印
void SeqListPrint(SeqList* ps);
//查找 找到返回下标值,找不到返回-1
int SeqListFind(SeqList* ps, SeqListDataType val);
//清空内容
void SeqListClear(SeqList* ps);
//销毁顺序表
void SeqListDestroy(SeqList* ps);
函数实现
SeqList.c文件
#include "SeqList.h"
void SeqListInit(SeqList* ps, int capacity) {
//检查指针的合法性
assert(ps != NULL);
ps->_capacity = capacity;
ps->_size = 0;
//malloc返回值为void*;进行返回值进行强转,在前面加上DataType*
//开辟成功,返回空间首地址,开辟失败,返回空
//malloc需要的头文件是stdlib.h
ps->_arr = (SeqListDataType*)malloc(sizeof(SeqListDataType) * capacity);
if (ps->_arr == NULL) {
printf("malloc failed\n");
exit(-1);
}
}
//检查容量
void SeqListCheckCapacity(SeqList* ps) {
assert(ps != NULL);
//当capacity与size相等的时候,此时顺序表已满,需要增容
if (ps->_capacity == ps->_size) {
//这里按照两倍扩容
int newCapacity = ps->_capacity * 2;
SeqListDataType* p = (SeqListDataType*)realloc(ps->_arr, sizeof(SeqListDataType) * newCapacity);
if (p == NULL) {
printf("realloc failed\n");
exit(-1);
}
ps->_arr = p;
ps->_capacity = newCapacity;
}
}
//尾插
void SeqListPushBack(SeqList* ps, SeqListDataType val) {
assert(ps != NULL);
SeqListCheckCapacity(ps);
ps->_arr[ps->_size] = val;
ps->_size++;
}
//尾删
void SeqListPopBack(SeqList* ps) {
assert(ps != NULL);
if (SeqListEmpty(ps)) {
return;
}
ps->_size--;
}
//判空
int SeqListEmpty(SeqList* ps) {
assert(ps != NULL);
return 0 == ps->_size;
}
//头插
void SeqListPushFront(SeqList* ps, SeqListDataType val) {
assert(ps != NULL);
SeqListCheckCapacity(ps);
//挪动数据
for (int i = ps->_size - 1; i >= 0; --i) {
ps->_arr[i + 1] = ps->_arr[i];
}
//头插
ps->_arr[0] = val;
ps->_size++;
}
//头删
void SeqListPopFront(SeqList* ps) {
assert(ps != NULL);
if (SeqListEmpty(ps)) {
return;
}
//挪动数据
for (int i = 0; i < ps->_size - 1; ++i) {
ps->_arr[i] = ps->_arr[i + 1];
}
//大小减1
ps->_size--;
}
//任意位置插入
void SeqListInsert(SeqList* ps, int pos, SeqListDataType val) {
assert(ps != NULL);
//检查pos位置的合法性,ps可以等于ps->_size是因为可以在尾部插入数据
assert(pos >= 0 && pos <= ps->_size);
SeqListCheckCapacity(ps);
for (int i = ps->_size - 1; i >= pos; --i) {
ps->_arr[i + 1] = ps->_arr[i];
}
ps->_arr[pos] = val;
ps->_size++;
}
//任意位置删除
void SeqListErase(SeqList* ps, int pos) {
assert(ps != NULL);
//检查pos位置的合法性,ps不可以等于ps->_size是因为尾部ps->_size并没有数据
assert(pos >= 0 && pos < ps->_size);
if (SeqListEmpty(ps)) {
return;
}
for (int i = pos; i < ps->_size - 1; ++i) {
ps->_arr[i] = ps->_arr[i + 1];
}
ps->_size--;
}
//打印
void SeqListPrint(SeqList* ps) {
assert(ps != NULL);
for (int i = 0; i < ps->_size; ++i) {
printf("%d ", ps->_arr[i]);
}
printf("\n");
}
//查找 找到返回下标值,找不到返回-1
int SeqListFind(SeqList* ps, SeqListDataType val) {
assert(ps != NULL);
for (int i = 0; i < ps->_size; ++i) {
if (ps->_arr[i] == val) {
return i;
}
}
return -1;
}
//清空内容
void SeqListClear(SeqList* ps) {
assert(ps != NULL);
ps->_size = 0;
}
//销毁顺序表
void SeqListDestroy(SeqList* ps) {
assert(ps != NULL);
if (ps->_arr != NULL) {
free(ps->_arr);
ps->_arr = NULL;
ps->_size = ps->_capacity = 0;
}
}
接口测试
testSeqList.c文件
#include "SeqList.h"
int main() {
SeqList s;
SeqListInit(&s, 2);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListPushFront(&s, 10);
SeqListPushFront(&s, 20);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
SeqListPushFront(&s, 100);
SeqListPushFront(&s, 200);
SeqListPrint(&s);
SeqListInsert(&s, 2, 15);
SeqListPrint(&s);
SeqListErase(&s, 1);
SeqListPrint(&s);
//如果我们想修改数据中的15,可以先进行查找,找到位置后,进行替换即可
int pos1 = SeqListFind(&s, 15);
if (pos1 != -1) {
s._arr[pos1] = 25;
}
SeqListPrint(&s);
int pos2 = SeqListFind(&s, 35);
if (pos2 == -1) {
printf("没找到\n");
}
system("pause");
return 0;
}
顺序表
1.可动态增长的数组
2.数据在数组中存储时必须是连续的
顺序表的缺点
1.中间 / 头部的插入删除很慢,需要挪动数据。时间复杂度为O(N)
2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,
满了以后增容到200, 我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
2,3可总结为:空间不够时,增容会有一定消耗和空间浪费
优点:
1.随机访问(最大的优点),任一元素均可随机存取
2.缓存命中率比较高