🍉JavaScript数据结构之数组及栈

简介: 🍉JavaScript数据结构之数组及栈

前言


最近在看JavaScript数据结构与算法一书,这里就带大家复习一下数组和栈的数据结构吧~


一、数组


几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构。 小tips:JavaScript的第一个版本并没有支持数组。数组存储一系列同一种数据类型的值。


虽然在JavaScript里,也可以在数组中保存不同类型的值,但我们还是要遵守最佳实践,避免这么做。


1.1 创建和初始化数组


1.1.2 使用new关键字


let arr1 = new Array();// 简单地声明并初始化一个数组
let arr2 = new Array(5);// 创建一个指定长度的数组
let arr3 = new Array(1,2,3,4,5);// 将数组元素作为参数传递给它的构造器
复制代码


然而使用new创建数组并不是最好的方式。如果你想在JavaScript中创建一个数组,只要中括号([])的形式就可以了。


1.1.3 使用字面量创建数组


let arr1 = [];
let arr2 = [1,2,3,4,5]
复制代码


1.2 访问元素和迭代数组


访问特定位置的元素,直接在中括号里传递数值位置即可。


1.2.1 循环迭代数组,打印元素


for(let i = 0;i < arr2.length;i++){
    console.log(i)
}
复制代码


1.2.2 求斐波那契数列的前20个数


const fibonacci = [];
fibonacci[1] = 1;
fibonacci[2] = 1;
// 根据前面两项即可求出第三项
for(let i = 3;i < 20;i++){
    fibonacci[i] = fibonacci[i-1] + fibonacci[i-2]
}
// 循环数组并打印结果
for(let i = 1 ; i < fibonacci.length;i++){
    console.log(fibonacci[i])
}
复制代码


小伙伴们可以直接复制以上代码,然后敲下F12键,然后粘贴代码,敲回车结果就出来啦!


99928eb873ec4555871946fc0506f0f3_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


1a3a026d395247289d707143f39a6a27_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


1.3 数组添加元素


在数组中添加和删除元素也很容易但有时也会很棘手。


let newArr = [0,1,2,3,4,5]
复制代码


1.3.1 在数组末尾插入元素


给newArr添加一个元素(比如6)


newArr[newArr.length]=6
复制代码


在JavaScript中,数组是一个可以修改的对象!如果添加元素,它就会动态增长。在C和Java等其他语言中,我们要决定数组的大小,要添加新的元素就要创建一个新的数组!


使用push方法


可以添加任意个元素


newArr.push(6);
newArr.push(6,7)
复制代码


1.3.2 在数组开头插入元素


首先腾出数组里第一个元素,把所有的元素向右移动一位。把我们想要的值赋给第一个位置上


Array.prototype.insertFirstPosition = function(value){
for(let i = this.length; i >= 0;i--){
    this[i]=this[i-1];
}
this[0] = value;
};
复制代码


使用unshift()方法


该方法背后的逻辑和insertFirstPosition方法的行为是一样的


newArr.unshift(-1)
newArr.unshift(-2,-1)
复制代码


1.4 删除元素


1.4.1 从数组末尾删除元素


newArr.pop(5);
newArr.pop(4,5);
复制代码


通过push和pop方法,能用数组来模拟栈


1.4.2 从数组开头删除元素


newArr.shift(0);
复制代码


1.5 在任意位置添加或删除元素


使用splice方法,简单地通过指定位置/索引,就可以删除相应位置上指定数量的元素 splice方法接受的第一个参数表示想要删除或插入元素的索引值。第二个参数是删除元素的个数(如果为0则是添加元素)。第三个参数往后,就是要添加到数组里的值。


1.6 二维和多维数组


JavaScript之支持一维数组,并不支持矩阵,但是我们可以数组套数组,实现矩阵或任一多维数组


let averageTemp = [];
averageTemp[0]=[1,2,3];
averageTemp[1]=[4,5,6];
复制代码


1.6.1 迭代二维数组的元素


使用一个嵌套的for循环来处理。


function printMatrix(myMatrix){
for(let i = 0;i<myMatrix.length;i++){
    for(let j = 0;j<myMatrix[i].length;j++){
    console.table(myMatrix[i][j])
    }
    }
}
复制代码


1.7 JavaScript的数组方法参考


ec8dd55383064fa08acea40185aae449_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg


1.7.1 数组合并


const zero = 0;
const arr1 = [1,2,3];
const arr2 = [4,5,6];
let newArr = arr1.concat(zero,arr1,arr2);
console.log(newArr)
// output:[1,2,3,0,4,5,6]
复制代码


concat方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序连接指定数组。


1.7.2 迭代器函数


