【算法】算法入门 2020_01_18

一个人比较缓慢,大多数在于信息不全面、思维碰撞缺乏,在B站上也没有找到相关群体
故此,留一个群号,希望可以共同进步,相互监督,940774008

导论

Algorithm: method for solving a problem. 解决问题的方法
Data structure: method to store information. 存储问题相关信息的方法

最早起源追溯:Euchlid 欧几里得 公元前300
正式确立为一门学科:Church and Turing 丘奇和图灵 in 1930s

Why Study:
To solve problems that could not otherwise be addressed. 解决只有算法能解决的问题
For intellectual stimulation. 启发智力

Great algorithms are the poetry of computation — Francis Sullivan
注:部分算法是由本科生发明的,并且还有很多算法正在等待发现

eg.动态连通性问题

在这里插入图片描述

开发流程

  • 建立问题模型Model
  • 查找解决该问题的基本basic操作(复杂操作=基本操作的线性组合)
  • 时间消耗 与 内存空间 的取舍(不同的倾向的算法有不同的用途)
  • 通过实践验证模型,如果结果不是想要的?想办法找出问题的根源(debug)
  • 提出新的算法
  • 循环以上步骤直至满意为止

算法入门

案例·动态连通性问题

数学模型·总结性质

自反性:P点连接自身
对称性:P连通Q,则Q也联通P
传递性:P联通Q,Q联通R,则P也联通R

= = 是不是突然就想到 Math·集合?

在这里插入图片描述

算法模型·总结基操

Connected components ==> 相互连接的点的最大集合·Set
查询是否联通操作 ==> 检查这两个点是否在同一个component
联通操作 ==> 将包含两个点的component替换为它们的并集union
快速查找 ==> 查找包含p点的component

abstract public calss UF
    UF(int N)						//Number of objects N can be Huge!
    void union(int p,int q)			 //Add connection between p and q
    boolean connected(int p,int q)    //Are p and q connected?
    int find(int p)                   //Component identifier for p (0 to N-1)
    int count()
算法实现·不断优化

惊人的现实: No linear-time algorithm exists. 不存在线性增长的算法,所以就别浪费太多的脑力。差不多就行

①QuickFind

使用数组,如果查询某一个节点所属 集合 直接通过 arr(节点index) 即可,find方法的时间复杂度为O(1)
但其union方法 调用一次的时间复杂度为 O(N),N为节点个数,遍历整个数组,更改所有连接后为同一个集合的节点

②QuickUnion

改进思想:QuickFind 是一种 一维线型思想更改所有受到union方法影响的节点数组项,那么能否只修改 两个 节点数组项(进行连接的两个节点的两个根节点·二维平面树型思想)

③QuickUnionWeight

改进思想:QuickUnion 在Union方法上做了较大改进,但find方法也从 O(1) 变为了 O(N)【逐渐逼近N => 约等于N】,原因为这个树在极端情况下会成为一个瘦高树【竖着的线型】,find时间需要遍历所有节点 => 遍历整个数组, 也就是另一种慢。那么如何将find方法时间复杂度进行改进?—— 约束 瘦高树的形成 => 也就是确保每次都是小树接入大树,而不是大树的根节点改为小树的根节点。通过另一个 weight权值数组 记录当前子树的已连接的节点个数。

④QuickUnionWeightPathCompress

改进思想:进一步优化 瘦高树——为什么不完全展平?将每个节点都指向根节点 => 树层级只为2 => MySQL B+Tree 3层

数据结构·存储信息

Integer Index Array[N] 整数索引数组

Quick-find => 如何表示p点与q点连接——当且仅当 p && q 索引拥有相同的数组项 id (component Id)
Quick-Union =>

数据结构·初始化:将所有索引的数组项都设置为自身,表明:初始状态下各个点都是相互独立的

image-20200117103843598
时空复杂度·改进源泉

1次调用 [ 空间复杂度为 S[N] ]

在这里插入图片描述

M union-find ops on N objects
在这里插入图片描述

lg* N 【迭代对数函数】:使N变为1所需取2的对数的次数,通常来说:lg*N <= 5(N=2^65536)

带权 时间复杂度 Log2 N 证明

假定:包含名为X节点的树 一直为 小树(最差情况)
关系:X节点的深度 与 其所在树的节点总数增长的关系为:Deepth + 1 <=> 包含X的新小树节点总数较原总数增长 2 倍
( 至少甚至更多,因为大树节点总数 >= 小树节点总数 )
推算:若初始包含X的小树 仅为 X,深度为1,且节点总数为 N,那么 2^? = N => ? = Log2 N / lgN 次

测试设计

在进行深层次的代码构建前,应想好,如何设计 输入与输出
确保API的可用性——确保任何一种算法的实现都执行我们期望它进行的操作

在这里插入图片描述

真实代码实现
import scala.collection.mutable
import scala.io.StdIn

