前面我们已经掌握了c语言基础,下面我们继续学习用c语言实现数据结构。那么数据结构的优点又是什么呢?下面我们一起探究。
文章目录
- 一、什么是数据结构
- 二、顺序表的概念和结构
-
- [1 线性表](#1 线性表)
- [2 顺序表](#2 顺序表)
- 2.1顺序表和数组的区别
- 2.2顺序表的分类
-
- [2.2.1 静态顺序表](#2.2.1 静态顺序表)
- [2.2.2 动态顺序表](#2.2.2 动态顺序表)
- 三、动态顺序表的实现
-
- [3.1 定义顺序表结构](#3.1 定义顺序表结构)
- [3.2 初始化顺序表](#3.2 初始化顺序表)
- [3.3 销毁顺序表](#3.3 销毁顺序表)
- [3.4 打印](#3.4 打印)
- [3.3 顺序表插入数据](#3.3 顺序表插入数据)
-
- [3.3.1 变容](#3.3.1 变容)
- [3.3.2 尾部插入](#3.3.2 尾部插入)
- [3.3.3 头部插入](#3.3.3 头部插入)
- [3.3.4 尾部删除](#3.3.4 尾部删除)
- [3.3.5 头部删除](#3.3.5 头部删除)
- [3.3.6 指定位置插入数据](#3.3.6 指定位置插入数据)
- [3.3.7 删除指定位置数据](#3.3.7 删除指定位置数据)
- [3.3.8 查找数据](#3.3.8 查找数据)
一、什么是数据结构
当我们想要使用大量同一类型数据时,通过手动定义大量的独立的变量对于程序员来说可读性非常差。我们可以借用数组这样的数据结构将大量的数据组织在一起,结构也可以理解为组织数据的方式。
概念:数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么⽅式构成,以及数据元素之间呈现的结构。
优点:
- 组织和管理数据
- 提高算法效率
- 节省内存空间
- 简化代码
- 提高代码的可读性和可维护性
- 支持抽象数据类型(ADT)
- 适应不同的应用场景
其实最基础的的数据结构是数组 。
下面我们要实现的顺序表就是基于数组完成的。
二、顺序表的概念和结构
1 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构 ,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
注意:顺序表是线性表的一种。
但是顺序表的特征是:物理结构书连续的,逻辑结构一定是连续的。
2 顺序表
2.1顺序表和数组的区别
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝
2.2顺序表的分类
2.2.1 静态顺序表
c
struct SeqList
{
int arr[100];//定长数组
int size;//数组当前有效的数据个数;
};
在静态顺序表中,我们给的是一个定长数组。如果像上面我们申请了100个空间,那么这个空间就固定了。数组大小给小了,空间不够用。数组大小给大了,空间就浪费了。
2.2.2 动态顺序表
c
struct SeqList
{
int * arr;
int size; //有效的数据个数
int capacity;//空间大小
};
在动态顺序表中,我们可以看到在结构体中多了一个结构体成员。capacity其实就是空间容量,通过capacity可以动态增容。
三、动态顺序表的实现
我们要先创建头文件(.h)和源文件(.c)。也可以把测试文件创建出来(test.c)。
用这种方式可以让代码更清晰。头文件就是声明顺序表的方法就像书的目录一样,源文件就是实现顺序表的方法。
3.1 定义顺序表结构
c
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size;
int capacity;
}SL;
我们要先在头文件中定义顺序表结构,我们用的是动态顺序表。
在顺序表中我们不一定只存放int一种类型的数组,所以我们给这个类型起个别名typedef int SLDataType,在用到其他类型的时候再进行更改。为了下面方便调用结构体,我们也给这个结构体起个别名叫SL。
3.2 初始化顺序表
在头文件中函数声明
c
void SLInit(SL* ps);
在源文件中写具体的函数实现
c
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
在初始化阶段还没有给结构体中的arr成员分配内存空间,表面该指针具备任何的内存地址。
size和capacity成员在结构体中为整形变量所以在初始化是将其置为0。
3.3 销毁顺序表
在头文件中函数声明
c
void SLDestroy(SL* ps);
在源文件中写具体的函数实现
c
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
在动态顺序表中我们申请的空间是在堆上申请的,所以在创建完顺序表后销毁。
3.4 打印
在头文件中函数声明
c
void SLPaint(SL s);
在源文件中写具体的函数实现
c
void SLPaint(SL s)
{
for (int i = 0; i < s.size; i++)
{
printf("%d ", s.arr[i]);
}
printf("\n");
}
3.3 顺序表插入数据
3.3.1 变容
动态顺序表在插入数据之前,如果size和capacity大小相同我们要先扩容,也就是改变capacity的容量。这就是动态顺序表的精髓所在。
增容通常来说是成倍数增加,一般是原空间的2倍或者3倍 。
c
SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->arr, capacity * 2 * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
那么这里还有一个问题,如果capacity本身就为0怎么办?所以这里还需要判断一下。创建一个newCapacity如果capacity为0就给4个空间大小,如果不为0就把空间大小置为原来的2倍。
扩容正确代码如下:
c
SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * 2 * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
3.3.2 尾部插入
在头文件中函数声明
c
void SLPushBack(SL* ps, SLDataType x);
在源文件中写具体的函数实现
c
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);//ps不能为NULL
SLCheckCapacity(ps);//扩容
ps->arr[ps->size++] = x;
}
因为是尾插,假设x为5,size的下标为4。
尾插一个5,size向后移一个位置。
3.3.3 头部插入
在头文件中函数声明
c
void SLPushFront(SL* ps, SLDataType x)
在源文件中写具体的函数实现
c
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i>0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
首先扩容
我们用for循环,从size的位置开始,i--,判断条件为i>0。
如下图所示把ii-1位置的值给i位置,这样就把下标为0的位置空出来了。
把x给下标为0的位置。不要忘了让size++。
3.3.4 尾部删除
在头文件中函数声明
c
void SLPopBack(SL* ps);
在源文件中写具体的函数实现
c
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
--ps->size;
}
尾删不需要扩容,但是size不能为空。直接从后面往前删,使size--就可以了。
3.3.5 头部删除
在头文件中函数声明
c
void SLPopFront(SL* ps);
在源文件中写具体的函数实现
c
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i<ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
}
从下标为0的位置开始,遍历到size-1。如图从1开始让下一个位置的值覆盖这一位置的值。这样也就实现了头部删除。
3.3.6 指定位置插入数据
在头文件中函数声明
c
void SLInsert(SL* ps, int pos, SLDataType x)
在源文件中写具体的函数实现
c
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i>pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
assert(pos >= 0 && pos <= ps->size) ,要使指定插入的位置>=0并且是指定插入位置<=size位置。
从下标为pos位置开始,整体往后移动。再把要插入的值插入pos位置。
3.3.7 删除指定位置数据
在头文件中函数声明
c
void SLErase(SL* ps, int pos);
在源文件中写具体的函数实现
c
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
for (int i = pos; i<ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
}
从下标为pos位置开始,遍历到size-1位置。通过ps->arr[i] = ps->arr[i + 1];让pos后面数据整体往前覆盖一个
3.3.8 查找数据
在头文件中函数声明
c
int SLFind(SL* ps, SLDataType x);
在源文件中写具体的函数实现
c
int SLFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
遍历数组,通过一个if语句判断,如果可以在数组查找到要找的数据返回下标。
到这里顺序表的实现基本就完了,谢谢大家观看。