《漫画算法:小灰的算法之旅》

整体还可以,先了解计算机内存物理结构会好一点。了解算法对开阔思路很有帮助。

egg:少年高斯1+50,等差数列求和,高斯算法

基本概念

算法衡量维度

  • 时间复杂度
  • 空间复杂度(space complexity)

多数情况下,时间度更重要一些,我们愿意多分配一些内存空间,也要提升程序的执行速度。

算法应用

  • 运算
  • 查找
  • 排序
  • 最优决策

数据结构组成方式

  • 线性结构,包括数组、链表、栈、队列、哈希表
  • 树,如二叉树、二叉堆
  • 其他由基本数据结构变形而来,用来解决某些特定问题,如跳表、哈希链表、位图等

对数:若a的x次方等于N(a>0,且a不等于1),那么数x叫做以a为底N的对数(logarithm)。记做x=logaN)。其中a叫做对数的底数,N叫做真数。

常见复杂度

  • T(n) = 3n
  • T(n) = 5logn
  • T(n) = 2
  • T(n) = 0.5n2+0.5n

渐进时间复杂度(asymptotic time complexity)用大写O表示,也称为大O表示法

原则

  • 如果运行时间是常数量级,则用常数1表示
  • 只保留时间函数中的最高阶项
  • 如果最高阶存在,则省去最高阶项前面的系数

例子: T(n)=2 写作 T(n)=O(1) T(n)=3n 写作 T(n)=O(n) T(n)=5logn 写作 T(n)=O(logn) T(n)=0.5n2 + 0.5n 写作 T(n)=O(n2)

当n足够大时,4种时间复杂度用时 O(n) < O(logn) < O(n) < O(n2)

常见空间复杂度情形

  • 常量空间 O(1)
  • 线性空间 O(n)
  • 二维空间 O(n2)
  • 递归空间 O(n)

数据结构

数组(array)是有限个相同类型的变量所组成的有序集合,数组中的每一个变量被称为元素。

  • 数组是最为简单、最为常用的数据结构
  • 数组在内存中是顺序存储,因此可以很好实现逻辑上的顺序表
  • 适合读操作多,写操作少的场景

数组的优缺点

  • 数组拥有高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素。二分查找就利用了这点
  • 插入和删除元素会导致大量元素被迫移动,影响效率

链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。

  • 若说数组在内存中是顺序存储,那么链表在内存中存储方式是随机存储
  • 单向链表的每个节点包含两部分
    • 存放数据的变量data
    • 下一个节点的指针next
  • 双向链表
    • 指向前置节点的prev指针
    • 存放数据的data
    • next指针

垃圾回收机制(GC),如java,就是当内存中的变量没有外部引用指向它们,这时就会被自动回收

可以这么理解 数据分物理结构和逻辑结构,数组链表属于物理结构,而基于物理结构实现的机构如,顺序表、栈、队列、树、图属于逻辑结构

栈(stact)是一种线性数据机构,栈中的元素遵循(FILO First in last out)。最早进入的元素放的位置叫做栈底(bottom),最后进入的元素放的位置叫做栈顶(top)。

  • 栈通常用于对”历史“的回溯,如面包屑导航,浏览页面可以轻松返回上一级或者更上一级

队列(queue)也是一种线性数据机构,队列的出口端叫做队头(front),入口段叫做队尾(rear)。

  • 队列常用于对”历史“的回放,如多线程中争夺公平锁的等待队列

栈和队列都是既可以用数组实现,也可以用链表实现。

双端队列,综合了栈和队列的优点,可以从队头的一段入队或出队,也可以从队尾一段入队或出队 优先队列,不遵循先入先出,而是谁优先级高谁先出队,不属于线性结构,是基于二叉堆来实现的。

  • 最大优先队列,最大元素优先出队
  • 最小优先队列

散列表也叫哈希表(hash table),这种数据结构提供了键(key)和值(value)的映射关系,时间复杂度接近于O(1)。

  • 散列表本质上也是一个数组。通过一个”中转站“对数组下标和key进行转换,这个中转站就叫做哈希函数。
  • 哈希冲突无法避免,主要有两种解决方法
    • 开放寻址法
    • 链表法
  • 散列表的读操作就是通过给定的key,在散列表中查找对应的value

树(tree)是n个节点的有限集。

  • 当n=0时,称为空树
  • 有且仅有一个特点的称为根节点
  • 当n>1时,其余节点可分为m个互不相交的有限集,每个集合本身又是一个树,称为根的子树

