绪论
从本章开始就是开始数据结构的开端,本章将会写出数据结构中的顺序表的代码实现,多会以注释的方法来描述一些细节(注释是我们程序员必须常用的工具)。
话不多说安全带系好,发车啦(建议电脑观看)。
附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要
目录
1.线性表
2.顺序表
2.1顺序表的结构:
2.1.1 顺序表的初始化:
2.1.2 顺序表的摧毁
2.1.3 顺序表的放入数据
2.1.4 顺序表的删除数据
2.1.5 打印顺序表的数据
2.2顺序表的源代码:
1.线性表
知识点:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串..
细节(注意点):
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组(地址连续)和链式结构(地址不连续)的形式存储。
2.顺序表
知识点:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储(本质就是一个数组只不过用结构体包装了一下)。
顺序表分为:静态顺序表(实现开辟好数组的空间大小)以及动态顺序表(用动态内存管理的方式来进行内存管理)
细节:
我们要实现一个顺序表的话首先我们要知道顺序表的框架:
- 顺序表的结构体
- 数组(a)
- 顺序表中的元素个数(size)
- 容量(用于动态顺序表,是动态申请的大小,用于和元素个数比较判断申请空间是否足够)
- 有了这个结构后,我们就需要实现一个顺序表的基本功能
- 将数据放进顺序表中
- 将顺序表中的数据删除
- 初始化顺序表(主要针对于动态开辟空间提前申请空间)
- 归还(摧毁)所借的空间
- 将顺序表中的数据打印出来
下面这要讲的是动态的顺序表、如果需要静态的将结构体中的容量去掉,再把数组改一下即可(若有问题的话可以评论我都会看),我们就把顺序表想成一个数组就能很好的理解了
2.1顺序表的结构:
顺序表所要的结构是由数组、元素个数、容量组成的
代码实现如下:
typedef struct SeqList//将结构体名称重命名为
{
SLDataType* a;//开辟SLDataType(用typedef重定义类型名,这样方便与改变结构体内的数据类型)类型的空间
//数组的本质是指针所以为了更方便理解就直接写成指针的形式SLDataType* a
int size;//元素个数
int capacity;//容量
}SeqList;//重命名为SeqList 这样方便后面使用
//宏定义如下:
//#define SLDataType int
//使用结构体类型
//SeqList s;即可
//不用写成 struct SeqList s;
2.1.1 顺序表的初始化:
将容量capacity和个数size进行简单的初始化,主要是申请一片空间来给a来存数据
代码如下:
void InitSeqList(SeqList* obj)//将结构体用指针接收
{
assert(obj);
obj->capacity = INIT_CAPACITY;//通过指针来访问结构体中的成员,将capacity先初始化为INIT_CAPACITY(用宏来确定capacity的起始大小,这样方便后改变)
obj->size = 0;//0个成员
obj->a = (SLDataType*)malloc(sizeof(SLDataType) * obj->capacity);//malloc动态申请结构体大小的capacity个空间
if (obj->a == NULL)//判断一下是否申请成功
{
perror("malloc");//如果失败就报错
return;
}
}
//宏
//#define INIT_CAPACITY 4 (这是应该定义在头文件中的)
//调用的方法
//SeqList s;(应该定义在测试test.c文件中)
//InitSeqList(&s);
//而一般的结构的实现又是放在SeqList.c的文件中,这样来进行分源管理
2.1.2 顺序表的摧毁
顺序表的摧毁主要是为了将向操作系统借的空间归还,以及再将容量和元素个数归为0
void DestorySeqList(SeqList* obj)//指针接收结构
{
assert(obj);//判断结构是否为空,防止访问到NULL指针(这是一个好习惯)
free(obj->a);//直接释放所借用的空间
obj->a = NULL;//再将其置为NULL
obj->capacity = obj->size = 0;//将容量和个都置为0,摧毁了自然就没了
}
//调用方法
//SeqList s;
//DestorySeqList(&s);
2.1.3 顺序表的放入数据
1.从尾部插入数据
尾部插入就比较的简单了,因为顺序表其实是一个数组所以直接在最后位置插入数据就行(此时最后位置的下标就是元素个数)。
void SeqListBackPush(SeqList* obj, SLDataType x)//将结构体用指针接收通过指针来找到成员,x是所要尾插的数据 { assert(obj);//判断结构体是否为NULL If_Add_Capacity(obj);//判断数据是否已经把所借的容量填满了 obj->a[(obj->size)++] = x;//在a的最后位置插入数据,可以发现其实size个数就是最后位置的下标 }
但要注意判断容量是否满足,如果容量已经是满的了(size == capacity)就需要扩容,If_Add_Capacity (判断是否要增容)
void If_Add_Capacity(SeqList* obj) { if (obj->size == obj->capacity)//判断已有成员个数是否等于容量,若等则进去 { SLDataType* ptr = (SLDataType*)realloc(obj->a, sizeof(SLDataType) * obj->capacity * 2);//进来后就说明空间不够了,需要开空间 //一般多直接开辟比容量大两倍的空间 即 对a开辟结构体大小为原capacity两倍的空间 if (ptr == NULL)//判断是否申请成功 { perror("realloc");//不成功则报错 return; } obj->a = ptr;//因为可能是异地扩容所以还要将ptr赋值给数组a obj->capacity *= 2;//容量 乘于 2 ptr = NULL;//无用的指针置为NULL(好习惯) } }
2.从头部插入
在一个数组中若想从头部插入 你就需要把数据先全部往后挪动一位,再将这个数据存放到第一个位置处。
void SeqListFrontPush(SeqList* obj, SLDataType x) { assert(obj);//判空 If_Add_Capacity(obj);//判是否满了 for (int i = obj->size; i > 0; i--)//将所有数据往后移一位 { obj->a[i] = obj->a[i - 1];//此处只要是未满的就能直接就行移位并不会有事 } obj->a[0] = x;//在a[0]位置处添加数据 obj->size++;//元素个数++,这可别忘了! }
3.指定位置插入
在满足pos位置是一个正常的位置的前提下,并且同样需要判断是否要扩容, 要在某个位置处插入,其本质其实和头插有些类似,需要把插的位置后的数据全部往后挪一位后,最后再在那个位置插入数据即可。
void SeqListInsert(SeqList* obj, int pos, SLDataType x)//在pos位置处添加数据 { assert(obj);//判空 pos -= 1;//换成下标 assert(pos >= 0 && pos <= obj->size);//判断这个位置是否有问题 If_Add_Capacity(obj);//判断是否满了 int i = obj->size;//和头插的方法几乎一样 for (i; i > pos; i--)//将从位置处开始的数据全部往后挪一位 { obj->a[i] = obj->a[i - 1];//从尾部开始防止覆盖 } obj->a[i] = x;//在位置处插入数据 obj->size++;//size++ 别忘了! }
2.1.4 顺序表的删除数据
1. 从尾部删除
在删除之前我们需要判断一下是否还有数据在顺序表中(assert(obj->size > 0)),对于一个数组来说我们删除时直接对元素个数进行 - - 即可并不会去真正的删除,当下一次插入数据时就直接覆盖了,也不会有什么影响。
void SeqListBackPop(SeqList* obj) { assert(obj);//判空 assert(obj->size > 0);//为真就过、为假就会报错,若没有数据那就是有问题的 obj->size--;//此处的尾删并不直接将空间归还,而仅仅只是把元素个数-1这样 //就不会访问到,即使后面需要再次添加数据也就直接覆盖了,因为要归还空间的成本太高了 }
2.从头部删除
同样我们需要先判断一下顺序表中是否还有数据、对于头部删除来说直接将第一个数据覆盖了就好 。
void SeqListFrontPop(SeqList* obj) { assert(obj);//判空 assert(obj->size > 0);//判断是否有数据 for (int i = 0; i < obj->size - 1; i++)//直接从第2个位置开始往前覆盖掉即可 { obj->a[i] = obj->a[i + 1]; } obj->size--;//注意要 - - }
3.指定位置删除
判断pos是否在顺序表中、最后将从pos+1位置开始数据覆盖即可。
void SeqListErase(SeqList* obj, int pos) { assert(obj);//判空 assert(obj->size > 0);//是否有数据 pos -= 1;//换成下标 assert(pos >= 0 && pos <= obj->size);//是否符合要求 for (int i = pos; i < obj->size - 1; i++)//和头删对应此处就应该是从pos+1位置处开始往前覆盖 { obj->a[i] = obj->a[i + 1];//将pos位置处先覆盖 , 然后以此往后 } obj->size--;//注意 - - }
2.1.5 打印顺序表的数据
就和数组的打印一样,直接遍历打印即可
void SeqListPirnt(SeqList* obj)//指针接收结构体
{
assert(obj);//判空
for (int i = 0; i < obj->size; i++)//从下标为0的位置处开始往后遍历
{
printf("%d ", obj->a[i]);//结构体访问成员:*obj表示结构体 在 .访问 就还能写成 (*obj).a[i] 这两个是等价的一般喜欢用前面方法
}
printf("\n");//换行
}
如果有任何问题,欢迎讨论!
2.2顺序表的源代码:
我将全部放在一个里面对于分源内容请自行分开,或者直接合并也行(合并方法将声明以及包含自身的头文件去掉即可直接使用)
//SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define INIT_CAPACITY 4
//sequence
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;
}SeqList;
void InitSeqList(SeqList * obj);
void DestorySeqList(SeqList* obj);
void SeqListBackPush(SeqList* obj,SLDataType x );
void SeqListBackPop(SeqList* obj);
void SeqListPirnt(SeqList* obj);
void SeqListFrontPush(SeqList* obj, SLDataType x);
void SeqListFrontPop(SeqList* obj);
void SeqListInsert(SeqList* obj, int pos, SLDataType x);
void SeqListErase(SeqList* obj, int pos);
//SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqLIst.h"
//sequence 顺序
void If_Add_Capacity(SeqList* obj)
{
if (obj->size == obj->capacity)
{
SLDataType* ptr = (SLDataType*)realloc(obj->a, sizeof(SLDataType) * obj->capacity * 2);
if (ptr == NULL)
{
perror("realloc");
return;
}
obj->a = ptr;
obj->capacity *= 2;
ptr = NULL;
}
return;
}
void InitSeqList(SeqList* obj)
{
assert(obj);
obj->capacity = INIT_CAPACITY;
obj->size = 0;
obj->a = (SLDataType*)malloc(sizeof(SLDataType) * obj->capacity);
if (obj->a == NULL)
{
perror("malloc");
return;
}
}
void DestorySeqList(SeqList* obj)
{
assert(obj);
free(obj->a);
obj->a = NULL;
obj->capacity = obj->size = 0;
}
void SeqListBackPush(SeqList* obj, SLDataType x)
{
assert(obj);
If_Add_Capacity(obj);
obj->a[(obj->size)++] = x;
}
void SeqListBackPop(SeqList* obj)
{
assert(obj);
assert(obj->size > 0);//为真就过、为假就会报错
obj->size--;
}
void SeqListPirnt(SeqList* obj)
{
assert(obj);
for (int i = 0; i < obj->size; i++)
{
printf("%d ", obj->a[i]);
}
printf("\n");
}
void SeqListFrontPush(SeqList* obj, SLDataType x)
{
assert(obj);
If_Add_Capacity(obj);
for (int i = obj->size; i > 0; i--)
{
obj->a[i] = obj->a[i - 1];
}
obj->a[0] = x;
obj->size++;
}
void SeqListFrontPop(SeqList* obj)
{
assert(obj);
assert(obj->size > 0);
for (int i = 0; i < obj->size - 1; i++)
{
obj->a[i] = obj->a[i + 1];
}
obj->size--;
}
void SeqListInsert(SeqList* obj, int pos, SLDataType x)
{
assert(obj);
pos -= 1;//换成下标
assert(pos >= 0 && pos <= obj->size);
If_Add_Capacity(obj);
int i = obj->size;
for (i; i > pos; i--)//从最后开始将填充数据
{
obj->a[i] = obj->a[i - 1];
}
obj->a[i] = x;
obj->size++;
}
void SeqListErase(SeqList* obj, int pos)
{
assert(obj);
assert(obj->size > 0);
pos -= 1;//换成下标
assert(pos >= 0 && pos <= obj->size);
for (int i = pos; i < obj->size - 1; i++)
{
obj->a[i] = obj->a[i + 1];
}
obj->size--;
}
//test.c
//测试是否能用
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqLIst.h"
int main()
{
SeqList s;
InitSeqList(&s);
SeqListPush(&s, 1);
SeqListPush(&s, 2);
SeqListPush(&s, 3);
SeqListPush(&s, 4);
SeqListPush(&s, 5);
SeqListPop(&s);
SeqListPop(&s);
SeqListPop(&s);
SeqListFrontPush(&s, 0);
SeqListFrontPush(&s, -1);
SeqListInsert(&s, 1, 3);
SeqListErase(&s, 2);
SeqListPirnt(&s);
DestorySeqList(&s);
return 0;
}
分源管理时的头文件 :
持续更新大量数据结构细致内容,三连关注哈