day1:快速了解数据结构

137 阅读5分钟

数组

创建

// 直接创建
const arr = [1, 2, 3, 4]

// 利用构造函数创建
const arr = []

// 创建指定长度并有值的数组
const arr = (new Array(7)).fill(1)   //得到一个长度为7,且每个元素都初始化为1的数组

访问与遍历

访问:中括号中指定想要的索引

遍历:

for循环

const len = arr.length 
for(let i=0;i<len;i++) { 
    // 输出数组的元素值,输出当前索引 
    console.log(arr[i], i) 
 }

forEach 方法

arr.forEach((item, index)=> { 
    // 输出数组的元素值,输出当前索引 
    console.log(item, index) 
})

map 方法 和forEach方法区别:map会返回新的数组

const newArr = arr.map((item, index)=> { 
    // 输出数组的元素值,输出当前索引 
    console.log(item, index) 
    // 在当前元素值的基础上加1 
    return item+1
})

从性能上看,for 循环遍历起来是最快的

二维数组

简单来说就是,数组里面套数组,别名:“矩阵”

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

image.png

初始化二维数组

// for 循环
const len = arr.length 
for(let i=0;i<len;i++) { 
    // 将数组的每一个坑位初始化为数组 
    arr[i] = [] 
}

访问

对比一维数组访问,需要两层循环

// 缓存外部数组的长度 
const outerLen = arr.length 
for(let i=0;i<outerLen;i++) { 
    // 缓存内部数组的长度 
    const innerLen = arr[i].length 
    for(let j=0;j<innerLen;j++) { 
        // 输出数组的值,输出数组的索引 
        console.log(arr[i][j],i,j) 
     } 
 }

数组 API

如concat、some、slice、join、sort、pop、push 等等这些

跳转数组方法

栈和队列

栈和队列的实现一般都要依赖于数组,两者的区别在于,它们各自对数组的增删操作有着不一样的限制

数组增删方法

新增元素

① unshift —— 数组头部新增

② push —— 数组尾部新增

③ splice —— 任何位置新添元素

const arr = [1,2] 
// 参数:索引值,删除个数,新添加元素的值
arr.splice(1,0,3) // [1,3,2]

删除元素

① shift —— 删除头部元素 ② pop —— 输出尾部元素 ③ splice —— 两个参数即删除

后进先出(LIFO,Last In First Out)的数据结构

特征:

  • 只允许从尾部添加元素
  • 只允许从尾部取出元素

对应到数组的方法,刚好就是 push 和 pop

栈顶元素 === 数组尾部的元素

// 初始状态,栈空 
const stack = [] 
// 入栈过程 
stack.push('东北大板') 
stack.push('可爱多') 
stack.push('巧乐兹') 
stack.push('冰工厂') 
stack.push('光明奶砖') 
// 出栈过程,栈不为空时才执行 
while(stack.length) { 
    // 单纯访问栈顶元素(不出栈) 
    const top = stack[stack.length-1] 
    console.log('现在取出的冰淇淋是', top) 
    // 将栈顶元素出栈 
    stack.pop() 
} 
// 栈空 
stack // []

队列

和栈是相反的结构

先进先出(FIFO,First In First Out)的数据结构

(好像排队买东西,先到先得)

特征:

  • 只允许从尾部添加元素
  • 只允许从头部移除元素

对应数组的push 和 shift 方法

☆注意

出栈:栈顶元素——数组的最后一个元素

队列出队:队头元素——数组的第一个元素

const queue = [] 
queue.push('小册一姐') 
queue.push('小册二姐') 
queue.push('小册三姐') 
while(queue.length) { 
    // 单纯访问队头元素(不出队) 
    const top = queue[0] 
    console.log(top,'取餐') 
    // 将队头元素出队 
    queue.shift() 
} 
// 队空 
queue // []

链表

链表中,数据单位的名称叫做“结点”,而结点和结点的分布,在内存中可以是离散的 每一个结点的结构都包括了两部分的内容:数据域和指针域

数据域存储的是当前结点所存储的数据值;

指针域则代表下一个结点(后继结点)的引用

结点间关系是通过 next 指针来维系,关系到结点添加、删除操作

{ 
    // 数据域 
    val: 1, 
    // 指针域,指向下一个结点 
    next: { val:2, next: ... } 
}

image.png 注意:想访问链表中的任何一个元素,我们都得从起点结点开始,逐个访问 next,一直访问到目标结点为止。通常会设定一个 head 指针来专门指向链表的开始位置

结点创建

// 需要一个构造函数
function ListNode(val) { 
    this.val = val; 
    this.next = null;
}

// 传值,指定next
const node1 = new ListNode(1) 
node1.next = new ListNode(2)

image.png

添加

尾部新添元素

任意位置新添元素

// 如果目标结点本来不存在,那么记得手动创建 
const node3 = new ListNode(3) 
// 现在node3的 next 指针指向 node2(即原来的 node1.next) 
node3.next = node1.next
// 把node1的 next 指针指向 node3 
node1.next = node3

image.png

删除

☆ 改变指针方向

node1.next = node3.next

image.png

注意:node3 就成为了一个完全不可抵达的结点了,它会被 JS 的垃圾回收器自动回收掉

思路: 删除结点,重点不是定位目标结点,而是定位目标结点的前驱结点

// 利用 node1 可以定位到 node3 
const target = node1.next 
node1.next = target.next

数组和链表

JS 数组和常规数组关系:“JS 数组未必是真正的数组”

// 数组定义:存储在连续的内存空间里
const arr = [1,2,3,4]

// 如果非连续的内存
const arr = ['haha', 1, {a:1}]

相对于数组来说,链表有一个明显的优点,就是添加和删除元素都不需要挪动多余的元素

  • 高效的增删操作 (时间复杂度O(1))
  • 麻烦的访问操作 (时间复杂度O(n))

二叉树

树结构

image.png

  • 树的层次计算规则:根结点所在的那一层记为第一层,其子结点所在的就是第二层,以此类推。
  • 结点和树的高度计算规则:叶子结点高度记为1,每向上一层高度就加1,逐层向上累加至目标结点时,所得到的的值就是目标结点的高度。树中结点的最大高度,称为“树的高度”。
  • 的概念:一个结点开叉出去多少个子树,被记为结点的“度”。比如我们上图中,根结点的“度”就是3。
  • 叶子结点:叶子结点就是度为0的结点(简单理解:最后一层,不再有分支)。

二叉树

由根结点、左子树和右子树组成,且左右子树都是二叉树

image.png

☆ 注意:普通的树并不会区分左子树和右子树,但在二叉树中,左右子树的位置是严格约定、不能交换的。对应到图上来看,也就意味着 B 和 C、D 和 E、F 和 G 是不能互换的。

二叉树结构:

  • 数据域
  • 左侧子结点(左子树根结点)的引用
  • 右侧子结点(右子树根结点)的引用
// 二叉树结点的构造函数 
function TreeNode(val) { 
    this.val = val; 
    // 把左侧子结点和右侧子结点都预置为空
    this.left = this.right = null; 
}

// 新建二叉树
const node = new TreeNode(1)

image.png

以这个结点为根结点,我们可以通过给 left/right 赋值拓展其子树信息,延展出一棵二叉树

image.png