在编程中,如果想深入,数据结构是我们必须要了解的一块。学习/理解数据结构的动机可能不同。一方面,可能是为了面试,一方面,可能只是为了提高自己的技能或项目需要。无论出于何种动机,如果不知道数组结构是什么以及何时使用它们,学习数据结构就是一个乏味且无趣的过程😵本文讨论何时使用它们。在本文中,我们将了解数组和对象。我们将尝试使用大O表示法来理解何时选择数据结构。BigOnotation大零表示法一般用来描述算法的复杂度,比如执行时间或者占用的内存(磁盘)空间等,尤其是最坏的情况。数组的数组是使用最广泛的数据结构之一。数组中的数据以有序方式构建,即数组中的第一个元素存储在索引0处,第二个元素存储在索引1处,依此类推。JavaScript为我们提供了一些内置的数据结构,数组就是其中之一👌在JavaScript中,定义数组最简单的方法是:letarr=[]上面这行代码创建了一个动态数组(长度未知),为了了解数组的元素是如何存储在内存中的,让我们看一个例子:letarr=['John','Lily','William','Cindy']数组。名称在内存中的存储方式如下:为了理解数组的工作原理,我们需要执行一些操作:添加元素:在JavaScript数组中,我们有不同的方法在数组末尾、开关处、和特定指数。在数组末尾添加一个元素:JavaScript中的数组有一个默认的属性length,代表数组的长度。除了length属性,JS还提供了push()方法。使用这个方法,我们可以直接在末尾添加一个元素。arr.push('Jake')那么这个命令的复杂性是什么?我们知道,默认情况下,JS提供了length属性,push()相当于使用下面的命令:arr[arr.length-1]='Jake'由于我们总是可以访问到数组的length属性,所以添加最后的元素总是O(1)👏不管数组有多大。在数组的开头添加一个元素:对于这个操作,JavaScript提供了一个默认方法unshift(),这个方法将元素添加到数组的开头。letarr=['John','Lily','William','Cindy']arr.unshift('Robert')console.log(arr)//['Robert','John','Lily','William','Cindy']unshift方法的复杂度好像和push方法一样:O(1),因为我们只是在前面加了一个元素。事实并非如此,让我们看看当我们使用unshift方法时会发生什么:在上图中,当我们使用unshift方法时,所有元素的索引应该增加1。这里我们有一个相对较少的数组,并且我们看不到存在的问题。想象一下使用一个相当长的数组,然后使用像unshift这样的方法会导致延迟,因为我们必须移动数组中每个元素的索引。因此,unshift操作的复杂度为O(n)😋。如果您要处理更大长度的数组,请明智地使用unshift方法。要在特定索引处添加元素,我们可以使用splice()方法,其语法如下:splice(startingIndex,deleteCount,elementToBeInserted)因为我们要添加一个元素,deleteCount将为0。例如,如果我们想要要在数组的索引2处添加一个新元素,我们可以使用:letarr=['John','Lily','William','Cindy']arr.splice(2,0,'Janice')console。log(arr)//['John','Lily','Janice','William','Cindy']你认为这个操作的复杂度是多少?在上面的操作中,我们在索引2处添加了元素,因此索引2之后的所有后续元素都必须递增或移位1(包括之前索引2处的元素)。可以观察到,我们没有移动或递增所有元素的索引,而是递增索引2之后的元素的索引。这是否意味着操作是`O(n/2)?不是😮。根据BigO规则,可以从复杂性中去除常量,而且,我们应该考虑最坏的情况。因此,这个操作的复杂度是O(n)😳。删除元素:就像添加元素一样,删除元素可以在不同的位置进行,在结尾处、开头处和特定索引处。删除数组末尾的元素:与push()一样,JavaScript提供了一个默认方法pop()用于删除/删除数组末尾的元素。letarr=['Janice','Gillian','Harvey','Tom']arr.pop()console.log(arr)//['Janice','Gillian','Harvey']arr.pop()console.log(arr)//['Janice','Gillian']这个操作的复杂度是O(1)。因为,无论数组有多大,删除最后一个元素都不需要更改数组中任何元素的索引。删除数组开头的元素:JavaScript提供了一个默认方法shift()来删除数组的第一个元素。letarr=['John','Lily','William','Cindy']arr.shift()console.log(arr)//['Lily','William','Cindy']arr.shift()console.log(arr);//['William','Cindy']从上面我们很容易看出shift()操作的复杂度是O(n),因为删除第一个元素后,我们必须所有元素的索引都移动或递减1。在特定索引处删除:对于此操作,我们再次使用splice()方法,但这次我们只使用前两个参数,因为我们不打算添加该索引处的新元素。letarr=['苹果','橙子','梨子','香蕉','西瓜']arr.splice(2,1)console.log(arr)//['苹果','橙子','香蕉','Watermelon']类似于用拼接添加元素。在此操作中,我们将递减或移动索引2之后的元素索引,因此复杂度为O(n)。查找元素:查找只是访问数组的元素,我们可以使用方括号表示法访问数组的元素(例如:arr[4])。你认为这个操作的复杂性是什么?让我们用一个例子来证明它:letfruits=['Apple','Orange','Pear']我们之前已经看到数组的所有元素都是按顺序存储的,并且总是分组在一起。所以如果你执行fruits[1],它会告诉计算机找到名为fruits的数组并获取第二个元素(数组从索引0开始)。由于它们是按顺序存储的,计算机不必遍历整个内存来查找元素,因为所有元素都按顺序组合在一起,它可以直接在fruits数组中查找。因此,数组中查找操作的复杂度为O(1)。现在我们已经完成了对数组的基本操作,让我们总结一下什么时候可以使用数组:当你想执行像push()(在末尾添加一个元素)和pop()(从末尾删除一个元素)这样的操作时,数组是合适的,因为这些操作的复杂度是O(1)。除此之外,查找操作可以在数组中非常快速地执行。使用数组时,在特定索引处或开头添加/删除元素等操作可能会非常慢,因为它们的复杂度为O(n)。对象与数组一样,对象是最常用的数据结构之一。对象是一种哈希表,它允许我们存储键值对,而不是像我们在数组中看到的那样将值存储在编号的索引处。定义一个对象最简单的方法是:letobj1={}例子:letstudent={name:'Vivek',age:13,class:8}我们看看上面的对象是如何存储在内存中的:可以看到,对象键值对是随机存储的,这与所有元素存储在一起的数组不同。这也是数组和对象之间的主要区别,其中键值对随机存储在内存中。我们还看到有一个哈希函数。那么这个哈希函数有什么作用呢?哈希函数从对象中取出每个键并生成一个哈希值,然后将这个哈希值转换到存储键值对的地址空间中。例如,如果我们将以下键值对添加到学生对象中:student.rollNumber=322rollNumber键通过哈希函数,然后转换为存储键和值的地址空间。现在我们对对象如何存储在内存中有了基本的了解,让我们来执行一些操作。对于对象,我们没有单独的方法来将元素添加到前面或后面,因为所有键值对都是随机存储的。只有一个操作是向对象添加新的键值对。例子:student.parentName='NarendraSinghBisht'从上图我们可以得出这个操作的复杂度总是O(1),因为我们不需要改变任何索引或者操作对象本身,我们可以直接添加一个键-值对,存储在随机地址空间中。删除和添加元素一样,对象的删除操作非常简单,复杂度为O(1)。因为,我们不必在删除时更改或操作对象。deletestudent.parentName查找的复杂度为O(1),因为在这里,我们也只使用键来访问值。访问对象中值的一种方法:student.class在对象中添加、删除和查找的复杂度为O(1)???所以我们可以得出结论,我们应该每次都使用对象而不是数组?答案是不。虽然对象很棒,但在处理对象时需要考虑一个小案例,即哈希碰撞。在处理对象时不应总是处理这种情况,但了解它有助于我们更好地理解对象。那么什么是哈希冲突?当我们定义一个对象时,我们的计算机会为该对象分配一些内存空间。我们需要记住,我们内存中的空间是有限的,因此有可能两个或多个键值对具有相同的地址空间,这种情况称为哈希冲突。为了更好地理解它,让我们看一个例子:假设为以下对象分配了5个空间块我们观察到两个键值对存储在同一个地址空间中。这怎么可能?当散列函数返回一个散列值,该散列值转换为多个键的相同地址空间时,就会发生这种情况。因此,多个键被映射到同一个地址空间。由于散列冲突,添加和访问对象值的复杂度为O(n),因为要访问特定值我们可能必须遍历各种键值对。哈希冲突不是我们每次使用对象时都需要处理的事情。这只是一个特例,也表明对象不是完美的数据结构。除了*hash冲突之外,还有一种情况在处理对象时必须注意。JS为我们提供了一个内置的keys()方法来遍历对象的键。我们可以将此方法应用于任何对象,例如:object1.keys()。keys()方法遍历对象并返回所有键。这种方法虽然看似简单,但我们需要明白,对象中的键值对是随机存储在内存中的,因此,遍历对象的过程变慢了,不像遍历一个按顺序将它们组合在一起的数组。总而言之,当我们想要执行添加、删除和访问元素等操作时,我们可以使用对象,但是在使用对象时,我们需要注意遍历对象,因为它可能很耗时。除了遍历,我们还应该明白,有时候由于哈希冲突,访问对象操作的复杂度可能会变成O(n)。本文转载自微信公众号“伟大的走向世界”,您可以通过以下二维码关注。转载本文请联系大千世界公众号。
