算法系列--基础知识

基础定义

数据结构与算法区别

  • 数据结构:一组数据的存储结构;
  • 算法:操作数据的一组方法;

时间复杂度

  1. 表示算法的执行时间与数据规模之间的增长关系;
  2. 只关注循环执行次数最多的代码,这段代码执行次数 n 的量级;
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积;

常见时间复杂度

  • 多项式量级:O(1), O(logn), O(n), O(nlogn), O(n^k)
  • 非多项式量级:O(2^n)、O(n!)

空间复杂度

  1. 表示算法的存储空间与数据规模之间的增长关系;

常见空间复杂度

  • O(1), O(n),O(n^2)

4种时间复杂度

  • 最好情况时间复杂度:代码在最好的情况下,循环部分执行次数;
  • 最差情况时间复杂度:代码在最坏的情况下,循环部分执行次数;
  • 平均时间复杂度:又称加权平均时间复杂度,各种情况求和概率*执行次数;
  • 均摊时间复杂度:大部分情况下时间复杂度相同,特殊情况不同,所以把特殊情况均摊到大部分情况,即以大部分情况为准;

数据结构

数组

  1. 定义:数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组相同类型的数据;
  2. 随机访问实现,一维数组内存寻址公式:a[i]_address = base_address + i * data_type_size
  3. 二维数组的内存寻址公式: a[i][j] = base_address + (i*n+j)*data_type_size
  4. javascript中Array实际上是一种高级数组结构,将很多数组操作的细节封装起来,如数组插入、移除、越界等判断;
// 重写数组pop方法 (push,shift,unshif同理)
function myPop() {
  let arr = this
  let n = arr.length
  // 注意边界
  if (n < 1) {
    return
  }
  let temp = arr[n-1]
  arr.length = n - 1
  return temp
}
Array.prototype.myPop = myPop
/**
 * 数组splice方法重写
 * @param {Number} index 删除位置下标
 * @param {Number} num 删除数量
 * @param {Array} add 新增数组
 * @returns {Array} 返回删除的数组
 */
function mySplice(index, num, ...add) {
  let arr = this
  let n = arr.length
  if (n < 1) {
    return []
  }
  // 检查参数
  if (index > n - 1 || index + num > n) {
		return []
  }
  let del = []
  let before = []
  let after = []
  for (let i = 0; i < n; i++) {
		if (i < index) {
     before.push(arr[i])
    } else if (i < index + num) {
     del.push(arr[i])
    } else {
     after.push(arr[i])
    }
  }
  // 改变原数组
  let newArr = [...before, ...add, ...after]
  arr.length = newArr.length
  for (let i = index; i < newArr.length; i++) {
    arr[i] = newArr[i]
  }
  return del
}
Array.prototype.mySplice = mySplice

链表

  1. 定义:通过一组零散的内存块串联使用;
  2. 常见类型:单链表、双链表、循环链表;
  3. 特点:链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高;
  4. 链表代码技巧:
    • 理解指针或引用含义;
    • 警惕指针丢失或内存泄漏;(先指向尾部,再连接)
    • 利用哨兵简化实现难度;
    • 留意边界条件处理;(null, 1个,2个,头尾等情况)
    • 举例画图;
// 节点类
class Node {
  constructor (val) {
    this.val = val
    this.next = null
  }
}

// 单链表类
class LinkedList {
  constructor () {
    this.length = 0
    this.head = null
  }

  /**
   * 尾部添加一个元素
   * @param {*} val
   */
   push (val) {
     let node = new Node(val)
     if (this.length === 0) {
       this.head = node
       this.length++
       return
     }
     let current = this.head
     // 遍历链表,找到最后一项
     while (current.next) {
       current = current.next
     }
     current.next = node
     this.length++
   }

   /**
    * 尾部删除一项,返回删除的项
    * @returns {Node} 删除项
    */
    pop () {
      if (this.length === 0) {
        return null
      }

      let current = this.head
      let before = null
      // 遍历链表,找到最后一项
      while (current.next) {
        before = current
        current = current.next
      }
      before.next = null
      this.length--
      return current
    }

  /**
   * 头部添加一个元素
   * @param {*} val
   */
   unshift (val) {
     let node = new Node(val)
     if (this.length === 0) {
       this.head = node
       this.length++
       return this.length
     }

     let current = this.head
     node.next = current
     this.head = node
     this.length++
     return this.length
   }

   /**
    * 删除头部元素
    * @returns {Node} 返回删除的元素
    */
    shift () {
      if (this.length === 0) {
        return null
      }
      let current = this.head
      this.head = current.next
      this.length--
      return current
    }

