目录
前言:
顺序表就是一个可以动态增长的数组,顺序表就是对数组进行增删改查的操作。
性质: 1,可动态增长的数组 2,数据在数组中存储时必须是连续的
优点: 1,可以随机访问。 2,缓存命中率比较高。
缺点:中间或者头部插入删除很慢,需要挪动数据,需要扩容,会造成空间的浪费。尾插尾删快,头插头删慢时间复杂度O(N),因为需要挪动数据,,顺序表的缺点正好可以由链表的优点来弥补。
链表不好排序,不能进行随机访问,顺序表和链表优势互补。
一,完整代码
SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
#define MAX_INT 4
#define ADD_CAP 4
typedef struct SeqList
{
SLDataType* a;
int size; //有效数据个数
int capacity; // 容量
}SL,SeqList;
//初始化结构体
void SeqListInit(SL* ps);
//检测是否需要增容
void SeqListCheckCapacity(SL* ps);
//尾插
void SeqListPushBack(SL* ps, SLDataType x);
//尾删
void SeqListPopBack(SL* ps);
//打印
void SeqListPrint(SL* ps);
//头插头删
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);
//任意位置的插入删除
void SeqListInsert(SL* ps, int pos, SLDataType x);
void SeqListErase(SL* ps, int pos);
//顺序表查找
int SeqListFind(SL* ps1, SLDataType x);
//销毁,动态开辟的空间需要主动销毁
void SeqListDestory(SL* ps);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//初始化结构体
void SeqListInit(SL* ps)
{
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * MAX_INT);
if (ps->a == NULL)
{
printf("申请内存失败\n");
exit(-1);
}
ps->size = 0;
ps->capacity = MAX_INT;
}
//检测是否需要增容
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//增容, 增加2倍比较合适,因为频繁增容会增加函数的开销
/*SLDataType* pr = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * (ADD_CAP + ps->capacity));*/
ps->capacity *= 2;
SLDataType* pr = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity);
if (pr != NULL)
{
ps->a = pr;
}
else
{
return;
}
}
}
//尾插
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//打印
void SeqListPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//尾删
void SeqListPopBack(SL* ps)
{
assert(ps);
ps->size--;
}
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
//头删
void SeqListPopFront(SL* ps)
{
assert(ps);
int start = 0;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
//任意位置的插入删除
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos < ps->size && pos >= 0);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps, int pos)
{
assert(pos);
assert(pos < ps->size && pos >= 0);
int start = pos;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
//顺序表查找,返回下标
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1; // 没有找到
}
//销毁,动态开辟的空间需要主动销毁
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//销毁动态开辟的空间
SeqListDestory(&s);
}
int main()
{
TestSeqList1();
return 0;
}
二,整体过程
初始化结构体
SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
#define MAX_INT 4
#define ADD_CAP 4
typedef struct SeqList
{
SLDataType* a;
int size; //有效数据个数
int capacity; // 容量
}SL,SeqList;
//初始化结构体
void SeqListInit(SL* ps);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//初始化结构体
void SeqListInit(SL* ps)
{
/*s.size = 0;
s.a = NULL;
s.capacity = 0;*/
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * MAX_INT);
if (ps->a == NULL)
{
printf("申请内存失败\n");
exit(-1);
}
ps->size = 0;
ps->capacity = MAX_INT;
}
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
int main()
{
TestSeqList1();
return 0;
}
首先把顺序表的结构定义好,我们使用数组来动态开辟空间保存顺序表中的数据动态增长的顺序表,可以实现动态增长
定义数组指针a指向动态开辟的空间
定义size记录数据的有效个数
定义capacity记录当前顺序表的最大容量
尾插
SeqList.h
//尾插
void SeqListPushBack(SL* ps, SLDataType x);
SeqList.c
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
ps->capacity *= 2;
SLDataType* pr = (SLDataType*)realloc(ps->a,sizeof(SLDataType) * ps->capacity);
if (pr != NULL)
{
ps->a = pr;
}
else
{
return;
}
}
ps->a[ps->size] = x;
ps->size++;
}
这里增容可以自行选择增加的大小,建议增加2倍,因为频繁的realloc增容会增加函数的开销。通常我们使用realloc修改原来的空间都使用一个新的指针pr来接收,如果我用ps->a来保存这个地址的话就会有问题,因为realloc修改空间大小空间不足时候失败会返回NULL(一般情况都是不会失败的),就会使得ps->变成NULL本来存在一个有效地址结果变成空指针,这样会导致内存泄漏。
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
}
为了能好的测试出来结果,我们现在把打印功能完善一下。
打印
SeqList.h
//打印
void SeqListPrint(SL* ps);
SeqList.c
//打印
void SeqListPrint(SL* ps)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
}
测试结果如下:
尾删
SeqList.h
//尾删
void SeqListPopBack(SL* ps);
SeqList.c
void SeqListPopBack(SL* ps)
{
assert(ps);
ps->size--;
}
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//尾删
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
}
测试结果如下:
头插
SeqList.h
//检测是否需要增容
void SeqListCheckCapacity(SL* ps);
//头插
void SeqListPushFront(SL* ps, SLDataType x);
SeqList.c
//检测是否需要增容
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
ps->capacity *= 2;
SLDataType* pr = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity);
if (pr != NULL)
{
ps->a = pr;
}
else
{
return;
}
}
}
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
1,写到这里我们发现,头插和尾插一样,最先都要进行判断容量,如果不足就需要增容,这里我们可以单独写一个增容的函数,方便调用,在尾插也可以把那一段代码修改成SeqListCheckCapacity
2,头插就需要我们挪动数据进行插入解下如下:
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//头插
SeqListPushFront(&s, -1);
SeqListPushFront(&s, -2);
SeqListPushFront(&s, -3);
SeqListPrint(&s);
}
测试结果如下:
头删
SeqList.h
//头删
void SeqListPopFront(SL* ps);
SeqList.c
void SeqListPopFront(SL* ps)
{
assert(ps);
int start = 0;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
原理和头插一样,也需要挪动数据,把数据的后一位给前一位
解析下如图所示:
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//头删
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
}
测试结果如下:
任意位置的插入&&删除
SeqList.h
//任意位置的插入删除
void SeqListInsert(SL* ps, int pos, SLDataType x);
void SeqListErase(SL* ps, int pos);
SeqList.c
//任意位置的插入删除
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos < ps->size && pos >= 0);
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps, int pos)
{
assert(pos);
assert(pos < ps->size && pos >= 0);
int start = pos;
while (start < ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
1,任意位置的插入删除其原理和头插头删一样的,只不过区间范围缩小了
2, 头插的区间范围是[0,ps->size-1]
任意位置的插入就是[pos,ps->size-1],
同样我们定义变量end,end不断减减,头插结束位置是下标0,而任意位置插入结束位置是pos.
3,头删的区间范围是[0,ps->size-1]
任意位置的删除就是[pos,ps->size-2],
同样我们定义变量start,start不断加加,
头删开始下标是0结束位置是下标ps->size-2。
而任意位置插入开始位置下标是pos,结束位置下标是ps->size-2。
test.c
测试结果如下:
顺序表的查找
SeqList.h
//顺序表查找
int SeqListFind(SL* ps1, SLDataType x);
SeqList.c
//顺序表查找,返回下标
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1; // 没有找到
}
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//顺序表的查找,查找到某个数
int pos = SeqListFind(&s, 3);
if (pos != -1)
{
SeqListErase(&s, pos);
}
SeqListPrint(&s);
}
测试结果如下:
顺序表的销毁
SeqList.h
//销毁
void SeqListDestory(SL* ps);
SeqList.c
//销毁
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
test.c
void TestSeqList1()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 6);
SeqListPushBack(&s, 7);
SeqListPushBack(&s, 8);
SeqListPushBack(&s, 9);
SeqListPushBack(&s, 10);
SeqListPrint(&s);
//销毁动态开辟的空间
SeqListDestory(&s);
}