目录
目录
1.线性表
线性表( linear list)是n个具有相同特性的数据元素的有限序列。
![](https://i-blog.csdnimg.cn/blog_migrate/76d1489f4668b7b9f9df735ad5ff4b1a.png)
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表简单来说就是一个数组,并且这个数组的空间是连续的,且存储的数据也必须是从头开始连续存储。
顺序表一般可以分为:
静态顺序表
静态顺序表:使用定长数组存储元素。
静态顺序表的长度是固定的。 静态顺序表的空间是固定的,空间给大了,很浪费;空间给小了,不够实用。所以实用性较低。
动态顺序表
动态顺序表:使用动态开辟的数组存储
动态顺序表可以规避一些空间浪费,但是还是会有一点点浪费。
每次扩容可以稍微多扩容一点点,减少扩容的频率。
扩容是根据需求扩容,最常用的扩容倍数就是:2倍 1.5倍。 (视情况而定)
realloc
原地扩容:直接扩容;
异地扩容:分配给别人
realloc有可能是原地也有可能是异地。
下面我们实现动态顺序表。
本章实现顺序表的头插 尾插 头删和尾删等功能进行实现。
头插 尾插 头删 尾删
主函数Test.c
main函数
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
#include<stdio.h>
int main()
{
TestSL1();
TestSL2();
TestSL3();
TestSL4();
TestSL5();
TestSL6();
return 0;
}
TestSL1测试头尾插
//测试尾插头插
void TestSL1()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
SLPushFront(&sl, 10);
SLPushFront(&sl, 20);
SLPushFront(&sl, 30);
SLPushFront(&sl, 40);
SLPrint(&sl);
SLDestroy(&sl);
}
TestSL2测试头尾删
//测试尾删头删
void TestSL2()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(&sl);
//SLPopBack(&sl);
//SLPrint(&sl);
SLPushFront(&sl, 10);
SLPushFront(&sl, 20);
SLPushFront(&sl, 30);
SLPushFront(&sl, 40);
SLPrint(&sl);
SLDestroy(&sl);
}
TestSL3测试头尾删
//测试头删
void TestSL3()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
}
TestSL4测试任意下标位置的插入
//任意下标位置的插入
void TestSL4()
{
//SL* ptr = NULL;
//SLInit(ptr);
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLInsert(&sl, 2, 20);
SLPrint(&sl);
SLInsert(&sl, 6, 20);
SLPrint(&sl);
SLInsert(&sl, 0, 20);
SLPrint(&sl);
SLInsert(&sl, 10, 20);
SLPrint(&sl);
SLDestroy(&sl);
}
TestSL5测试任意下标位置的删除
//任意下标位置的删除
void TestSL5()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
//SLErase(&sl, 3);
//SLPrint(&sl);
}
TestSL6测试顺序表的查找
void TestSL6()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
int tmp = SeqListFind(&sl, 3);
if (tmp == -1)
{
printf("很抱歉,没有找到该数据!\n");
}
else
{
int p = tmp + 1;
printf("找到了!该数的在表中第%d个位置。", p);
}
}
头文件&函数声明SeqList.h
头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
-
SeqList声明
//声明一个结构体
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//如果后期a的类型改变就很方便
int size;//有效数据
int capacity;//空间容量
}SL;//SL是这个结构体的类型,用typedef定义更加方便了
-
SeqList初始化
//初始化
void SLInit(SL* ps);
-
SeqList释放&销毁
//释放销毁
void SLDestroy(SL* ps);
-
SeqList展示
//展示
void SLPrint(SL* ps);
-
尾插 头插 尾删 头删
void SLPushBack(SL* psl, SLDataType x);//尾插
void SLPushFront(SL* psl, SLDataType x);//头插
void SLPopBack(SL* psl);//尾删
void SLPopFront(SL* psl);//头删
-
任意下标位置的插入删除
void SLInsert(SL* psl, int pos, SLDataType x);
void SLErase(SL* psl, int pos);
-
顺序表查找
int SeqListFind(SL* psl, SLDataType x);
函数实现SeqList.c
初始化SLInit
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//关于初始化 可以首先置为NULL 也可以首先放点值
// memset一般用于数组初始化 直接初始化更加清晰
释放销毁SLDestroy
//销毁
void SLDestroy(SL* ps)
{
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
扩容SLCheckCapacity
这个函数是我们自己封装,可以不用去函数声明。
在使用SLCheckCapacity之前一定要先声明或者放在使用之前。
//扩容
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)//容量满了需要扩容的条件
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
SLDataType* tmp = (SLDataType*)raelloc(ps->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL)
{
perror("CheckCapacity");//
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
当size==capicity的时候,说明空间被占满,此时就需要扩容。
- 原地扩容和异地扩容判断和最优写法
- realloc函数存储空间起始位置是可以传NULL空指针的
- realloc函数里所写的空间大小是扩容后的空间大小,而不是需要扩的空间大小。
- 异地扩容对之前的空间不需要手动free,而是操作系统已经帮助释放了原来旧的空间
打印SLPrint
//展示
void SLPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
尾插SLPushBack
- 首先在size位置放值x
- size往后移动++
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);//扩容
ps->a[ps->size] = x;
ps->size++;
}
头插SLPushFront
在顺序表的前面插入数据,不能向前扩容和插入空间。
因为这段空间整体是连续的,地址是连续的,又要求数据是连续的。所以我们唯一可用的方法就是把数据一个一个往后挪动。我们也可以用函数memcpy和memove来实现,这里我们就手动来写。另一种不用挪动数据的方法是链表,我们后面会一一介绍。
首先把size-1的位置移动到size的位置,全部都向后挪动一位后,再在第一个位置放数值。
void SLPushFront(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)//注意可以等于0 size为1 但是不能为负数会越界访问
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
//头插的时间复杂度为O(N),头插N个数据的时间复杂度是O(N²)。所以不要轻易使用。
尾删SLPopBack
尾删也是非常简单的,有人可能会说把要删除的值赋值为-1或者NULL。但是我们并不能确定顺序表元素的类型,万一类型改变,这里再去改变就不方便了。如果这个值本来就是-1,就不能很好的尾删了。所以直接size--即可。后期插入等操作会把这个值给覆盖掉。
- size-- 意味着着只有--之后的数值有效。意味着我们头尾插会把无效数据覆盖。
- 无效数据不需要free
- 动态内存是不支持分期free。
- 后续操作会覆盖无效数据,空间可以重复利用。
- 单独free,顺序表的空间就不连续了
//尾删
void SLPopBack(SL* psl)
{
assert(psl);
// 空
// 温柔的检查
/*if (psl->size == 0)
{
return;
}*/
// 暴力检查
assert(psl->size > 0); //防止空了还在删
//psl->a[psl->size - 1] = -1;
psl->size--;
}
这里我们要注意过度删除的问题,正常运行编译器不会报错,但是如果在oj上就会很难发现问题何在。所以这里我们使用断言, 如果空了还在删除就中止程序。
头删SLPopFront
- 头插是从最后一个开始,依次向右挪动数据覆盖
- 头删是从最头一个开始,依次向左挪动数据覆盖
- size--即可
//头删
void SLPopFront(SL* ps)
{
assert(ps->size);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
任意下标位置的插入SLInsert
- 注意pos是下标
- size是数据个数,看做下标的话,他是最后一个数据的下一个位置
- 顺序表是连续的!!
void SLInsert(SL* psl, int pos, SLDataType x)
{
assert(psl);
assert(pos >= 0 && pos <= psl->size);
SLCheckCapacity(psl);
// 挪动数据
int end = psl->size - 1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
若pos==size,就不会进入循环,直接进行尾插。
任意下标位置的删除SLErase
void SLErase(SL* psl, int pos)
{
assert(psl);
assert(pos >= 0 && pos < psl->size);
// 挪动覆盖
int begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin - 1] = psl->a[begin];
++begin;
}
psl->size--;
}
顺序表某一值的查找
// 顺序表查找
int SeqListFind(SL* psl, SLDataType x)
{
assert(psl); // 断言
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i; //查找到,返回该值在数组中的下标
}
}
return -1; // 没查找到
}
菜单
建议:
把所有功能测试成功,再去写【菜单】,这样好【调试】。
写菜单可以使用:switch&case 或 if&else
如果我们要把用户输入的数据分布放到每个函数实现内部,菜单中代码比较少的话,就用switch&case
如果我们要把用户输入的数据放到菜单里面,菜单中代码就会比较多,用if&else
void menu()
{
printf("*******************************\n");
printf("1、尾插数据 2、尾删数据\n");
printf("3、头插数据 4、头删数据\n");
printf("5、打印数据 6、查找数据 0、退出 \n");
printf("*******************************\n");
}
int main()
{
SeqList s;
SLInit(&s);
int option = 0;
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &option);
if (option == 1)//尾插
{
printf("请依次输入你的要尾插数据个数和数据:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
int x = 0;
scanf("%d", &x);
SLPushBack(&s, x);
}
}
else if (option == 2)//尾删
{
printf("请依次输入你的要尾删数据个数:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
SLPopBack(&s);
}
}
else if (option == 3)//头插
{
printf("请依次输入你的要头插数据个数和数据:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
int x = 0;
scanf("%d", &x);
SLPushFront(&s, x);
}
}
else if (option == 4)//头删
{
printf("请依次输入你的要头删数据个数:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
SLPopFront(&s);
}
}
else if (option == 5)//打印
{
SLPrint(&s);
}
else if (option == 6)
{
printf("请依次输入你要查找的数据:>");
int n = 0;
scanf("%d", &n);
int tmp = SeqListFind(&s, n);
if (tmp == -1)
{
printf("很抱歉,没有找到该数据!\n");
}
else
{
int p = tmp + 1;
printf("找到了!该数的在表中第%d个位置。", p);
}
}
else if (option == 0)
{
break;
}
else
{
printf("无此选项,请重新输入\n");
}
} while (option != 0);
SLDestroy(&s);
return 0;
}
代码整合
SeqList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLDateType;
typedef struct SeqList
{
SLDateType* a;
int size;
int capacity;
}SeqList;
// 对数据的管理:增删查改
void SeqListInit(SeqList* ps);
void SeqListDestroy(SeqList* ps);
void SeqListPrint(SeqList* ps);
void SeqListPushBack(SeqList* ps, SLDateType x);
void SeqListPushFront(SeqList* ps, SLDateType x);
void SeqListPopFront(SeqList* ps);
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
void SLInit(SeqList* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//销毁
void SLDestroy(SeqList* ps)
{
assert(ps);
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
//扩容
void SLCheckCapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
//打印
void SLPrint(SeqList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//尾插
void SLPushBack(SeqList* ps, SLDateType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//头插
void SLPushFront(SeqList* ps, SLDateType x)
{
assert(ps);
SLCheckCapacity(ps);
// 挪动数据
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
//尾删
void SLPopBack(SeqList* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
void SLPopFront(SeqList* ps)
{
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
void SLInsert(SeqList* ps, int pos, SLDateType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
// 挪动数据
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SLErase(SeqList* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
// 挪动覆盖
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
assert(ps); // 断言
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i; //查找到,返回该值在数组中的下标
}
}
return -1; // 没查找到
}
Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
#include<stdio.h>
//测试尾插头插
void TestSL1()
{
printf("以下是尾插\n");
SeqList sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushBack(&sl, 6);
SLPushBack(&sl, 7);
SLPushBack(&sl, 8);
SLPushBack(&sl, 9);
SLPrint(&sl);
printf("\n");
printf("以下是头插\n");
SLPushFront(&sl, 10);
SLPushFront(&sl, 20);
SLPushFront(&sl, 30);
SLPushFront(&sl, 40);
SLPrint(&sl);
printf("\n");
SLDestroy(&sl);
}
//测试尾删
void TestSL2()
{
SeqList sl;
SLInit(&sl);
printf("以下是尾删\n");
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLDestroy(&sl);
printf("\n");
}
//测试头删
void TestSL3()
{
SeqList sl;
SLInit(&sl);
printf("以下是头删\n");
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
printf("\n");
}
//任意下标位置的插入
void TestSL4()
{
//SL* ptr = NULL;
//SLInit(ptr);
SeqList sl;
SLInit(&sl);
printf("以下是任意坐标的插入\n");
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLInsert(&sl, 2, 20);
SLPrint(&sl);
SLInsert(&sl, 5, 14);
SLPrint(&sl);
SLDestroy(&sl);
printf("\n");
}
//任意下标位置的删除
void TestSL5()
{
SeqList sl;
SLInit(&sl);
printf("以下是任意坐标的删除\n");
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
printf("\n");
}
//某一值的查找
void TestSL6()
{
SeqList sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPrint(&sl);
int tmp = SeqListFind(&sl, 3);
if (tmp == -1)
{
printf("很抱歉,没有找到该数据!\n");
}
else
{
int p = tmp + 1;
printf("找到了!该数的在表中第%d个位置。", p);
}
printf("\n");
}
void menu()
{
printf("*******************************\n");
printf("1、尾插数据 2、尾删数据\n");
printf("3、头插数据 4、头删数据\n");
printf("5、打印数据 6、查找数据 0、退出 \n");
printf("*******************************\n");
}
int main()
{
SeqList s;
SLInit(&s);
int option = 0;
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &option);
if (option == 1)//尾插
{
printf("请依次输入你的要尾插数据个数和数据:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
int x = 0;
scanf("%d", &x);
SLPushBack(&s, x);
}
}
else if (option == 2)//尾删
{
printf("请依次输入你的要尾删数据个数:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
SLPopBack(&s);
}
}
else if (option == 3)//头插
{
printf("请依次输入你的要头插数据个数和数据:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
int x = 0;
scanf("%d", &x);
SLPushFront(&s, x);
}
}
else if (option == 4)//头删
{
printf("请依次输入你的要头删数据个数:>");
int n = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
SLPopFront(&s);
}
}
else if (option == 5)//打印
{
SLPrint(&s);
}
else if (option == 6)
{
printf("请依次输入你要查找的数据:>");
int n = 0;
scanf("%d", &n);
int tmp = SeqListFind(&s, n);
if (tmp == -1)
{
printf("很抱歉,没有找到该数据!\n");
}
else
{
int p = tmp + 1;
printf("找到了!该数的在表中第%d个位置。", p);
}
}
else if (option == 0)
{
break;
}
else
{
printf("无此选项,请重新输入\n");
}
} while (option != 0);
SLDestroy(&s);
return 0;
}
2.2 顺序表的问题及其思考
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。