zoukankan      html  css  js  c++  java
  • 算法笔记(杂)

    如何估算复杂度:电脑1s 1e9 如果时间复杂度2000w以内,则可以很稳的跑出来
    bool数组会省空间 粗略估计空间限制 128Mb=3200w个int=1600w个long long
    递归每次调用会消耗十几个Byte 1
    求阶乘:f(0).f(1).....全部都在执行
    如果在递归中开数组a[m],最终空间复杂度O(m*n),不要在递归中乱开数组

    [l,r]
    rand()%(r-l+1)+l 会生成一个[l,r]内的随机数 rand()可以看作是生成一个很大的数,而%(区间长度)会生成0~区间长度-1的数,再+l就可以

    合并两个数组,时间复杂度为两个数组的总长s

    归并复杂度O(nlogn)每层n一共logn层

    sort函数是左闭右开区间

    平面分割问题的递推公式

    memset(a,0x3f,sizeof()) 0x3f代表一个极大值 memset按字节赋值

    关于(简化版)桶排序:实质上为以数组的编码为输出信息,记录在该编码下的数据个数,并按照各编码的数据个数依次输出 ,为了提高算法效率,在每次循环中使用循环读入,读入变量t,
    并使a[t]++ps:时间复杂度为O(M+N)M、N分别是桶编号的最大值和读入数的个数 (该算法同时可用于去重)
    (不足之处在于需要知道编码的最大值,即
    成绩排序问题中的满分成绩或者最高成绩)


    冒泡排序:n个数,对n-1个数进行归位即可 即第一层循环最值为n-1;第二层为n-i-1 ps:该算法执行效率很低

    插入排序:第一层++ 第二层从已排序的最大位置-- 需要使用temp


    快速排序:说白了就是给基准数据找其正确索引位置的过程.其中包含着二分、分治的思想 以及递归
    本质就是把基准数大的都放在基准数的右边,把比基准数小的放在基准数的左边,这样就找到了该数据在数组中的正确位置.
    平均时间复杂度O (NlogN)。在极端情况下其时间复杂度可能是O(N^2)无法发挥分治的优势及每一轮无法将数据分为两个部分仅仅确定了基准元素的位置
    造成这种结果的原因就是基准元素选择的问题,一般情况下,基准元素选择数组中的第一个元素但有时尽管选择了数组中任意一个元素,还是有可能导致其分治优势无法发挥,因此有了平均时间复杂度。可采用1、挖坑法 (见csdn)2、指针交换法(见啊哈算法)(重要!!!!:在搜索时从基准数的对方向开始搜索)其原因在于:该排序方法要求基准数的左侧均为小于基准数的值,而在i的移动过程中,其保证了经过过的元素均为小于基准数的元素,而j从右向左移动的过程中,又要寻找小于基准数的值,并且同时满足i<j这一基本条件,因此在逻辑判断下,必须从基准元素的对侧开始搜索^^)D;)
    随机快排的额外空间复杂度是O(logn).

    队列(先进先出)(3变量):队列是一种特 殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为“出队”,而在队列 的尾部(tail)进行插入操作,这称为“入队”。当队列中没有元素时(即 head==tail),称为 空队列。广度优先搜索以及队列优化的 Bellman-Ford 短路算法的核心数据结构
    通常将其三个基本元素封装为一个结构体即“一个数组,两个变量”且使用的第一步为向队列中插入元素,其中需要注意的时队尾tail始终要比真实的数据长度多一个距离,便于入队。


    栈(先进后出)(2变量):栈限定为只能在一端进行插入和删除操作。栈的实现 也很简单,只需要一个一维数组和一个指向栈顶的变量 top就可以了。我们通过 top来对栈 进行插入和删除操作。
    eg:用于回文判断

    链表:主要由指针进行操作,需要用到malloc函数进行动态内存空间申请,进行动态的数据存储。需创建需要的节点结构体,通常包含数据存储部分与地址部分(struct嵌套),地址从上结点指向下一节点,整个链表从head表头开始,结束于表尾NULL,在操作过程中,通常需要有两个指针,分别为前、后指针,辅助进行数据存储。

    数组模拟链表:开两个数组,一个负责存放元素,另一个负责存储每个元素其右侧元素的标号(位置)将需要插入的元素放到数组最后方,在通过调整位置数组,来达到插入元素的目的。

    深度优先搜素(dfs):涉及递归过程,理解的关键在于“当下如何做”,即将当前的这一步解决后进入下一步(当然用到递归)基本框架为:判断边界、尝试每种可能、继续下一步、返回。从第一层到最后一层,先纵向搜索,然后再横向搜索,而这正是深度优先搜索的核心思想。大概率用到bool,其核心步骤为状态的回溯,即通过bool判断是否进行下去。注意,这里的回溯并非必要,根据题目内容选择是否回溯。例如纸盒问题需要回溯以便元素下次运行的使用,但在炸弹人问题中并不需要回溯来遍历整个地图,仅需标记,并判断是否走过即可,且下一步与上一步之间并不存在数据上的直接联系。是否回溯要看该元素在接下来的步骤中是否会被再次用到。
    单纯的dfs在解决一些问题时会tle,这里提供优化dfs的几种方法:
    1、存储函数值,即在每一次调用函数后,存储函数的返回值。
    2、记忆化搜索,即添加一个bool数组以及一般数组,在某一步结束后改变bool的值,并存储返回值,在下次需要时直接调用该值,避免了函数的多次调用。
    3、根据题目要求考虑是否要记忆路径和存储路径
    记忆化搜索的基本逻辑与单纯DFS有些差别,单纯DFS关注的是何时停止搜索,而记忆化搜索关注的是何时返回值 即边界条件有两个,一是何时搜到底停止记忆化搜索,这个边界也要给我返回值, 二是满足某种条件直接返回值结束记忆化搜索
    单纯DFS的变化主要体现在参数上+1 -1,但是记忆化搜索中的变化主要体现在函数上 即 dfs(x,y)+1 -1

    记忆化搜索可以避免多算一些不用的状态,而递推往往需要算许多无用的状态,但递推可以有滚动数组等骚操作进行优化
    动态规划中的状态表示如果太简单,会导致后效性、最优子结构不满足等问题,如果太过复杂,会导致时间、内存爆炸

    动态规划常见问题:最值,方法多少、存在性问题


    广度优先搜索(bfs):需要用到队列,与dfs不同的是bfs每个点只入队一次,不需要回溯,需要记录数据,即关键点在于状态的选取以及标记,即记录每个点的状态。一个数据入队后,由该数据进行的搜索完成后,该数据出队即head++。

    种子填充法:用于求一个图中独立子图的个数,需要用到dfsfs在用一个数字对未被标记的元素进行替换例如用-1替换掉原图中不等于0的数字。

    图的遍历:存储图的方法的一种为邻接矩阵存储法,即一个二维数组,一维和二维分别存储两个图点,一个为当前点,一个为顶点,并且[1][2]表示1点与2点直接有连接,即为遍历判断条件。
    另外,在各边权值相同的情况下,可优先考虑bfs(权值就是边的权重,其意义表示链接两个结点的边的大小或者长度等)

    Floyd-Warshall算法(可以解决负权):三重循环,直接可将有向权图的任意两点之间的最短路求出,核心思想为在两点之间找一中间点,判断经过该点时的路径与两点直接相连的距离的大小关系,并在邻接矩阵中进行替换。变量为k、i、j,k为不断优化的中间点,其表示将1-k的点均作为中间点时的情况。但该算法不能解决带有“负权环”的问题(即循环一次,路径减少一次)。

    Dijkstra算法(通过边实现松弛)(不能解决负权):求单源最短路径。其核心思路是不断的找离源点最近的点进行边的松弛。将估计值转变为确定值

    数组实现邻接表存储图:两个数组,实现一个数组的一个元素(代表某一顶点的一条边)指向另一数组,并在该数组中=找到其剩余的边。其存储效果好于邻接矩阵存储图的效果。

    Bellman-Ford 算法:核心代码仅有4行,可以完美的解决负权图的问题,n个点,(最多)松弛n-1次,每次从源点开始。同时该算法可以检测图中是否还有负权回路。
    优化:将dis数组(即1号点到各点的初始距离)另存一个数组,并在每次松弛后检验数组是否发生了变化,若无变化,证明已完成优化,可提前退出。


    Bellman-Ford算法的队列优化:初始时将原点加入队列,每次从队首取出一个顶点并与其相邻的所有顶点进行松弛尝试,若某个相邻顶点松弛成功,且这个相邻顶点不在队列内,则将其加入队列,对当前顶点处理完毕后立即出队,并对下一个新队首进行同样操作。队列为空时停止循环。每个顶点仅入队一次。(关键之处在于只有那些在前一遍松弛中改变了最短路程估计值的顶点,才可能引起他们邻接点最短路程估计值的改变,即只对队列中的点进行处理,降低了时间复杂度。)

    二叉树、堆:堆是一种特殊的完全二叉树,堆有最小堆(即父结点数值均大于子节点数值)、最大堆(即子节点数值均大于父节点)等。二叉树的重要性质:最后一个非叶结是第n/2个结点(n为总结点数)。
    有关堆的排序即如何建立一个最大/小堆。方法是先建立一个堆排序的函数siftup/siftdown在通过循环将堆建成,循环初始值为n/2,即从最后一个非子叶结点开始向上调整,并不断递减。建立最小堆时可以先建立最大堆。。再通过交换与排序转换成最小堆。这种支持插入和寻找最值元素的数据结构称为优先队列。


    动态规划:拆分问题,寻找最佳选择,得到状态与状态之间的联系,再此过程中只保存最优解。经典问题“数字三角形寻找最大路径”。其中几个个最基本的概念:最优子结构、边界、状态转移公式(其实质就是贪心+递归+存储记录结果)。
    那么递归到动规的一般转化方法为:
    如果该递归函数有n个参数,那么就定义一个n维数组,数组下标是递归函数参数的取值范围(也就是数组每一维的大小).数组元素的值就是递归函数的返回值(初始化为一个标志值,表明还未被填充),这样就可以从边界值开始逐步的填充数组,相当于计算递归函数的逆过程(这和前面所说的推导过程应该是相同的).

    尤其注意初始条件与边界 初始条件就是用转移方程算不出来的,但又需要他的定义边界就是数组不要越界

    当算到某一个状态时,保证它需要用到的前面的状态都已经算过了

    1、确定状态 (通过最后一步将问题转化为规模更小的子问题)2、转移方程 3、初始条件与边界情况 4、计算顺序

    有时某题型是选与不选两种状态进行dp


    贪心算法:看到这类问题时,首先要联想到贪心算法:针对一组数据,我们定义了限制值和期望值,希望从中选出几个数据,在满足限制值的情况下,期望值最大。实际上,用贪心算法解决问题的思路,总不能给出最优解。所以一般先需要适当证明其正确性。见Oi Wiki

    0-1背包问题:该问题隶属动态规划问题,每个背包都有取或者不取两种状态,在考虑当前背包时,用当前总空间数减去当前背包所消耗的空间数,用减去的状态与取后的状态进行比较,决定是否取。主要形式是两层循环分别枚举背包大小与考虑的物品的数量即一个二维数组。其状态转
    移方程式为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])淘淘摘苹果一题并非严格的0-1背包问题,因为其w[i]始终为1,所以该问题也可以使用贪心算法,运行效率更高。
    (ps:运行效率:枚举< 剪枝+记忆化搜索< 动态规划 <贪心)其实本质上是搜索的元素不断的减少。

    queue的基本操作 push(a)压入队列 pop()将队首弹出 empty()判断队列是否为空不为空返回0 front()队首元素
    string 的基本操作 substr(a,size)主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度size 是包括从起始符开始的长度。如果没有指定长度_Count或_Count+_Off超出了源字符串的长度,则子字符串将延续到源字符串的结尾 find(子串,从什么母串位置开始找)能找到返回值就是子串开始出现的位置 否则返回-1 length()字符串长度

    广搜题面 xx最少几步
    杨辉三角的应用 在某些系数出现杨辉三角某一行的形式时 想到构建一个杨辉三角 a[i]=a[n-1-i]=(n-i)*pc[i-1]/i; a[0]=1 a[n-1]=1

    堆排序:堆实际上是一棵完全二叉树 完全二叉树包括:满二叉树、最后一层从左到右依次补齐的二叉树
    建立大根堆 小根堆的复杂度是O(logN) 完全二叉树的高度 heapinsert从叶节点添加数据 heapify一个值改变整体向下调整的操作 堆排序的过程就是每次取出根节点与最后一位交换并缩短数组长度然后使用heapify

    队列结构与栈结构之间可以互相转化 即可以用队列结构实现深度优先搜索 但需要两个queue实现stack 用两个stack实现queue

    队列是可以利用 temp 交换的!

    题目的问法以及数据状况决定了最优解法的形式

    可以使用栈判断逆序

    哈希表:<key,value>结构 (map)检查以及取出value的时间复杂度为O(1);

    证明一个链表是否有环:第一种方法用set 第二种方法使用一个快指针一个慢指针 两个指针相遇后 快指针回到头部 速度与慢指针相同 两个指针最终会在第一个入环处相遇

    二叉树的遍历不仅可以用递归实现 也可以用stack实现 遍历的实质是访问时打印的顺序 后继节点 前驱节点是指在中序遍历的情况下一个结点的前 、后结点 一个节点的后继节点是其右子树上最左的结点 如果没有右子树那么通过parent找到父节点的上方找到一个相对于它是找到节点的左节点的一个节点 需要parent指针
    二叉树的序列化与反序列化:如何利用文件记录一棵树使得下次使用时能够重建一棵树
    简而言之 可以把树的序列化看为将树的存储方式转化为一串字符串存入文件中以便下次树的重建 可以先序、中序、后序进行序列化,当当前结点一个字数不存在时,返回一个‘#’的字符串 利用递归完成序列化 另外需要空占位符
    使用哪种遍历方式序列化就要用相应的方式反序列化

    平衡树:一棵树的任意一个结点的左子树与右子树的高度差<=1,则该树平衡递归实现,某节点的高度为返回值+1 传参为bool和高度

    搜索二叉树BST:某棵树的任意一个结点左子树比它小,右子树比他大即在中序遍历中保证升序 通常不出现重复结点

    完全二叉树:堆的结构 如何判断呢?按层遍历 1、一个节点有right没有left 则false 2、如果一个节点不满足1后面遍历到的所有的结点都必须是叶节点


    master公式(也称主方法)是用来利用分治策略来解决问题经常使用的时间复杂度的分析方法分治策略中使用递归来求解问题分为三步走,分别为分解、解决和合并,所以主方法的表现形式:T [n] = aT[n/b] + f (n)(直接记为T [n] = aT[n/b] + T (N^d))a >= 1 and b > 1 是常量,其表示的意义是n表示问题的规模,a表示递归的次数也就是生成的子问题数,b表示每次递归是原来的1/b之一个规模,f(n)表示分解和合并所要花费的时间之和
    ①当d<logb a时,时间复杂度为O(n^(logb a))
    ②当d=logb a时,时间复杂度为O((n^d)*logn)
    ③当d>logb a时,时间复杂度为O(n^d)

    哈希表:哈希表的经典实现结构为数组+链表 ,JVM中实现通过数组+红黑树 如果哈希表的效率变差,就需要整体扩容

    布隆过滤器:准备k个哈希函数 来处理每个URL 当一个URL在数据区内,则其对应的所有的地方就是黑的(类似色域?) 但布隆过滤器数组必须开的很大,空间越大,失误率降低。哈希函数比特类型的数组开多大:m=-(n*lnp)/(ln2)^2 n为样本量 p为预期失误率 确定哈希函数的个数:k=ln2*m/n=0.7*m/n; 真实失误率:(1-e^(-n*k/m))^k;

    一致性哈希:为解决负载均衡并且把数据迁移的代价降得很低 用于集群化、抗压

    并查集:检查两个元素是否是一个集合、把两个元素各自所在的集合合并在一起。

    设计分治递归时从问题的最小规模开始考虑
    对于归并的每一层,两侧都是有序的,因为前面一步已经排过序了

    树状数组:可以解决大部分基于区间上的更新以及求和问题树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟 。复杂度O(logN)
    C[i] = A[i - 2k+1] + A[i - 2k+2] + ... + A[i]; //k为i的二进制中从最低位到高位连续零的长度
    SUM(i) = C[i] + C[i-2^k1] + C[(i - 2^k1) - 2^k2] + .....;表示前i项和
    2^k = x&(-x); x&(-x),当x为0时结果为0;x为奇数时,结果为1;
    x为偶数时,结果为x中2的最大次方的因子。
    而且这个有一个专门的称呼,叫做lowbit,即取2^k。
    树状数组就是一个基于二进制的应用
    基本运用:1、单点更新、单点查询 2、单点更新、区间查询
    进阶运用:3、区间更新、单点查询
    在遇到这种问题时,需要差分
    什么是差分呢?
    差分:a[]={....) b[]={...} a[0]=0 b[i] = a[i]-a[i-1] 这样,对a数组[x,y]区间进行+k修改时,改变的只是b[x] 和b[y+1]
    b[x] = b[x]+k; b[y+1] = b[y+1]-k; 减小了区间修改时的复杂度
    即以b为基础构建树状数组
    4、区间更新、区间查询
    还是利用差分数组b[] 对于数组a[i] 要求i~n的Sum a[i]
    我们需要维护两个树状数组sum1[i]=b[i] sum2=b[i]*(i-1)
    int sum1[50005]; //(b[1] + b[2] + ... + b[n])
    int sum2[50005]; //(1*b[1] + 2*b[2] + ... + n*b[n])

    背包dp中的空间优化 注意更新的顺序;
    从右往左更新 因为dp[i]前的点已经被当前状态的信息代替,但我们更新需要旧信息,所以我们需要从最右端开始更新

    赌徒游戏就是对无后效性的最好诠释
    它的状态转移方程为dp[i][j]i代表哪个面朝上,j代表此时的sum 状态转移需要一个循环来完成

    dp有时不止一维

    dp出现要记录冗余信息时,可以采取记忆化搜索
    求最长/短上升、下降子序列用dpO(N^2)最简单

    pair可以将两个数据打包pair<int,int> pr;
    pr = make_pair(1,2);
    pair 可以用来做返回值(一次返回两个)

    再vector中存放自己定义的结构体时,为了push_back能够放入,自己要定义一个构造函数然后就可以.push_back(student(1,2));要排序的话,还需要自己定义<,并且压入弹出元素时,仅在尾部处理,即push_back pop_back 因为处理尾部的话,复杂度为O(1),而对头部的处理复杂度为O(N);

    deque 双向队列 支持push_back push_front 复杂度O(1) 支持下标访问但复杂度为O(N) 用于单调队列
    优先队列默认大根堆

    set .count()询问是否有该元素 在别的容器中代表元素个数

    并查集是用来维护不相交集合的数据结构,支持两个操作:
    ank(x,y) 查询x,y是否在一个集合
    union(x,y)将x,y所在集合合并
    带路径压缩的并查集,单次操作的平均复杂度接近O(1);
    路径压缩优化即将所有节点的父节点都设为其祖先

    构造贪心:每步最优或者按序选择(先排序)
    二分:在一段整数区间中,每个整数可能满足或者不满足条件,且满足单调性(先是一段不满足条件的数,后是一段满足条件的数)我们就可以通过二分来快速确定第一个满足条件的数,这种做法称为~二分答案
    必要时自己添加边界 满足单调性,可以二分答案 二分答案后还要处理
    最小值最大,最大值最小 立刻想到二分 也可以用lower upper典型题:洛谷P1182

    最优化问题不好做就尝试性二分将其转换为一个判定性问题

    保留两位或者较小位的数时,可以倍增后再算 达到整数运算的目的

    差分前缀和:di=ai-a(i-1)

    两个指针维护的技巧

    建树需要先存图,再递归建树
    链式前向星存图+建树:需要next存储同一起始结点的下一个边 head存储是第几个点 to存储当前点的对点
    deep存储深度 f存储树的父亲节点 vis用于判断该点是否在当前点的父亲节点中出现过

  • 相关阅读:
    Win7 on VirtualBox 看不到 usb device
    framebuffer line_length 參數
    booting logo & booting animation
    charing animation
    vim
    [筆記] Ubuntu Linux 使用 apt-get 指令移除軟體並清理遺留的垃圾
    git 指令
    adb devices 偵測不到 手機
    apt-get 相關設定
    Ubuntu 14 設定 遠端連線,讓別台電腦可以連線進來
  • 原文地址:https://www.cnblogs.com/delta-cnc/p/12298977.html
Copyright © 2011-2022 走看看