Technology Math - chaolunner/CloudNotes GitHub Wiki

找出单身狗

有2n+1个数,其中有n个数出现过两次,找出其中只出现一次的数

  • 解析:任何数异或0值不变,任何数与自己异或值为0。 因此一个数两次异或同一个数,值不变。

  • 算法实现:

    public void FindSingle()
    {
        int[] arr = {1, 2, 3, 4, 5, 4, 3, 2, 1};
        int t = 0
        for (int i = 0; i < arr.Length; i++)
        {
            t ^= arr[i];
        }
        Debug.Log("single = " + t);
    }
    

找出两只单身狗

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写出程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

  • 解析:将所有元素异或(即为2个不同元素的异或),找到第一个为1的位,然后根据该位是1还是0将数组分成2组(这两个数字必然在不同组中),最后对这2组所有元素进行异或。

  • 算法实现:

    public void FindTwoDiffNum(int[] arr)
    {
        int t = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            t ^= arr[i];
        }
        int count = 0;
        while ((t & 0x01) == 0)
        {
            t >> 1;
            count++;
        }
        int leftResult = 0, rightResult = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            if (((arr[i] >> count) & 0x01) == 0)
            {
                leftResult ^= arr[i];
            }
            else
            {
                rightResult ^= arr[i];
            }
        }
        Debug.Log(leftResult + "," + rightResult);
    }
    

斐波那契数列

  • 定义:斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........ 这个数列从第3项开始,每一项都等于前两项之和。

  • 算法实现:

    private void Start()
    {
       // 输出第十位的数字
       print(Foo(10));
    }
    
    private int Foo(int a)
    {
        if (a <= 0)
        {
            return 0;
        }
        else if (a > 0 && a <= 2)
        {
            return 1;
        }
        else
        {
            return Foo(a - 1) + Foo(a - 2);
        }
    }
    

二分查找

  • 定义:二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

  • 算法实现:

    static int BinSearch(int[] array, int key)
    {
        int low = 0;
        int high = array.Length;
        while (low <= high)
        {
              int middle = (low + high) / 2;
              if (key == array[middle])
              {
                  return middle;
              }
              else if (key > array[middle])
              {
                  low = middle + 1;
              }
              else if (key < array[middle])
              {
                  high = middle - 1;
              }
        }
        return -1;
    }
    

二叉树的4种遍历

  • 前序遍历:先访问根节点,然后前序遍历左子树,再前序遍历右子树。如下图所示,前序遍历结果为:ABDFECGHI
  • 中序遍历:中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树。如下图所示,中序遍历结果为:DBEFAGHCI
  • 后序遍历:从左到右先叶子后节点的方式遍历访问左右子树,最后访问根节点。如下图所示,后序遍历结果为:DEFBHGICA
  • 层序遍历:从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问。

二分查找树(BST)

Binary Search Tree,BST的中序遍历必定是严格递增的

性质:

  • 若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值。
  • 若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值。
  • 它的左、右子树也分别为二叉查找树。

红黑树(RBT)

Red-Black Tree 自平衡二叉树。

性质:

  • 节点是红色或黑色。
  • 根节点是黑色的。
  • 叶子节点是黑色的。
  • 红色节点的子节点是黑色的(红色节点不能相邻)。
  • 从根节点到任意一个叶子节点的路径上,黑色节点的个数必须相等(这个个数就称为黑高度)。也就是说黑高度必须相等。

k-dimensional树(KD-Tree)

