数据结构与算法-数组

前言:

几乎所有的编程语言都原生支持数组类型。因为数组是最简单的内存数据结构。

创建一个数组:

let arr = new Array()

let arr = new Array(5) // 指定长度

let arr = new Array(1,2,3,4,5) // 将数组元素作为参数传给构造函数

let arr = [1,2,3,4,5] // 中括号创建数组

访问元素和迭代数组:

for (let i = 0; i < arr.length; i ++) {console.log(arr[i])}

斐波那契数列

已知斐波那契数列的前三项分别是0,1,1,从第三项开始,每一项都等于前两项之和。求斐波那契数列前20个数。

const arr = new Array(20);
arr[0] = 0;
arr[1] = 1;
arr[2] = 1;
for (let i = 3;i<arr.length;i++) {
    arr[i] = arr[i-1] + arr[i-2]
}

斐波那契数列的经典算法面试题可以看这篇推文->斐波那契数列的算法

添加数组元素

1、在末尾插入元素

在js中,数组是一个可以修改的对象。如果添加元素,它会动态的增长。在其他语言如C和java中,想添加元素需要创建一个全新的数组。不能直接往里面添加元素

arr[arr.length] = 10

arr.push(10) //一个
arr.push(10,11) //两个
2、在开头插入元素
arr.unshift(-1)//一个
arr.unshift(-1,-2)//两个

不用unshift怎么实现在开头插入一个元素

var arr = [3,4,5,6]
for (var i = arr.length;i>0;i--){
    arr[i] = arr[i-1]
}
arr[0] = 2
3、在数组末尾删除元素
arr.pop();

push和pop方法都是用数组模拟栈。

4、在数组开头删除元素
arr.shift();

不用shift怎么实现在开头删除一个元素

Array.prototype.move= function() {
    for(var i =0;i<this.length;i++){
        this[i] = this[i+1]
    }
    return this.removeUndefined()
}
Array.prototype.removeUndefined = function(){
    const newArr = [];
    for (var i = 0; i< this.length;i++) {
        if (this[i] !== undefined) {newArr.push(this[i])}
    }
    return newArr
}
// -----------------------------------------------
var arr = [3,5,6,7,8]
arr.move() // [5,6,7,8]
5、任意位置添加或删除元素
arr.splice(_index,_number,[element]);
// _index:删除或插入的下标
// _number个数
// [element]替换的元素

二维数组(矩阵)

js只支持一维数组,我们可以用数组套数组,实现二维数组(矩阵)或多维数组。

const dataModel = [
    [65,23,45],
    [21,11,77]
]

迭代二维数组的元素

for(let i = 0;i < dataModel.length;i++) {
    for (let j = 0;j < dataModel[i].length;j++) {
        console.log(dataModel[i][j])
    }
}

多维数组

// 三维数组
const arr = new Array();
arr[0] = [];
arr[0][0] = [1];
arr[0][0][0] =1;

创建一个3*3正方形魔方立体矩阵

const arr = [];
const r = 3; // 正方体边长
for (var x =0;x<r;x++) {
    arr[x] = []; //必须层层初始化数组
    for (var y=0;y<r;y++) {
        arr[x][y] = [];
        for (var z=0;z<r;z++) {
            arr[x][y][z] = x+y+z;
        }
    }
}
魔方中最小的值为0+0+0等于0,最大的值为2+2+2=6

循环遍历三维数组

for (var x = 0; x< arr.length; x++) {
    for (var y =0;y<arr[x].length;y++) {
        for (var z = 0;z<arr[x][y].length;z++) {
            console.log(arr[x][y][z])
        }
    }
}

四维数组使用极少,在此不表。

常用数组方法

方法名

对应版本

功能

原数组改变

concat①

-

合并数组,并返回合并之后的数据

x

forEach

ES5

遍历数组所有的项。接收回调参数为传参。回调函数接受三个参数,分别为value,index,self;无返回值

x

every

ES5

同forEach,回调函数对每个数组元素返回布尔值,全部为true,由every返回true

x

some

ES5

同forEach,回调函数对每个数组元素返回布尔值,若有一个为true则返回true