我们可以用循环语句来处理,例如for语句。 JavaScript内置了许多数组可用的迭代方法。 例:我们需要一个函数和一个数组:如果数组里的元素可以被2整除,函数就返回true,否则返回false。


function isEven(x){
console.log(x);
return x % 2 === 0 ? true : false
}
ES6写法:
const isEven = x=>x%2 === 0;
let newArr = [2,4,5,6,8]
复制代码


1.用every方法迭代


every方法会迭代数组中的每一个元素,直到返回false


newArr.every(isEven)
复制代码


数组newArr第3个元素为奇数,isEven函数返回false,然后every执行结束


2.用some方法迭代


它和every的行为相反,会迭代数组的每一个元素,直到函数返回true。


newArr.some(isEven)
复制代码


3.用forEach方法迭代


如果要迭代整个数组,可以用forEach方法。它和使用for循环的结果相同。


4.使用map和filter方法


JavaScript还有两个会返回新数组的迭代方法。


第一个是map方法。


const myMap = newArr.map(isEven);
复制代码


数组myMap里的值是[true,true,false,true,true]。 它保存了传入map方法的isEven函数的运行结果。这样就很容易知道一个元素是否是偶数。


第二个是filter方法。


它返回的新数组由使函数返回true的元素组成。


const myFilter = newArr.filter(isEven)
output:[2,4,6,8]
复制代码


5. 使用reduce方法


reduce方法接受一个有如下四个参数的函数:previousValue、currentValue、index、array是可选的参数。因为index和array是可选的参数,所以有时可不传。这个函数会返回一个将被叠加到累加器的值, reduce方法停止执行后会返回这个累加器。


如果要对一个数组中的所有元素求和,这就很有用。


newArr.reduce((a,b)=>a+b);
复制代码


这三个方法(map.filter,reduce)是JavaScript函数式编程的基础


1.7.3 ES6中数组新增方法


fe4d7137a9414394b1acf9bda2c0dd61_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg


除了这些新的方法,还有一种用for...of循环来迭代数组的新做法。


1. 使用for...of循环迭代


for(const n of newArr){
    console.log(n%2 === 0 ? true : false)
}
复制代码


2. 使用@@iterator对象


ES6还为Array类增加一个@@iterator属性,需要通过Symbol.iterator来访问。


let iterator = newArr[Symbol.iterator]();
console.log(iterator.next().value);//2
console.log(iterator.next().value);//4
复制代码


iterator = newArr[Symbol.iterator]();
for(const n of iterator){
    console.log(n)
}
复制代码


3. 数组的entries、keys和values方法


ES6还增加了三种从数组中得到迭代器的方法。


entries方法


entries方法返回包含键值对的@@iterator



let myEntries = newArr.entries();
console.log(myEntries.next().value)//[0,2]
console.log(myEntries.next().value)//[1,4]
复制代码


使用集合、字典、散列表等数据结构时,能够取出键值对是很有用的,这个功能会在后面的内容在大显身手。


keys方法


keys方法会返回包含数组索引的@@iterator


const myKeys = newArr.keys();
console.log(myKeys.next())//{value:0,done:false}
console.log(myKeys.next())//{value:1,done:false}
复制代码


一旦没有可迭代的值,myKeys.next()就会返回一个value属性为undefined、done属性为true的对象。如果done属性的值为false,就意味着还有可迭代的值。


values方法


values方法返回的@@iterator则包含数组的值。


const myValues = newArr.values();
console.log(myValues.next());
console.log(myValues.next());
复制代码


4.使用Array.from方法


Array.from方法根据已有的数组创建一个新的数组。


// 复制newArr数组
let newArr1 = Array.from(newArr)
// 还可以传入一个过滤值的函数
let newArr2 = Array.from(newArr,x=>x % 2 == 0)
复制代码


5. 使用Array.of方法


Array.of方法会根据传入的参数创建一个新的数组


let arr1 = Array.of(1);
let arr2 = Array.of(1,2,3,4,5,6);
复制代码


我们也可以用该方法复制已有数组


let newArr = Array.of(...arr2)
复制代码


效果和Array.from(arr2)的效果是一样的,不过这里使用了解构的方法。


6. 使用fill方法


fill方法用静态值填充数组


let newArr1 = newArr.fill(0)// 数组的所有值都变为0
let newArr2 = newArr.fill(0,1)// [1,2,3,4,5,6]变更为[1,0,0,0,0,0]
let newArr3 = newArr.fill(0,3,5)// 把0填充到数组索引为3到5的位置(不包含3和5)
复制代码


创建数组并初始化值的时候,fill方法非常好用


// 创建一个长度为5值都为0的数组
let arr = Array(5).fill(0)
复制代码


7. 使用copyWithin方法


copyWithin方法复制数组中的一系列元素到同一数组指定的起始位置