kd-tree(k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的情况。

创建k-d树:

  • 建立根节点。
  • 选取方差最大的坐标轴作为分割轴,数据方差大说明沿该坐标轴方向上数据点分散的比较开。在这个方向上,进行数据分割可以获得最好的平衡。
  • 选择该坐标轴的中位数作为分割点。
  • 遍历数据,小于中位数的传递给根节点的左子树,大于中位数的传递给根节点的右子树。
  • 递归执行步骤2-4,直到所有数据都被建立到KD Tree的节点上为止。

最邻近搜索:

  • 从根节点开始,递归的往下移。往左还是往右的决定方法与插入元素的方法一样(如果输入点在分区面的左边则进入左子节点,在右边则进入右子节点)。
  • 一旦移动到叶节点,将该节点当作当前最佳点
  • 解开递归,并对每个经过的节点运行下列步骤:
    • 如果当前所在点比当前最佳点更靠近输入点,则将其变为当前最佳点。
    • 检查另一边子树有没有更近的点,如果有则从该节点往下找。
  • 当根节点搜索完毕后完成最邻近搜索。

典型的单源最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

给出一个无向图,用Dijkstra算法找出以A为起点的单源最短路径步骤如下:

算法描述:

设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

宽度优先搜索算法(又称广度优先搜索,其英文全称是Breadth First Search)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

广度优先搜索使用队列(queue)来实现,整个过程可以看做一个倒立的树形:

  • 把根节点放到队列的末尾。
  • 每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
  • 找到所要找的元素时结束程序。
  • 如果遍历整个树还没有找到,结束程序。

深度优先搜索属于图算法的一种,是一个针对图和树的遍历算法,英文缩写为DFS即Depth First Search。

其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。

深度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形:

  • 把根节点压入栈中。
  • 每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
  • 找到所要找的元素时结束程序。
  • 如果遍历整个树还没有找到,结束程序。

几何剖分技术

  • 定义:几何剖分技术是一种能将场景中的几何物体通过层次性机制组织,使用时可以快速剔除层次树的整个分支,从而加快索引几何体的过程。四叉树(quad tree)和八叉树(octree)是一种常用的空间剖分方法,它将已知的空间分成四/八个子空间作为节点,每个节点又划分成四/八个子空间,依此递归,直到达到指定深度。

  • 定义:A* (A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。注意——是最有效的直接搜索算法,之后涌现了很多预处理算法(如ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。

  • 过程:

    • 将起点A添加到open列表中(A没有计算花费F是因为当前open列表只有这一个节点)。
    • 检查open列表,选取花费F最小的节点M(检查M如果为终点时则结束寻路,如果open列表为空则寻路失败,直接结束)。
    • 遍历与M相邻的每一节点N:
      • 如果N是障碍物,那么不管它。
      • 如果N在closed列表中,那么不管它。
      • 如果N不在open列表中:添加它然后计算出它的花费F(n) = G + H
      • 如果N已在open列表中:检查F花费是否更小。如果是,更新它的花费F和它的父节点(即设置N的父节点为M)。
    • 将节点M从open列表中移除并加入到closed列表中。
    • 重复2,3,4步。
  • F(n) = G + H 公式说明

    G = 从起点A移动到N的移动代价,使用N的父节点的G值 + 估值函数(N的父节点,N节点)求得。

    H = 从N移动到终点B的估算成本,使用估值函数(N节点,终点B)求得。

    估值函数:max(dx, dy)、sqrt(dx * dx, dy * dy)、min(dx, dy) * 0.414 + max(dx + dy),这些算法都可以。

    保证估值恒小于实际路径长,A*就是成立的。这也是为什么不能直接使用曼哈顿算法 dx + dy 的原因。

    你甚至可以取一个常数0,这样A*就退化为广搜了。

    如下图中,横向和纵向的移动代价为 10,对角线的移动代价为 14。

    之所以使用这些数据,是因为实际的对角移动距离是 2 的平方根,或者是近似的 1.414 倍的横向或纵向移动代价。

    使用 10 和 14 就是为了保证估值恒小于实际路径长并避免浮点计算,优化性能。

  • 算法实现:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    /// <summary>
    /// A*算法寻路
    /// </summary>
    public class AstarFind : MonoBehaviour
    {
        AstarGrid grid;
    
        void Start()
        {
            grid = GetComponent<AstarGrid>();
            FindPath(grid.startPos, grid.endPos);
        }
    
        /// <summary>
        /// 使用A*算法寻路
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        void FindPath(Vector2 start,Vector2 end)
        {
            //和A*算法无关,只是为了显示使用
            int showFindNum=1;
    
            //开启列表
            List<Cell> openLs = new List<Cell>();
            //关闭列表
            List<Cell> closeLs = new List<Cell>();
    
            //起点
            Cell startCell = grid.GetCell(start);
            //终点
            Cell endCell = grid.GetCell(end);
            Debug.LogFormat("寻路开始,start({0}),end({1})!",start,end);
    
            //将起点作为待处理的点放入开启列表,
            openLs.Add(startCell);
    
            //如果开启列表没有待处理点表示寻路失败,此路不通
            while(openLs.Count>0)
            {
                //遍历开启列表,找到消费最小的点作为检查点
                Cell cur = openLs[0];
                for (int i = 0; i < openLs.Count; i++)
                {
                    if(openLs[i].fCost<cur.fCost&&openLs[i].hCost<cur.hCost)
                    {
                        cur = openLs[i];
                    }
                }
                Debug.Log("当前检查点:" + cur.ToString()+" 编号:"+showFindNum+"  open列表节点数量:"+openLs.Count);
                //显示在界面,和A*算法无关
                cur.obj.transform.Find("Num").GetComponent<Text>().text=showFindNum.ToString();
                showFindNum++;
    
                //从开启列表中删除检查点,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。
                openLs.Remove(cur);
                closeLs.Add(cur);
    
                //检查是否找到终点
                if(cur==endCell)
                {
                    Debug.Log("寻路结束!");
                    grid.CreatePath(cur);
                    return;
                }
    
                //根据检查点来找到周围可行走的点
                //1.如果是墙或者在关闭列表中则跳过
                //2.如果点不在开启列表中则添加
                //3.如果点在开启列表中且当前的总花费比之前的总花费小,则更新该点信息
                List<Cell> aroundCells = GetAllAroundCells(cur);
                foreach (var cell in aroundCells)
                {
    
                    if (cell.isWall || closeLs.Contains(cell))
                        continue;
    
                    int cost= cur.gCost+ GetDistanceCost(cell, cur);
    
                    if(cost<cell.gCost||!openLs.Contains(cell))
                    {
                        cell.gCost = cost;
                        cell.hCost = GetDistanceCost(cell,endCell);
                        cell.parent = cur;
                        Debug.Log("cell:" + cell.ToString() + "  parent:" + cur.ToString() + "  " + cell.PrintCost());
                        if(!openLs.Contains(cell))
                        {
                            openLs.Add(cell);
                        }
    
                        //显示在界面,和A*算法无关
                        cell.obj.transform.Find("Cost").GetComponent<Text>().text = cell.fCost.ToString();
                    }
                }
            }
            Debug.Log("寻路失败!");
        }
    
        // 取得周围的节点
        public List<Cell> GetAllAroundCells(Cell cell)
        {
            List<Cell> list = new List<Cell>();
            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    // 如果是自己,则跳过
                    if (i == 0 && j == 0)
                        continue;
                    int x = cell.x + i;
                    int y = cell.y + j;
                    // 判断是否越界,如果没有,加到列表中
                    if (x < grid.gridSize && x >= 0 && y < grid.gridSize && y >= 0)
                    {
                        list.Add(grid.GetCell(new Vector2(x,y)));
                    }
                }
            }
            return list;
        }
    
        /// <summary>
        /// 估价函数,启发函数
        /// A*本身不限制H使用的估计算法,如max(dx,dy)、sqrt(dx*dx,dy*dy)、min(dx,dy)*(0.414)+max(dx+dy)这些都可以(可惜曼哈顿算法dx+dy不在此列),
        /// 记住一点,只要你能保证H值恒小于实际路径长,A*就是成立的。你甚至可以取一个常数0,这样A*就退化为广搜了。
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        int GetDistanceCost(Cell a, Cell b)
        {
            int cntX = Mathf.Abs(a.x - b.x);
            int cntY = Mathf.Abs(a.y - b.y);
            // 判断到底是那个轴相差的距离更远
            if (cntX > cntY)
            {
                return 14 * cntY + 10 * (cntX - cntY);
            }
            else
            {
                return 14 * cntX + 10 * (cntY - cntX);
            }
        }
    }
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    /// <summary>
    /// 单独的格子
    /// </summary>
    public class Cell
    {
        public int x;
        public int y;
        public bool isWall;
        // 与起点的长度
        public int gCost;
        // 与目标点的长度
        public int hCost;
        // 总的路径长度
        public int fCost
        {
            get { return gCost + hCost; }
        }
    
        // 父节点
        public Cell parent=null;
        public GameObject obj;
    
        public Cell(int x,int y,bool isWall,GameObject obj)
        {
            this.x = x;
            this.y = y;
            this.isWall = isWall;
            this.obj = obj;
        }
    
        public override string ToString()
        {
            return "("+x+","+y+")";
        }
    
        public  string PrintCost()
        {
            return "{ fCost:" + fCost + ",hCost:" + hCost + "}";
        }
    }
    
    /// <summary>
    /// 地面的格子构建
    /// 这里只是简单的demo,没有优化也没有调整,只是纯展示A*算法
    /// </summary>
    public class AstarGrid : MonoBehaviour
    {
        public GameObject CellAsset;
        public GameObject PointAsset;
    
        public int gridSize = 10;
        int cellSize = 50;
    
        List<string> wallList = new List<string>() { "5_5", "5_6", "5_7" };
        public List<Cell> cells = new List<Cell>();
    
        public Vector2 startPos = new Vector2 (3,6);
        public Vector2 endPos = new Vector2 (7,6);
    
        void Awake ()
        {
            CreateGrid();
        }
    
        void CreateGrid()
        {
            //创建格子;
            for (int i = 0; i < gridSize; i++)
            {
                for (int j = 0; j < gridSize; j++)
                {
                    GameObject obj= GameObject.Instantiate<GameObject>(CellAsset, transform);
                    RectTransform trans = obj.transform as RectTransform;
                    trans.anchoredPosition = new Vector2(i,j) * cellSize;
                    bool isWall=false;
                    trans.Find("Text").GetComponent<Text>().text = i + "," + j;
                    if(wallList.Contains( i+"_"+j))
                    {
                        isWall = true;
    
                        obj.GetComponent<Image>().color=Color.black;
                    }
                    Cell cell = new Cell(i, j, isWall, obj);
                    cells.Add(cell);
                }
            }
    
            //创建起点
            GameObject obj1 = GameObject.Instantiate<GameObject>(PointAsset, transform);
            RectTransform trans1 = obj1.transform as RectTransform;
            trans1.anchoredPosition = startPos * cellSize;
            obj1.GetComponent<Image>().color = Color.red;
            trans1.localScale = Vector3.one * 1.2f;
            obj1.name = "start";
    
            //创建终点
            GameObject obj2 = GameObject.Instantiate<GameObject>(PointAsset, transform);
            RectTransform trans2 = obj2.transform as RectTransform;
            trans2.anchoredPosition = endPos * cellSize;
            obj2.GetComponent<Image>().color = Color.green;
            trans2.localScale = Vector3.one * 1.2f;
            obj2.name = "end";
        }
    
        public Cell GetCell(Vector2 pos)
        {
            return cells.Find((c) => { return c.x == (int)pos.x && c.y == (int)pos.y; });
        }
    
        public void CreatePath(Cell endPos)
        {
            Cell cell = endPos;
            while(cell!=null)
            {
                GameObject obj1 = GameObject.Instantiate<GameObject>(PointAsset, transform);
                RectTransform trans1 = obj1.transform as RectTransform;
                trans1.anchoredPosition = cellSize*new Vector2(cell.x,cell.y);
                obj1.GetComponent<Image>().color = new Color(Color.blue.r, Color.blue.g, Color.blue.b,0.5f);
                cell = cell.parent;
            }
        }
    }
    
⚠️ **GitHub.com Fallback** ⚠️