x

map

ES5

同forEach,回调函数对每个数组元素

x

filter

ES5

同forEach,回调函数返回布尔值,返回结果true的元素组成的新数组

x

reduce②

ES5

同forEach,迭代数组的所有项,并构建一个最终值,由reduce返回

x

join③

-

使用分隔符,将数组转为字符串并返回

x

indexOf

ES5

查询并返回数据的索引

x

lastIndexOf

ES5

反向查询并返回数据的索引

x

reverse

-

反转数组,返回结果

sort

-

按照字母顺序对数组排序,支持传入指定的排序方法函数作为参数

valueOf④

-

返回对象

x

toString⑤

-

将数组作为字符串返回

x

①
const arr = [1,2];
arr.concat([3,4,5]); //  [1, 2, 3, 4, 5]
arr; // [1,2]
②
// 作用1 求和
const arr = [1,2,3,4,5]
const sum = arr.reduce((result,item) => {
    console.log(result) // 计算结果
    console.log(item) // 当前元素
    return result+item
}, 0)
// 作用2 求数组项最大值
const arr = [1,2,3,4,5]
const sum = arr.reduce((result,item) => {
    return Math.max(result,item)
})
// 作用3 扁平一个二维数组

var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
var res = arr.reduce((prev, cur) => prev.concat(cur), []);
console.log(res)   // [1,2,8,3,4,9,5,6,10]
③
var a = [1,2,3]
a.join() // '1,2,3'
a.join('-') // '1-2-3'
④
var a = [1,2,3]
a.valueOf() // [1,2,3]
⑤
var a = [1,2,3]
a.toString() // '1,2,3'

ES6和2015+的数组新功能

copyWithin

copyWithin

ES6

浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

改变原数组

copyWithin(target, [start], [end])

target:数组索引,复制序列该位置(原来的元素会被替换掉)。如果是负数,target 将从末尾开始计算。如果 target 大于等于 arr.length,将不会发生拷贝。

start:开始复制元素的起始位置。如果是负数,start 将从末尾开始计算。如果 start 被忽略,copyWithin 将会从 0 开始复制。

end:开始复制元素的结束位置。copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。如果是负数, end 将从末尾开始计算。如果 end 被忽略,copyWithin 方法将会一直复制至数组结尾(默认为 arr.length)。

// 一个位数复制
const arr = [1,2,3,4,5]
arr.copyWithin(0,1,2) // [2, 2, 3, 4, 5]
arr // [2, 2, 3, 4, 5]
const arr = [1,2,3,4,5]
arr.copyWithin(0,0,1) // [1, 2, 3, 4, 5]
const arr = [1,2,3,4,5]
arr.copyWithin(-1,1,2) // [1, 2, 3, 4, 2]
// 两个位数复制
const arr = [1,2,3,4,5]
arr.copyWithin(0,1,3) // [2, 3, 3, 4, 5]
// 全位数复制
const arr = [1,2,3,4,5]
arr.copyWithin() // [1, 2, 3, 4, 5]

includes

includes

ES6

如果数组中存在指定元素,则返回true,否则false

不改变原数组

const a = ['aaa','bbb', 'ccc']
a.includes('aaa') // true
a.includes('ddd') // false

find

find

ES6

根据回调函数找到数组里符合条件的元素并返回

不改变原数组

findIndex

findIndex

ES6

根据回调函数找到数组里符合条件的元素并返回下标

不改变原数组

fill

fill

ES6

使用固定值填充数组

改变原数组

var b = []
b.fill(0) // []
var b = [1,1,1]
b.fill(0) // [0, 0, 0]
var b = new Array(5)
b.fill(0) // [0, 0, 0, 0, 0]

from

from

ES6

将一个类数组对象或者可遍历对象转换成一个真正的数组。

改变原数组

什么是类数组对象?

所谓类数组对象,最基本的要求就是具有length属性的对象,并且可以通过索引来访问或设置里面的元素,但是不能使用数组的方法。

let arrayLike = {
        0: 'tom', 
        1: '65',
        2: '男',
        3: ['jane','john','Mary'],
        'length': 4
 }

