C语言数据结构——变长数组(柔性数组)

前言

这是一位即将大二的大学生(卷狗)在暑假预习数据结构时的一些学习笔记,供大家参考学习。水平有限,如有错误,还望多多指正。

本文主要介绍了如何手动实现一个变长数组,以及实现其部分功能(如删除、查找、添加、排序等)

变长数组介绍

变长数组又可以叫柔性数组,与一般数组不同,它是一个动态的数组,具体表现为可以根据数组里面元素个数的多少而自动的进行扩容,以便达到变长(柔性)的特点。

预备知识

为了实现自动边长扩容这一特点,在本文的代码中,主要使用了C语言中malloc这一函数,来进行动态内存分配,以下是关于malloc函数使用的简要说明
头文件引入:

#include

内存分配:

int * pArr = (int *)malloc(sizeof(int) * len);

其中,len为要申请的长度
内存释放:

free(pArr);

数组的创建

我们用结构体来定义一个数组,并且把数组的部分属性封装到这个结构体里

struct MyArr
{
	int * pBase;  //利用指针  储存数据
	int len;  //数组长度 --> 数组所能容纳最大元素个数
	int cnt;  // 数组有效长度
	int increment;  // 自动增长因子
}

由于数组每次扩充时,其长度是成倍的增加,而这个倍数就是自动增长因子,即 increment

方法函数的书写

初始化——init()

void init(struct Arr * parr, int length);  //length为初始长度

声明一个数组后,我们首先要对其进行初始化。
其实现原理也很简单,就是给结构体内部的指针变量(数组)动态开辟一段空间,并且初始化其最大长度和有效长度,并且给出自动增长因子的大小
具体函数实现如下:

void init(struct Arr * parr, int length) //注意要把地址传过来,才能真正改变内部数据
{
    parr -> pBase = (int*)malloc(sizeof(int) * length);  //初始内存大小
    if(NULL == parr -> pBase)  //内存分配失败
    {
        printf("内存分配失败");
        exit(0);   //程序终止
    }
    else
    {
        parr -> len = length;  //初始长度
        parr -> cnt = 0;  //初始元素个数
        parr -> increment = 2;  // 默认增长因子为2
    }
}

判空——is_empty()

bool is_empty(struct Arr * pArr);

本函数用于判断数组是否为空,所以只需要判断结构体内 cnt 参数是否为 0 即可
数组为空的话返回 true ,否则返回 false
具体函数实现如下:

bool is_empty(struct Arr * pArr)
{
	if(pArr -> cnt == 0) return true;
	else return false;
}

判满——is_full()

bool is_full(struct Arr * parr);

用于判断数组是否已满,所以只需要判断结构体内 cnt 参数是否与len参数相等即可
数组满的话返回 true ,否则返回 false
具体函数实现如下:

bool is_full(struct Arr * pArr)
{
	if(pArr -> cnt == pArr -> len) return true;
	else return false;
}

显示——show()

void show(struct Arr * pArr)

实现原理也很简单,即把整个数组遍历一遍

void show(struct Arr * pArr)
{
	if(is_empty(pArr))  //parr已经就是地址,所以这里写parr就可以 不用再取地址
		printf("数组为空");
	else
	{
		for(int i = 0; i < pArr -> cnt; i ++)
			printf("%d ", pArr -> pBase[i]);  //!!!
		printf("\n");
	}
}

遍历前可以先判断一下数组是否为空
以及注意一下数组内元素的书写

扩容——enlarge()

void enlarge(struct Arr * pArr);

这是笔者认为整个程序中的灵魂,即动态扩容,就是当结构体内有效长度 cnt 与最大长度 len 相等时,通过自动增长因子 increm 对原有数组进行成倍的变长扩容.

实现原理主要分三步走:

  1. 动态开辟。即动态开辟一个临时指针变量 temp ,长度为原有数组 pBase 长度的 increment
  2. 数据转移。将原有数组 pBase 内的数据转移过来,然后再对原有的数组 pBase 进行内存释放(减少内存的浪费)
  3. 重新指向。最后再把原有数组 pBase 重新指向 temp ,即可实现动态扩容

函数具体实现:

