《漫画算法》读后笔记(四)

460 阅读5分钟

看完了《漫画算法—小灰的算法之旅》第一遍,觉得有些意犹未尽,因此第二次重温时,打算开始做一些笔记,这样可以加深自己对算法知识的理解,也能对没有看过这本书的朋友提供一些帮助。本文为了各位看官方便,由一篇该为四篇,这是第四篇———应用算法

1.位图算法

我们有{0,6,3,4}这数组,在位图中数据结构初始化状态应该就是这样的,首先最大是6,那我们申请l大小为6的数组 通过位图算法处理后,得到的位图是这样的 这种算法的缺点在于,最大值和最小值之间不能相差太大,否则浪费申请数组的空间。这里看看一个关于用户标签的需求,如图:

简而言之,就是使用一个整数而非关系型数据来保存相关数据,具体的工作原理就涉及到位运算了。先看看用位图算法实现标签保存:

/**
 * <p>文件描述:位图算法实现<p>
 * <p>@author 烤鱼<p>
 * <p>@date 2019/11/13 0013 <p>
 * <p>@update 2019/11/13 0013<p>
 * <p>版本号:1<p>
 *
 */
class MyBitmap(var size: Int) {
    private var words: LongArray
    private val num: Long = 1L

    init {
        words = LongArray(getWordIndex(size - 1) + 1)
    }

    // 查询标签
    fun getBit(bitIndex: Int): Boolean {

        if (bitIndex < 0 || bitIndex >= size) {
            throw IndexOutOfBoundsException("超过Bitmap有效范围")
        }
        val wordIndex = getWordIndex(bitIndex)
        return (words[wordIndex] and num.shl(bitIndex)) != 0L

    }

    // 保存标签
    fun setBit(bitIndex: Int) {
        if (bitIndex < 0 || bitIndex >= size) {
            throw IndexOutOfBoundsException("超过Bitmap有效范围")
        }
        val wordIndex = getWordIndex(bitIndex)
        words[wordIndex] = words[wordIndex] or num.shl(bitIndex)
    }

    // 标签位置
    private fun getWordIndex(bitIndex: Int): Int {
        return bitIndex.shr(6)
    }

}

测试代码:

// 测试位图算法
    fun testBitmap() {
        // 存储长度
        val bmp = MyBitmap(128)
        // 存入标签
        bmp.setBit(126)
        // 存入标签
        bmp.setBit(75)
        // 存入标签
        bmp.setBit(64)
        // 查询标签
        println(bmp.getBit(126))
        // 查询标签
        println(bmp.getBit(78))
    }

测试结果:

true
false

看了作者的位图算法运用之后,我第一个反应就是我删除标签又该怎么办呢?测试了一番没有找到答案,于是我去看了看这个方法的应用情景:

  • 判断一个数是否存在某数据中

假如有40亿数据,我们如何快速判断指定一个数是否存在?

申请512M的内存 512M=512*1024*1024B*8=4294967296比特(bit) 这个空间可以装40亿了

一个bit位代表一个int

读入40亿个数,设置相应的bit

读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在

  • 判断整形数组是否重复

这个很简单,通过上面实现的位图算法即可实现:

// 测试位图算法
    @Test
    fun testBitmap() {
        // 存储长度
        val bmp = MyBitmap(128)

        val array = intArrayOf(2,8,56,72,96,120,48,56,72)

        for (i in array){
            if(!bmp.getBit(i)){
                bmp.setBit(i)
            }else{
                println("数组重复:$i")
               
            }
        }
    }
  • 给数组排序 (前面的排序算法有介绍)
什么是Bitmap算法?

2.LRU 算法

LRU全称是Least Recently Used,即最近最久未使用的意思。 LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

该算法在Redis里面已经实现了,而且android.util.LruCache.LruCache以及androidx.collection.LruCache也实现了,我们这里就权当练练手。大家可以直接去看源码。

/**
 * <p>文件描述:LRU 缓存<p>
 * <p>@author 烤鱼<p>
 * <p>@date 2019/11/14 0014 <p>
 * <p>@update 2019/11/14 0014<p>
 * <p>版本号:1<p>
 *
 */