let arr = [1,2,3,4,5,6];
// 把4,5,6复制到数组的前三个位置
arr.copyWithin(0,3);
// 把4、5两个值复制到位置1,2
arr.copyWithin(1,3,5)
复制代码


1.7.4 排序元素


在JavaScript里提供了一个排序方法和一组搜索方法。


reverese方法反序输出数组


let arr = [1,2,3,4,5,6]
arr.reverse()
复制代码


sort方法对数组做排序


sort方法在对数组做排序时,把元素默认为字符串进行相互比较。


我们可以传入自己写的比较函数arr.sort((a,b)=>a-b),这些就实现了对arr的升序排序。


1.自定义排序


我们也可以对任何对象类型的数组排序,可以创建compareFunction来比较元素。


const friends = [{name:'橙子',age:18},{name:'柚子',age:22},{name:'西瓜',age:17},];
 function compareFunction(a,b){
if(a.age<b.age){
    return 1
}
if(a.age>b.age){
    return -1
}
    return 0
}
friends.sort(compareFunction)
复制代码


最后输出就会按年龄降序排序


81d2f236d4a54392931b3cd30218775f_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


2.字符串排序


let names = ['Ana','ana','john','John']
console.log(names.sort())
// output:['Ana','John','ana','john']
复制代码


JavaScript在做字符比较的时候,是根据字符对应的ASCII值来比较的。 如果给sort传入一个忽略大小写的函数,这个时候sort函数不会有任何作用,它会按照现在的大小写字母顺序排序。


如果希望小写字母排在前面,那么需要使用localCompare方法。


names.sort((a,b)=>a.localCompare(b))
复制代码


此外localCompare还可以对带有重音符号的字符做排序。


1.7.5 搜索


搜索有两种方法:


indexOf方法返回与参数匹配的第一个元素的索引。

lastIndexOf返回与参数匹配的最后一个元素的索引。


若没有找到则返回-1


1.ES6--find和findIndex方法


find和findIndex方法接受一个回调函数,搜索一个满足回调函数条件的值。


find方法返回第一个满足条件的值,findIndex方法则返回这个值在数组里的索引。如果没有满足条件的值,find会返回undefined,而findIndex返回-1。


2.ES7--使用includes方法


如果数组里存在某个元素,includes方法会返回true,否则返回false。


arr.includes(15)
复制代码


如果给includes方法传入一个起始索引,搜索会从索引指定的位置开始。


1.7.6 输出数组为字符串


toString()


如果想把数组里所有的元素输出为一个字符串,可以使用toString方法。


join()


如果想用一个不同的分割符把元素隔开,可以用join方法


const numberString = arr.join('-')
console.log(numberString)
//  output:1-2-3-4-5-6
复制代码


二、栈


2.1 栈数据结构


栈是一种遵从后进先出原则的有序集合。


2.1.1 创建一个基于数组的栈


class Stack{
  constructor() {
    this.items=[]
  }
}
复制代码


由于栈遵循LIFO原则,需要对元素的插入和功能进行限制,接下来,要为栈声明一些方法。


2.1.2 向栈添加元素


push(element){
  this.items.push(element)
}
复制代码


2.1.3 从栈移除元素


pop(){
return this.items.pop();
}
复制代码


2.1.4 查看栈顶元素


peek(){
    return this.items[this.items.length-1]
}
复制代码


2.1.5 检查栈是否为空


isEmpty(){
    return this.items.length === 0;
}
复制代码


2.1.6 获取栈的长度


size(){
    return this.items.length;
}
复制代码


2.1.7 清空栈元素


clear(){
    this.items=[];
}
复制代码


OK!栈已完成


0e348fc9ad5a464aa90cf061f4f3c394_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


2.2 创建一个基于JavaScript对象的类Stack


创建一个Stack类最简单的方式是使用一个数组来存储其元素。在使用数组时,大部分方法的时间复杂度是O(n)。如果数组有很多元素的话,所需的时间会更长。另外数组是元素的一个有序集合,为了保证元素排列有序,它会占用更多的内存空间。如果我们能直接获取元素,占用较少的内存空间,并且仍然保证所有元素按照我们的需要排列,那不是更好吗?


我们可以使用一个JavaScript对象来存储所有的栈元素。


class Stack{
  constructor() {
    this.count = 0;
    this.items = {};
  }
}
复制代码


在这个版本的Stack类中,我们将使用一个count属性来帮助我们记录栈的大小(也能帮助我们从数据结构中添加和删除元素)


2.2.1 向栈中插入元素


push(element) {
    this.items[this.count] = element;
    this.count++
  }
复制代码


要向栈中添加元素,我们将使用count变量作为items对象的键名,插入的元素则是它的值。在向栈插入元素后,我们递增count变量。


