学习JavaScript数据结构与算法(数组)

176 阅读9分钟

数组

数组是最简单的内存数据结构,存储一系列同一种类型的值。虽然在JavaScript里,也可以在数组中保存不同类型的值,但我们还是遵守最佳实践,避免这么做(大多数语言都没这个能力)。

1.创建和初始化数组

// new关键字方式
let daysOfWeek = new Array(); 
daysOfWeek = new Array(7);
daysOfWeek = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');

// 字面量方式
let daysOfWeek = [];
let daysOfWeek = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];

console.log(daysOfWeek.length); // 7

访问元素和迭代数组

// 通过循环迭代数组、打印元素
for(let i = 0; i < daysOfWeek.length; i++) {
    console.log(daysOfWeek[i]);
}

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

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])
}

2.添加元素

假如我们有一个数组numbers,初始化成了0到9。

let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8,  9];

2.1 在数组末尾插入元素

numbers[numbers.length] = 10;

使用push方法

numbers.push(11);
numbers.push(12,13);

2.2 在数组开头插入元素

首先要腾出数组里第一个元素的位置,把所有元素向右移动一位。我们可以循环数组中的元素,从最后一位(长度值就是数组的末尾位置)开始,将对应的前一个元素(i-1)的值赋值给它(i),依次处理,最后把我们想要的值赋值给第一个位置(索引0)上。

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

numbers.insertFirstPosition(-1)

使用unshift方法

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

numbers.unshift(-2);
numbers.unshift(-4,-3);

3.删除元素

3.1 从数组末尾删除元素

numbers.pop();

3.1 从数组开头删除元素

Array.prototype.reIndex = function(myArray) {
    const newArray = [];
    for(let i = 0; i < myArray.length; i++) {
        if(myArray[i] !== undefined) {
            newArray.push(myArray[i]);
        }
    }
    return newArray
}

Array.prototype.removeFirstPosition = function() {
    for(let i = 0; i < this.length; i++) {
        this[i] = this[i+1];
    }
    return this.reIndex(this);
}

numbers = numbers.removeFirstPosition();

使用shift方法

numbers.shift();

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

// 删除从数组索引5开始的3个元素
numbers.splice(5,3); 
// 把数2、3、4插入数组里,放到之前删除元素的位置上
numbers.splice(5,0,2,3,4);
// 从索引5开始删除了3个元素,但也从索引5开始添加了元素2、3、4
numbers.splice(5,3,2,3,4);

5.二维和多维数组

let averageTemp = [];
averageTemp[0] = [1,2,3,4];
averageTemp[1] = [5,6,7,8];

5.1 迭代二维数组的元素

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

5.2 多维数组

假设我们要创建一个3x3x3的矩阵,每一格里包含矩阵的i(行)、j(列)及z深度之和。

const matrix3x3x3 = [];
for(let i = 0; i < 3; i++) {
    matrix3x3x3[i] = [];
    for(let j = 0; j < 3; j++) {
        matrix3x3x3[i][j] = [];
        for(let z = 0; z < 3; z++) {
            matrix3x3x3[i][j][z] = i+j+z;
        }
    }
}

用以下代码输出这个矩阵的内容

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

6.JavaScript的数组方法参考

6.1 数组合并

const zero = 0;
const positiveNumbers = [1,2,3];
const negativeNumbers = [-3,-2,-1];
let numbers = negativeNumbers.concat(zero,positiveNumbers); // [-3,-2,-1,0,1,2,3]

6.2 迭代器函数

假设数组中的值是从1到15;如果数组里的元素可以被2整除(偶数),函数就返回true,否则就返回false。

function isEven(x) {
    // 如果x是2的倍数,就返回true
    console.log(x);
    return x % 2 === 0;
}
// 使用箭头函数改写isEven函数
const isEven = x => x % 2 === 0;

let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

  • 1.用every方法迭代 every方法会迭代数组中的每个元素,直到返回false。