/**
 * @Project Algorithms
 * @Author INBreeze
 * @Date 2020/1/17 10:52
 * @Description
 */
object Test {
  def main(args: Array[String]): Unit = {
    val N = StdIn.readInt()
    val uf: UF = new QuickFindUF(N)
    while (StdIn.readInt()!= -1){
      val p = StdIn.readInt()
      val q = StdIn.readInt()
      if(!uf.isConnected(p,q)){
        uf.union(p,q)
        println(p+" "+q)
      }
    }
  }
}

abstract class UF {
  abstract def isConnected(p: Int, q: Int): Boolean

  abstract def union(p: Int, q: Int): Unit

  abstract def find(p: Int): Int

  abstract def count(): Int
}

// 最快查找
class QuickFindUF(N: Int) extends UF {
  private var i = -1
  private val id: Array[Int] = Array.fill(N) {
    i += 1
    i
  }

  def isConnected(p: Int, q: Int): Boolean = id(p) == id(q)

  /**
   * 调用一次的时间复杂度为 n
   * 调用n次的时间复杂度为 n*n = n^2^
   *
   * @param p 被修改的数组项
   * @param q 连接的另一个点
   */
  def union(p: Int, q: Int): Unit = {
    if (id(p) != id(q)) {
      id.map {
        value =>
          if (value == id(p)) id(q)
          else value
      }
    }
  }

  def find(p: Int): Int = id(p)

  def count(): Int = {
    val countSet = mutable.Set[Int]()
    id.foreach(countSet + _)
    countSet.size
  }
}

class QuickUnion(N: Int) extends UF {
  private var i = -1
  private val id: Array[Int] = Array.fill(N) {
    i += 1
    i
  }

  private def root(i: Int): Int = {
    var res = i
    while (id(res) != res) {
      res = id(res)
    }
    res
  }

  def isConnected(p: Int, q: Int): Boolean = root(p) == root(q)

  /**
   * 最快合并,只需要 极少次 访问数组,但缺点是当 N 的量级足够大时,树会很瘦高
   * 一次调用的时间复杂度也将逐渐逼近 N
   *
   * @param p 被修改根节点的点
   * @param q 连接的另一点
   */
  def union(p: Int, q: Int): Unit = id(root(p)) = root(q)

  def find(p: Int): Int = root(p)

  def count(): Int = {
    var res = 0
    id.foreach(
      value =>
        if (value == id(value))
          res += 1
    )
    res
  }
}

class QuickUnionWight(N: Int) extends UF {
  private var i = -1
  private val id: Array[Int] = Array.fill(N) {
    i += 1
    i
  }
  private val weight: Array[Int] = Array.fill(N)(1)

  /**
   * 优化:为什么不将整个树展平? 将每个节点都指向根节点
   * @param i 当前节点索引
   * @return 当前节点的根节点索引
   */
  private def root(i: Int): Int = {
    var res = i
    while (id(res) != res) {
      //将父节点的父节点索引 赋值给 当前节点【将当前节点指向祖父节点】
      id(i)=id(id(i))
      res = id(res)
    }
    res
  }

  def isConnected(p: Int, q: Int): Boolean = root(p) == root(q)

  /**
   * 通过将小树合并至大树的根节点,有效的防止 瘦高巨长树的出现
   * 这样Find的时间就不会出现 时间复杂度逼近 N 的情况 而是变成  log2 N / lgN
   * N=1000 TimeCast=10 N=100w TC=20 N=10ww TC=30
   *
   * @param p 被修改根节点的点
   * @param q 连接的另一点
   */
  def union(p: Int, q: Int): Unit = {
    val i = root(p)
    val j = root(q)
    if (i != j) {
      //左小右大:修改左子树根节点数组项,修改右子树权重数组项[改进④路径压缩]
      if (weight(i) < weight(j)) {
        id(i) = j
        weight(j) += weight(i)
      }
      else {
        id(j) = i
        weight(i) += weight(j)
      }
    }
  }

  def find(p: Int): Int = root(p)

  def count(): Int = {
    var res = 0
    id.foreach(
      value =>
        if (value == id(value))
          res += 1
    )
    res
  }
}
实际应用

渗透问题・Percolation

When N is large, theory guarantees a sharp threshold p*. 概率为多大时,该模型将会渗透
・p > p: almost certainly percolates.
・p < p*: almost certainly does not percolate.

在这里插入图片描述

将渗透问题 转换为 动态联通问题,通过 蒙特卡罗模拟 百万次 得出 近似逼近结论:0.593,

在这里插入图片描述

开发算法的一般步骤

Steps to developing a usable algorithm.
・Model the problem.             //1.数学模型
・Find an algorithm to solve it. //2.算法模型+简单实现
・Fast enough? Fits in memory?   //3.时空复杂度分析
・If not, figure out why.        //4.时空优化
・Find a way to address the problem. //5.创建新算法实现
・Iterate until satisfied.	    //6.循环迭代
The scientific method.
Mathematical analysis.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值