数据结构与算法之数组

206 阅读8分钟

1. 什么是数组

  (1) 数组是一种线性表数据结构。

  线性表:数据排成像一条线一样的结构 数据最多只有前和后两个方向,如数组、链表、队列、栈采用的就是线性表。

  线性表:数非线性表:数据之间并不是简单的前后关系,如二叉树、堆、图采用的就是非线性表。

  (2) 用一组连续的内存空间来存储一组具有相同类型的数据。

2. 为什么在大多数编程语言中,数组要从0开始编码,而不是从1开始呢?

  (1) 从数组存储的内存模型上来看,数组的下标应该是指偏移,如果用a来表示数组的首地址,a[0]就是偏移为0的位置,即首地址,a[k]就是偏移k个type_size的位置,则计算a[k]的内存地址公式为:

a[k]_address = base_address + k * type_size

  (2) 而数组若是从1开始计数,那么a[k]的内存地址计算公式则为:

a[k]_address = base_address + k * type_size

  (3) 比较上述两个公式,从1开始计数的,每次随机访问数组元素都会多一次减法运算,即CPU就会多一次减法指令。而数组作为非常基础的数据结构,通过下标随机访问数组元素又是其非常基础的编程操作,因此其效率的优化就要尽可能做到极致,所以为了减少一次减法操作,数组选择了从0开始编号,而不是从1开始。

  (4) 数组从0开始编码也有一定的历史原因,作为编程语言的鼻祖C语言,其数组从0开始编码,其他语言如java,仿C语言中数组的写法。

3. 适合使用数组的场景

  (1) 如果特别关注性能或希望使用基本类型,可以选用数组。

  (2) 如果数据大小事先已知,且对数据的操作非常简单,可以直接使用数组。

  (3) 当要表示多维数组时,使用数组往往更直观。

  (4) 在业务开发时,直接使用容器更省时省力;在底层开发时,如开发网络框架、性能的优化,使用数组优于容器。

4. 数组与链表的区别

  (1) 数组静态分配内存,链表动态分配内存。

  (2) 数组在内存中连续,链表不连续。

  (3) 数组元素在栈区,链表元素在堆区。

  (4) 数组利用下标定位,时间复杂度是O(1),链表定位元素时间复杂度是O(n)。

  (5) 数组插入或删除元素的时间复杂度是O(n),链表插入或删除元素的时间复杂度是O(1)。

5. js中的数组

  在javascript中,数组是一种特殊的对象。

5.1 数组的创建

  在javascript中,数组的创建有两种方式。

  (1) 字面量方式

const nums = [1, 5, 6, 9]
console.log(nums, nums.length) // [ 1, 5, 6, 9 ] 4

  (2) 构造函数方式

let nums = new Array(1, 5, 6, 9)
console.log(nums, nums.length) // [ 1, 5, 6, 9 ] 4

  在js中,数组的数据可以是不同类型的。

let nums = [1, 'Jane', true, null]
console.log(nums, nums.length) // [ 1, 'Jane', true, null ] 4

  可以通过Array.isArray()判断一个对象是否是数组。

let nums = [1, 5, 6, 9]
console.log(Array.isArray(nums)) // true

5.2 数组的读写

  数组的读写采用循环遍历的方式。

let nums = [1, 5, 6, 9]
for (let i = 0; i < nums.length; i++) {
  console.log(nums[i])
}
// 结果:
// 1
// 5
// 6
// 9

5.3 数组的深拷贝与浅拷贝

  (1) 浅复制:将数组赋给另一个数组,当改变其中一个数组的值时,另一个数组也会随之改变。

let nums = [1, 2, 3, 4, 5, 6]
let newNums = nums
nums[0] = 9
console.log(nums, newNums) // [ 9, 2, 3, 4, 5, 6 ] [ 9, 2, 3, 4, 5, 6 ]

  (2) 深复制:不改变原来的数组是情况下去创建一个新的数组。

let nums = [1, 2, 3, 4, 5, 6]
let newNums = []
for (let i = 0; i < nums.length; i++) {
  newNums[i] = nums[i]
}
nums[0] = 9
console.log(nums, newNums) // [ 9, 2, 3, 4, 5, 6 ] [ 1, 2, 3, 4, 5, 6 ]

5.4 数组的存取函数

  (1) indexOf():返回指定查找的值在目标值中是否存在,若存在,返回该值在数组中的索引,若不存在则返回-1。

let words = ['q', 'w', 'e', 'r', 't']
console.log(words.indexOf('w')) // 1
console.log(words.indexOf('p')) // -1

  (2) join() 与 toString()

  两者都可以将数组转成字符串,但join()还可以以某种形式将数组转成字符串。

let words = ['h', 'e', 'l', 'l', 'o']
console.log(words.join()) // h,e,l,l,o
console.log(words.join('')) // hello
console.log(words.toString()) // h,e,l,l,o

  (3) concat() 与 splice()

  两者都是通过已有数组创建新的数组,但concat()是通过合并多个数组来形成新的数组,而splice()则是截取一个数组的子集作为一个新的数组。