Array.from的第一个作用就是将类数组对象转换为真正数组

Array.from(arrayLike) // ['tom','65','男',['jane','john','Mary']]

如果将上面代码中length属性去掉呢?那会得到一个长度为0的空数组

let arrayLike = {
        0: 'tom', 
        1: '65',
        2: '男',
        3: ['jane','john','Mary']
 }
Array.from(arrayLike) // []

这里将代码再改一下,就是具有length属性,但是对象的属性名不再是数字类型的,而是其他字符串型的。

let arrayLike = {
        'name':'a',
        'age': 10,
        'length': 2
 }
Array.from(arrayLike) //[undefined, undefined]

再修改一下,把length和属性数目脱钩呢?length属性决定了转换为数组的长度

let arrayLike = {
        0: 'tom', 
        1: '65',
        'length': 1
 }
Array.from(arrayLike) // ['tom']

Array.from的第二个作用就是可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组

var a = [1,2,3]
Array.from(a) // [1, 2, 3]
Array.from(a,t => t*2) // [2, 4, 6]

Array.from的第三个作用就是将字符串转换为数组。

const b = 'vme50'
Array.from(b) //['v', 'm', 'e', '5', '0']

of

of

ES6

根据传入的参数创建一个新数组

-

const arr1 = Array.of(1);    // [1]
const arr2 = Array.of(1,2,3); // [1,2,3]
const arr3 = Array.of(...[4,5,6]); // [4,5,6]

// 某种情况下,of跟from的效果一样
const copyArr = [1,2,3]
Array.of(copyArr);
Array.from(copyArr);

for...of
const arr = ['a','b','c']
for (const t of arr) {
    console.log(t);
}
// 'a'
// 'b'
// 'c'

迭代器 entries keys values 方法

详见我的另外一篇博文 [戳我]

排序

反向排序
var a = [1,2,3,4]
a.reverse() //[4, 3, 2, 1]

会改变原数组

升序排序
var a = [2,3,1,4]
a.sort() // [1, 2, 3, 4]

会改变原数组

以为这么简单就让你掌握了升序排序?试试这个

var a = [8,10,11,9]
a.sort()

会觉得是8,9,10,11吗?看看结果:

为什么呢?因为sort方法在对数组元素进行排序的时候,元素默认成字符串进行相互比较。

我们可以传入比较函数,实现升序或降序排序.

a.sort((x,y) => {...})

升序排序:

a.sort((x,y) => x-y)

降序排序:

a.sort((x,y) => y-x)

拆解和原理请看我的另外一篇博文:戳我

字符串排序
let names = ['Ana', 'ana', 'john', 'John']
names.sort();
// or
names.sort((x,y) => x>y ? 1 : -1);
// ['Ana','John','ana','john']

排序原理请见【附录:字符串排序-ASCii码大小】

如果我们想忽略大小写进行排序,则:

names.sort((x,y) => x.toLowerCase() > y.toLowerCase() ? 1  : -1); // 升序
// (4) ['ana', 'Ana', 'John', 'john']

如果希望小写字母排在前面。

则通过这样比较:

names.sort((x,y) => x.localeCompare(y)); // 升序
// ['ana', 'Ana', 'john', 'John']

这个方法同样对带有重音符号的字符排序也有效。

关于localeCompare详见附录:localeCompare与中文排序

搜索

indexOf

返回与参数匹配的第一个元素的索引

lastIndexOf

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

ES6 - find

可接受值类型参数或回调函数(当true返回),返回第一个满足的元素。若无则为undefined

ES6 - findIndex

同上,返回第一个满足的元素的下标。若无则为-1

可接受索引值进行范围搜索。

ES7 - includes

接受值类型传参。若存在返回true,不存在返回false

接受索引传参。若有传参则从该索引开始搜索(包括传入索引)

var arr = ['a','b','c']
arr.includes('a') // true
arr.includes('a',1) // false
var arr = ['a','a','b','c']
arr.includes('a',1) //true

数组转字符串

var arr = ['a',2,'c']
arr.toString() //'a,2,c'
arr.join('-') //'a-2-c'
arr.join() //'a,2,c'

类型数组

