数据结构与算法(一)数组的相关概念和底层java实现
一、前言
从今天开始,笔者也开始从0学习数据结构和算法,但是因为这次学习比较捉急,所以记录的内容并不会过于详细,会从基础和底层代码实现以及力扣相关题目去写相关的文章,对于详细的概念并不会过多讲解
二、数组基础
- 数组这个结构,其实就是将数据码成一排进行存放

如上,就是一个数组,有6个空间,可以存放6个元素。且命名可以随意,例如我student数组可以叫students,也可以是studentList。
具体这个元素是什么类型,在Java中要求是固定的,比如6个Int类型,6个String类型,或者6个Object自定义类型。 - 在数组中,有一个很重要的概念,叫作索引

在计算机的视角中,索引从0开始,之后依次递增,如果我们数组空间为n,那我们最后一个元素的索引是n-1,第一个元素的索引是0,有了索引,我们就可以直接访问数组第i个元素是什么。
例如我现在要对students数组中取索引为2的数据,那么students[2]即可取出,而students[2]对应的是数组的第三个元素。
索引可以有语意,也可以没有语意。例如我是一个学生列表,我的学生学号就对应的索引,此时索引有语意。再或者说我是一个分数列表,那对于这个列表的元素,分数想放在哪个索引对应的空间就放在哪里,此时索引没有语意。
- 代码实现
在java中如果我要声明一个数组,非常简单:
比如我声明一个Int类型的数组,就是int[] arr,String类型数组就是String[]
而给这个数组赋值的对象可以通过new int[n]/new String[n]这样的形式,n代表你想开辟的数组的空间大小,例如拿上面的6个空间的数组举例:
public static void main(String[] args) {//想要使用数组,就需要事先开辟空间,就必须声明数组能装多少个元素int[] arr = new int[6];}
而我们要对这个数组进行赋值操作也很简单,写一个for循环,我们可以根据java中数组的方法arr.length()对其进行循环,为数组的每一个元素进行赋值吗,arr.length()就是代表这个数组的长度,即数组的空间大小,然后arr[i] = i即可为数组的每一个元素赋值。
public static void main(String[] args) {//想要使用数组,就需要事先开辟空间,就必须声明数组能装多少个元素int[] arr = new int[6];for (int i = 0; i < arr.length; i++) {arr[i] = i;}}
而通过arr.length()去进行for循环比你要通过直接拿一个具体的数字硬编码的形式去进行赋值要好很多,代码更加的灵活,例如你的数组长度可能会发生变化,你通过arr.length()的话只需要专注于你的数组长度即可。
当然java中声明数组有另外的形式,当你想要在数组声明的时候就有它的初始值,那这种情况下,new int[]方括号内就可以不带空间大小,而是通过new int[]{}的形式,花括号内填写元素的初始值。
例如,我声明一个scores数组,初始化3个初始值,我就可以这样写:
int[] scores = new int[]{99,100,98};
这样我们的数组就开辟成有3个Int值的数组,我们可以写一个for循环打印验证下:
int[] scores = new int[]{99,100,98};for (int i = 0; i < scores.length; i++) {System.out.println(scores[i]);}
当然你也可以使用java的for增强循环,意义就是在scores数组中取出每一个score元素。在这个基础上我把每一个score元素的值打印出来,这种特性是因为数组有这可迭代的能力或者叫作可遍历的能力,这里不过多强调。
int[] scores = new int[]{99,100,98};for (int score : scores) {System.out.println(score);}
大家都可以试一下,两种形式的结果都是一样的。
而我们如果想要修改数组中元素的值,也非常简单,比如我想将上面的scores索引为0的元素的值修改为100,代码如下:
scores[0] = 100;
- 数组优点
数组最大的优点就是快速查询,例如students[2],快速查询索引为2的学生。
之前我们讲解索引的时候说过索引有语意和没有语意的区别,那么为了更好地放大数组的优点,数组最好应用于“索引有语意”的情况。而对于索引没有语意的情况,往往会使用其他的数据结构进行查询,这就是后话了。
很多场景中,虽然我们能给索引定义出来语意,但并非所有语意的索引都适用于数组,例如我想通过以人的身份证号作为索引,去找对应的人的一些属性。而由于身份证号作为索引一般都非常的大,对于计算机来说,开辟一个身份证号数字大小的空间是不值当甚至不可能的,且就算可以也会浪费相当多的空间,例如我数组就十个人,我要用身份证号作为索引去开辟,那势必会浪费特别大的空间。
- 在索引无语义的情况下使用数组
如果索引有语意,那么使用数组就太简单了,直接根据索引查询具体的元素即可
但是如果我们就是使用数组去处理索引没有语意的情况,也可以处理,但是会出现很多新的问题:
①数组只是一个提供待存放元素的空间的工具
例如我们的数组有6个空间,但是我们现在需要考察的或者说只放了三个元素,那么访问没有元素的空间可能就是非法的,因为从用户的角度来看,是没有3,4,5对应的元素的。

那么问题一就是索引没有语意,如何表示没有元素的空间
问题二如何添加元素,如何删除元素?例如我们之前创建数组的时候已经规定好了大小,如果我们要再往其中添加元素超过了预定的空间大小该怎么办呢,这些都会一一解决。
而java为我们提供的数组是没有这些方法的,我们必须自己编写:
其实我们可以将Java提供的数组称为静态数组,我们自己编写的数组称为动态数组。
由于数组本身是静态的,创建的时候必须指定它的大小,我们管这个叫容量capacity,也就是我们的数组空间最多可以装多少个元素。
ps:我们数组空间最多可以装多少个元素和我们数组空间实际有多少个元素没有关系。我们把数组空间实际有多少个元素称为size,而数组初始化的时候,一个元素都没有,此时size=0,其实就指向了第一个没有元素的索引。而后面对数组操作的时候,我们就需要维护数组的size,例如增加或者删除元素的时候。

- 动态数组代码实现
那么我们现在就可以实现开始的动态数组了:
package com.mbw.Array;public class DynamicArray {//初始化数组private int[] array;//数组实际元素个数private int size;//之前讲解过capacity容量这个属性,其实它本质上等于array.length,所以没有必要设计//带有初始容量的构造函数public DynamicArray(int capacity){array = new int[capacity];size = 0;}//不带初始容量的构造函数,所以我们给它预先创建一个容量为10的数组,容量够不够后面可以解决public DynamicArray(){array = new int[10];size = 0;}//判断数组是否为空public boolean isEmpty(){return size == 0;}//获取数组当前元素个数public int gitSize(){return size;}//获取数组容量public int getLength(){return array.length;}
}
三、向动态数组中添加元素
①向数组末尾添加元素
之前说过,size指向数组中第一个没有元素的索引,我们向数组末尾添加元素,其实就相当于在size的位置添加元素,例如我们现在需要向数组末尾添加一个66的元素,其实就相当于是在size=0这个位置添加一个66的元素,与此同时由于元素个数为1,size=1,往后挪一格指向索引为1的没有元素的位置。
那么按照这样的逻辑,我们的代码可以这样写:
public void addLast(int e){array[size] = e;size ++;}
②向任意位置添加元素
比如我现在想要在索引为1的位置添加一个元素,那么我从size-1的索引对应的元素开始,一直到Index对应的元素为止,这中间的所有原来的元素都要往后挪动一格,然后此时index对应的元素虽然还是原来的值,但是此时我们可以将它放心的把新的值赋值给它了。因为此时我其他的元素已经做了往后挪的处理。最后将size++即可。

那么由上述逻辑,我们可以写出以下程序:
public void add(int index, int e) {//如果size容量,则报错if (size == array.length) {throw new IllegalArgumentException("add failed,array is full");}//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index > size) {throw new IllegalArgumentException("add failed,index should >= 0 or index <= size");}//如果此时index 就等于 size,此时相当于前面的向数组末尾增加元素,不用走for循环if (index == size) {array[index] = e;size++;return;}//然后从最后一个元素开始,一直到需要添加的位置为结尾,将该元素赋值给后一个空间for (int i = size - 1; i >= index; i--) {array[i + 1] = array[i];}array[index] = e;size++;}
那么有了上述逻辑代码,其实我们之前写的向数组末尾添加元素,就可以改写为:
//往数组末尾添加元素public void addLast(int e) {add(size, e);}
那想必你还可以拓展向数组头增加元素的方法:
public void addFirst(int e) {add(0, e);}
然后我们就可以试一下,为了方便后续验证,我们可以重写toString()方便我们查看数组详情:
@Overridepublic String toString() {return "DynamicArray{" +"array=" + Arrays.toString(array) +", size=" + size + ", capacity=" + getLength() +'}';}
然后试一下刚才我们写的add():
public static void main(String[] args) {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(dynamicArray.size, 20);dynamicArray.add(dynamicArray.size, 23);dynamicArray.add(dynamicArray.size, 25);dynamicArray.addLast(100);dynamicArray.addFirst(10);dynamicArray.add(1, 30);System.out.println(dynamicArray.toString());}

四、获取数组索引对应的元素和修改对应索引的元素
这两个方法都比较简单
①获取数组索引对应的元素
我们需要注意index的范围,Index<0不用多说,当Index>=size,我们知道size是指向第一个没有元素的索引,所以我们自然不能让index等于size,哪怕size指向的这个元素确实因为删除方法有值,但其实这个元素仍然是非法的,我们不能让用户访问到。
//获取index索引上的元素public int get(int index){if(index < 0 || index >= size){throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}return array[index];}
②修改Index索引对应的元素
同理,我们可以写出以下代码:
//修改Index索引上的元素public void set(int index, int e){if(index < 0 || index >= size){throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}array[index] = e;}
五、包含、搜索和删除
首先说一下包含
①查看元素是否存在数组当中
逻辑很简单,我们只需从索引为0开始,size为结尾遍历一遍我们的数组,如果存在,那么就返回true,如果没有则返回false
//查看数组是否包含epublic boolean contains(int e){for(int i = 0;i< size; i++){if(array[i] == e){return true;}}return false;}
②返回具体元素对应的索引
这个逻辑其实很像上述之前的包含,若存在,则返回找到的元素的索引,没找到返回无效索引-1
public int find(int e){for(int i = 0;i< size; i++){if(array[i] == e){return 1;}}return -1;}
但是有些比较细心的朋友可能会觉得这个程序存在纰漏,如果我的数组存在同样的元素值,那这个就不太对了,我们这个程序顶多称为若存在,则返回找到的第一个元素的索引,没找到返回无效索引-1。如果需要详细一点,可以返回一个int数组或者列表,将找到的元素对应的索引全部放到其中返回。
③删除指定位置元素
删除其实逻辑很简单,了解了add其实就是从最后一个元素开始一直到index对应的索引位置结束,将其中的元素往后挪一个位置,删除就是反过来,我们从要删除的索引的后一个位置开始,一直到size前一个(数组最后一个有值元素对应的空间)索引对应的元素,往前挪一格,其实说是挪动,更像是一次赋值,最后size别忘了减一

可能有伙伴会觉得,我这样删除,size减一后,不就和它的意义不一致了吗,因为我的size指向的是第一个没有元素的空间,现在却指向了往前挪动的所有元素中最后一个索引对应的元素,可是大家可以想一下,size指向了有值的元素对客户是否有影响呢,其实是没有的,客户通过方法是访问不到size对应的元素的值的,也不会影响我的add等操作,所以其实size–后存在有值元素,也不会影响后续的操作,那么根据上述逻辑,我们可以完成删除代码:
//从数组中删除Index位置的元素,返回删除的元素public int remove(int index{//如果size容量,则报错//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index >= size) {throw new IllegalArgumentException("add failed,index should >= 0 or index < size");}int result = array[index];for(int i = index + 1;i < size ; i++){array[i-1] = array[i];}size --;return result;}
那有了删除代码,我们可以同add完成删除第一个和删除最后一个元素:
public int removeFirst(){return remove(0);}public int removeLast(){return remove(size - 1);}
且大家可以不用担心删除空数组删除的情况,如果说此时数组是空数组(size=0),调用删除第一个元素方法,index=0,那么会被上面remove的if语句中size = index命中从而抛出异常,如果调用删除最后一个元素的方法,idex-1=-1,从而被上面remove的if语句中index < 0命中从而抛出异常.
那我们有些朋友可能不想删除指定位置的元素,而是想从数组中删除特定元素的值。那么我们就可以结合之前的find()和remove()完成这个操作,需要注意的是,由于find存在只能找到第一个元素对应的索引的问题,我们的这个删除特定元素方法也只能删除一个,如果数组存在相同元素的话,那么其实是没有删除干净的,感兴趣的小伙伴可以自行拓展:
public void deleteElement(int e){int index = find(e);if(index != -1){remove(index);}}
我们可以试验一下:
public static void main(String[] args) {DynamicArray dynamicArray = new DynamicArray();dynamicArray.add(dynamicArray.size, 20);dynamicArray.add(dynamicArray.size, 23);dynamicArray.add(dynamicArray.size, 25);dynamicArray.addLast(100);dynamicArray.addFirst(10);dynamicArray.add(1, 30);System.out.println(dynamicArray.toString());//删除四个元素dynamicArray.remove(1);dynamicArray.deleteElement(100);dynamicArray.removeFirst();dynamicArray.removeLast();System.out.println(dynamicArray.toString());}
最后应该剩下的就是20,23,结果也是正确的,size=2,前两个元素就是20,23.

感兴趣的小伙伴还可以对这个基础的数组进行扩展,比如添加泛型等等,这个届时和后面的动态数组的拓展代码我会一起给一个示范代码
package com.mbw.array;import java.util.Arrays;public class DynamicArray<T> {//初始化数组private T[] array;//数组实际元素个数private int size;//之前讲解过capacity容量这个属性,其实它本质上等于array.length,所以没有必要设计//带有初始容量的构造函数public DynamicArray(int capacity) {array = (T[])new Object[capacity];size = 0;}//不带初始容量的构造函数,所以我们给它预先创建一个容量为10的数组,容量够不够后面可以解决public DynamicArray() {array = (T[])new Object[10];size = 0;}//判断数组是否为空public boolean isEmpty() {return size == 0;}//获取数组当前元素个数public int gitSize() {return size;}//获取数组容量public int getLength() {return array.length;}//往数组末尾添加元素public void addLast(T e) {add(size, e);}public void addFirst(T e) {add(0, e);}public void add(int index, T e) {//如果size容量,则报错if (size == array.length) {throw new IllegalArgumentException("add failed,array is full");}//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index > size) {throw new IllegalArgumentException("add failed,index should >= 0 or index <= size");}if (index == size) {array[index] = e;size++;return;}//然后从最后一个元素开始,一直到需要添加的位置为结尾,将该元素赋值给后一个空间for (int i = size - 1; i >= index; i--) {array[i + 1] = array[i];}array[index] = e;size++;}//获取index索引上的元素public T get(int index){if(index < 0 || index >= size){throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}return array[index];}//修改Index索引上的元素public void set(int index, T e){if(index < 0 || index >= size){throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}array[index] = e;}//查看数组是否包含epublic boolean contains(T e){for(int i = 0;i< size; i++){if(array[i] == e){return true;}}return false;}//查看元素对应的数组索引位置,若存在,则返回找到的第一个元素的索引,没找到返回无效索引-1public int find(T e){for(int i = 0;i< size; i++){if(array[i] == e){return i;}}return -1;}//从数组中删除Index位置的元素,返回删除的元素public T remove(int index){//如果size容量,则报错//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index >= size) {throw new IllegalArgumentException("add failed,index should >= 0 or index < size");}T result = array[index];for(int i = index + 1;i < size ; i++){array[i-1] = array[i];}size --;return result;}public T removeFirst(){return remove(0);}public T removeLast(){return remove(size - 1);}public void deleteElement(T e){int index = find(e);if(index != -1){remove(index);}}@Overridepublic String toString() {return "DynamicArray{" +"array=" + Arrays.toString(array) +", size=" + size + ", capacity=" + getLength() +'}';}public static void main(String[] args) {DynamicArray<Integer> dynamicArray = new DynamicArray<>();dynamicArray.add(dynamicArray.size, 20);dynamicArray.add(dynamicArray.size, 23);dynamicArray.add(dynamicArray.size, 25);dynamicArray.addLast(100);dynamicArray.addFirst(10);dynamicArray.add(1, 30);System.out.println(dynamicArray.toString());dynamicArray.remove(1);dynamicArray.deleteElement(100);Integer removeFirst = dynamicArray.removeFirst();Integer removeLast = dynamicArray.removeLast();System.out.println(removeFirst);System.out.println(removeLast);System.out.println(dynamicArray.toString());}
}
六、动态数组
我们之前实现的数组仍然存在一个问题,它内部其实使用的还是一个java的静态数组,对于静态数组来说,它的容量是有限的,可是很多时候我们使用数组类很难预估我们究竟会放多少元素类,容量太大浪费空间,容量太小又有可能不够用,所以我们需要一种解决方案,使得我们数组容量是可伸缩的,这就是动态数组。

例如对于下面一个capacity=4的数组,目前已经放满了,如果我还想接着往下增加元素,根据现有逻辑是会抛出异常的,但是我们可以通过一些其他方法实现这个操作,我们可以开辟一个新的数组newData,新的数组要比原来的容量大上一些,比如之前是4个空间,我新数组的容量就是8,然后将原来数组的元素放到新数组newData中,从而达成通过NewData取代原来的数组,并且扩容的目的。
那么对于扩容的大小,我们更希望它能和原来的数组是成比例的正相关的,而不是在其基础上增加一个固定的常数,比如我如果固定增加的太小,那么当我的数组容量达到很大时候,这个时候就会频繁触发扩容,反之扩容一次增加的容量太大,那么在一开始数组容量很小的时候,一次性扩容很大的容量就会造成很多的空间浪费。所以我们就每次扩容为原来数组的2倍即可。当然关于倍数的选择和你具体的业务有关,拿我们java的动态数组arrayList来说,它每次扩容就是原来的1.5倍。
所以我们可以有如下代码:
private void resize(int newCapacity){T[] newArray = (T[])new Object[newCapacity];for (int i = 0; i < size; i++) {newArray[i] = array[i];}array = newArray;}
如上risize()扩容方法,大家可能会想for循环,每次新增一旦需要扩容,每次都要for循环,会不会太影响效率,这个我们后面的文章会专门解析这个问题。而对于引用问题,原来的数组由于我们将新数组替代它了,原来的数组就会被java的垃圾回收机制回收掉,不会影响内存空间。下面我们就可以应用resize()了
在add方法,如果满了,则扩容为原来2倍:
public void add(int index, T e) {//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index > size) {throw new IllegalArgumentException("add failed,index should >= 0 or index <= size");}//如果size=容量,则自动扩容,每次扩容为原来数组容量的2倍if (size == array.length) {resize(2 * array.length);}if (index == size) {array[index] = e;size++;return;}//然后从最后一个元素开始,一直到需要添加的位置为结尾,将该元素赋值给后一个空间for (int i = size - 1; i >= index; i--) {array[i + 1] = array[i];}array[index] = e;size++;}
在remove方法,如果发现当前size是数组容量的1/2,那么我们可以将数组缩小为原来的1/2
public T remove(int index) {//如果size容量,则报错//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index >= size) {throw new IllegalArgumentException("add failed,index should >= 0 or index < size");}T result = array[index];for (int i = index + 1; i < size; i++) {array[i - 1] = array[i];}size--;//如果发现size=当前数组容量一半,则将数组缩容至原来一半if (size == array.length / 2) {resize(array.length / 2);}return result;}
然后我们可以测试一下:
public static void main(String[] args) {DynamicArray<Integer> dynamicArray = new DynamicArray<>();for (int i = 0; i < dynamicArray.getLength(); i++) {dynamicArray.addLast(i);}System.out.println(dynamicArray.toString());dynamicArray.addLast(100);System.out.println(dynamicArray.toString());dynamicArray.addFirst(101);System.out.println(dynamicArray.toString());dynamicArray.removeLast();dynamicArray.removeLast();System.out.println(dynamicArray.toString());}

结果符合代码逻辑,至此,我们从底层实现的自定义动态数组就完成了。
而对于泛型数组,这里我给一个整体的代码实现,有java功底的朋友应该不会觉得困难:
package com.mbw.array;import java.util.Arrays;public class DynamicArray<T> {//初始化数组private T[] array;//数组实际元素个数private int size;//之前讲解过capacity容量这个属性,其实它本质上等于array.length,所以没有必要设计//带有初始容量的构造函数public DynamicArray(int capacity) {array = (T[]) new Object[capacity];size = 0;}//不带初始容量的构造函数,所以我们给它预先创建一个容量为10的数组,容量够不够后面可以解决public DynamicArray() {array = (T[]) new Object[10];size = 0;}//判断数组是否为空public boolean isEmpty() {return size == 0;}//获取数组当前元素个数public int gitSize() {return size;}//获取数组容量public int getLength() {return array.length;}//往数组末尾添加元素public void addLast(T e) {add(size, e);}public void addFirst(T e) {add(0, e);}public void add(int index, T e) {//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index > size) {throw new IllegalArgumentException("add failed,index should >= 0 or index <= size");}//如果size=容量,则自动扩容,每次扩容为原来数组容量的2倍if (size == array.length) {resize(2 * array.length);}if (index == size) {array[index] = e;size++;return;}//然后从最后一个元素开始,一直到需要添加的位置为结尾,将该元素赋值给后一个空间for (int i = size - 1; i >= index; i--) {array[i + 1] = array[i];}array[index] = e;size++;}//获取index索引上的元素public T get(int index) {if (index < 0 || index >= size) {throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}return array[index];}//修改Index索引上的元素public void set(int index, T e) {if (index < 0 || index >= size) {throw new IllegalArgumentException("get failed;index should >= 0 or index < size");}array[index] = e;}//查看数组是否包含epublic boolean contains(T e) {for (int i = 0; i < size; i++) {if (array[i] == e) {return true;}}return false;}//查看元素对应的数组索引位置,若存在,则返回找到的第一个元素的索引,没找到返回无效索引-1public int find(T e) {for (int i = 0; i < size; i++) {if (array[i] == e) {return i;}}return -1;}//从数组中删除Index位置的元素,返回删除的元素public T remove(int index) {//如果size容量,则报错//如果index比size还要大(中间会出现非法元素)或者index<0,也需报错if (index < 0 || index >= size) {throw new IllegalArgumentException("add failed,index should >= 0 or index < size");}T result = array[index];for (int i = index + 1; i < size; i++) {array[i - 1] = array[i];}size--;//如果发现size=当前数组容量一半,则将数组缩容至原来一半if (size == array.length / 2) {resize(array.length / 2);}return result;}public T removeFirst() {return remove(0);}public T removeLast() {return remove(size - 1);}public void deleteElement(T e) {int index = find(e);if (index != -1) {remove(index);}}@Overridepublic String toString() {return "DynamicArray{" +"array=" + Arrays.toString(array) +", size=" + size + ", capacity=" + getLength() +'}';}public static void main(String[] args) {DynamicArray<Integer> dynamicArray = new DynamicArray<>();for (int i = 0; i < dynamicArray.getLength(); i++) {dynamicArray.addLast(i);}System.out.println(dynamicArray.toString());dynamicArray.addLast(100);System.out.println(dynamicArray.toString());dynamicArray.addFirst(101);System.out.println(dynamicArray.toString());dynamicArray.removeLast();dynamicArray.removeLast();System.out.println(dynamicArray.toString());}private void resize(int newCapacity) {T[] newArray = (T[]) new Object[newCapacity];for (int i = 0; i < size; i++) {newArray[i] = array[i];}array = newArray;}
}相关文章:
数据结构与算法(一)数组的相关概念和底层java实现
一、前言 从今天开始,笔者也开始从0学习数据结构和算法,但是因为这次学习比较捉急,所以记录的内容并不会过于详细,会从基础和底层代码实现以及力扣相关题目去写相关的文章,对于详细的概念并不会过多讲解 二、数组基础…...
歌曲推荐《最佳损友》
最佳损友 陈奕迅演唱歌曲 《最佳损友》是陈奕迅演唱的一首粤语歌曲,由黄伟文作词,Eric Kwok(郭伟亮)作曲。收录于专辑《Life Continues》中,发行于2006年6月15日。 2006年12月26日,该曲获得2006香港新城…...
多元共进|科技促进艺术发展,助力文化传承
科技发展助力文化和艺术的传播 融合传统与创新,碰撞独特魅力 一起来了解 2023 Google 开发者大会上 谷歌如何依托科技创新 推动艺术与文化连接 传承和弘扬传统文化 自 2011 年成立以来,谷歌艺术与文化致力于提供体验艺术和文化的新方式,从生成…...
Java集合(Collection、Iterator、Map、Collections)概述——Java第十三讲
前言 本讲我们将继续来讲解Java的其他重要知识点——Java集合。Java集合框架是Java编程语言中一个重要的部分,它提供了一套预定义的类和接口,供程序员使用数据结构来存储和操作一组对象。Java集合框架主要包括两种类型:一种是集合(Collection),存储一个元素列表,…...
topscoding主题库模板题
目录 模板题 【模板题】分因数(P1101) 【模板题】区间素数 III(P1113) 进制转换 III (任意转任意) (P2463) AB Problem(高精度加法) A-B Problem(高精度减法&…...
Linux--进程间通讯--FIFO(open打开)
1. 什么是FIFO FIFO命名管道,也叫有名管道,来区分管道pipe。管道pipe只能用于有血缘关系的进程间通信,但通过FIFO可以实现不相关的进程之间交换数据。FIFO是Linux基础文件类型中的一种,但是FIFO文件在磁盘上没有数据块,…...
哪里可以了解轻量的工作流引擎?
如果想要实现高效率的办公,可以使用轻量的工作流引擎低代码技术平台。随着工作量日益繁重起来,传统的办公制作方式已经无法满足现实需要的,采用轻量级的表格制作工具,就能在无形中缓解办公压力,创造更高效、灵活、优质…...
lvs负载均衡、LVS集群部署
四:LVS集群部署 lvs给nginx做负载均衡项目 218lvs(DR 负载均衡器) yum -y install ipvsadm(安装这个工具来管理lvs) 设置VIP192.168.142.120 创建ipvsadm的文件用来存放lvs的规则 定义策略 ipvsadm -C //清空现有…...
如何应对核心员工提离职?
最近一年互联网行情不好,很多大厂都在裁员,但裁员并不是不要人做事了。原来你这个岗位10个人做,企业有钱赚养得起,现在企业不怎么赚钱了,只能养4个人了。那么会有六个被裁掉。这时候对企业价值最大的4个人会被留下。也…...
建站系列(八)--- 本地开发环境搭建(WNMP)
目录 相关系列文章前言一、准备工作二、Nginx安装三、MySQL安装四、PHP安装及Nginx配置五、总结 相关系列文章 建站系列(一)— 网站基本常识 建站系列(二)— 域名、IP地址、URL、端口详解 建站系列(三)— …...
21天学会C++:Day8----范围for与nullptr
目录 编辑 1. 范围for 2. nullptr 1. 范围for 我们在写C语言循环遍历代码的时候,无论是用 for循环,while循环都需要考虑循环的起始条件,循环变量的递增逻辑,循环的结束条件。麻烦不说还可能会出错。 int main() {int arr[]…...
Linux——环境变量
✅<1>主页::我的代码爱吃辣 📃<2>知识讲解:Linux——环境变量 ☂️<3>开发环境:Centos7 💬<4>前言:环境变量(environment variables)一般是指在操作系统中用来指定操作…...
Screen的详细全面安装教程及Screen的用法
Screen可以大大提高终端使用效率,是Linux系统管理和运维的必备技能。当我们开启Screen后,只要Screen进程没有终止,其内部运行的会话都可以恢复。即使网络连接中断,用户也可以重新进入已开启的Screen中,对中断的会话进行…...
生成树、Prufer序列的计数问题:0912T1
看到生成树计数,很容易想到生成树计数 然后发现每个点有度数限制,我们可以先考虑枚举每个点的度数(也可以是Prufer 序列中的出现次数) 假设出现次数为 a a a,可以得出其生成树方案为 n ! ∏ ( a i − 1 ) ! \frac{…...
SQL_牛客网_SQL264_求每个登陆日期的次日留存率
牛客每个人最近的登录日期(五) 牛客每天有很多人登录,请你统计一下牛客每个日期新用户的次日留存率。 有一个登录(login)记录表,简况如下: id user_id client_id date 1 2 1 2020-10-12 2 3 2 2020-10-12 3 1 2 2020-10-…...
Hive 基础知识
目录 1.基础概念1.1 定义1.2 组件1.3 元数据1.4 内部表和外部表 2. Hive与关系型数据库的对比3. Hive 数据存储4. 参考文献 1.基础概念 1.1 定义 Hive是一个基于Hadoop的数据仓库基础设施工具,它可以将结构化的数据文件映射为一张数据库表,并提供类SQL查…...
【数据结构】树的基础知识及三种存储结构
💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …...
ABB 3BHB003688R0101接口模块
通信接口:接口模块通常具有多种通信接口,如以太网、串行通信、Modbus、Profibus等,以便与其他设备和系统进行数据交换。 协议支持:它们支持各种通信协议,确保与不同制造商的设备和控制系统兼容。 数据转换和适配&…...
精简 jre 涉坑记录
主要参考:https://zhuanlan.zhihu.com/p/91496457 主要问题: 1)jre 中有 client 和 server 之分。参考:关于JDK的Server和Client模式的切换_jacksonary的博客-CSDN博客 2)对 copy 出来的 rt 进行打 zip 包时&#x…...
Java程序员学习算法路线规划总结
文章目录 前言:必须清楚得基本数据结构:1.需掌握哪些算法?2.学习步骤以及路线 前言:必须清楚得基本数据结构: 数组(Array) 链表(Linked List) 栈(Stack&…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...
云安全与网络安全:核心区别与协同作用解析
在数字化转型的浪潮中,云安全与网络安全作为信息安全的两大支柱,常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异,并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全:聚焦于保…...