const arr1 = ['hello', 'hi', 'get', 'milk']
const arr2 = ['apple', 'banana', 'pear']
let arr = arr1.concat(arr2)
console.log(arr) // [ 'hello', 'hi', 'get', 'milk', 'apple', 'banana', 'pear' ]
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
let newNums = nums.splice(3, 2) // 3 表示索引,2 表示删除的个数
console.log(newNums, nums) // [ 4, 5 ] [ 1, 2, 3, 6, 7, 8, 9 ]

5.5 数组的可变函数

  可变函数:不去引用数组中的某个元素就能改变数组的内容。

  (1) push()、unshift()、pop() 与 shift()

  push():在数组末尾添加元素

  unshift():在数组开头添加元素

  pop():删除数组末尾的元素

  shift():删除数组的第一个元素

let nums = [7, 23, 15, 61, 39]
nums.push(53)
console.log(nums) // [ 7, 23, 15, 61, 39, 53 ]
nums.unshift(20)
console.log(nums) // [ 20,  7, 23, 15, 61, 39, 53 ]
nums.pop()
console.log(nums) // [ 20, 7, 23, 15, 61, 39 ]
nums.shift()
console.log(nums) // [ 7, 23, 15, 61, 39 ] 

  (2) splice()、sort() 与 reverse()

  splice():不仅可以用来删除元素,还可以用来添加元素

  sort():以ascii码的大小进行排序,因而数字排列不准确,但可以自定义排序规则

  reverse():将数组内的元素翻转

let nums = [51, 29, 43, 17, 21, 8]
nums.splice(3, 0, 65) // 2 表示索引,0 表示不删除元素,65表示往数组中添加的元素
console.log(nums) // [ 51, 29, 43, 65, 17, 21, 8 ]
nums.sort()
console.log(nums) // [ 17, 21, 29, 43, 51, 65, 8 ] 不准确
console.log(nums.reverse()) // [ 8, 65, 51, 43, 29, 21, 17 ]
let words = ['hello', 'hi', 'get', 'milk']
console.log(words.sort()) // [ 'get', 'hello', 'hi', 'milk' ] 按字母a-z排列
// 若使用排序时要想避免数字排序不准确的问题就需要在调用sort()时传入一个函数,该函数可以比较出大小
nums.sort((a, b) => { // 从小到大排序
  return a - b // 两数相减,若结果为正,被减数大于减数,若结果为0,两数相等,若结果为负,被减数小于减数
})
console.log(nums) // [ 8, 17, 21, 29, 43, 51, 65 ]

5.6 迭代器方法

5.6.1 不返回新数组

  (1) forEach():遍历数组

let nums = [1, 2, 3, 4, 5, 6]
nums.forEach(function(item) {
  console.log(item, item * item)
})
// 结果:
// 1 1
// 2 4
// 3 9
// 4 16
// 5 25
// 6 36

  (2) every():返回Boolean类型,对于应用的所有元素,返回true

let nums = [1, 3, 5, 7]
function isOdd(num) {
  console.log(num)
  return num % 2 !== 0
}
// console.log(nums.every(isOdd))
// 结果:
// 1
// 3
// 5
// 7
// true

  (3) some():与every()不同的是只要有一个元素使该函数返回true,那么就返回true

let nums = [1, 3, 5, 7]
function isOdd(num) {
  console.log(num)
  return num % 2 !== 0
}
console.log(nums.some(isOdd))
// 结果:
// 1
// true

  (4) reduce():既可以对数组元素进行求和,也可以将数组元素连接成字符串

let nums = [1, 2, 3, 4, 5]
let sum = nums.reduce((a, b) => {
  return a + b
})
console.log(sum) // 15
let words = ['I ', 'like ', 'china']
let str = words.reduce((item, i) => {
  return item + i
})
console.log(str) // I like china

5.6.2 返回新数组

  (1) map():与forEach一样,但map()返回的是一个新数组

let nums = [52, 16, 87, 63, 29]
let result = nums.map(item => {
  console.log(item)
  return item += 5
})
console.log(result)
// 结果:
// 52
// 16
// 87
// 63
// 29
// [ 57, 21, 92, 68, 34 ]

  (2) filter:与every相似,区别在于当所有元素使该函数为true时,它返回的是一个新的数组,而不是Boolean类型

let nums = []
for (let i = 0; i < 10; i++) {
  nums[i] = Math.floor(Math.random() * 101)
}
let result = nums.filter(item => item >= 60)
console.log(nums, result) // [ 5, 18, 33, 74, 6, 69, 79, 78, 61, 93 ] [ 74, 69, 79, 78, 61, 93 ]

5.7 数组的其他常用方法

  (1) indexOf() 与 lastIndexOf()

  indexOf():返回返回调用String对象中第一次出现的指定值的索引,若不存在则返回-1

  lastIndexOf():返回指定元素在数组中的最后一个的索引,如果不存在则返回-1