JS与JAVA C这些语言不同,它是弱语言。所以JS的数组可以储存任何类型的值。

为了规范开发,我们希望数组只能存储同种数据类型的值。这个时候我们就需要创建类型数组:

const myArr = new TypeArray();

TypeArray可以替换为:

类型数组在使用webgl API、进行位操作、处理文件、图象大有用处。

TS中的数组

TS中的数组在编译时会进行类型检测,来确保所有数组元素都属于相同数据类型。

const arr  = [1,2,3,4]

根据类型判断,ts能够理解arr数组的声明和const arr: number[]是一样的。因此当我们在声明数组对象时赋予了初始值,可以不需要显式声明数组变量的类型。

function testFn(a:Person) {}
interface Person {
    name: string,
    age: number
}

上面ts规范了一个Person接口,使得函数testFn只能接受的参数类型为{name:xx,age:xxx}的对象。

const friends = [{name:'小明',age:3},{name:'小李',age:4}]

由于friends数组没有显式的类型声明。我们可以这么写:

const friends: Person[]

附录:字符串排序-ASCii码大小

ASCII码编码顺序从小到大为:空格、数字、大写字母、小写字母,也就是空格符、0-9、A-Z、a-z。

记住几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为 48。

1、数字比字母要小。如 “7”<“F”;

2、数字0比数字9要小,并按0到9顺序递增。如 “3”<“8” ;

3、字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z” ;

4、同个字母的大写字母比小写字母要小32。如“A”<“a” 。

由于js中 Array.prototype.sort方法会将参数转换为字符串进行排序,由此:

var b = [1,2,3]
b.sort() // [1, 2, 3]

因为0-9的ASCCII码升序排序,所以sort默认升序排序

再看:

var a = ['','a','A','1','a1','aa','aA','11','1a','1A','Aa','AA','A1']
a.sort() // ['', '1', '11', '1A', '1a', 'A', 'A1', 'AA', 'Aa', 'a', 'a1', 'aA', 'aa']

排序顺序依然为顺序,字符串的ASCii码排序规则为:

①空排序,长度为0的排在前面

②对X位进行排序,只要X位Ascii码小的都排在前面,无论长度。

③对X位Ascii码相同的字符串根据Y位进行排序,Y位空的排在最前面,Y位小开始升序排序(依然无视长度)

举个例子:

var arrr = ['a1a','a2','a1A1']
undefined
arrr.sort()// ['a1A1', 'a1a', 'a2'] 小写字母ascii值最大

长度更长的字符串排序顺序也是严格遵守按位排序的规则

附录:localeCompare与中文排序

localeCompare的定义用本地特定的顺序来比较两个字符串。也就是说字符串不再严格遵从Ascii的方法。

基于Ascii的升序排序如上图,而我们

const x = 'a';
const y = 'A1';
x.localeCompare(y) // -1

返回值1:说明当前字符串x大于对比字符串y

返回值-1:说明当前字符串x小于对比字符串y

返回值等于0:说明当前字符串x等于对比字符串y

也就是说a小于A1,升序为a -> A1,与基于Ascii的排序恰恰相反(在中文习惯里,小写要排在大写前面,而Ascii里小写的ascii值最大)

const x = '1';
const y = 'a';
x.localeCompare(y) // -1

由此而知基于中文的localeCompare的升序为:数字<小写字母<大学字母

中文排序

const array = ['刘一','陈二','张三','李四' ,'王五' ,'赵六' ,'孙七','周八','吴九','郑十'];

中文排序我们直观的方法是对文字的拼音进行诸位排序,也就是期待结果为:

['陈二', '李四', '刘一', '孙七', '王五', '吴九', '张三', '赵六', '郑十', '周八']
array.sort()
// ['刘一', '吴九', '周八', '孙七', '张三', '李四', '王五', '赵六', '郑十', '陈二']

可以看见sort排序完全不符合中文习惯

这时我们可以:

array.sort((a, b) =>
     a.localeCompare(b)
)
// 如果为了兼容某些浏览器或系统环境
array.sort((a, b) =>
     a.localeCompare(b, 'zh-Hans-CN')
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值