C/C++数据结构之动态数组篇

数据结构之——动态数组(顺序表)

1.动态数组和静态数组

静态数组:静态数组在内存中位于栈区,是在编译时就已经在栈上分配了固定大小,程序结束由系统释放。在运行时不能改变数组大小。

//静态数组
	int N = 10;
	int a[N];	/*定义一个数组大小为10的数组,因为静态数组在编译阶段就会确定数组大小,所以这里的N必须是一个确定的值
	如果在这里没有给出N值的大小,会导致编译失败*/

动态数组:动态数组是malloc(在c语言中使用malloc函数)或者new(在c++中使用new操作符)出来的,位于内存的堆区,它的大小是在程序运行时给定的,可以动态的改变数组的大小,以及动态的增,删,查,改数组中的元素。

//动态数组
struct Array
{
	int* a;		//这里只需要定义一个指针,在程序运行起来之后如果在堆区开辟了一块数组空间,就让这个指针指向数组的首地址即可
	int size;   //size用于记录当前数组储存了多少个元素
	int acpcaity;	//acpcaity用于记录当前数组的容量大小(该数组最多能存储多少个元素)
}

2.如何维护一个动态数组

在这里插入图片描述
根据上图将维护一个数组分为以下三点:
1.当数组未创建时只需要让arr指针指向NULL,size,acpcaity都初始化为零,当成功在堆区创建了一个数组之后我们只需要把arr指针指向该数组的首地址即可。

2.每次对数组进行添加元素或者删除元素时,都要对应的对size进行相应的加减操作,size必须准确的记录数组中的元素个数

3.每次对数组扩容时都要实时更新当前数组大小给acpcaity,以便于更准确的控制数组大小如果acpcaity大于数组真实容量,可能导致程序崩溃。
列: 如果当前acpcaity=5;而当前数组实际容量为4,这时通过arr[4]插入第5个元素时,就会因为非法访问内存而导致程序崩溃

如果acpcaity小于数组真实容量,会导致数组频繁扩容,造成资源浪费。

以下是一个动态数组的小案列,用C语言实现c++中的vector容器(没有学过的c++的家人们完全可以忽略这句话,因为这就是一个简单动态数组的实现,vector容器的底层也是用动态数组实现的)

//头文件(vector.h)
#pragma once
#include<stdio.h>

//如果想储存其他数据类型只需将这里的int换成你想要储存的数据类型即可
typedef int anytype;	//储存int类型的数据
//typedef double anytype;     //储存double类型的数据

typedef struct STL_Vector
{
	anytype* arr;		//定义一个指针,用于指向后面创建的数组的首地址
	int size;			//记录数组当前元素个数
	int capacity;		//记录数组当前容量大小
}vector;

//接口函数
void VectorInit(vector * v);				 //初始化结构体
void Add_Capacity(vector* v);			  	 //当数组容量不够时扩容
void push_back(vector* v, anytype x);		 //尾部插入元素
void push_front(vector* v, anytype x);		 //头部插入元素
void My_print(vector v);					 //遍历数组
void pop_back(vector* v);					 //删除最后一个元素
int find(vector v, anytype x);				 //查找指定元素的位置,返回元素位置
void pop_front(vector* v);					 //删除第一个元素下标
int pos_insert(vector* v,int pos, anytype x);//指定下标位置插入元素
int pos_delect(vector* v, int pos);			 //指定下标位置删除元素

以下是接口函数的实现