let nums = [2, 5, 8, 6, 7]
console.log(nums.indexOf(3)) // -1
console.log(nums.indexOf(6)) // 3
console.log(nums.lastIndexOf(5)) // 1
console.log(nums.lastIndexOf(1)) // -1

  (2) includes():用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

let nums = [1, 2, 3, 4, 5, NaN]
console.log(nums.includes(2)) // true
console.log(nums.includes(6)) // false
console.log(nums.includes(3, 3)) // false
console.log(nums.includes(4, -1)) // false
console.log(nums.includes(NaN)) // true

  (3) fill():用一个固定值填充一个数组中从起始索引到终止索引内的全部元素,但不包括终止索引

let nums = [1, 2, 3, 4, 5];
nums = new Array(nums.length).fill(0);
console.log(nums) // [ 0, 0, 0, 0, 0 ]

  (4) find() 与 findIndex()

  find():返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined

const fruits = [
  {name: 'apples', quantity: 3},
  {name: 'bananas', quantity: 2},
  {name: 'pears', quantity: 5}
];

function findBananas(fruit) { 
  return fruit.name === 'bananas';
}

console.log(fruits.find(findBananas)) // { name: 'bananas', quantity: 2 }

  findIndex():返回数组中满足提供的测试函数的第一个元素的索引,否则返回-1

const grades = [45, 68, 76, 31, 93];

function isPass(grade) {
  return grade >= 60;
}

console.log(grades.findIndex(isPass)) // 1

5.8 二维数组

5.8.1 二维数组的创建

// let nums = [[1, 3, 5], [2, 9, 6]]
// console.log(nums[1][2]) // 6

5.8.2 二维数组的访问

  (1) 按列访问

  内层循环行,内层循环列

let nums = [[1, 3, 5], [2, 9, 6]]
for (let i = 0; i < nums.length; i++) { // 行
  for (let j = 0; j < nums[i].length; j++) { // 列
    console.log(nums[i][j])
  }
}
// 结果:
// 1
// 3
// 5
// 2
// 9
// 6

  (2) 按行访问

  内层循环列,内层循环行

let nums = [[1, 3, 5], [2, 9, 6]]
for (let i = 0; i < nums[0].length; i++) { // 行
  for (let j = 0; j < nums.length; j++) { // 列
    console.log(nums[j][i])
  }
}
// 结果:
// 1
// 2
// 3
// 9
// 5
// 6

5.9 对象数组

5.9.1 对象数组的创建

function Point(x, y) {
  this.x = x
  this.y = y
}

function show(arr) {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i], arr[i].x, arr[i].y)
  }
}

let p1 = new Point(1, 2)
let p2 = new Point(6, 9)
let p3 = new Point(5, 7)
let p4 = new Point(8, 3)

let arr = [p1, p2, p3, p4]
show(arr)
// 结果:
// Point { x: 1, y: 2 } 1 2
// Point { x: 6, y: 9 } 6 9
// Point { x: 5, y: 7 } 5 7
// Point { x: 8, y: 3 } 8 3

5.9.2 对象数组也可以使用数组的方法

let p5 = new Point(9, 4)
// 末尾添加元素
arr.push(p5)
show(arr)
// 结果:
// Point { x: 1, y: 2 } 1 2
// Point { x: 6, y: 9 } 6 9
// Point { x: 5, y: 7 } 5 7
// Point { x: 8, y: 3 } 8 3
// Point { x: 9, y: 4 } 9 4

// 头部添加元素
let p6 = new Point(7, 2)
arr.unshift(p6)
show(arr)
// 结果:
// Point { x: 7, y: 2 } 7 2
// Point { x: 1, y: 2 } 1 2
// Point { x: 6, y: 9 } 6 9
// Point { x: 5, y: 7 } 5 7
// Point { x: 8, y: 3 } 8 3
// Point { x: 9, y: 4 } 9 4

// 尾部删除元素
arr.pop()
show(arr)
// 结果:
// Point { x: 7, y: 2 } 7 2
// Point { x: 1, y: 2 } 1 2
// Point { x: 6, y: 9 } 6 9
// Point { x: 5, y: 7 } 5 7
// Point { x: 8, y: 3 } 8 3

// 头部删除元素
arr.shift()
show(arr)
// 结果:
// Point { x: 1, y: 2 } 1 2
// Point { x: 6, y: 9 } 6 9
// Point { x: 5, y: 7 } 5 7
// Point { x: 8, y: 3 } 8 3

参考来源

  1. www.runoob.com/
  2. mp.weixin.qq.com/s?src=11&ti…

最后

如果喜欢我的文章请 " 点赞 " " 评论 " " 关注 ",大家的支持就是我坚持下去的动力!若是以上内容有任何错误或者不准确的地方,欢迎留言指出,若你有更好的想法,也欢迎一起交流学习!