一、概念及结构:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1、静态顺序表:使用定长数组存储元素
#define N 100
typedef unsigned int size_t;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType a[N];//定长数组
size_t size; //有效数据个数
}SeqList;
2、动态顺序表:使用动态开辟的数组存储元素
typedef unsigned int size_t;
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; //动态开辟数组
size_t size; //有效数据个数
size_t capicity;//顺序表的容量
}SeqList;
二、接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
动态顺序表:
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef unsigned int size_t;
typedef int SLDataType;
//顺序表的动态存储
typedef struct SeqList
{
SLDataType* a; //动态开辟数组
size_t size; //有效数据个数
size_t capicity;//容量空间的大小
}SeqList;
//顺序表数据的增删查改
//检查空间容量,若空间满了,进行扩容
void SeqListCheckList(SeqList* psl);
//顺序表打印
void SeqListPrint(SeqList* psl);
//顺序表初始化
void SeqListInit(SeqList* psl);
//顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType n);
//顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType n);
//顺序表头删
void SeqListPopFront(SeqList* psl);
//顺序表尾删
void SeqListPopBack(SeqList* psl);
//顺序表查找
int SeqListFind(SeqList* psl, SLDataType n);
//顺序表销毁
void SeqListDestory(SeqList* psl);
//顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
//顺序表在pos位置插入n值
void SeqListInsert(SeqList* psl, size_t pos, SLDataType n);
补充:
为什么在函数的参数是结构体指针而不是结构体
(1)因为函数在传参是对实参的一份临时拷贝,如果参数是结构体,那么修改的是拷贝的结构体(形参),实参不会发生改变。如果参数是结构体指针,那么传的是实参的地址,在函数中,就可以通过传入的地址找到实参,修改实参。
(2)在函数传参时,会将形参压栈,栈区的空间是有限的,一个结构体指针4个字节,而一个结构体可能占很多字节,所以传结构体指针的效率更高,也更安全。
动态顺序表增删查改函数实现
检查空间容量,若空间满了,进行扩容
void SeqListCheckList(SeqList* psl);
void SeqListCheckList(SeqList* psl)
{
assert(psl);//(1)
if (psl->size == psl->capicity)//(2)
{
//(3)
size_t new_capacity = psl->capicity == 0 ? 4 : psl->capicity * 2;
SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * new_capacity);
//(4)
if (tmp == NULL)
exit(-1);
else
{
psl->a = tmp;
psl->capicity = new_capacity;
}
}
}
(1)psl不能为NULL
即使在顺序表中没有数据的情况下,结构体依旧存在,所以psl不能为NULL
(2)判断顺序表的容量是否需要扩容
(3)如果容量为0,扩容4,若容量不为0,扩容二倍
(4)如果开辟失败,程序结束
顺序表打印
void SeqListPrint(SeqList* psl);
void SeqListPrint(SeqList* psl)
{
assert(psl);
//(1)
for (int i = 0; i < (int)psl->size; i++)
{
printf("%d ", *(psl->a + i));
}
printf("\n");
}
(1)打印顺序表的每一个数据
顺序表初始化
void SeqListInit(SeqList* psl);
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->a = NULL;
psl->capicity = 0;
psl->size = 0;
}
顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType n);
void SeqListPushBack(SeqList* psl, SLDataType n)
{
assert(psl);
SeqListCheckList(psl);//(1)
psl->a[psl->size] = n;//(2)
psl->size++;//(3)
}
(1)检查容量
(2)写入数据
(3)有效数据个数+1
顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType n);
void SeqListPushFront(SeqList* psl, SLDataType n)
{
assert(psl);
SeqListCheckList(psl);//(1)
int end = psl->size - 1;//(2)
//(3)
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
//(4)
psl->a[0] = n;
psl->size++;
}
(1)检查容量
(2)找到最后一个数据的位置
(3)从最后一个数据开始,依次向后移动一个位置
问:为什么不从第一个位置开始?
答:从第一个位置开始,会覆盖还没有移动的数据
(4)将数据写入第一个位置
顺序表头删
void SeqListPopFront(SeqList* psl);
void SeqListPopFront(SeqList* psl)
{
assert(psl);
//(1)
size_t beg = 1;
while (beg < psl->size )
{
psl->a[beg - 1] = psl->a[beg];
beg++;
}
//(2)
if (psl->size == 0)
psl->size = 0;
else
psl->size--;
}
(1)从第二个数据开始,每一个数据向前移动一个位置
(2)如果有效数据个数为0,0为最小数据个数,不能--
如果有效数据个数非0,数据个数--
顺序表尾删
void SeqListPopBack(SeqList* psl);
void SeqListPopBack(SeqList* psl)
{
assert(psl);
if (psl->size == 0)
psl->size = 0;
else
psl->size--;
}
顺序表查找
int SeqListFind(SeqList* psl, SLDataType n);
int SeqListFind(SeqList* psl, SLDataType n)
{
assert(psl);
//(1)
for (int i = 0; i < (int)psl->size; i++)
{
if (psl->a[i] == n)
return i;
}
return -1;
}
(1)从第一个元素开始,依次查找,若存在,返回值为n的位置,否则返回-1
顺序表销毁
void SeqListDestory(SeqList* psl);
void SeqListDestory(SeqList* psl)
{
assert(psl);
//(1)
psl->size = 0;
psl->capicity = 0;
free(psl->a);//(2)
psl->a = NULL;//(3)
}
(1)将顺序表的有效数据和容量置为0
(2)free动态开辟的数组
(3)防止psl->a成为野指针,将它置为NULL
顺序表删除
pos
位置的值
void SeqListErase(SeqList* psl, size_t pos);
void SeqListErase(SeqList* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);//(1)
//(2)
size_t beg = pos + 1;
while (beg <= psl->size - 1)
{
psl->a[beg - 1] = psl->a[beg];
beg++;
}
psl->size--;
}
(1)检查pos位置是否存在有效数据
(2)从pos+1位置开始依次向前移动一个位置,将pos位置数据删除,有效数据个数-1
顺序表在
pos
位置插入n值
void SeqListInsert(SeqList* psl, size_t pos, SLDataType n);
void SeqListInsert(SeqList* psl, size_t pos, SLDataType n)
{
assert(psl);
//(1)
if (pos > psl->size)
{
printf("越界\n");
return;
}
SeqListCheckList(psl);//(2)
//(3)
size_t end = psl->size ;
while (pos < end)
{
psl->a[end] = psl->a[end - 1];
end--;
}
//(4)
psl->a[pos] = n;
psl->size++;
}
(1)检查插入位置是否合法,插入位置最大为psl->size(这种情况为尾插)
(2)检查容量
(3)从psl->size-1位置开始,依次向后移动一个位置
(4)将数据写入pos位置
测试
#include"SeqList.h"
void testSeqlist()
{
SeqList a;
SeqListInit(&a);
SeqListPushBack(&a, 1);
SeqListPushBack(&a, 2);
SeqListPushBack(&a, 3);
SeqListPrint(&a);
SeqListPushFront(&a, 7);
SeqListPushFront(&a, 8);
SeqListPushFront(&a, 9);
SeqListPrint(&a);
SeqListPopBack(&a);
SeqListPopFront(&a);
SeqListPrint(&a);
SeqListInsert(&a, 0, 6);
SeqListInsert(&a, 5, 6);
SeqListInsert(&a, 3, 6);
SeqListPrint(&a);
SeqListErase(&a, 3);
SeqListPrint(&a);
printf("%d\n", SeqListFind(&a,6));
SeqListDestory(&a);
SeqListPrint(&a);
}
int main()
{
testSeqlist();
return 0;
}
结果: