数据结构和算法Javascript描述-列表(1-3章学习笔记)

468 阅读9分钟

版权声明

版权是一件很重要的事情,能写一本书或者翻译一本书需要付出艰辛的劳动。刚刚我已经跟机械工业出版社的人联系,欣喜的是他们说可以发布学习笔记,我才贴出来。 虽然贴出来了,但是如果有任何不妥的地方,本人承诺立即删除文章。也希望各位读者能够尊重别人的劳动果实。

前言

为什么要读《数据结构和算法》,或者把这个问题推广下为什么要不断的读书呢?是工作做不下去了吗?其实不是的,工作已经稳稳的。是热爱吗?也不是的,其实我心里也很不想读。因为一个很直观的感受,读了这些东西感觉也没啥用,真的,这些知识点不会直接对工作产生任何影响,读了感觉也就是在浪费时间。而且读书确实是一件蛮痛苦的事情。那说了这么多,为什么还要读呢?其实是面试刺激的,找工作经历了一段时间的面试,让自己发现了自己原来是个渣渣,尤其是在算法方面。所以现在要重新读书,把那些自己薄弱的地方补起来,让自己变得更强大。也让自己的视野能够更开阔,看到更大的风景。

第1章 javascript编程环境和模型

1.1 JavaScript环境

  • 浏览器环境
  • node环境
  • spiderMonkey(Mozilla开发的类似于node的环境)

1.2 JavaScript编程实践

  • 声明和初始化变量——什么是局部变量、什么是全局变量
  • Javascript中的算术运算和数学库函数——关于js中的加减乘除和Math函数
  • 判断结构——if、else、switch的用法
  • 循环结构——for、while的用法
  • 函数——如何声明一个函数
  • 变量作用域——js中变量的作用域是函数作用域而不是块作用域
  • 递归——js中是用于递归的,如果递归深度超过了js的处理能力,就要寻找其他方法
  • 对象和面向对象编程——js如何定义对象和继承对象的

个人点评:此处讲的知识点实在太浅显了,连入门都算不上,完全可以不用这段。如果大家想了解js的话,还是要去看ES6的相关教程。

第2章 数组

2.1 JavaScript中关于数组的定义

数组:一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字。

2.2 使用数组

2.2.1 创建数组

  1. 声明一个数组变量
  2. 通过Array构造函数
    let arr = [];
    let numberArr = new Array(1,2,3)

2.2.2 读写数组

通过[]操作符来读取数组中的元素

2.2.3 由字符串生成数组

通过split方法把字符串变成数组。

2.2.4 对数组的基本操作

let arr = [1,2,3]
let brr = arr

这一节讲的就是简单的这种赋值,顺便还特别浅显的说了下深复制和浅复制的区别。感兴趣的同学可以自行了解。面试必考!!!

2.3 存取函数

2.3.1 查找元素

这一节主要讲了几个方法的运用,比较浅显。es6中,新增了find、includes等方法可以自行了解

  • indexOf
  • lastIndexOf

2.3.2 数组的字符串表示

主要讲的是如何把数组转化成字符串

  • join
  • toString

2.3.3 由已有数组创建新数组

  • concat
  • splice

2.4可变函数

以下笔记不再写三级标题的序号了,整本书分的节太多了,略显啰嗦。为了省事,小标题序号省略。也对很啰嗦的章节进行归纳。

这一节主要也是讲数组的一些常见方法

  • push
  • pop
  • shift
  • unshift
  • splice
  • reverse
  • sort

2.5 迭代器方法

这一节讲数组遍历的方法

  • forEach
  • every
  • some
  • reduce
  • map
  • filter

2.6 二维和多维数组

这一节讲如何创建和遍历二维数组。

2.7 对象数组

这一节讲的是数组中的元素可以是对象,以及如何获取数组中某个对象。

2.8 对象

这一节讲的是对象的某个属性可以是数组,以及如何访问对象的数组属性

个人点评:这一章讲的也都是地球人都知道的知识,而且讲的也都很浅显。不过这一章所涉及到的知识点倒都是重点,无论面试还是工作,都经常用到。