numbers.every(isEven);

数组numbers的第一个元素是1,它不是2的倍数(1是奇数),因此isEven函数返回false,然后every执行结束。

  • 2.用some方法迭代 some方法会迭代数组中的每个元素,直到返回true。
numbers.some(isEven);

numbers数组中的第一个偶数是2(第二个元素)。第一个被迭代的元素是1,isEven会返回false。第二个被迭代的元素是2,isEven返回true--迭代结束。

  • 3.用forEach方法迭代 它和使用for循环的结果相同
numbers.forEach(x => {
    console.log(x % 2 === 0)
})
  • 4.使用map和filter方法
const myMap = numbers.map(isEven); 
// [false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]

const eventNumbers = numbers.filter(isEven);
// [2,4,6,8,10,12,14]
  • 5.使用reduce方法
numbers.reduce((previous,current) => previous + current);
// 120

6.3 ES6和数组的新功能

  • 1.使用for...of迭代
for(const n of numbers) {
    console.log(n % 2 === 0 ? 'even' : 'odd')
}
  • 2.使用@@iterator对象 ES6为Array类增加了一个@@iterator属性,需要通过Symbol.iterator来访问。
let iterator = numbers[Symbol.iterator]();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next().value); // 4
console.log(iterator.next().value); // 5

然后不断调用迭代器的next方法,就能依次得到数组中的值。

可以使用下面代码来输出numbers数组中的15个值。

iterator = numbers[Symbol.iterator]();
for(const n of iterator) {
    console.log(n)
}
  • 3.数组的entries、keys和values方法 entries方法返回包含键值对的@@iterator。
let aEntries = numbers.entries(); // 得到键值对的迭代器
console.log(aEntries.next().value); // [0,1] - 位置0的值为1
console.log(aEntries.next().value); // [1,2] - 位置1的值为2
console.log(aEntries.next().value); // [2,3] - 位置2的值为3

我们也可以使用下面的代码。

aEntries = numbers.entries();
for(const n of aEntries) {
    console.log(n);
}

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

const aKeys = numbers.keys(); // 得到数组索引的迭代器
console.log(aKeys.next()); // {value: 0, done: false}
console.log(aKeys.next()); // {value: 1, done: false}
console.log(aKeys.next()); // {value: 2, done: false}

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

const aValues = numbers.values(); 
console.log(aValues.next()); // {value: 1,done: false}
console.log(aValues.next()); // {value: 2,done: false}
console.log(aValues.next()); // {value: 3,done: false}
  • 4.使用from方法 Array.from方法根据已有的数组创建一个新数组。
// 复制numbers数组
let numbers2 = Array.from(numbers);

// 传入过滤值的函数
let evens = Array.from(numbers,x => (x % 2 == 0))
  • 5.Array.of方法 Array.of方法根据传入的参数创建一个新数组。
let numbers3 = Array.of(1);
let numbers4 = Array.of(1,2,3,4,5,6);
// 等同于
let numbers3 = [1];
let numbers4 = [1,2,3,4,5,6];
// 复制已有数组
let numbersCopy = Array.of(...numbers);
  • 6.使用fill方法 fill方法用静态值填充数组。
let numbersCopy = Array.of(1,2,3,4,5,6);

numbersCopy.fill(0);    // [0,0,0,0,0,0]
numbersCopy.fill(2,1);  // [0,2,2,2,2,2]
numbersCopy.fill(1,3,5); // [0,2,2,1,1,2]

let ones = Array(6).fill(1); // [1,1,1,1,1,1]
  • 7.使用copyWithin方法 copyWithin方法复制数组中的一系列元素到同一数组指定的起始位置。
let copyArray = [1,2,3,4,5,6];
copyArray.copyWithin(0,3); // [4,5,6,4,5,6]

copyArray = [1,2,3,4,5,6];
copyArray.copyWithin(1,3,5); // [1,4,5,4,5,6]

6.4 排序元素

let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
// 反序输出数组numbers
numbers.reverse(); // [15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]
numbers.sort();  // [1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]

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

我们可以传入自己写的比较函数。

numbers.sort((a,b) => a-b)
// 等价于
function compare(a,b) {
    if(a < b) {
        return -1;
    }
    if(a > b) {
        return 1;
    }
    return 0;
}
numbers.sort(compare)
  • 1.自定义排序 我们可以对任何对象类型的数组排序,也可以创建compareFunction来比较元素。例如,对象 Person有名字和年龄属性,我们希望根据年龄排序。
const friends = [
    { name: 'John', age: 30 },
    { name: 'Ana', age: 20 },
    { name: 'Chris', age: 25 }
]

function comparePerson(a,b) {
    if(a.age < b.age) {
        return -1;
    }
    if(a.age > b.age) {
        return 1;
    }
    return 0;
} 

console.log(friends.sort(comparePerson);
  • 2.字符串排序
let names = ['Ana', 'ana', 'john', 'John'];
console.log(names.sort()); // ["Ana", "John", "ana", "john"]

JavaScript在做字符比较的时候,是根据字符对应的ASCII值来比较的。

let names = ['Ana', 'ana', 'john', 'John'];
console.log(names.sort((a,b) => {
    if(a.toLowerCase() < b.toLowerCase()) {
        return -1;
    }
    if(a.toLowerCase() > b.toLowerCase()) {
        return 1;
    }
    return 0;
}))
// ["Ana", "ana", "john", "John"]

6.5 搜索

搜索有两个方法:indexOf方法返回与参数匹配的第一个元素的索引;lastIndexOf返回与参数匹配的最后一个元素的索引。

console.log(numbers.indexOf(10)); // 9
console.log(numbers.indexOf(100)); // -1

numbers.push(10);
console.log(numbers.lastIndexOf(10)); // 15
console.log(numbers.lastIndexOf(100)); // -1
  • 1.ES6--find和findIndex方法
let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
function multipleOf13(element,index,array) {
    return (element % 13 == 0);
}

console.log(numbers.find(multipleOf13)); // 13
console.log(numbers.findIndex(multipleOf13)); // 12

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

  • 2.ES7使用includes方法 如果数组里存在某个元素,includes方法会返回true,否则返回false
console.log(numbers.includes(15)); // true
console.log(numbers.includes(20)); // false

let numbers2 = [7,6,5,4,3,2,1];
console.log(numbers2.includes(4,5)); // false

6.6 输出数组为字符串

let numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
console.log(numbers.toString());
// 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

const numberString = numbers.join('-');
console.log(numberString);
// 1-2-3-4-5-6-7-8-9-10-11-12-13-14-15

7.类型数组

类型数组用于存储单一类型的数据。

let length = 5;
let int16 = new Int16Array(length);

let array16 = [];
array16.length = length;

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

8.TypeScript中的数组

TypeScript会在编译时进行类型检测,来确保只对所有值都属于相同数据类型的数组进行操作。

9.小结

数组是最常用的数据结构,本节涉及了如何声明和初始化数组、给数组赋值以及添加和删除数组元素,还有二维、多维数组以及数组的主要方法。这对编写自己的算法很有用。

10.经典题目

题目来自《剑指offer》面试题3:二维数组中的查找 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的二维数组和一个整数,判断数组中是否含有该整数。

思路:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说,如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。

    var find = function(nums,rows,columns,target) {
        let found = false;
        if(nums !== null && rows > 0 && columns > 0) {
            let row = 0;
            let column = columns -1;
            while(row < rows && column >= 0) {
                console.log(row,column)
                if(nums[row][column] === target) {
                    found = true;
                    break;
                }else if(nums[row][column] > target) {
                    --column;
                }else {
                    ++row;
                }
            }
        }
        console.log(found)
        return found;
    };
    find([[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]],4,4,5)