void enlarge(struct Arr * parr)
{
	int * temp = (int *)malloc(sizeof(int) * parr -> len * parr ->increment);  //创建临时数组
	for(int i = 0; i < parr -> len; i ++) //数据转移
		temp[i] = parr -> pBase[i];  
	free(parr -> pBase);
	
	parr -> pBase = temp;  //重新指向
	
	if(NULL == parr -> pBase)  //异常处理
	{	
		printf("ERROR");
		exit(0);
	}
}

追加——append()

void append(struct Arr * parr, int val);  //val为要追加的数据

所谓追加,就是在已有的数组的末尾再添加一个数
函数具体实现:

void append(struct Arr * parr, int val)
{
	if(is_full(parr))   //如果数组已满,要先扩容
	{
		enlarge(parr);
	}
	
	parr -> pBase[parr -> cnt ++] = val;
}

注意由于追加了一个数,所以要有 ++ 操作符

插入——insert()

void insert(struct Arr * parr, int pos, int val);  //pos是插入的位置,val是插入的值

参数注意说明: pos值从 1 开始,是指插入到第 pos 个数上

实现原理:首先要找到第 pos 个元素,确定下标,然后把该元素以及后面的元素与后移一位,最后插入即可。
函数具体实现:

void insert(struct Arr * parr, int pos, int val)
{
	if(is_full(parr))   //长度不够
	{
		enlarge((parr));
	}
	
	if(pos < 1 || pos > parr -> cnt)  //pos值不正确
	{
		printf("位置不正确\n");
		return ;
	}
	else
	{
		for(int i = parr -> cnt - 1; i >= pos - 1; i --)
			parr -> pBase[i + 1] = parr -> pBase[i];  //元素后移
		parr -> pBase[pos - 1] = val;
		parr -> cnt ++;
	}
}

该函数的实现稍有复杂,主要是在对元素后移时要搞清楚下标,什么时候 +1 什么时候 -1 ,这是操作实现的关键

删除——delet()

void deleat(struct Arr * parr, int pos); //pos值为删除的元素位置

实现原理:找到对应 pos 的元素,将该元素后面的元素前移一位,实现覆盖即可,与插入操作大同小异。
函数具体实现:

bool delet(struct Arr * parr, int pos)
{
	if(is_empty(parr)) return false;
	else if(pos < 1 || pos > parr -> cnt) return false;
	else
	{
		for(int i = pos - 1; i < parr -> cnt - 1; i ++)
			parr -> pBase[i] = parr -> pBase[i + 1];
		parr -> cnt --;
	}
}

注意最后不要忘记 cnt –

倒置——inversion()

void inversion(struct Arr * parr);

该函数主要功能就是实现数组内元素的倒置
本文利用的是双指针前后夹击的思想对数组元素进行倒置操作,其他方法亦可
函数具体实现:

void inversion(struct Arr * parr)
{
	int i = 0;  //头指针
	int j = parr -> cnt - 1;  //尾指针
	while(i < j)  //前后夹击
	{
		int t = parr -> pBase[i];
		parr -> pBase[i] = parr -> pBase[j];
		parr -> pBase[j] = t;
		i ++, j --;
	}
}

排序——sort()

void sort(struct Arr * parr);

该函数实现的是对数组进行排序(从小到大
直接在函数体里套个排序算法即可,我这里是因为好久没写快排了,所以写了个快排再熟悉一下找找手感。其实个人感觉冒泡就够用了
ps:u1s1 y总的快排板子真好用,吹爆y总hhh
具体函数实现:

void sort2(struct Arr * parr)
{
	quick_sort(parr -> pBase, 0, parr -> cnt - 1);
}
void quick_sort(int a[],int l,int r)  //快排
{
	if(l >= r) return;
	
	int x = a[l + r >> 1],i = l - 1,j = r + 1;
	while(i < j){
		do i ++; while(a[i] < x);
		do j --; while(a[j] > x);
		
		if(i < j)
		{
			int t = a[i];
			a[i] = a[j];
			 a[j] = t;
		}
	}
	quick_sort(a, l, j);
	quick_sort(a, j + 1, r);
}

总结

总的来说,变长数组在使用过程中还是很便利的,在大部分(所有)oop语言中都存在变长数组,如C++里的vector,Java里的ArrayList(C语言在C99中好像也引入了这一概念)
我也是初学者,才疏学浅,很多更实用的功能还没有实现。如果有错误,还望大家多多指正。
最后感谢大家的阅读,祝列位阖家欢乐,福寿康宁!!!

你可能感兴趣的:(数据结构,c语言,柔性数组)