简述
数据结构是计算机存储、组织数据的方式。常见的数据结构包括数组、链表、栈、队列、树、图等。比如说,数组是一种线性的数据结构,它在内存中连续存储元素,适合随机访问,但插入和删除操作可能效率较低。例如,要存储一个班级学生的成绩,就可以使用数组。
链表则是通过指针将各个节点连接起来,插入和删除操作相对高效,但随机访问较慢。像实现一个网页的浏览历史记录,链表就是一个不错的选择。数据结构的选择取决于具体的应用场景和需求,以达到最优的性能和效率。
动态数组
数组的长度和内容可以根据需要进行修改,用户只需要往里面加入和删除数据,也不需要担心数组的容量是否足够,不需要了解具体的实现细节
指针和数组的关系
#include <stdio.h>
#include <malloc.h>
int * pArr;
int main()
{
pArr = malloc(10*sizeof(int));//分配10个整数空间,并且把地址赋值给pArr
//指针[x]意思是代表指针所指向的空间的里面的下标为x的元素
pArr[1] = 101; //==*(pArr+1)
printf("%d\n",*(pArr+1));
return 0;
}
动态数组的实现
(1)增加一个元素
#include <stdio.h>
#include <malloc.h>
int * pArr;//指向数组的指针
int shuzuDaXiao = 0;//数组的大小,也就是一共有几个元素
int shuzuYiShiYong = 0;//数组已经被使用了几个空间
//创建一个大小为size的数组
void createArr(int size)
{
pArr = malloc(sizeof(int)*size);//分配size个整数空间,并且把地址赋值给pArr
shuzuDaXiao = size;//数组的带下就是size
}
//往数组的尾部加入元素
void tianJiaYuanSu(int weizhi, int elm)
{
if(weizhi>shuzuYiShiYong||weizhi<0)//判断用户给的位置是否跳空了
{
printf("能不能按顺序添加或者填写正确的位置,不要跳空\n");
}
if(shuzuYiShiYong==shuzuDaXiao)//如果已使用量等于数组的大小(总量),就不能再往里面加入了
{
printf("数组空间不够了,我来想想办法,包容量扩充到原来的2倍:%d,此时你想要加入的元素是:%d\n",shuzuDaXiao*2,elm);
//因为数组空间不够了,我们重新分配一个比元素更大(2倍)的空间来装数据
int * p = malloc(sizeof(int)*(shuzuDaXiao*2));
//空间分配好了,开始搬运原来的数据
for (size_t i = 0; i < weizhi; i++)
{
printf("开始拷贝前半段-----------\n");
p[i] = pArr[i];//把原来数据直接报到新数组的对应位置上
}
//从要插入的位置开始的元素,都要往后移动一个,为新元素腾出空间
for (size_t i = weizhi; i < shuzuYiShiYong; i++)
{
p[i+1] = pArr[i];
printf("开始拷贝后半段,已使用量为%d +++++++++++\n",shuzuYiShiYong);
}
//因为有了新空间,所以要释放掉就空间
free(pArr);
//因为在程序的其他地方都是使用pArr对数组进行操作,所以这里让pArr指向新空间
pArr = p;
shuzuDaXiao = shuzuDaXiao*2;//数组的大小也增加了2倍
}
else//没有扩容
{
//把元素往后面移动一个,先移动后面的再移动前面的
for (size_t i = shuzuYiShiYong; i > weizhi; i--)
{
pArr[i] = pArr[i-1];
}
}
pArr[weizhi] = elm;//把elm这个元素放到数组下标为【shuzuYiShiYong】的位置
shuzuYiShiYong++;//把数组已使用的量+1
printf("已使用量为%d **************\n",shuzuYiShiYong);
}
(2)删除一个元素
void shanchuYuansu(int xiabiao)//删除最后一个元素
{
//从要删除的元素开始,往后的所有元素都往前移动一个位置
for (size_t i = xiabiao; i < shuzuYiShiYong-1; i++)
{
pArr[i] = pArr[i+1];//把后一个元素赋值给前一个元素
}
shuzuYiShiYong--;//所谓删除元素就是把使用量减少
if(shuzuYiShiYong*2 == shuzuDaXiao)//如果数组使用量是数组大小的一般就可以开始缩容
{
printf("数组空间太多了,我来想想办法,缩小成原来的一般:%d,此时你想要删除的元素是:%d\n",shuzuYiShiYong,pArr[shuzuYiShiYong]);
int * p = malloc(sizeof(int)*shuzuYiShiYong);//缩容成原来的一般
// 把原来的数据拷贝到新的空间
for (size_t i = 0; i < shuzuYiShiYong; i++)
{
p[i] = pArr[i];
}
free(pArr);
//因为在程序的其他地方都是使用pArr对数组进行操作,所以这里让pArr指向新空间
pArr = p;
shuzuDaXiao = shuzuYiShiYong;//更新数组大小与原来的一半
}
}
(3)主函数
int main()
{
createArr(1);
tianJiaYuanSu(0,100);
tianJiaYuanSu(0,101);
tianJiaYuanSu(0,102);
tianJiaYuanSu(0,103);
tianJiaYuanSu(0,104);
tianJiaYuanSu(0,105);
tianJiaYuanSu(2,109);
// shanchuYuansu(0);
// shanchuYuansu(2);
// shanchuYuansu(4);
for (size_t i = 0; i < shuzuYiShiYong; i++)
{
printf("%d\n",pArr[i]);
}
// printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");
return 0;
}
通过以上代码,即可对一个动态数组实现增加、删除的功能。
补充:时间复杂度和空间复杂度
时间复杂度和空间复杂度是评估算法性能的两个重要指标。
时间复杂度:它衡量的是算法运行所需的时间与输入规模之间的关系。通常用大 O 记号来表示,例如 O(n)、O(n²) 等。O(n) 表示算法的运行时间与输入规模 n 呈线性关系,随着 n 的增加,运行时间也线性增长。比如顺序查找算法,在最坏情况下需要遍历整个数组,其时间复杂度就是 O(n)。 而 O(n²) 则表示算法的运行时间与输入规模的平方成正比。例如冒泡排序算法,在最坏情况下,每一轮比较都需要进行 n 次,总共需要进行 n 轮,所以时间复杂度为 O(n²)。
空间复杂度:指的是算法在运行过程中所需的额外存储空间与输入规模之间的关系。例如,一个算法如果只需要固定数量的额外空间来存储变量,不随输入规模的增加而增加,那么它的空间复杂度就是 O(1)。但如果一个算法需要创建一个与输入规模 n 成正比的数组来存储数据,那么它的空间复杂度就是 O(n)。
在实际应用中,需要综合考虑时间复杂度和空间复杂度,以选择最优的算法来解决问题。比如在资源有限的环境中,可能更倾向于选择空间复杂度低的算法;而在对运行时间要求较高的场景,时间复杂度则更为关键。