//.c文件(vector.c)
#include"vector.h"
//初始化结构体
void VectorInit(vector* v){
	v->arr = NULL;
	v->capacity = v->size = 0;
}
//创建数组或对已经满的数组进行扩容
void Add_Capacity(vector* v){
	if (v->size == v->capacity){	//当size=capacity时,说明数组可能还未创建,或数组已经满了
		int newspace = v->capacity == 0 ? 4 : v->capacity * 2;	/*如果数组容量为零就先给4个整形的大小,否则就在原来的
		基础上乘以2,每次扩容2倍*/
		anytype* tem = (anytype*)realloc(v->arr, newspace * sizeof(anytype));
		/*1.扩容成功,返回新空间的地址
		  2.扩容失败,返回NULL*/
		if (tem == NULL){
			printf("扩容失败!");
			exit(-1); //扩容失败直接退出程序
		}
		v->arr = tem;  
		v->capacity = newspace;
	}
}
//尾插数据
void push_back(vector* v, anytype x){
	Add_Capacity(v); //首先要确定数组是否创建,容量是否大于零,如果数据未创建,先创建数组,如果已创建但是容量已满,就扩容
	if (v->arr != NULL){
		v->arr[v->size] = x;
		v->size++;
	}
		
}
//头插数据
void push_front(vector* v, anytype x){
	Add_Capacity(v);
	int ret = v->size;
	for (ret; ret>=0; ret--){
		v->arr[ret] = v->arr[ret-1];
	}
	if (v->arr != NULL){
		v->arr[0] = x;
		v->size++;
	}

}
//尾删数据
void pop_back(vector* v){
	if (v->size > 0){   //首先判断数组中是否有数据,有数据才删除,以下同理
		v->size--;
	}
}
//头删数据
void pop_front(vector* v){
	if (v->size > 0){
		int ret = v->size;
		for (int i= 0; i<ret; i++){
			v->arr[i] = v->arr[i+1];
		}
		v->size--;
	}
	
}
//查找数据,成功返回数据下标,失败返回-1
int find(vector v, anytype x){
	if (v.size > 0){
		for (int i = 0; i < v.size; i++){
			if (v.arr[i] == x){
				return i;
			}
		}
	}
	return -1;
}
//指定下标添加数据
int pos_insert(vector* v, int pos, anytype x){
	Add_Capacity(v);
	if (pos >= 0 && pos <= v->size){
		int i = v->size;
		for (i - 1; pos <= i; i--){
			v->arr[i] = v->arr[i-1];
		}
		v->arr[pos] = x;
		v->size++;
		return 0;
	}
	return -1;
}
//指定下标删除数据
int pos_delect(vector* v, int pos){
	Add_Capacity(v);
	if (pos >= 0 && pos < v->size){
		int i = v->size;
		for (pos; pos < i; pos++){
			v->arr[pos] = v->arr[pos+1];
		}
		v->size--;
		return 0;
	}
	return -1;
}
//遍历数组
void My_print(vector v){
	if (v.arr == NULL){
		printf("数组未创建!\n");
		return;
	}
	else if (v.size == 0){
		printf("数组为空!\n");
		return;
	}
	for (int i=0; i < v.size; i++){
		printf("%d ",v.arr[i]);
	}
	printf("\n");
}

3.动态数组和静态数组的优缺点

静态数组
优点:
(1).容量大小固定,下标固定,查找数据快,效率高
缺点:
(1).如果数组满了,就不能继续插入数据了
(2). 很难确定数组的容量大小,给大了浪费,给大小不够放
(3).同一个数组只能储存一种数据类型
动态数组
优点:
(1).可以根据数据量的大小动态扩容和缩小容量
(2).可以通过下标快速查找数据
缺点:
(1).头插数据,中间插入数据时,需要挪动数据,速度慢效率低
(2).同一个数组只能储存一种数据类型

面试常见考点

1.请你说说delete和free的区别

(1). delete是操作符,free是库函数
(2). delete用于在c++中释放new创建的空间,free用于释放c语言中malloc创建的空间
(3). 调用free之前需要检查要释放的指针是否为空,而delete则不需要

2.请你说说malloc和new的区别

(1).new/delete是C++操作符,需要编译器支持,而malloc/free是库函数,需要引入头文件。

(2).使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要指定要开辟空间的大小。

(3). new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

(4).new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

(5). new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。

(6). C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

(7).new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
转载自

总结