2.2.2 验证一个栈是否为空和它的大小


size() {
    return this.count
  }
  isEmpty() {
    return this.count === 0
  }
复制代码


2.2.3 从栈中弹出元素


由于我们没有用数组来存储元素,需要手动实现移除元素的逻辑。


首先,我们需要检验栈是否为空,如果为空,就返回undefined。


如果不为空,count--,保存栈顶元素,删除栈顶元素再返回栈顶元素。由于我们使用的是JavaScript对象,可以使用JavaScript的delete运算符从对象中删除一个特定的值

pop() {
    if (this.isEmpty()) {
      return undefined
    }
    this.count--;
    const result = this.items[this.count];
    delete this.items[this.count];
    return result;
  }
复制代码


2.2.4 查看栈顶元素和将栈清空


peek() {
    return this.items[this.count - 1]
  }
  clear() {
    this.items = {}
    this.count = 0
  }
复制代码


2.2.5 创建toString方法


在数组版本中,我们不需要关心toString方法的实现,因为数据结构可以直接使用数组已经提供的toString方法。


对于使用对象的版本,我们将创建一个toString方法像数组一样打印出栈的内容。


toString() {
    if (this.isEmpty()) {
      return ''
    }
    let objString = `${this.items[0]}`
    for (let i = 1; i < this.count;i++ ){
      objString=`${objString},${this.items[i]}`
    }
    return objString
  }
复制代码


实现了toString方法后,我们就完成这个版本的Stack类。


除了toString方法,我们创建的其他方法的复杂度均为O(1),代表我们可以直接找到目标元素对其进行操作(push、pop或peek)。


2.3 保护数据结构内部的数据


在创建别的开发者也可以使用的数据结构或对象时,我们希望保护内部的元素,只有我们暴露出的方法才能修改内部结构。不幸的是,我们在Stack类中的声明的items和count属性并没有得到保护,我们可以直接访问它们,

Object.getOwnPropertyNames(stack) Object.keys(stack)因为JavaScript的类就是这样工作的。


ES6类是基于原型的。尽管基于原型的类能节省内存空间并在扩展方面优于基于函数的类,但这种方式不能声明私有属性(变量)或方法。


下面来看看其他使用JavaScript来实现私有属性的方法。


2.3.1 下划线命名约定


class Stack{
    constructor(){
    this._count = 0;
    this._items = {};
    }
}
复制代码


不过这种方式只是一种约定,并不能保护数据。


2.3.2 用ES6的限定作用域Symbol实现类


ES6新增了一种叫作Symbol的基本类型,它是不可变的,可以用作对象的属性。


const _items = Symbol();
class Stack{
  constructor() {
    this[_items] = [];
  }
}
复制代码


这种方法创建了一个假的私有属性,因为ES6新增的Object.getOwnProperty-Symbol方法能够取到类里面声明的所有Symbol属性。


const stack = new Stack();
stack.push(5);
stack.push(8);
let objectSymbols = Object.getOwnPropertySymbols(stack);
console.log(objectSymbols.length);
console.log(objectSymbols);
console.log(objectSymbols[0]);
stack[objectSymbols[0]].push(1);
console.log(stack);
复制代码
output:
1
[ Symbol() ]
Symbol()
Stack { [Symbol()]: [ 5, 8, 1 ] }
复制代码


我们可以进行任意的数组操作,但我们操作的是栈,不应该出现这种行为。


2.3.3 用ES2015的WeakMap实现类


用一种数据类型看可以确保属性是私有的,这就是WeakMap。WeakMap可以存储键值对,其中键是对象,值可以是任意数据类型。


const items = new WeakMap();
class Stack{
  constructor() {
    items.set(this,[])
  }
  push(element) {
    const s = items.get(this);
    s.push(element)
  }
  pop() {
    const r = items.get(this);
    r.pop()
    return r
  }
}
复制代码


现在items在Stack类里是真正的私有属性,但是采用该方法,代码的可读性不高,而且在拓展该类时无法继承私有属性。


最后


⚽本文介绍了JavaScript数据结构中的数组及栈,相信你对它们又有了更深的理解~

⚾如果这篇文章对你有帮助的话,麻烦点赞收藏哟~

相关文章
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
236 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
69 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
60 4
|
2月前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
2月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
2月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
54 4
|
2月前
|
数据采集 存储 JavaScript
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
本文介绍了如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。Puppeteer作为一个强大的Node.js库,能够模拟真实浏览器访问,支持JavaScript渲染,适合复杂的爬取任务。文章详细讲解了安装Puppeteer、配置代理IP、实现爬虫代码的步骤,并提供了代码示例。此外,还给出了注意事项和优化建议,帮助读者高效地抓取和分析招生数据。
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
|
2月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!