线性结构(数组)
1. 线性结构
所有的结点被一条线穿起来就是线性结构。
2. 连续存储(数组)
2.1 什么是数组?
2.2 需要的头文件
#include<stdio.h>
#include<stdlib.h>//exit函数的头文件
#include<malloc.h>//malloc函数的头文件
2.3 保存数组信息的结构体
数组实现线性存储,为了需要以下变量
- 用于存储数组第一个元素地址的数组元素数据类型的指针;
- 用于存储数组长度的整形变量;
- 用于存储数组内元素个数的整形变量。
为了便于使用,我们将以上变量作为成员,构建一个结构体,具体代码如下:
//定义了一个结构体数据类型,名字叫做Arr,用来对数组进行操作
struct Arr
{
int *pBase;//存储数组第一个元素地址
int len;//数组所能最大容纳元素的个数
int cnt;//当前数组有效元素的个数
};
注: 由于个别情况需要对数组进行扩容,可能需要加一个用于控制自增因子的变量,本次操作的代码为了方便理解,采用加倍扩容的方式,详见后面代码。
2.4 数组初始化
我们需要编写一个函数来对数组进行初始化,数组初始化需要使上一步结构体变量中的指针存储数组第一个元素的地址,并且需要知道初始化的长度,所以函数的参数应该是一个指针和整型变量,具体代码如下:
//初始化
void init_arr(Arr * arr,int length)
{
//使用malloc函数动态分配内存空间
//通过sizeof函数来获取int类型变量所占内存大小
arr->pBase=(int *)malloc(sizeof(int)*length);
//如果初始化成功pBase必然不为空
if(arr->pBase==NULL)
{
printf("内存分配失败\n");
exit(-1);//终止程序
}
else
{
//对其他的成员变量进行赋初值
arr->len = length;
arr->cnt = 0;
}
return;
}
2.5 判断数组是否为空
只需通过结构体中的成员变量即可了解,代码如下:
//数组是否空
bool is_empty(Arr * arr){
//cnt是用来记录数组中元素的个数的,所以当cnt==0时数组即为空
if(arr->cnt==0)
return true;
else
return false;
}
2.6 判断数组是否存满
代码如下:
// 数组是否满
bool is_full(Arr * arr)
{
//当数组长度与元素个数相等时,数组即存满
if(arr->cnt==arr->len)
return true;
else
return false;
}
2.7 对数组进行扩容
采用加倍的策略来对数组进行扩容,具体操作包括:
- 将变量len的数值加倍
- 对数组进行扩容
//增加数组长度
void add_len(Arr * arr)
{
(arr->len) *= 2;
//realloc函数可以对数组进行扩容和缩短,
//当第二个参数大于数组的长度时,会对数组扩容,
//当参数小于数组长度时,数组会缩短,但是容易造成数据丢失
//realloc保存在“stdlib.h”头文件中
realloc(arr,arr->len);
return;
}
2.8 在数组末尾追加数据
追加数据只需要传数据即可,但是在追加数据之前需要判断数组是否以满,如果数组满了,需要对数组进行扩容,具体代码如下:
//追加
void append(Arr * arr,int var)
{
//调用“is——full”函数,判断数组是否以满
if(is_full(arr))
//如果数组满了则调用“add——len”函数对数组扩容
add_len(arr);
//把传进来的值存到数组中
arr->pBase[(arr->cnt)] = var;
//数组元素个数加一
(arr->cnt)++;
return;
}
2.9 指定位置插入数据
指定位置的插入操作需要比追加多一个参数用于控制插入的位置,而在插入数据之前需要将要插入的位置空出来,这就需要从这个位置的元素开始整体往后挪,而为了不丢失数据,所以需要从最后一个数据开始挪,详细代码如下:
//插入
void insert(Arr * arr,int pos,int var)
//函数的参数需要人为的赋予意义和范围,
//所以在学习一些库函数或者Java中的方法时,需要阅读官方文档。
//例如这里的pos就是人为的规定从一开始,代表位置
{
//防止数组越界,增加程序的健壮性
if(pos<1||pos>arr->cnt)
{
printf("插入位置越界\n");
return;
}
//防止数组空间不足
if(is_full(arr))
add_len(arr);
//挪动数组中的元素
for(int i = arr->cnt-1;i>=pos-2;i--)
arr->pBase[i+1] = arr->pBase[i];
//赋值操作
arr->pBase[pos-1] = var;
(arr->cnt)++;
return;
}
2.10 删除指定数据
删除指定位置的元素后,需要把空出位置之后的数组元素向前挪动,将空位填补,防止数组内容中断,具体代码如下:
//删除
bool delete_arr(Arr * arr,int pos)
{
if(pos<1||pos>arr->cnt)
{
printf("删除位置越界\n");
return false;
}
//数组元素向前挪动的代码
for(int i = pos;i<arr->cnt;i++)
{
arr->pBase[i-1] = arr->pBase[i];
}
(arr->cnt)--;
return true;
}
2.11 从小到大排序(冒泡)
排序的算法有很多,本次采用学习C语言最容易遇见的冒泡排序来进行从小到大的排序。
冒泡排序:从头开始与之后的元素依次比较,如果前面的比后面的大就交换位置,这样就可以确定最大的值,并将其固定在最后的位置,然后用同样的方式,可以固定剩下元素中的最大值,依次进行下去,即可将数组排序。如果不理解建议在b站搜索冒泡排序观看视频学习。也可以研究以下代码:
//从小到大排序(冒泡排序)
void sort_arr(Arr * arr)
{
//第一次循环固定查找最大值次数
for(int i = (arr->cnt)-1;i>0;i--)
{
//第二次循环控制每一次查找最大值时,元素比较的次数
for(int j = 0;j<i;j++)
{
//元素交换的代码
if(arr->pBase[j] > arr->pBase[j+1])
{
arr->pBase[j] += arr->pBase[j+1];
arr->pBase[j+1] = arr->pBase[j] - arr->pBase[j+1];
arr->pBase[j] = arr->pBase[j] - arr->pBase[j+1];
}
}
}
}
2.12 显示输出数组
打印数组就比较简单了,只需要先判断数组是否包含元素,如果包含元素就通过一个循环将数组打印,代码如下:
//显示输出
void show_arr(Arr * arr)
{
if(is_empty(arr))
printf("数组为空\n");
else
for(int i=0;i<arr->cnt;i++)
printf("%d\n",arr->pBase[i]);
return;
}
2.13 数组倒置
数组倒置需要两个参数,让第一个参数i控制第一个元素,第二个元素j控制最后一个元素,然后交换i和j控制元素的位置,交换位置后i向后移动一步,j向前移动一步,且在i跑到j后结束交换的操作,代码如下:
//倒置
void inversion_arr(Arr * arr)
{
int i = 0;
int j = arr->cnt-1;
while(i<j)
{
arr->pBase[i] += arr->pBase[j];
arr->pBase[j] = arr->pBase[i] - arr->pBase[j];
arr->pBase[i] = arr->pBase[i] - arr->pBase[j];
i++;
j--;
}
return;
}
2.14 测试主函数
以下为我自己测试功能时的main函数:
int main(void)
{
Arr arr;
init_arr(&arr,3);
append(&arr,13);
append(&arr,14);
append(&arr,15);
append(&arr,16);
insert(&arr,2,100);
insert(&arr,100,999);
show_arr(&arr);
printf("=================\n");
inversion_arr(&arr);
show_arr(&arr);
printf("=================\n");
sort_arr(&arr);
show_arr(&arr);
return 0;
}
3. 总结
通过学习对数组的操作更加便于我们对其他知识的掌握,与之后通过链表的方式实现线性存储相比较,更容易对知识进行掌握,而如果你学习过Java,请你想一想,你对面向对向与面向过程的理解。