目录
线性表
线性表( linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
概念及结构
顺序表是用一段 物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
连续存储顺序,是不能跳跃的
顺序表
顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
静态顺序表
使用定长数组存储元素
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N 定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
静态顺序表价值较低 -- 不考虑
动态顺序表
扩容方式 -- 使用 realloc 库函数 -- 有两种扩容方式 原地扩容 与 异地扩容 后者代价大
扩容选择 -- 两倍
扩容两倍的原因
一次扩容多了,存在空间的浪费。一次扩容少了,频繁扩容,效率损失
接口实现
SeqList.h
#pragma once //方式二次引用
#define _CRT_SECURE_NO_DEPRECATE //VS里面的检查 -- 其他编辑器不需要
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//或者
//#ifndef __SEQLIST_H__
//#define __SEQLIST_H__
//……
//#endif
//静态的顺序表 -- 缺陷:当不知道要存储多少数据的时候就体现出来了
//#define N 10
//typedef int SLDataType;
//struct Seqlist
//{
// SLDataType a[N];
// int size; //存储数据的个数
//};
//动态顺序表 -- 有两种扩容方式 原地扩容与异地扩容 后者代价大
//重命名类型 -- 方便后续改变存储类型
typedef int SLDataType;
typedef struct Seqlist
{
SLDataType* a;
int size; //存储数据的个数
int capacity;//存储空间的大小
}SL;
//初始化 -- 这里要接收地址,用指针接收,利用地址来与外部产生联系
void SLInit(SL* psl);
//销毁 --
void SLDestory(SL* psl);
//顺序表的打印 -- 方便检查
void SLPrint(SL* psl);
//头插头删 尾插尾删
//尾插
void SLPushBack(SL* psl, SLDataType x);
//头插
void SLPushFront(SL* psl, SLDataType x);
//尾删
void SLPoPBack(SL* psl);
//头删
void SLPoPhFront(SL* psl);
// 顺序表查找 -- 没找到返回-1
int SLFind(SL* psl, SLDataType x);
//顺序表在pos位置插入x
void SLInsert(SL* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos);
//修改
void SLModify(SL* psl, size_t pos, SLDataType x);
SeqList.c
#include "SeqList.h"
//初始化
void SLInit(SL* psl)
{
assert(psl); //自身断言不可以为空 不可以传NULL过来
psl->a = NULL;
psl->size = psl->capacity = 0;
}
//销毁
void SLDestory(SL* psl)
{
assert(psl);
//if (psl->a) //free自己会检查是否为NULL -- free NULL 没意义
//{
free(psl->a);
psl->a = NULL;
psl->size = psl->capacity = 0;
//}
}
//检查并扩容
void SLCheckCapacity(SL* psl)
{
//检查容量
if (psl->size == psl->capacity)
{
int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2; //扩容两倍 -- 当为0的时候赋值4字节
SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapacity * sizeof(SLDataType)); //三种情况,原地、异地与失败 -- 为防止失败应使用临时指针
if (tmp == NULL) // 这里开辟空间大小容易出错,必须要记得后面的* sizeof(SLDataType) 不然开辟的空间不够
{
perror("realloc fail");
return;
//exit(-1); //或者这样直接返回,更直接
}
psl->a = tmp;
psl->capacity = newCapacity;
}
}
//打印
void SLPrint(SL* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
//尾插
void SLPushBack(SL* psl, SLDataType x)
{
//assert(psl);
//检查并扩容
//SLCheckCapacity(psl);
//psl->a[psl->size] = x;
//psl->size++;
//当SLInsert写好后就不需要这些了,直接调用
SLInsert(psl, psl->size, x);
}
//头插
void SLPushFront(SL* psl, SLDataType x)
{
//assert(psl);
//SLCheckCapacity(psl);
//挪动数据
//int end = psl->size - 1;
//while (end >= 0)
//{
// psl->a[end+1] = psl->a[end];
// --end;
//}
//psl->a[0] = x;
//psl->size++;
//这里也不需要了,直接调用
SLInsert(psl, 0, x); // -- 这就相当与头插了
}
//尾删
void SLPoPBack(SL* psl)
{
//assert(psl);
//比较温柔的检查
//if (psl->size == 0)
//{
// return;
//}
//暴力的检查 -- 会直接报错 在C++中更常用
//assert(psl->size > 0);
//psl->size--; //realloc是不可以释放一部分的空间的所以不要free
// //也不需要清除掉数据,但是没必要,可以直接覆盖
// //这种的情况是由size为基准,由它访问
// //但是有bug:删掉的多了就会出现数据的越界
//写完之后就可以替换掉之前的了
SLErase(psl, psl->size-1);
}
//头删
void SLPoPhFront(SL* psl)
{
//assert(psl);
//assert(psl->size > 0);
//一
//int begin = 0;
//while (begin < psl->size - 1)
//{
// psl->a[begin] = psl->a[begin + 1];
// ++begin;
//}
//--psl->size;
//写完之后就可以替换掉之前的了
SLErase(psl, 0);
}
//缩容:以时间换空间 -- 不常用
//不缩容:以空间换时间 -- 现在的设备比较空间都比较强
//realloc 缩容可能会异地缩容
//顺序表查找 -- 没找到返回-1
//这里直接遍历数组就可以了,在数据少的情况下这样反而更快 O(N) 级别
int SLFind(SL* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
//顺序表在pos位置插入x -- 在前面插入
void SLInsert(SL* psl, size_t pos, SLDataType x) //size_t 更符合库函数的设计
{
assert(psl);
assert(pos <= psl->size);
SLCheckCapacity(psl);
//挪动数据 法一: 用这种方法要注意算术转化,容易死循环
//int end = psl->size - 1;
//while (end>=(int)pos)
//{
// psl->a[end] = psl->a[end - 1];
// --end;
//}
//挪动数据 法二:这种方法更好,不需要强制转化
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end - 1];
end--;
}
psl->a[pos] = x;
++psl->size;
}
// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos;
while (begin < psl->size - 1)
{
psl->a[begin] = psl->a[begin + 1];
++begin;
}
--psl->size;
}
//修改
void SLModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos < psl->size);
psl->a[pos] = x;
}
Test.c
#include "SeqList.h"
//尾插、头插、销毁测试
void TestSeqList1()
{
SL s;
//初始化
SLInit(&s);
//尾插
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
//打印观察
SLPrint(&s);
//头插
SLPushFront(&s, 10);
SLPushFront(&s, 20);
SLPushFront(&s, 30);
//打印观察
SLPrint(&s);
//销毁
SLDestory(&s);
}
//尾插、尾删、删多了测试
TestSeqList2()
{
//SLInit(NULL); //测试传空指针 -- 到assert会直接报错
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPrint(&s);
//尾删
SLPoPBack(&s);
SLPoPBack(&s);
SLPrint(&s);
//再尾删
SLPoPBack(&s);
SLPoPBack(&s);
SLPrint(&s);
//头删
SLPushBack(&s,10);
SLPushBack(&s,20);
SLPushBack(&s,30);
SLPushBack(&s,40);
SLPrint(&s);
SLDestory(&s);
}
//插入测试
TestSeqList3()
{
SL s;
SLInit(&s);
SLInsert(&s, 0, 30);
SLPushBack(&s, 40);
SLPrint(&s);
}
//尾插、删除测试
TestSeqList4()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPrint(&s);
SLErase(&s, 0);
SLPrint(&s);
int x = 0;
scanf("%d", &x);
int pos = SLFind(&s, x);
if (pos != -1)
{
SLErase(&s, pos);
}
SLPrint(&s);
}
void memu()
{
printf("****************************\n");
printf("1、尾插数据 2、头插数据\n");
printf("3、位置插入 7、打印数据 \n");
printf(" -1、退出 \n");
printf("****************************\n");
}
int main()
{
//TestSeqList1(); //测试的时候尽量单独用一个函数测试
//TestSeqList3();
//TestSeqList4();
SL s;
SLInit(&s);
int option = 0;
int x = 0;
int pos = 0;
do
{
memu();
printf("请输入你的操作:>");
scanf("%d", &option);
switch (option)
{
case 1:
printf("请连续输入你想要插入的数据,以-1结束\n");
scanf("%d", &x);
while (x != -1)
{
SLPushBack(&s,x);
scanf("%d", &x);
}
break;
case 2:
printf("请连续输入你想要插入的数据,以-1结束\n");
scanf("%d", &x);
while (x != -1)
{
SLPushFront(&s, x);
scanf("%d", &x);
}
break;
case 3:
printf("请输入你想要插入的数据\n");
scanf("%d", &x);
printf("请输入你想要插入的位置\n");
scanf("%d", &pos);
SLInsert(&s, pos, x);
break;
case 7:
SLPrint(&s);
break;
default:
break;
}
} while (option != -1);
SLDestory(&s);
return 0;
}
菜单不完善了,很简单
我写这篇的时候发现这里不允许使用 //// 来二层注释
结束语
别有幽愁暗恨生,此时无声胜有声。
唐·白居易 《琵琶行》