3 列表

3.1 列表的抽象数据类型定义

列表是一组有序的数据。每个列表的数据项被称为元素。在js中元素可以是任意数据类型。列表可以保存多少元素并没有事先限定,实际使用受程序内存限制。

列表的方法和属性

方法和属性 含义
listSize(属性) 列表的元素个数
pos(属性) 列表的当前位置
length(属性) 返回列表中元素的个数
clear(方法) 清空列表中的所有元素
toString(方法) 返回列表的字符串形式
getElement(方法) 返回当前位置的元素
insert(方法) 在现有的元素后插入新元素
append(方法) 在列表的末尾添加新元素
remove(方法) 从列表中删除元素
front(方法) 将当前列表的位置移动到第一个元素
end(方法) 将当前列表的位置移动到最后一个元素
prev(方法) 将当前位置前移一位 (原书中为后移一位)
next(方法) 将当前位置后移一位 (原书中为前移一位)
hasNext(方法) 判断是否有后一位
hasPrev(方法) 判断是否有前一位
currPos(方法) 返回列表的当前位置
moveTo(方法) 将当前位置移动到指定位置

3.2 实现列表类

原书是使用es5的写法,我改成class写法。具体怎么实现,其实很简单。代码如下,原书中有很多错误和不严谨的地方,我已经订正,注释的的为原书中的代码。

class List {
  constructor() {
    this.listSize = 0
    this.pos = 0
    this.dataStore = []
    this.length = this.listSize
  }
  // 定义一个工具函数find,用于查找列表中是否包含某个元素。我们对NaN也做兼容处理,所以不能使用indexOf方法
  find(element) {
    let dataStore = this.dataStore
    for (let i = 0; i < dataStore.length; i++) {
      if (
        element === dataStore[i] ||
        (Number.isNaN(dataStore[i]) && Number.isNaN(element))
      ) {
        return i
      }
    }
    return -1
  }
  // append方法 给列表添加元素
  append(element) {
    this.dataStore[this.listSize++] = element
  }
  // remove 从列表中删除元素
  remove(element) {
    let index = this.find(element)
    if (index > -1) {
      this.dataStore.splice(index, 1)
        --this.listSize
      return true
    }
    return false
  }
  // length 属性,原书中实现这个属性的时候,写成了方法,我这里订正为属性
  set length(value) {}
  get length() {
    return this.listSize
  }
  // 显示列表中的元素
  toString() {
    return this.dataStore
  }

  // 向现有的元素后面插入新元素
  insert(element, after) {
    let insertPos = this.find(after)
    if (insertPos > -1) {
      this.dataStore.splice(insertPos + 1, 0, element)
        ++this.listSize
      return true
    }
    return false
  }
  // 清空列表中的所有元素,原书中的代码使用delete,而后又修改dataStore的length属性,我个人测试的时候报错,修改如下
  /*
  clear () {
    delete this.dataStore
    this.dataStore.length = 0
    this.listSize = this.pos = 0
  }
  */
  clear() {
    this.dataStore = []
    this.listSize = this.pos = 0
  }
  // 判断给定的值是否在列表中,原书是遍历数组,然后==判断。这个很不妥,一个是存在类型转换,第二个是不能判断NaN,订正如下
  /*
  contains (element) {
    for (var i = 0; i < this.dataStore.length; i++) {
      if (this.dataStore[i] == element) {
        return true
      }
    }
    return false
  }
  */
  contains(element) {
    return this.dataStore.includes(element)
  }
  // 将当前列表的位置移动到第一个元素
  front() {
    this.pos = 0
  }
  // 将当前列表的位置移动到最后一个元素
  end() {
    this.pos = this.listSize - 1
  }
  // 将当前位置前移一位,个人感觉原书这样子很不妥,pos为0的时候,竟然还可以继续往前移动,有点奇怪

  prev() {
    --this.pos
  }