二叉树(binary tree)是树的一种特殊形式。即每个节点最多有2个子节点

  • 满二叉树(每个分支都是慢的)
  • 完全二叉树(最后一个节点之前的节点都齐全即可)

二叉树主要用于

  • 查找
  • 维持相对顺序

二叉查找树又叫做二叉排序树(binary sort tree)

  • 若左子树不为空,则左子树所有及诶点需小于根节点的值
  • 右子树需大于根节点的值
  • 左右子树也都是二叉查找树

二叉树的自平衡方式

  • 红黑树
  • AVL树
  • 树堆

二叉树的遍历方式(2大类4种)

  • 深度遍历优先
    • 前序遍历(root–>left–>right)(可以用栈,利用栈的回溯特性)
    • 中序遍历(left–>root–>right)
    • 后序遍历(left–>right–>root)
  • 广度遍历优先
    • 层序遍历(可以用队列)

二叉堆。本质上是一种完全二叉树,分为两个类型

  • 最大堆
  • 最小堆

二叉堆基于堆的自我调整,就是把一个不符合堆的完全二叉树,调整成一个堆。本质就是让所有非叶子节点依次下沉。

  • 二叉堆虽然是完全二叉树,但存储方式并不是链式存储,而是顺序存储,即二叉堆的所有节点都存储在数组中
  • 二叉堆是实现堆排序和优先队列的基础

排序算法

常见排序算法(按时间复杂度分大体分3类)

  • 时间复杂度O(n2)
    • 冒泡排序
    • 选择排序
    • 插入排序
    • 希尔排序(性能略优于O(n2),但大于O(logn))
  • 时间复杂度O(nlogn)
    • 快速排序
    • 归并排序
    • 堆排序
  • 时间复杂度线性
    • 计数排序(利用数组下标来确认元素的正确位置)
    • 桶排序
    • 基数排序

稳定排序与不稳定排序:衡量标准为值相同的元素排序后是否保持排序前的顺序

冒泡排序(bubble sort),这是一种基础的交换排序。

  • 就是比较右边元素的大小,如果比右边大就交换位置。
  • 鸡尾酒排序可以看做是冒泡排序的升级排序法(鸡尾酒排序的比较和交换是双向的)

快速排序,快速排序跟冒泡一样也属于交换排序,不同的是,冒泡排序在每一轮只是把1个元素冒泡到数列的一段,而快速排序则在每一轮挑选一个基准元素,并让其他比它大的元素移到数列一遍,比它小的元素移动到数列另一边。这种思路叫做分治法。

绝大多数的递归逻辑,都可以用栈的方法来替代

堆排序和快速排序都是不稳定排序

计数排序局限性

  • 不适合最大和最小值差距太大
  • 数列元素不是整数时,也不适合计数排序

桶排序,每个桶(bucket)代表一个区间范围,里面可以有一个或多个元素

常见算法 20210826-suanfa

Q&A

Q1: 如何判断链表有环

  1. 遍历,每遍历一个新节点就从头检查新节点之前的所有节点和新节点对比,相同则说明这个节点被遍历过两次
  2. Hash,先创建一个以节点ID为key的hashset集合,用来存储遍历过的节点。然后从头节点遍历,每遍历一个新节点,都用新节点和hashset集合中存储的节点比较,如果存在与之相同的接地那ID,说明有环,如果不存在就把这个新节点ID存入hashset中
  3. 指针,两个指针,指针p1每次向后移动1个节点,指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。

Q2:实现一个最小栈

  1. 采用额外一个栈存储最小值,(存储栈中曾经的最小值)

Q3:求最大公约数

  1. 辗转相除法又名欧几里得算法(Euclidean algorithm)。定理:两个正整数a和b(a>b),他们的最大公约数等于a除以b的余数c和b之间的最大公约数。
  2. 更相减损术,出自中国古代《九章算术》。原理:两个正整数a和b(a>b),他们的最大公约数等于a减去b的差值c和b之间的最大公约数。
  3. 辗转相除法结合更相减损术

Q4:判断一个数是否是2的整数次幂

  1. 利用2进制,除了高位有1就不是

Q5:无序数组排序后的最大相邻差

  1. 利用计数排序思想

Q6:用栈实现队列

  1. 两个栈

算法实际应用

Bitmap(位图)

  • 主要用于对大量证书做去重和查询操作

LRU(Least Recently Used)是一种内存管理算法,LRU使用了哈希链表

A星寻路算法(A* search algorithm)是一种用于寻找有效路径的算法。