数据结构与算法
第一章:二分查找
第二章:数组
前言
随着计算机行业的发展,出现了许许多多的数据结构,我们今天来了解一下最基础的数据结构
提示:以下是本篇文章正文内容,下面案例可供参考
一、数组是什么?
数组(Array)是一种线性数据结构,用于存储相同类型的元素集合。它是一种有序的数据结构,可以通过==索引(下标)==来访问和操作其中的元素。数组是计算机科学中最基本、最常见的数据结构之一,被广泛应用于编程语言和算法中。
特点:
1、有序集合: 数组中的元素按顺序存储,并通过数字索引访问。索引从0开始,用于唯一标识数组中的每个元素。
2、相同类型元素: 数组中的元素类型必须相同(整数、浮点数、字符等),这意味着数组是同质的。
3、固定大小: 大多数编程语言中的数组具有固定的长度,一旦创建,其大小通常无法改变。
4、连续存储: 数组中的元素在内存中是连续存储的,这也是它能够通过索引快速访问元素的原因。
空间占用:
Java 中数组结构上是这样的:
- 8字节 markword
- 4字节 class 指针(压缩 class 指针的情况)
- 4 字节数组大小(决定了数组最大容量为232)
- 数组元素 + 对齐字节(Java 中所有对象大小都是 8 字节的整数倍,不足的要用字节补齐)
随机访问:即根据索引查找元素,在找到索引的前提下,时间复杂度为O(1) ,查找的快慢与数据规模没关系。
二、动态数组
1.数组的操作
import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;
// 动态数组实现类
public class DynamicArray implements Iterable<Integer>{
private int size = 0; // 逻辑大小
private int capacity = 8; // 容量
private int[] array = {};
public void addLast(int element){
// array[size] = element;
// size++;
add(size, element);
}
//添加元素
public void add(int index,int element){
checkAndGrow();
if(index >= 0 && index < size) {
System.arraycopy(array, index, array, index + 1, size - index);
}
array[index] = element;
size++;
}
//检查容量
private void checkAndGrow() {
if(size == 0){
array = new int[capacity];
} else if(size == capacity){//检查容量
capacity += capacity >> 1;
//扩容到1.5倍
int[] newArray = new int[capacity];
System.arraycopy(array,0,newArray,0,size);
array = newArray;
}
}
//根据下标获取元素
public int get(int index) {
return array[index];
}
//遍历方法一
//遍历要执行的操作,传入参数每个元素
public void forI(Consumer<Integer> consumer) {
for (int i:array) {
consumer.accept(i);
}
}
//遍历方法二
//迭代器遍历
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int i = 0;
@Override
public boolean hasNext() {//遍历过程中有没有下一个元素,有返回 true,没有返回 false
return i < size;
}
@Override
public Integer next() {// 返回当前元素,并移动到下一个元素
return array[i++];
}
};
}
//遍历方法三
//用Stream流来遍历
public IntStream stream() {
return IntStream.of(Arrays.copyOfRange(array,0,size));
}
//删除元素
public int remove(int index) {
int removed = array[index];
if (index < size - 1) {
System.arraycopy(array, index + 1, array, index, size - index - 1);
}
size--;
return removed;
}
}
2.二维数组
储存格式
int[][] array = {
{11, 12, 13, 14, 15},
{21, 22, 23, 24, 25},
{31, 32, 33, 34, 35},
};
- 外面的二维数组占 32 个字节,其中
[0] , [1] , [2]
分别保存了指向三个一维数组的引用 - 三个一维数组各占 40 个字节
- 它们在内存布局上是连续的
我们可以通过数组名来访问到二维数组的每一个元素,例如:array[1][1]就可以访问到下标为一的行中下标为1的元素。所以array[i][j]
中i
表示的是行的索引,j
表示的是列的索引。
二维数组的遍历:
有两种方式来遍历二维数组,分别是先遍历行在遍历列和先遍历列再遍历行,那么这两种情况下哪种情况的运行时间更短呢?
结论是先遍历行的运行速度更块,那么原因是什么呢?接下来我会为大家介绍原因:
原因就是局部性原理:
-
时间局部性(Temporal Locality): 如果在一段时间内访问了某个数据,那么在不久的将来,很可能会再次访问同一数据。这意味着,最近访问过的数据很可能在近期内会再次被访问到。这种现象是因为程序在执行时倾向于重复使用相同的数据项或指令。
-
空间局部性(Spatial Locality): 如果在某个时间点上访问了某个数据,那么在接下来的一段时间内,很可能会访问其附近的数据。这意味着,一旦访问了某个数据项,附近的数据项也可能被访问到。这种现象是因为程序在执行时往往按照连续的内存位置访问数据,比如数组、相邻的数据结构等。
内存中的数据以块的形式被加载到CPU的缓存中,先遍历行再遍历列可以利用内存的局部性原理,即空间局部性。行优先遍历意味着连续的元素在内存中是相邻的,这样一次缓存加载可以获取多个连续的元素,减少了内存访问次数,提高了缓存的命中率。而先遍历列则会破坏内存的局部性,导致缓存不命中率增加。CPU缓存会根据数据的访问模式进行预取(prefetching),如果连续的数据被访问,CPU缓存更容易预测下一个数据,从而提高访问速度。行优先遍历更有利于CPU缓存的预取机制。
总结
以上就是本篇文章主要的内容,本篇文章为大家介绍了最基础的数据结构——数组,读完了本篇文章,相信大家对于数组已经有了自己的了解,能够再平常对数组的使用中更加熟练。如果大家有什么问题可以提出来,欢迎大家提出相关的建议。