  // 将当前位置后移一位,同样我个人感觉原书中提供的方法不太妥当,prev可以一直减少,为啥next不可以一直增加,所以对程序进行了一点修正
  /*
  next() {
    if(this.pos<this.listSize){
      ++this.pos
    }
  }
  */

  next() {
    ++this.pos
  }
  // 返回列表的当前位置
  currPos() {
    return this.pos
  }
  // 将当前位置移动到指定位置,个人感觉原书中的方法不妥,订正如下
  /*
  moveTo(position){
    this.pos = position
  }
  */
  moveTo(position) {
    if (position < 0 || position > this.listSize - 1) {
      return false
    }
    this.pos = position
    return true
  }
  // 获取当前元素
  getElement() {
    return this.dataStore[this.pos]
  }
  // 判断是否有后一位,我个人感觉这个方法语义上有问题,已经到列表的最后一位了,为什么hasNext还会返回true。但是这个我没改正,因为改正之后发现不能使用迭代器了。所以可能原书的方法是没问题的,是我想的有问题。

  hasNext() {
    return this.pos < this.listSize
  }

  // 判断是否有前一位 原书的方法,个人感觉有问题,为什么明明都是第一位了,hasPrev还是为true。唯一能想到的理由就是为了遍历。所以不订正了,使用原程序
  
  hasPrev(){
    return this.pos >=0
  }
}

对列表进行测试,这个章节在原书中也非常简略,感觉作者有点缺乏诚意。


// 初始化一个列表
let names = new List()
// 调用append方法
names.append('a')
names.append('b')
names.append('c')
console.log(names.length)  // 3
// 调用remove方法
names.remove('b')
console.log(names.length) // 2
// 调用toString方法
console.log(names.toString())  // ["a", "c"]
// 调用insert方法
names.insert('d', 'c')
console.log(names.toString()) // ["a", "c", "d"]

// 调用contains方法
console.log(names.contains('b')) // false
console.log(names.contains('f')) // flase

// 调用end方法,将位置一定到最后一位
names.end()
console.log(names.getElement()) // d
// 调用currPos方法获取位置
console.log(names.currPos()) // 2
// 调用hasNext方法,判断是否还有后一位
console.log(names.hasNext()) // true
// 调用hasPrev方法,判断是否还有前一位
console.log(names.hasPrev()) // true

// 调用front方法,将位置移动到首位
names.front()
console.log(names.getElement()) // a
// 调用currPos方法获取位置
console.log(names.currPos()) // 0
// 调用hasNext方法,判断是否还有后一位
console.log(names.hasNext()) // true
// 调用hasPrev方法,判断是否还有前一位
console.log(names.hasPrev()) // true

// 调用next方法,向后移动一位
console.log(names.getElement()) // a
names.next()
console.log(names.getElement()) // c

// 调用prev方法,向前移动一位
console.log(names.getElement()) // c
names.prev()
console.log(names.getElement()) // a

3.3 使用迭代器访问列表

正序迭代器

for (names.front(); names.hasNext(); names.next()) {
  console.log(names.getElement())
}
// a c d

倒序迭代器


for (names.end(); names.hasPrev(); names.prev()) {
  console.log(names.getElement())
}
// d c a

3.4 一个基于列表的应用

书中给了一个使用列表的案例——用列表存储电影名,然后对列表进行添加、删除、遍历等操作。类似案例可以是图书书目、音乐列表等。因为案例很简单,3.2节对列表进行测试的时候,列表的方法我都已经测过了,所以不再贴出来书上的案例了。

个人点评:列表对于前端工程师来说是一个新的概念。做前端的人,大部分都用不到列表这个数据结构,数组似乎就可以搞定一切。为什么要搞一个列表的感念呢,我个人暂时也不清楚,大概是为了交流方便把。比如数组在不同的语言有不同的表现,但是列表就要求,可能不同的语言都要统一一种实现方式。

另外原书中的代码有很多不严谨或者不正确的地方,也有很多做法跟我想得不一样的地方。也不知道对还是错,现在也评估不出来。欢迎小伙伴交流沟通。