顺序表应该是数据结构中最简单的一个了,接下来的时间我也会更加努力的学习,提升自己的同时给家人们带来更优质的文章,最后希望该篇文章能帮助到大家,作者本人水平有限,如果文章中错误或者不足的地方欢迎大家指出。

  • 13
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: C/C++ 是应用广泛的编程语言,其在数据结构应用方面也十分重要。面试中相关的 C/C++ 数据结构问题主要围绕数组、链表、二叉树和图等方面。以下是一些常见问题及其解答: 1. 如何反转一个单向链表? 答:可以使用三个指针来实现:cur 代表当前节点,pre 代表上一个节点,next 代表下一个节点。每次遍历时,将 cur 的 next 指向 pre,然后将三个指针分别向后移动即可。 2. 如何判断两个链表是否相交,并找出相交的点? 答:可以分别遍历两个链表,得到各自的长度。然后让长的链表先走 n 步,使得两个链表剩余的长度相等。接下来同时遍历两个链表,比较节点是否相同即可找出相交的点。 3. 如何判断一个二叉树是否为平衡二叉树? 答:可以计算每个节点的左右子树深度差,如果任何一个节点的深度差大于1,则此树不是平衡二叉树。可以使用递归实现,每次计算当前节点的深度,然后递归判断其左右子树是否平衡。 4. 如何实现图的深度优先搜索(DFS)和广度优先搜索(BFS)算法? 答:DFS 可以使用递归实现。从某个节点开始,逐个访问其未被访问的邻接节点,并将其标记为已访问。然后对每个未被访问的邻接节点递归调用 DFS 函数。BFS 可以使用队列实现。从某个节点开始,将其加入队列,并标记为已访问。然后从队列中弹出节点,并访问其所有未被访问的邻接节点,并将其加入队列中。重复此过程直到队列为空。 以上是一些常见的 C/C++ 数据结构面试问题及其解答。在面试中,除了掌握相关算法和数据结构知识外,还需多做练习和积累经验,才能更好地应对各种面试问题。 ### 回答2: C语言是一种用于编写系统级程序的高级编程语言,具有简单、高效、灵活等特点,是许多操作系统、编译器等软件的首选语言,也是许多企业在进行面试时重点考察的技能。在C/C++数据结构面试题中,经常会涉及到各种数据结构相关的算法和应用,测试面试者的算法思维能力和实现能力。 其中,常见的数据结构包括链表、栈和队列、二叉树、搜索树、哈希表等。在面试时,会常常涉及代码设计和实现,比如实现链表的插入、删除、查找操作,实现二叉树的遍历、查找操作等。 此外,在数据结构面试中,还经常涉及排序和查找算法,如冒泡排序、快速排序、归并排序、二分查找、哈希查找等。同时,面试者还需要解决一些较为复杂的算法问题,如图的最短路径问题,最小生成树问题等。 总之,C/C++数据结构面试题涵盖了运用数据结构的各种算法和实现方法,需要面试者具备扎实的编程基础和算法思维能力。在备战面试时,可以多做练习,熟悉常用的数据结构和算法,提高理解和实现能力,从而更好地应对面试挑战。 ### 回答3: 面试过程中常见的C/C++数据结构面试题有很多。以下就介绍几个常见的题目并给出解答。 1. 求两个有序数组的中位数 题目描述:给定两个升序排列的整形数组,长度分别为m和n。实现一个函数,找出它们合并后的中位数。时间复杂度为log(m+n)。 解答:这个问题可以使用二分法求解。首先,我们可以在两个数组中分别选出所谓的中间位置,即(i+j)/2和(k+l+1)/2,其中i和j分别是数组A的起始和结束位置,k和l分别是数组B的起始和结束位置。判断A[i+(j-i)/2]和B[k+(l-k)/2]的大小,如果A的中间元素小于B的中间元素,则中位数必定出现在A的右半部分以及B的左半部分;反之,则必定出现在A的左半部分以及B的右半部分。以此类推,每一次都可以删去A或B的一半,从而达到对数级别的时间复杂度。 2. 堆排序 题目描述:对一个长度为n的数组进行排序,时间复杂度为O(nlogn)。 解答:堆排序是一种常用的排序算法,在面试中也经常被考察。堆排序的具体过程是首先将数组构建成一个最大堆或最小堆,然后不断将堆顶元素与最后一个元素交换,并将最后一个元素从堆中剔除。这样,每次剔除后,堆都会重新调整,使得剩下的元素仍然保持堆的性质,直到堆中只剩下一个元素为止。 3. 链表反转 题目描述:反转一个单向链表,例如给定一个链表: 1->2->3->4->5, 反转后的链表为: 5->4->3->2->1。 解答:链表反转题目也是非常常见,其思路也比较简单。遍历链表,将当前节点的next指针指向前一个节点,同时记录当前节点和前一个节点,直至遍历到链表末尾。 以上这三个问题分别从二分法、堆排序和链表三个方面介绍了常见的C/C++数据结构面试题,希望能帮助面试者更好地准备面试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐个愿望~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值