目录
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
静态顺序表
定义结构体:
#define N 10//方便修改长度
typedef int SLDateType//方便修改类型
typedef struct SeqList//简化代码
{
SLDateType a[N];
size_t size;
}SL;
通常情况下我们需要存储的数据是未知的,所以我们一般使用动态顺序表而非静态顺序表。
动态顺序表
typedef struct SeqList
{
SLDateType *a;//指向数组首元素
size_t size;//实际存储数据
size_t capacity;//数组容量,满则扩容
}SL;
初始化
void SLInit(SL* s)//初始化,不然可能存在随机值
{
s->a = nullptr;
s->capacity = s->size = 0;
}
注意这里要用指针或引用的方式传递形参才能改变原对象的值。 (实参是形参的拷贝)
销毁
在出了作用域后我们就得进行销毁操作,以免发生内存泄露
void SLDestory(SL* s)//销毁
{
if (s->a)
{
free(s->a);
s->a = nullptr;
s->capacity = s->size = 0;
}
}
尾插
尾插实现很简单,需要注意扩容这里可以用2倍扩容,因为频繁扩容是有消耗的。
在我们使用realloc进行扩容时,如果指针为空,其作用相当于malloc
底下一段话:如果扩容失败,则会返回一个空指针
如果想避免为空所造成的后果,可以用一个中间变量的方式接收其返回值。
void SLPushBack(SL* s, SLDateType x)//尾插
{
if (s->size == s->capacity)//扩容
{
size_t newCapacity = s->capacity == 0? 4: s->capacity * 2;
SLDateType *tmp =(SLDateType*)realloc(s->a, newCapacity*sizeof(SLDateType));//原地或异地扩
if (tmp == nullptr)
{
perror("reaaloc fail");
exit(-1);
}
s->a = tmp;
s->capacity = newCapacity;
}
s->a[s->size] = x;
s->size++;
}
尾删
先问大家一个问题,尾删需要将删除的位置置0吗?
答案肯定不是,我们通过改变size的大小,使其输出前size个数据就行了,而且有新的数据进来它就会被覆盖掉。那仅仅使size减1就行了吗?
这里我只对size进行--,可以看到没任何问题
这里有个很迷惑人的错误,只有当你使用销毁中的free或者对越界的内存进行写操作才可能被检测出来(读基本不会被检查出来)。因为 编译器对越界的检查是一种抽查行为。(这里我用的是无符号整形修饰size所以free时并不会报错。)
我们可以用aseert或直接返回的方式检查
void SLPopBack(SL* s )//尾删
{
/*if (s->size == 0)
{
return;
}*/
assert(s);
assert(s->size>0);
s->size--;
}
为了避免对空指针解引用的情况出现,我们也可以在之前的函数面前都进行这样的断言:
assert(s);
头插
因为头插也涉及扩容,我们可以把实现扩容的代码封装成一个函数。
void SLCheckCapacity(SL* s)
{
assert(s);
if (s->size == s->capacity)//扩容
{
size_t newCapacity = s->capacity == 0 ? 4 : s->capacity * 2;
SLDateType* tmp = (SLDateType*)realloc(s->a, newCapacity * sizeof(SLDateType));//原地或异地扩
if (tmp == nullptr)
{
perror("reaaloc fail");
exit(-1);
}
s->a = tmp;
s->capacity = newCapacity;
}
}
头插挪动数据一定是从后往前挪,不然会导致数据被覆盖
void SLPushFront(SL* s, SLDateType x)//头插
{
assert(s);
SLCheckCapacity(s);
//挪动
int end = s->size - 1;
while (end >=0)
{
s->a[end + 1] = s->a[end];
end--;
}
s->a[0] = x;
s->size++;
}
尾插的时间复杂度为O(n),头插为O(n^2)。
头删
void SLPopFront(SL* s)//头删
{
assert(s);//从前往后
assert(s->size > 0);//防止越界
int begin = 1;
while (begin < s->size )
{
//s->a[begin] = s->a[begin + 1];
s->a[begin-1] = s->a[begin];
begin++;
}
s->size--;
}
注意这里begin的初始点要取1以免发生越界。
Insert
void SLInsert(SL* s, int pos, SLDateType x)
{
assert(s);
assert(pos <= s->size && pos >= 0);//可插入范围
SLCheckCapacity(s);//插入前检查容量
int end = s->size - 1;
while (end >= pos)//从后往前
{
s->a[end + 1] = s->a[end];
end--;
}
s->a[pos] = x;
s->size++;
}
注意pos可以为第size个位置,意为尾插
简化头插尾插代码:
void SLPushFront(SL* s, SLDateType x)//头插
{
SLInsert(s, 0, x);
}
void SLPushBack(SL* s, SLDateType x)//尾插
{
SLInsert(s, s->size, x);
}
erase
根据实现头删从前往后的逻辑,我们可以这样实现:
void SLErase(SL* s, int pos)
{
assert(s);
assert(pos < s->size&& pos >= 0);
//assert(s->size>0);
int begin = pos + 1;
while (begin < s->size)
{
s->a[begin - 1] = s->a[begin];
begin++;
}
s->size--;
}
注意 这里判断pos范围时间接判断了size的范围,所以可以不用判断size,by the way,删除最后一个数据时不同与插入可以在size位置插入,删除最后一个位置是size-1。
尾删头删复用:
void SLPopBack(SL* s )//尾删
{
SLErase(s, s->size - 1);
}
void SLPopFront(SL* s)//头删
{
SLErase(s, 0);
}
find查找
对数据增删查改的查就是找到相应数组下标返回
SLDateType SLFind(SL* s, SLDateType x)//查找
{
assert(s);
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == x)
{
return i;
}
}
return -1;//查找失败返回无效下标
}
打印函数
void SLPrint(SL* s)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
我们也可以指定从某个位置查找,只需添加一个参数即可。比如删除所有含5的元素:
菜单
这是一个简单的菜单模板,大家可以自己试一下,需要注意的是,在写程序时,通常是边写边调试,用菜单实际不方便调试。
void menu()
{
printf("***********************************************\n");
printf("1、尾插数据 2、尾删数据\n");
printf("3、头插数据 4、头删数据\n");
printf("5、打印数据 -1、退出\n");
printf("***********************************************\n");
}
int main()
{
SL s;
SLInit(&s);
int option = 0;
int n = 0;
do
{
menu();
printf("请输入您的操作: ");
scanf("%d", &option);
switch (option)
{
case 1:
printf("请输入尾插数据,以-1结束>");
scanf("%d", &n);
/*while (scanf("%d", &n) != EOF)*/
while (n != -1)//-1不存入数据
{
SLPushBack(&s, n);
}
break;
case 2:
SLPopBack(&s);
break;
case 3:
//
break;
case 4:
//
break;
case 5:
SLPrint(&s);
break;
default:
printf("输入错误,请重新输入");
break;
}
} while (option != -1);
SLDestory(&s);
//SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define N 10//方便修改长度
typedef int SLDateType;//方便修改类型
//typedef struct SeqList
//{
// SLDateType a[N];
// size_t size;
//}SL;
//动态:按需
typedef struct SeqList//简化代码
{
SLDateType *a;
size_t size;
size_t capacity;//数组容量,满则扩容
}SL;
void SLInit(SL* s);//初始化
void SLDestory(SL* s);//销毁
void SLPushBack(SL* s, SLDateType x);//尾插
void SLPopBack(SL* s);//尾删
void SLPushFront(SL* s, SLDateType x);//头插
void SLPopFront(SL* s);//头删
void SLCheckCapacity(SL* s);
void SLInsert(SL* s, int pos, SLDateType x);//插入
void SLErase(SL* s, int pos);//删除
SLDateType SLFind(SL *s,SLDateType x,int pos);//查找
void SLPrint(SL* s);
//SeqList.c
#include"SeqList.h"
void SLCheckCapacity(SL* s)
{
assert(s);
if (s->size == s->capacity)//扩容
{
size_t newCapacity = s->capacity == 0 ? 4 : s->capacity * 2;
SLDateType* tmp = (SLDateType*)realloc(s->a, newCapacity * sizeof(SLDateType));//原地或异地扩
if (tmp == nullptr)
{
perror("reaaloc fail");
exit(-1);
}
s->a = tmp;
s->capacity = newCapacity;
}
}
void SLInit(SL* s)//初始化,不然可能存在随机值
{
assert(s);
s->a = nullptr;
s->capacity = s->size = 0;
}
void SLPushBack(SL* s, SLDateType x)//尾插
{
SLInsert(s, s->size, x);
/*assert(s);
SLCheckCapacity(s);
s->a[s->size] = x;
s->size++;*/
}
void SLDestory(SL* s)//销毁
{
assert(s);
if (s->a)
{
free(s->a);
s->a = nullptr;
s->capacity = s->size = 0;
}
}
void SLPopBack(SL* s )//尾删
{
SLErase(s, s->size - 1);
/*if (s->size == 0)
{
return;
}*/
/*assert(s);
assert(s->size>0);
s->size--;*/
}
void SLPushFront(SL* s, SLDateType x)//头插
{
SLInsert(s, 0, x);
//assert(s);
//SLCheckCapacity(s);
从后往前挪动
//int end = s->size - 1;
//while (end >=0)
//{
// s->a[end + 1] = s->a[end];
// end--;
//}
//s->a[0] = x;
//s->size++;
}
void SLPopFront(SL* s)//头删
{
SLErase(s, 0);
//assert(s);//从前往后
//assert(s->size > 0);//防止越界
//int begin = 1;
//while (begin < s->size )
//{
// //s->a[begin] = s->a[begin + 1];
// s->a[begin-1] = s->a[begin];
// begin++;
//}
//s->size--;
}
void SLInsert(SL* s, int pos, SLDateType x)
{
assert(s);
assert(pos <= s->size && pos >= 0);//可插入范围
SLCheckCapacity(s);//插入前检查容量
int end = s->size - 1;
while (end >= pos)//从后往前
{
s->a[end + 1] = s->a[end];
end--;
}
s->a[pos] = x;
s->size++;
}
void SLErase(SL* s, int pos)
{
assert(s);
assert(pos < s->size&& pos >= 0);
//assert(s->size>0);
int begin = pos + 1;
while (begin < s->size)
{
s->a[begin - 1] = s->a[begin];
begin++;
}
s->size--;
}
SLDateType SLFind(SL* s, SLDateType x,int pos)//查找
{
assert(s);
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == x)
{
return i;
}
}
return -1;
}
void SLPrint(SL* s)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
以上就是我们顺序表的内容了