class LRUCache(private val limit:Int) {
    private val hashMap = LinkedHashMap<Int,String>()
    private var count = 0
    fun putValue(key:Int,value:String){
        if(count == limit){
            hashMap.keys.remove(hashMap.keys.first())
            count--
        }
        hashMap[key] = value
        count++
    }

    fun getValue(key: Int):String{
        val value = hashMap[key]?:""
        hashMap.keys.remove(key)
        hashMap[key] = value
        return value
    }

    override fun toString(): String {
        return hashMap.toString()
    }
}

测试代码:

// 测试LRU缓存
    fun testLruCache(){
        val cache = LRUCache(5)
        cache.putValue(1,"11")
        cache.putValue(2,"22")
        cache.putValue(3,"33")
        cache.putValue(4,"44")
        cache.putValue(5,"55")
        cache.putValue(6,"66")
        cache.getValue(3)
        cache.putValue(7,"77")
        cache.getValue(5)
        cache.putValue(8,"88")
        cache.putValue(9,"99")
        println(cache.toString())
    }

测试结果:

{3=33, 7=77, 5=55, 8=88, 9=99}

3.A星寻路算法

假设我们有一个7 * 5 大小的迷宫,上图中绿色的格子是起点,黄色的格子是终点,中间3个蓝色格子是一堵墙。从起点开始,每一步只能向上下左右移动一格,且不能穿越墙壁。如何用最少的步数到达终点呢?

解题思路:

  • OpenList 可到达的格子
  • CloseList 已到达的格子
  • F = G + H
  • G 从起点到到当前格子的成本

  • H 在不考虑障碍的情况下,从当前格子走到目标格子的距离

  • F 从起点到目标格子的总距离

源码:

/**
 * <p>文件描述:A 星寻路算法<p>
 * <p>@author 烤鱼<p>
 * <p>@date 2019/11/14 0014 <p>
 * <p>@update 2019/11/14 0014<p>
 * <p>版本号:1<p>
 *
 */
object Maze {
    class Grid( var x:Int,var y:Int){
        var f = 0
        var g = 0
        var h = 0
         var parent:Grid? = null

        fun initGrid(last:Grid?,end:Grid){
            this.parent = last
            if(last != null){
                this.g = last.g+1
            }else{
                this.g = 1
            }
            this.h = abs((this.x - end.x) + abs(this.y - end.y))
            this.f = this.g + this.h
        }
    }


    val gridList:Array<IntArray> = arrayOf(
        intArrayOf(0,0,0,0,0,0,0),
        intArrayOf(0,0,0,1,0,0,0),
        intArrayOf(0,0,0,1,0,0,0),
        intArrayOf(0,0,0,1,0,0,0),
        intArrayOf(0,0,0,0,0,0,0))

    fun aStarSearch(start:Grid,end:Grid):Grid?{
        val openList = mutableListOf<Grid>()
        val closeList = mutableListOf<Grid>()
        openList.add(start)
        while (openList.size > 0){
            val currentGrid = findMindGrid(openList)
            openList.remove(currentGrid)
            closeList.add(currentGrid)
            val neighbors = findNeighbors(currentGrid,openList, closeList)
            for (i in neighbors){
                if(!openList.contains(i)){
                    i.initGrid(currentGrid,end)
                    openList.add(i)
                }
            }
            for (i in openList){
                if (i.x == end.x && i.y == end.y){
                    return i
                }
            }
        }
        return null
    }

    private fun findMindGrid(openList: MutableList<Grid>): Grid {
        var tmpGrid = openList.first()
        for (grid in openList){
            if(grid.f < tmpGrid.f){
                tmpGrid = grid
            }
        }
        return tmpGrid
    }

