在C语言学习到一定阶段之后,接下来我们就进入到了数据结构的部分内容。
目录
数据结构与线性表
数据就像草原的一群羊一样,如果你要在草原上找到一个叫做“咩咩”的羊很难,但是如果你要找一头“3号”羊是比较简单的。数据结构就是像羊圈管理羊群一样管理数据。
因此可以说,数据结构就是计算机存储和组织数据的方式。
数组就是一种很基础的数据结构,用来存储一群同类型的数据。但是随着我们要对数据进行的操作愈加复杂(访问、修改、插入数据等等等等),频繁的访问数组已经严重影响了计算机运行的效率,因此简单的数组已经无法满足我们的需要了,因此我们需要进入第一个数据结构类型——线性表。
线性表是n个具有相同特性的数据元素的有限序列,是一种广泛应用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等等。
但是注意线性表在逻辑上是连续的线性结构,但是物理上不一定是连续的。线性表在物理上储存的时候通常以数组和链式结构的形式储存。
顺序表
顺序表在底层上是数组。
那么既然已经有数组了,我们为什么需要顺序表呢?举例说明:
int a[100]={1,2,3,4,...X...};
对于如上所示的100个元素的数组a。如果我们要修改其中一个数字就找先遍历数组找到对应元素修改,如果要插入和删除数组的话,都需要遍历这个100个元素的数组。这个效率显然是很低的。因此我们需要一个更好用、效率更高的工具:顺序表。
顺序表是在数组的基础上加上了增删查改等方法的一种储存形式。顺序表的特性是在物理结构上连续,在逻辑结构上也连续。
这是一个固定长度的数组
int a[10]={0};
这是动态内存开辟出来的数组,确定大小后再去申请。
int *arr
顺序表分类:
静态顺序表:
Stack SeqList
{
int arr[100];//定长数组
int size;//顺序表当前有效的数据个数
}
动态顺序表:
Stack Seqlist
{
int *arr;//
int size//有效数字个数
int capacity;//空间大小
};
相较于静态顺序表,动态顺序表可以动态增容。
接下来我们要写一段代码实现动态顺序表。
首先我们需要准备三个文件:
Seqlist.c——实现顺序表的方法
Seqlist.h——顺序表结构,顺序表声明,方法
test.c——测试代码
1.接下来我们要定义一个数据表
2.当创建号我们的顺序表之后,我们要对他进行初始化
这里我们用传值返回就会报错,原因:传值返回是值拷贝,但是这里s1没有值。
所以这里要用传址返回。
3.而动态内存创建后就必须有销毁
4.接下来我们要对顺序表进行各种操作:增删插改
声明:
尾部插入数据
原理展示:
要申请多大的空间?
增容一般是2到3倍(2倍更常见)。因为一次性给太多空间就会像静态顺序表那样浪费大量空间而得不偿失,而过少就会导致需要频繁地扩容而导致程序运行效率过低
尾部插入数据函数一:
尾部插入数据函数二:
因此这部分的代码为:
但是这个结构是比较脆弱的,如果用户输入了这么一个东西,它就会报错:读取访问权限冲突。
SLPushback(NULL, 5);
所以我们要对这个函数进行改造。
温柔的方式
//温柔的解决方式
if (ps->arr == NULL)
{
return;
}
if (ps->size == ps->capacity)//空间不够了
{
//code
}
暴力的方式
//暴力判断
assert(ps->arr!= NULL);
//等价于assert(ps)
尾部插入的代码:
//尾部插入数据
void SLPushback(SL* ps, STDatatype x)
{
//暴力判断
assert(ps->arr!= NULL);
//等价于assert(ps)
if (ps->size == ps->capacity)//空间不够了
{
//申请空间
//要增容只能用realloc函数,不能用malloc和calloc
int newcapacity = ps->capacity == 0 ? 4:2*ps->capacity;
STDatatype*tmp = realloc(ps->arr, newcapacity * 2 * sizeof(STDatatype));
//tmp指的是结构体当中的数组arr
if (tmp==NULL )//realloc可能申请失败所以要判断
{
perror("realloc error");
return 1;//程序推出
}
//申请成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
ps->arr[ps->size++] = x;
}
头部插入数据
原理图:
如图所示,如果要申请空间挪动数据的话从最后一位开始
代码为:
尾部删除数据
原理图:
代码:
头部删除数据:
原理图:
代码:
指定位置之前插入数据
原理:
代码; 指定位置删除数据
原理图:
代码:
指定位置查找数据
查找数据相对来说比较简单,这里受限于篇幅,只讲解暴力查找的算法,即循环遍历数组
完整的代码:
Seqlist.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include<assert.h>
//定义顺序表的结构
//#define N 100
//静态顺序表
//struct SeqList
//{
// int arr[N];
// int size;//有效数据个数
//};
typedef int STDatatype;
//这里是为了将来方便替换数组存储数据的类型
// 如果将来需要替换数据类型为char,只需要将int改为char即可
//动态顺序表
typedef struct SeqList
{
STDatatype* arr;
int size;//有效数据个数
int capacity;//顺序表空间大小
}SL;//这是为了将来为了方便调用结构体
//顺序表初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestory(SL* ps);
//校验顺序表空间够不够
void SLCheckCapacity(SL* ps);
//顺序表的打印
void SLPrint(SL s);
//顺序表头部/尾部插入数据
void SLPushback(SL*ps, STDatatype x);//尾部插入数据
void SLPushfront(SL*ps, STDatatype x);//头部插入数据
void SLPopback(SL*ps); //尾部删除数据
void SLPopfront(SL*ps);//头部删除数据
//指定位置之前插入数据/删除数据/查找数据
void SLInsert(SL* ps, int pos,STDatatype x);//插入数据
//ps表示在ps所在的数组里插入数据
//pos在指定顺序表里下标的位置
//x为插入的数据
void SLDelete(SL* ps,int pos);//删除数据
int SLFind(SL* ps, STDatatype x);//查找数据
Seqlist.c
#define _CRT_SECURE_NO_WARNINGS
#include"Seqlist.h"
//Seqlist.c——实现顺序表的方法
//Seqlist.h——顺序表结构,顺序表声明,方法
//顺序表的初始化
void SLInit(SL *ps)
{
ps->arr = NULL;//需要包含头文件<stdlib.h>
ps->size=ps->capacity=0;
}
//顺序表的销毁
void SLDestory(SL*ps)
{
if (ps->arr)//判断数据表的数组不为空
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//校验顺序表空间够不够
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)//空间不够了
{
//申请空间
//要增容只能用realloc函数,不能用malloc和calloc
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDatatype* tmp = realloc(ps->arr, newcapacity * 2 * sizeof(STDatatype));
//tmp指的是结构体当中的数组arr
if (tmp == NULL)//realloc可能申请失败所以要判断
{
perror("realloc error");
return 1;//程序推出
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
//顺序表的打印
void SLPrint(SL s)
{
for (int i = 0;i < s.size;i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
//尾部插入数据
void SLPushback(SL* ps, STDatatype x)
{
//温柔的方式
// if(ps == NULL)
// {
// return;
// }
//暴力判断
assert(ps);
//等价于assert(ps->arr!= NULL);
//校验空间够不够
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
//头部插入数据
void SLPushfront(SL* ps, STDatatype x)
{
assert(ps);
SLCheckCapacity(ps);
//先让顺序表中整体的数据向后移动一位。
for (int i = ps->size - 1;i >=0;i--)
{
ps->arr[i+1]=ps->arr[i];//最后为arr[1]=arr[0]
}
ps->arr[0] = x;
ps->size++;
}
//尾部删除数据
void SLPopback(SL* ps, STDatatype x)
{
assert(ps);
assert(ps->size != 0);
//顺序表不为空
//这段代码是否存在无所谓:ps->arr[ps->size - 1] = -1;
--ps->size;
}
//头部删除数据
void SLPopfront(SL* ps)
{
assert(ps);
assert(ps->size != 0);
//数据表整体向前移动一位
for (int i = 0;i <= ps->size - 1;i++)
{
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, STDatatype x)
{
assert(ps);
//pos必须>=0且<=size
assert(pos>=0&&pos<=ps->size);
SLCheckCapacity(ps);//校验空间够不够
//让pos位置及之后的数据集体向后一位
for (int i = ps->size-1;i >= pos;i--)
{
ps->arr[i+1] = ps->arr[i];//最后为arr[pos+1]=arr[pos]
}
//pos位置空出来了
ps->arr[pos] = x;
ps->size++;
}
//指定位置删除数据
void SLDelete(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos <ps->size);
for (int i=pos;i<ps->size-1;i++)
{
ps->arr[i] = ps->arr[i+1];//最后为arr[size-2]=arr[size-1]
}
ps->size--;
}
//指定位置查找数据
int SLFind(SL* ps, STDatatype x)
{
assert(ps);
//遍历数组查找
for (int i = 0;i <= ps->size - 1;i++)
{
if (x == ps->arr[i])
{
//找到了
return i;
}
else
{
continue;
}
}
return -1;//无效的下标表示没找到
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Seqlist.h"
void SLtest1()
{
SL s1;
SLInit(&s1);
//这里我们不能用传值返回
//传值返回是值拷贝,但是这里s1没有值
//所以这里要用传址返回
//增删查改操作。
//顺序表的插入
//测试尾插
SLPushback(&s1,1);
SLPushback(&s1,2);
SLPushback(&s1,3);
SLPushback(&s1,4);
//测试头插
SLPushfront(&s1, 5);
SLPushfront(&s1, 6);
SLPrint(s1);
// 测试头删
SLPopfront(&s1);
SLPopfront(&s1);
SLPrint(s1);
// 测试尾删
SLPopback(&s1);
SLPopback(&s1);
SLPrint(s1);
//顺序表的销毁
SLDestory(&s1);
}
void SLtest2()
{
SL s1;
//初始化
SLInit(&s1);
//尾部插入数据
SLPushback(&s1, 1);
SLPushback(&s1, 2);
SLPushback(&s1, 3);
SLPushback(&s1, 4);
//在指定位置之前插入数据
SLInsert(&s1, 0, 99);
SLInsert(&s1, s1.size, 88);
SLPrint(s1);//99 1 2 3 4 88
//删除指定位置的数据
SLDelete(&s1, 2);
SLPrint(s1);
//查找指定数据
int F=SLFind(&s1, 3);
if (F >= 0)
{
printf("找到了,位置是%d\n",F);
}
else
{
printf("没找到\n");
}
销毁
SLDestory(&s1);
}
int main()
{
SLtest1();
SLtest2();
return 0;
}
顺序表的内容并没有结束,但是受限于篇幅原因我只能先到这里了,感谢各位读者朋友的阅读,求一个赞,谢谢。