    /**
     * 指定位置插入元素
     * @param {Number} position 插入位置下标
     * @param {*} val 插入的元素
     * @returns {Number} 链表长度
     */
     insert (position, val) {
       // 越界直接返回
       if (position < 0 || this.position > this.length) {
         return this.length
       }
       let node = new Node(val)
       let current = this.head
       let before = null
       let i = 0
       while (i < position) {
         before = current
         current = current.next
         i++
       }

       node.next = current
       before.next = node
       this.length++
       return this.length
     }

     /**
      * 指定位置删除节点
      * @param {Number} position
      * @returns {Node} 返回删除的节点
      */
      remove (position) {
        if (this.length === 0) {
          return null
        }
        if (position < 0 || position > this.length - 1) {
          return null
        }

        let current = this.head
        if (position === 0) {
          this.head = current.next
          this.length--
          return current
        }

        let before = null
        let i = 0
        // 遍历找到指定下标节点
        while (i < position) {
          before = current
          current = current.next
          i++
        }
        before.next = current.next
        this.length--
        return current
      }
}

  1. 定义:栈是一种操作受限的线性表,只允许在一端插入和删除操作;
  2. 特点:先进后出,后进先出;
  3. 类型:顺序栈,链式栈;
  4. 入栈、出栈的时间复杂度都为O(1);
  5. 常见应用场景:浏览器路由、函数调用栈、表达式求值、括号匹配;
// 浏览器路由--双栈
class router {
  constructor () {
    this.url = ''
    this.go = []
    this.back = []
  }

  // 路由加载
  add (url) {
    this.url = url
    this.go.push(url)
    return this.url
  }

  // 前进按钮
  foward () {
    if (this.back.length === 0) {
      return
    }
    let url = this.back.pop()
    this.go.push(url)
    this.url = url
    return this.url
  }

  // 后退按钮
  backaway () {
    if (this.go.length === 0) {
      return
    }
    const url = this.go.pop()
    this.back.push(url)
    this.url = this.go[this.go.length-1]
    return this.url
  }
}
// 表达式求值 a+b*c
function getValue (exp) {
  // 数字栈
  let numStack = []
  // 运算符号栈
  let calStack = []
  // 运算符
  const o = ['+', '-', '*', '/']

  const n = exp.length
  let str = ''
  let i = 0
  while (i < n) {
    if (o.includes(exp[i])) {
      addCal(Number(str), exp[i])
      str = ''
    } else {
      str += exp[i]
    }
    i++
  }

  // 最后清空栈
  if (calStack.length) {
    let result = cal(numStack[0], Number(str), calStack[0])
    numStack[0] = result
  } else {
    numStack.push(Number(str))
  }

  return numStack[0]

  // 运算符入栈
  function addCal (num, exp) {
    if (calStack.length === 0) {
      numStack.push(num)
      calStack.push(exp)
      return
    }
    
    let expLevel = getLevel(exp)
    let lastLevel = getLevel(calStack[calStack.length - 1])

    if (lastLevel >= expLevel) {
      let result = cal(numStack[0], num, calStack[0])
      numStack.push(result)
      calStack.pop()
      calStack.push(exp)
    } else {
      numStack.push(num)
      calStack.push(exp)
    }
  }

  function getLevel (exp) {
    let level = {
      '+': 0,
      '-': 0,
      '*': 1,
      '/': 1
    }
    return level[exp]
  }

  // 表达式求值
  function cal (num1, num2, exp) {
    // case +,-,*,/
    return eval(`${num1}${exp}${num2}`)
  }

}

队列

  1. 定义:先进先出的一种数据结构;
  2. 只支持两种操作:入队和出队;
  3. 类型:顺序队列,链式队列,循环队列,阻塞队列,并发队列;
  4. 应用场景:线程池,数据库连接池,消息队列
// 循环队列
class CircularQueue {
  constructor (length) {
    this.list = []
    this.length = length
    this.head = 0
    this.tail = 0
  }
  /**
   * 入队
   */
  enqueue (item) {
    // 队列满了
    if ((this.tail + 1) % this.length === this.head) {
      return false
    }
    this.list[this.tail] = item
    this.tail = (this.tail + 1) % this.length
    return true
  }

  /**
   * 出队
   */
  dequeue () {
    // 队列为空
    if (this.tail === this.head) {
      return null
    }
    const result = this.list[this.head]
    this.head = (this.head + 1) % this.length
    return result
  }
}

如何学习

  1. 边学边练,适度刷题;
  2. 多问、多思考、多互动;
  3. 考查优先级:二分法、哈希表、二叉查找树、动态规划、分治法、堆、贪心、最小生成树、字典树、并查集等;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值