    private fun findNeighbors(grid: Grid, openList: MutableList<Grid>, closeList:MutableList<Grid>):MutableList<Grid>{
        val result = mutableListOf<Grid>()
        if(isValidGrid(grid.x,grid.y-1,openList,closeList)){
            result.add(Grid(grid.x,grid.y-1))
        }
        if(isValidGrid(grid.x,grid.y+1,openList,closeList)){
            result.add(Grid(grid.x,grid.y+1))
        }
        if(isValidGrid(grid.x-1,grid.y,openList, closeList)){
            result.add(Grid(grid.x-1,grid.y))
        }
        if(isValidGrid(grid.x+1,grid.y,openList, closeList)){
            result.add(Grid(grid.x+1,grid.y))
        }
        return result
    }
    private fun isValidGrid(x:Int, y:Int, openList: MutableList<Grid>, closeList: MutableList<Grid>):Boolean{
        if(x < 0 || x >= gridList.size || y < 0 || y >= gridList.first().size)return false
        if(gridList[x][y] == 1)return false
        if(containGrid(openList,x, y))return false
        if(containGrid(closeList,x, y))return false
        return true
    }

     fun containGrid(grids:MutableList<Grid>, x:Int, y:Int):Boolean{
        for (n in grids){
            if(n.x == x && n.y == y){
                return true
            }
        }
        return false
    }


}

测试代码:

    fun testMaze(){
        val start = Maze.Grid(2,1)
        val end = Maze.Grid(2,5)
        var resultGrid = Maze.aStarSearch(start, end)
        val path = mutableListOf<Maze.Grid>()
        while (resultGrid != null){
            path.add(Maze.Grid(resultGrid.x,resultGrid.y))
            resultGrid = resultGrid.parent
        }
        for (i in Maze.gridList.indices){
            for (j in Maze.gridList[i].indices){
                if(Maze.containGrid(path,i,j)){
                    print("*, ")
                }else{
                    print("${Maze.gridList[i][j]}, ")
                }
            }
        }
    }

测试结果:

0, 0, *, *, *, 0, 0, 0, 0, *, 1, *, *, 0, 0, *, *, 1, 0, *, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 

4.红包算法

此处的红包算法即微信的红包拆分,我觉得微信红包拆分的时候与金额大小是有关系的,比如100元红包发10个,根据目前的算法来分析有一个99.91,其余9个是1分的情况,但是现实基本上不会有。因此这个红包大小与金额和人数有什么关系我们只能蒙!作者这里提到了两种方法:

  • 二倍均值法

注意: 二倍均值法 除了最后一次,其余的红包最大值都小于人均红包的两倍,即和目前微信红包的情况不一样

  • 线段切割法

线段切割法只是简单的讲了一下具体原理,并没有给出代码。这里给大家看看我的实现:

 // 发送红包
    // count 红包个数 money 红包金额
    fun sendMoney(count:Int,money:Int):MutableList<String>{
        // 元一分
        val calculateMoney = money*100
        val result = mutableListOf<String>()
        val rl_lines = IntArray(count-1)
        var ral = 0
        for (i in 0 until count-1){
            rl_lines[i] = Random.nextInt(calculateMoney-ral-(count-i))+1
            ral += rl_lines[i]
        }
        result.add("红包金额${String.format("%.2f",(calculateMoney.toDouble()-ral)/100)}")
        for (i in rl_lines){
            result.add("红包金额${String.format("%.2f", i.toDouble()/100)}")
        }
        return result
    }

测试代码:

   
    fun testMoney(){
        val result = Money.sendMoney(5,10)
        print(result.toString())
    }

测试结果:(多次测试)

[红包金额0.21, 红包金额7.57, 红包金额1.72, 红包金额0.44, 红包金额0.06]
[红包金额0.73, 红包金额4.37, 红包金额2.82, 红包金额1.22, 红包金额0.86]
[红包金额0.19, 红包金额3.98, 红包金额4.82, 红包金额0.91, 红包金额0.10]
[红包金额0.42, 红包金额0.32, 红包金额4.54, 红包金额4.65, 红包金额0.07]
[红包金额0.26, 红包金额5.46, 红包金额2.53, 红包金额1.56, 红包金额0.19]
[红包金额0.24, 红包金额8.34, 红包金额0.71, 红包金额0.47, 红包金额0.24]
[红包金额2.11, 红包金额1.22, 红包金额3.66, 红包金额2.04, 红包金额0.97]

以上就是红包部分内容,大家也可以直接参考作者的博客如何实现抢红包算法


后记

1----本文由苏灿烤鱼整理编写,转载请注明

2----如果有什么想要交流的,欢迎留言。也可以加微信:Vicent_0310

3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正

传送门