目录:
-
基础
- 算法基础
- 数据结构
数据结构是由某一数据对象及该对象中所有数据元素之间的关系组成。
数据在计算中的存储方式,也称为数据的物理结构。 - 基本概念
- 数据
- 数据对象
- 数据元素
- 4种基本的逻辑结构
- 线性结构
- 集合结构
- 树形结构
- 图状结构
数据逻辑结构可分为线性结构和非线性结构两类。集合结构、树形结构、图状结构,统称为非线性结构。
- 数据存储主要有4种基本方法
- 顺序存储方法
最基本的存储方式,一般借助数据来实现。比如:ArrayList - 链式存储方法
比如:LinkedList - 索引存储方法
比如:B树、B+树 - 散列存储方法
比如:散列
- 顺序存储方法
- 算法和算法分析
- 算法
- 算法是为某一个特定问题而制定的求解步骤的一种描述,它是有限的指令序列,其中每一条指令表示一个或多个操作。一个算法应当具有下列重要特性:
- 输入
- 输出
- 确定性
- 有穷性
- 有效性
- 比如:查找、删除、插入、修改、求长度、定位、排序等操作
- 算法是为某一个特定问题而制定的求解步骤的一种描述,它是有限的指令序列,其中每一条指令表示一个或多个操作。一个算法应当具有下列重要特性:
- 算法分析和算法复杂度
- 时间复杂度
- 空间复杂度
- 算法
- 数据结构
- 线性表
- 线性表的定义和基本运算
- 线性表的定义
线性表是一种线性结构。线性结构的特点是数据元素之间是一种线性关系,数据元素“一个接一个地排列”。 - 特点:在数据元素的非空有限集中
- 存在唯一的一个被称为“第一个”的数据元素
- 存在唯一的一个被称为“最后一个”的数据元素
- 除第一个之外,集合中的每个元素均只有一个前驱。
- 除最后一个之外,集合中的每个元素均只有一个后继。
- 线性表的运算
求长度、插入元素、删除元素、查找元素、修改元素
- 线性表的定义
- 线性表的存储结构
- 顺序存储结构
- 实现:在Java中可以使用数组实现
- 算法:初始化、求表长、插入元素、删除元素、查找元素、修改元素
- 链式存储结构
- 单链表(每个节点包含两个部分:存放数据信息的称为数据域,存放其后继地址的称为指针域,最后一个节点的指针为空指针)
- 实现:在单链表的头部插入节点、在单链表的尾部插入节点
- 算法:初始化、求表长、查找元素、插入元素、删除元素、修改元素
- 循环链表(如果单链表的最后一个节点的指针指向链表的头指针)
- 双链表(每个节点包含三个部分:存放其前驱地址的指针域,存放数据信息的称为数据域,存放其后继地址的称为指针域)
- 静态链表
- 单链表(每个节点包含两个部分:存放数据信息的称为数据域,存放其后继地址的称为指针域,最后一个节点的指针为空指针)
- 总结
顺序表顺序插入快、查询、修改效率高,链表表中插入快、删除效率高
- 顺序存储结构
- 实例
- ArryList(顺序储存)
- LinkedList(链式存储中的双链表)
- 线性表的定义和基本运算
- 栈与队列
- 栈(Stack)
- 栈模型
- 栈是限定仅在表的一端(一般指表尾部)进行插入和删除操操作的线性表。
- 允许插入、删除的这一端称为栈顶(Top),另一端称为栈底(Bottom)。栈的插入操作通常称为入栈或进栈(push),而栈的删除操作则称为出栈或退栈(pop)。
- 当表中没有元素时称为空栈。
- 栈实现
栈是一种特殊的线性表,因此栈也可以采用两种存储结构:顺序存储结构和链式存储结构。- 顺序栈(在Java中可以使用ArrayList实现)
- 初始化
- 入栈:在表的尾部插入
- 出栈:删除表的最后一个元素
- 链表栈(在Java中可以使用LinkedList实现)
- 入栈:使用LinkedList的add(E e)方法
- 出栈:使用LinkedList的removeLast()
- 选用顺序栈作为栈,因为两者插入删除效率相差不大,但是链表栈所占用的内存大
- 顺序栈(在Java中可以使用ArrayList实现)
- 栈的应用
- Java虚拟机栈中的栈帧
- 二叉树的先序、中序、后序的非递归遍历。
- 栈模型
- 队列(Queue)
- 队列模型
- 队列是限定在表的一端进行插入,在表的另一端进行删除操作的线性表。
- 允许插入的一端称为队尾(rear),允许删除的一端称为队头(front)。队列的插入操作通常称为入队(offer),而队列的删除操作则称为出队(poll)。
- 当队列中没有元素的时候称为空队列。
- 队列实现
与线性表、栈类似,队列也有顺序存储和链式存储两种存储方式。- 顺序队列(在Java中可以使用ArrayList实现)
- 初始化
- 入队:在表的尾部插入
- 出队:删除表的第一个元素
- 链式队列(在Java中可以使用LinkedList实现,表尾插入、表头删除)
- 初始化
- 入队:使用LinkedList的offerLast(E e)方法
- 出队:使用LinkedList的pollFirst() 方法
- 使用链式队列作为队列,因为链表队列比顺序队列插入删除效率高。
- 顺序队列(在Java中可以使用ArrayList实现)
- 队列的应用
- 消息队列
- 程序设计中的二叉树的层次遍历、图的广度优先遍历、缓冲区的循环使用
- 操作系统中的作业管理、进程调度、I/0请求处理
- 队列模型
- 栈(Stack)
- 数组和串
- 数组的顺序存储
- 特殊矩阵的压缩存储 特殊矩阵的压缩存储_Curz酥的博客-CSDN博客
- 对称矩阵
- 三角矩阵
- 三对角矩阵
- 稀疏矩阵
- 广义表 数据结构与算法-线性结构:串、数组和广义表_广义表是线性结构吗_不要当程序员的博客-CSDN博客
- 串
- 树
- 树的基本概念
- 树的定义
树是由n(n>=0)个节点组成的有限集合。
空树、根节点、子树
树是一种非线性数据结构。 - 特点
- 它的每一个节点可以有零个或多个后继,除根节点外的所有节点有且仅有一个前驱;
- 这些节点按分支关系组织起来,清晰地反应了数据元素之间的层次关系,数据元素之间存在一对多的关系。
- 基本术语
结点、树叶(叶子)、非终端结点、孩子结点、父结点、祖先结点、子孙结点、兄弟结点、
度:一个结点拥有子树的个数,称为该结点的度
树的度:树内各结点的度的最大值。
深度:根结点到该结点的最大层数称为深度。如空树的深度为0,只有一个根结点的深度为1;
高度:叶子节点到该结点的最大层数称为高度。如空树的高度为-1,只有一个根结点的高度为1;
树的深度(高度):树中结点所处的最大层数称为树的高度。如空树的高度为0,只有一个根结点的树高度为1。
有序树:若有一颗树中所有子树从左到右的排序是由顺序的,不能颠倒次序,称该树为有序树。
在有序树中,最左边的子树的根称为第一个孩子,最右下的称为最后一个孩子。比如:二叉树
无序树
森林 - 树的基本算法
构造一个树、清空树、获取给定结点的第i个孩子、获取给定结点的双亲、遍历树
- 树的定义
- 二叉树
- 二叉树
- 概念
- 二叉树是n(n>=0)个结点的有限集,它或为空,或有一个根节点与两个不想交的、被分别称为左子树和右子树的二叉树组成。
- 二叉树是有序的,即若将其左、右子树颠倒,就成为了另一棵不同的二叉树。
- 二叉树中每个结点最多只能有两颗子树,且有左右之分。
- 性质
- 在非空二叉树中,第i层的结点总数不超过
, i>=1;
- 深度为h的二叉树最多有
个结点(h>=1),最少有h个结点;
- 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
- 在非空二叉树中,第i层的结点总数不超过
- 二叉树的存储
- 顺序存储:在Java中可以使用数组或者ArrayList存储。(层次遍历)
- 链式存储:链表中的每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来给出左孩子和右孩子所在的链结点的存储地址。当左孩子或右孩子不存在时,相应指针域为空(用null表示)。
- 二叉树的遍历
- 递归实现
- 先序遍历
- 中序遍历
- 后续遍历
- 非递归实现
- 先序遍历(利用栈的特性来实现)
- 中序遍历(利用栈的特性来实现)
- 后序遍历(利用栈的特性来实现)
- 层次遍历(利用队列实现)
- 递归实现
- 概念
- 满二叉树
- 概念:一颗深度为h且有
个结点的二叉树称为满二叉树。或者说,在一颗二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有的叶子节点都在同一层,这样的二叉树称作满二叉树。
- 概念:一颗深度为h且有
- 完全二叉树
- 概念:一颗深度为h的有n个结点的二叉树,对树中的结点按从上至下,从左至右的顺序进行编号,如果编号为i(0<=i<=n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这颗二叉树为完全二叉树。
- 特点:叶子节点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
- 性质
- 具有n个结点的完全二叉树的深度为
(注:[ ]表示向下取整)
- 有N个结点的完全二叉树各结点如果用顺序方式存储(层次遍历),则结点之间有如下关系:
- 若对二叉树的根节点从1开始编号,则 相应的i结点,如果i>1,则其父结点的编号为i/2;
如果2*i<=N,则其左孩子(即左子树的根结点)的编号为2*i;若2*i>N,则无左孩子;
如果2*i+1<=N,则其右孩子的结点编号为2*i+1;若2*i+i>N,则无右孩子。 - 若对二叉树的根节点从0开始编号,则相应的i号结点的父结点的编号为(i-1)/2,左孩子的编号为2i+1(2i+1<N),右孩子的编号为2i+2(2i+2<N)。
- 若对二叉树的根节点从1开始编号,则 相应的i结点,如果i>1,则其父结点的编号为i/2;
- 具有n个结点的完全二叉树的深度为
- 哈夫曼树
- 概念
路径、路径长度、树的路径长度、结点的权、结点的带权路径长度、树的带权路径长度WPL
带权路径长度最小的二叉树称为哈夫曼树或最优树。 - 性质
- 哈夫曼树没有度为1的结点
- 对于具有n个叶子结点的哈夫曼树共有2n-1个结点。
- 构造
- 在n个权值结点中,选取权值最小和次小的结点分别为左、右子树构造一棵二叉树,且置新的二叉树根节点的权值为其左、右子树根节点权值之和
- 重复步骤1
- 应用
- 哈夫曼树在编码问题中的应用(哈夫曼编码)
- 概念
- 二叉树
- 树和森林
- 存储结构
- 双亲表示法
- 孩子表示法
- 孩子兄弟表示法
- 树、森林与二叉树的转换
- 树转换为二叉树
- 森林转换为二叉树
- 二叉树还原成树或森林
- 树和森林的遍历
- 回溯法与树的遍历
- N皇后问题
- 4皇后问题
- 存储结构
- 树的基本概念
- 散列
- 散列函数
- 线性变换法(又可以叫直接法,不常用),这种方法所获得的地址集合和关键字值的集合的大小相同。
- 除留取余法(求模法,最简单,最常用)
- 数字分析法
- 。。。
- 处理冲突
- 开散列方法(拉链法,又叫分离链接法):就是把发生冲突的结点存储在散列表本身之外。
比如Java中HashMap就采用这种方法 - 开地址法:就是把发生冲突的结点存储在散列表中的另外一个槽中。需用使用一个公式探测散列地址序列。
简单来说就是:一旦发生冲突,就去寻找下 一个空的散列表地址,只要散列表足够大,空的散列地址总能找到。
- 开散列方法(拉链法,又叫分离链接法):就是把发生冲突的结点存储在散列表本身之外。
- 散列函数
- 优先队列(堆)
- 模型
优先队列是允许至少下列两种操作的数据结构:
insert(插入),等价于入队(enqueue)
deleteMin(删除最小者),等价于出队(dequeue) - 二叉堆(小顶推、大顶推)
- 性质
- 结构性质:二叉堆还是一个完全二叉树。拥有所有完全二叉树的所有性质。
- 堆序性质:任意结点小于它的所有后裔。
- 大顶堆常用于实现优先队列,且可用于构建堆排序算法。
- 小顶堆常用于问题如:查找流中的前 K 个最小元素。
- 基本的堆操作
- 构建堆(小顶堆)上滤+下滤
- insert():上滤
- finMin():下滤
- deleteMin()
- 堆排序(在排序中有,此处没有实现)
public class BinaryHeap<AnyType extends Comparable<? super AnyType>>{ public static final int DEFAULT_CAPACITY = 10; private int currentSize; // Number of elements in heap private AnyType [ ] array; // The heap array public BinaryHeap( ){ this( DEFAULT_CAPACITY ); } public BinaryHeap( int capacity ){ currentSize = 0; array = (AnyType[]) new Comparable[ capacity + 1 ]; } public BinaryHeap( AnyType [ ] items ){ currentSize = items.length; array = (AnyType[]) new Comparable[ currentSize + 2 ]; int i = 1; for( AnyType item : items ) array[ i++ ] = item; buildHeap( ); } public void insert( AnyType x ){ //如果堆中元素的数量等于数组的容量-1,也就是数组已经填充满了,那么就扩容 //注意数组array[0]不存储元素,从array[1]开始存储元素 if( currentSize == array.length - 1 ) enlargeArray( array.length * 2 + 1 ); // Percolate up int hole = ++currentSize; for( array[ 0 ] = x; x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 ) { array[ hole ] = array[ hole / 2 ]; } array[ hole ] = x; } public AnyType findMin( ){ if( isEmpty( ) ){ System.out.println("Heap is Empty!"); return null; } for(AnyType t:array) { System.out.print(t+","); } return array[ 1 ]; } public AnyType deleteMin( ){ if( isEmpty( ) ){ System.out.println("Heap is Empty!"); return null; } AnyType minItem = findMin( ); array[ 1 ] = array[ currentSize-- ]; percolateDown( 1 ); return minItem; } public boolean isEmpty( ){ return currentSize == 0; } public void makeEmpty( ){ currentSize = 0; } private void buildHeap( ){ for( int i = currentSize / 2; i > 0; i-- ) percolateDown( i ); } private void enlargeArray( int newSize ){ AnyType [] old = array; array = (AnyType []) new Comparable[ newSize ]; for( int i = 0; i < old.length; i++ ) array[ i ] = old[ i ]; } //上滤 private void percolateDown( int hole ){ int child; AnyType tmp = array[ hole ]; //下滤 for( ; hole * 2 <= currentSize; hole = child ) { child = hole * 2; if( child != currentSize && array[ child + 1 ].compareTo( array[ child ] ) < 0 ) { child++; } if( array[ child ].compareTo( tmp ) < 0 ) array[ hole ] = array[ child ]; else break; } array[ hole ] = tmp; } }
- 性质
- 优先队列的应用
- d-堆 - 完全d叉树
- 左式堆
- 左式堆的性质
- 左式堆的操作
- 斜堆
- 二项队列
- Java标准库中优先队列
- 模型
- 图
- 图的定义和术语
- 图的定义
图是由顶点和边构成的,图的顶点是图的元素,而图的边则表示了元素之间的相互关系。
具有有向边的图为有向图。
具有无向变的图为无向图。 - 图的特点
图中的每个元素,可以拥有0~n个前驱,也可以有0~n个后继,即图中元素之间的关系是任意的。 - 图的术语
1.端点和邻接点
2.顶点的度
3.完全图、稠密图、稀疏图
4.子图
5.路径、回路环
6.无向图:连通、连通图、连通分量:无向图的极大连通子图称为连通分量
7.有向图:强连通、强连通图、强连通分量
注意:任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量。
8.关节点、重连通图
9.权:每一条边对应的值
- 图的定义
- 图的存储表示
- 邻接矩阵:使用n行n列的2维数组进行存储。如果不是无权图,就用1表示两顶点连通,0表示两顶点没有连通。如果是有权图,就用权值表示两顶点连通。
- 图的邻接矩阵存储有下面特点:
- 图的邻接矩阵是唯一的。
- 无向图的邻接矩阵是按对角线对称的,即a ij = a ji;有向图的邻接矩阵则不一定是对称。
- 对于无向图,邻接矩阵的第i行非零元素的个数正好是第i个结点v i的度数。
- 对于有向图,邻接矩阵的第i行非零元素的个数正好是第i个结点v i的出度,第i列非零元素的个数正好是第i个结点v i的入度。
- 采用邻接矩阵存储图,非常易于确定图中任意两个顶点的边。但是要确定图中共有多少条边,必须按行、列对邻接矩阵的每个元素进行检测。对于一个具有N个顶点的图的邻接矩阵,需要花费O(n的平方)的时间。所以其检索效率较低。
- 图的邻接矩阵存储有下面特点:
- 邻接表:邻接表是一种结合了顺序存储和链式存储技术的存储结构。
其基本思想是,顺序存储每个顶点,且为每一个顶点建立一个单链表来存储其邻接点,以表示边的关系。- 图的邻接表存储有下面特点:
- 图的邻接表不是唯一的。因为在每个顶点对应的单链表中,各边结点的链接次序可以使任意的,这取决于建立邻接表的算法。
- 对于无向图,邻接表的顶点vi对应的链表长度正好顶点vi的度。
- 对于有向图,邻接表的顶点vi对应的链表长度正好顶点vi的出度。
- 对于有n个顶点,e条边的无向图,其邻接表有n个顶点和2e条边结点。在总边数小于n(n-1)/2时,邻接表比邻接矩阵要节省空间。
- 图的邻接表存储有下面特点:
- 邻接矩阵:使用n行n列的2维数组进行存储。如果不是无权图,就用1表示两顶点连通,0表示两顶点没有连通。如果是有权图,就用权值表示两顶点连通。
- 图的遍历
- 深度优先搜索 DFS
基本思想:首先访问出发顶点v0,接着选择一个与vo相邻的,且没有被访问过的顶点w访问之,再从w开始进行深度优先搜索。直到达到一个顶点,其所有的邻接点都已被访问过;就从最后所访问的顶点开始,依次退回到尚有邻接点未访问的顶点u,并从u开始进行深度优先搜索。直到所有顶点都被访问过,或者从任何一个已访问顶点出发,再也无法达到未曾访问的顶点,则搜索结束。
深度优先搜索法是一个递归过程,因此易于采用递归程序实现。 - 广度优先搜索 BFS
基本思想:首先访问出发顶点v,然后访问顶点v的全部未访问过的邻接点w0、w1、w2、w3......wn,然后按照w0、w1、w2、w3......wn的次序,访问每个顶点的所有未被访问过的邻接点,依次类推,直到图中所有和初始顶点v有路径连通的顶点都被访问过为止。
在使用广度优先搜索遍历图的时候,需要使用一个队列,以记录访问过的顶点。除出发顶点外,每次都取出队首顶点进行访问。每访问一个顶点,就将其加入队尾;利用队的先进先出特性,实现对邻接顶点访问次序的管理。
- 深度优先搜索 DFS
- 生成树和最小树
- 生成树
设G是一个连通无向图,若G'是包含G所有顶点的无回路的连通子图,则称G'是G的一颗生成树。
(具有n个顶点的连通图至少有n-1条边,而生成树就正好只有n-1条边。
在生成树中,任意加一条边,就会形成回路。)
如果采用深度优先搜索法对连通无向图G进行遍历,则遍历产生的生成树是G的一颗DFS生成树;(depth深度)
如果采用广度优先搜索法对连通无向图G进行遍历,则遍历产生的生成树是G的一颗BFS生成树。(breadth广度) - 最小树:如果图的每条边都对应一个数值,具有边上权值最小的生成树称为图的最小生成树。
- Prim算法(个人总结的步骤)
思想:从顶点出发- 步骤:设G=(V,E)为带权连通无向图,图的顶点集合为V,边集合为E。
- 设顶点集合V '是一个空集合,边集合E '是一个空集合。
- 任意选取一个顶点v0,将v0加入V ',找出以v0为端点且权值最小的边e1(E包含e1,E '不包含e1),另一个端点为v1,将v1加入V ',将e1加入E ';
- 找出以V '集合中的顶点为端点且权值最小的边e2(E包含e2,E '不包含e2),另一个端点为v2,将v2加入V ',将e2加入E ';
- 重复第三步骤,直到V ' 中包含了V中所有的顶点,即V '= V为止。
- 此事所有权值最小的边组成的树就是最小生成树。
- Prim算法总结
- 贪婪算法
- 步骤:设G=(V,E)为带权连通无向图,图的顶点集合为V,边集合为E。
- Kruskal算法(个人总结的步骤)
思想:从边出发- 步骤:设G=(V,E)为带权连通无向图,图的顶点集合为V,边集合为E。
- 设顶点集合V '包含V中所有顶点,边集合E '是一个空集合。
- 选权值最低的一条边e1,将其加入到E '中。
- 选权值最低的一条边e2(E包含e2,E '不含e2),且e2加入E '后不会使用V '中引入一个环(回路),可以称e2是安全边。
- 重复第三步骤,直到V '中的顶点完全连通。
- Kruskal算法总结
- Kruskal算法属于一种贪心策略,由于Kruskal算法每次加入E '的边是安全边中权值最小的,所以可以保证通过Kruskal算法得到的生成树是带权连通无向图的最小树。
需要指出的是,通过Kruskal算法得到的生成树不是唯一的(因为有权值相同的边都是安全边),但是所有最小生成树的网络代价是相同的。
- Kruskal算法属于一种贪心策略,由于Kruskal算法每次加入E '的边是安全边中权值最小的,所以可以保证通过Kruskal算法得到的生成树是带权连通无向图的最小树。
- 步骤:设G=(V,E)为带权连通无向图,图的顶点集合为V,边集合为E。
- Prim算法(个人总结的步骤)
- 生成树
- 图的应用
- 拓扑排序(一)
- 拓扑排序(二)
- 关键路径(一)
- 关键路经(二)
- 图的定义和术语
- 排序
- 插入排序
- 选择排序
- 快速排序
- 合并排序(分治)
- 基数排序
- 计数排序
- 桶排序
- 分治、树形、分配排序方法
- 查找
- 查找的基本概念
- 线性表的查找
- 顺序查找
- 二分查找
- 分块查找
- 树结构的查找
- 二叉查找树
- 由来
在线性表的查找中,二分查找是一种高效的查找方法。但是,为了使用二分查找方法,线性表必须是有序表,且顺序存储。若线性表的结点需要经常进行插入和删除操作,顺序存储就相当不方便。那么,二叉查找树就由此而生。 - 概念:对于二叉树中的每个结点,其值大于左子树的所有节点的值,小于右子树的所有节点的值,这样的二叉树称为二叉查找树。
- 算法
- 插入操作
首先要从根节点开始往下找到自己要插入的位置(即新节点的父节点);
具体流程是:
新节点与当前节点比较,如果相同则表示已经存在且不能再重复插入;
如果小于当前节点,则到左子树中寻找,如果左子树为空则当前节点为要找的父节点,新节点插入到当前节点的左子树即可;
如果大于当前节点,则到右子树中寻找,如果右子树为空则当前节点为要找的父节点,新节点插入到当前节点的右子树即可。 - 删除操作
删除操作主要分为三种情况,即要删除的节点无子节点,要删除的节点只有一个子节点,要删除的节点有两个子节点。
1. 对于要删除的节点无子节点可以直接删除,即让其父节点将该子节点置空即可。
2. 对于要删除的节点只有一个子节点,则替换要删除的节点为其子节点。
3. 对于要删除的节点有两个子节点,则首先找该节点的替换节点(即右子树中最小的节点,因为其其没有左孩子结点,替换方便),接着替换要删除的节点为替换节点,然后删除替换节点。 - 查询操作
先和根节点比较,如果相同就返回,如果小于根节点则到左子树中递归查找,如果大于根节点则到右子树中递归查找。因此在排序二叉树中可以很容易获取最大(最右最深子节点)和最小(最左最深子节点)值。
- 插入操作
- Java实现二叉排序树:Java实现二叉查找树
- 由来
- 平衡的二叉排序树(又称AVL树)
- 平衡二叉树
- 由来:对一棵查找树进行查询/新增/删除 等动作, 所花的时间与树的高度h 成比例, 并不与树的容量 n 成比例。如果可以让树维持矮矮胖胖的好身材, 也就是让h维持在O(lg n)左右, 完成上述工作就很省时间。能够一直维持好身材, 不因新增删除而长歪的搜寻树, 叫做balanced search tree(平衡树)。
- 性质:一颗平衡二叉树,如果有n个结点,其高度可保持O(log2^n),平均搜索长度也可以保持在O(log2^n)
- 主要算法:平衡二叉树的常用算法有AVL、红黑树、Treap、伸展树、SBT等。
- 定义平衡二叉树
定义二叉树的高度。设T是一颗二叉树,那么记号h(T)表示树T的高度。如果T是一颗空的二叉树,那么h(T)=-1;如果只有根结点,那么h(T)=0;如果T是一颗非空二叉树,Tl和Tr分别是T的根结点的左子树和右子树,那么h(T)=max{h(Tl),h(Tr)}+1。(也就是说树的高度=左、右子树中最大的高度+1)。
定义二叉树的平衡因子。设k是二叉树的T的结点,Tkl和Tkr分别是结点k的左子树和右子树,那么结点k的平衡因子为bf(k)=h(Tkr)-(Tkl)。(也就是说k结点的平衡因子=右结点的高度-左结点的高度)
如果二叉树T中任意结点k,都有| bf(k)|<=1,即所有结点的右子树和左子树的高度最多相差1,那么称T是一棵平衡二叉树。 - 例如:
注意平衡二叉树不是平衡的二叉排序树,但是平衡的二叉排序树是平衡二叉树 - 平衡树四种不平衡的情况及解决方法
- 第一种类型是由于在子树根节点的“左”子结点的“左”子树上插入结点,导致子树根节点的平衡因子由-1变成了-2,从而使平衡二叉树失去了平衡,这种类型称为LL型。
- 第二种类型是由于在子树根节点的“左”子结点的“右”子树上插入结点,导致子树根节点的平衡因子由-1变成了-2,从而使平衡二叉树失去了平衡,这种类型称为LR型。
- 第三种类型是由于在子树根节点的“右”子结点的“左”子树上插入结点,导致子树根节点的平衡因子由1变成了2,从而使平衡二叉树失去了平衡,这种类型称为RL型。
- 第四种类型是由于在子树根节点的“右”子结点的“右”子树上插入结点,导致子树根节点的平衡因子由1变成了2,从而使平衡二叉树失去了平衡,这种类型称为LL型。
- 上述四种类型中,第一种和第四种是对称的一组,它们的特点是在于子树的外侧发生变化而造成不平衡,称其为“外侧组”;而第二组和第三组是对称的另一组,它们是由于子树的内侧发生变化,称其为“内测组”。
- 第一种类型是由于在子树根节点的“左”子结点的“左”子树上插入结点,导致子树根节点的平衡因子由-1变成了-2,从而使平衡二叉树失去了平衡,这种类型称为LL型。
- 概念:每个结点的左右树的高度差不超过1(也就是平衡二叉树)的二叉查找树,称为平衡二叉排序树
例如:
该二叉树,根结点的右子树高度为3,左子树高度为2。结点上方的数字为平衡因子,因为右子树高度比左子树高度大1,所以根结点的平衡因子为1。 - 平衡二叉排序树插入元素维持平衡的方法(如下是一个完整的例子)
- 单旋转
- 二叉右旋
- 二叉左旋
- 二叉右旋
- 双旋转
- 先右后左双旋转
- 先左后右双旋转
- 先右后左双旋转
- 单旋转
- 算法
- 插入:平衡查找树维持平衡的例子,如上
- 删除:和插入没有本质区别,都是通过左旋或者右旋来完成。
- 查询:和二叉查找树查找方法相同
- 总结:平衡的二叉排序树查找的时间复杂度为O(log2^n)。那么插入和删除操作呢?
- Java代码实现平衡二叉排序树:Java实现平衡二叉排序树
- 平衡二叉树
- 二叉查找树
- 散列方法
- 散列表
- 文件
- 文件
- 文件概念
文件:通常称存储在外存储器中的记录为文件。是由大量性质相同的记录组成的集合。可按其记录的类型不同分为两类:操作系统文件和数据库文件。
记录
定长记录文件、不定长记录文件
单关键字文件、多关键字文件
记录的逻辑结构、记录的物理结构 - 文件操作:检索和修改(插入、删除、更新)
文件的操作可以有实时和批量两种不同的方式。
- 文件概念
- 存取方法
- 顺序存储
- 顺序文件:指记录按建立该文件时的先后次序(逻辑顺序)一次存放在外存储器上。即顺序文件中物理记录的顺序和逻辑记录是一致的。
- 特点:由于顺序文件是连续存取速度快,因此主要用于顺序存储、批量修改等情况。
- 磁带就是一种典型的顺序存取设备,因此存储在磁带上的文件只能是顺序文件。
- 顺序文件的检索和修改(插入、删除、更新)
- 随机存储
- 索引文件
- 概念:
索引表中的每一项称为索引项,索引项由记录和的关键字和记录的存放地址构成。
索引表和主文件总称为索引文件。比如:图书资料索引、词典索引等
索引表通常按键值的升序(递增次序)排序的。若主文件也按键值升序排列,则这样构成的索引文件称为索引顺序文件;若主文件是无序的所构成的索引文件称为索引无序文件。 - 索引文件的存储
在索引顺序文件中,通常不是对主文件中每一个记录都设置一个索引项,而是将主文件分成若干块,然后把每块中最大键值和该块的起始地址组成一个索引项。再把所有索引项按键值升序排列组成索引表。索引表本身也可看成是主文件,可再建立高一级的索引表,以提高查找效率。如果需要,还可建立高级的索引表......。各级索引表和主文件一起构成索引顺序文件。
有时主文件采用顺序文件组织形式很不方便,因为在形成文件,其记录并不一定按主关键字值升序来,如果记录按到达时间顺序排列,就形成一个无序文件。为了加快查找速度,可以把每个记录的键值及记录的开始地址构造索引项,再把所有的索引项按键值升序排列并将其共视为顺序文件,按索引顺序文件方法对他建立各级索引,构成索引无序文件。索引无序文件仅适用于随机存取。
索引表本身可以用多种方法进行组织,入多级索引、散列索引、B树索引等。由于B树能动态地维持树的平衡,查询速度快,仅介绍用B树以其变种B+树组织索引的方法 - 索引文件的检索和修改(插入、删除、更新)
- B-树(B树)
- 概念
平衡的多路查找树。每一个节点即存储了索引又存储了数据。 - 一棵m阶B树满足下列性质
- 所有节点的孩子节点数中的最大值为m,每个节点最多含有m-1个元素
- 根结点至少有两个子女;
- 每个节点都存储关键字值。
- 其左子节点的关键字值小于该节点,且右子节点的关键字值大于该节点。
- 操作
- 查找
查到即结束,返回结果 - 插入
分裂
- 查找
- 示例:M=3的B-树
- 概念
- B+树
- 由来:B+树是通过二叉查找树,再由平衡二叉树,B树演化而来的,在组织索引文件时比B树更常用。
- 概念
平衡的多路查找树。 - 一棵m阶B+树满足下列性质
- 性质和b树的性质基本一样,数据只存储在叶子节点
- 算法
- 插入:mysql索引B+树中查看
- 删除
- 检索:一直需要查找到叶子节点
- 更新
- 示例:4阶b+树
- B-树(B树)
- 索引顺序文件
- ISAM文件
- 索引顺序存取方法是一种专门为磁盘存取设计的文件组织方式。由于磁盘是以盘组、柱面和磁道三级地址存取的设备,则可对磁盘上的数据文件建立盘组、柱面和磁道三级索引。文件的记录在同一盘组上存取时,应先放在一个柱面上,然后再顺序存放在相邻的柱面上,对同一个柱面,则应按盘面的次序顺序存放。
- ISAM文件
- 多关键字文件
特点:在对文件进行检索操作时,不仅对主关键字进行简单询问,还经常需要对次关键字进行其他类型的询问检索。- 多重文件
- 倒排文件
- 概念:
- 散列文件
- 散列文件是指利用Hash法进行组织的文件。它类似于散列表,即根据文件中关键字的特点设计一种散列函数和处理冲突的方法将记录散列到存储设备上。
- 散列文件的存储
与散列表不同的是,对于文件来说,磁盘上的文件记录通常是成组存放的。若干个记录组成一个存储单位,在散列文件中,这个存储单位叫做桶(Bucket).假若一个桶能存放m个记录,这就是说,m个同义词的记录可以存放在同一个地址的桶中,而当第m+1个同义词出现时发生“溢出”。处理溢出也可采用散列表中处理冲突的各种方法,但对散列文件,主要采用链地址法。
当发生“溢出”时,需要将第m+1个同义词存放到另一个桶中,统常称此桶为“溢出桶”;相对的,称前m个同义词存放的桶为‘基桶’。溢出桶和基桶大小相同,相互之间指针链接。当在基桶中没有找到待查记录时,就顺时针指到溢出桶中进行查找。因此,希望同一散列地址的溢出桶和基桶在磁盘上的物理位置不要相距太远,最好在同一个柱面上。 - 散列文件的操作
- 查找
- 删除
- 修改
- 索引文件
- 顺序存储
- 文件
-
中级
- 高级数据结构
- 并查集:不相交集类 https://www.cnblogs.com/potatorain/p/9349453.html
- 树状数组
- 概念
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。
主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。
- 概念
- 线段树
- 概念
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
- 概念
- 倍增算法、ST表
【白话系列】倍增算法
ST表 - 算法之RMQ和LCA
- 概念
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
LCA(最近公共祖先)算法
解决LCA问题的三种算法
倍增求LCA
tarjan(离线)算法求LCA
- 概念
- Treap(树堆)
- 概念
树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。
其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。 - 操作
- 旋转:Treap维护堆性质的方法用到了旋转,这里先简单地介绍一下。Treap只需要两种旋转,这样编程复杂度比Splay等就要小一些,这正是Treap的特色之一。
例: - 插入
- 删除
- 旋转:Treap维护堆性质的方法用到了旋转,这里先简单地介绍一下。Treap只需要两种旋转,这样编程复杂度比Splay等就要小一些,这正是Treap的特色之一。
- 概念
- 树链剖分
- 概念
树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、BST、SPLAY、线段树等)来维护每一条链。
- 概念
- 哈希树:一种持久性数据结构,可用于实现集合和映射,旨在替换纯函数式编程中的哈希表。 在其基本形式中,哈希树在trie中存储其键的哈希值(被视为位串),其中实际键和(可选)值存储在trie的“最终”节点中
- 字典树:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。
典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
Trie的核心思想是空间换时间。 - 后缀树、后缀数组
- 分块
分块入门1~9 - 点分治
- 边分治
- AC自动机
- 红黑树
- 概念
红黑树是一种平衡二叉查找树(AVL树)的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
由于每一颗红黑树都是一颗二叉排序树,所以红黑树上的只读操行与普通二叉查找树相同。 - 性质
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。- 性质1. 节点是红色或黑色。
- 性质2. 根节点是黑色。
- 性质3. 所有叶子都是黑色。(叶子是NULL节点)
- 性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
是性质4导致路径上不能有两个连续的红色节点确保了这个结果。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
- 算法
- 查询:与普通二叉查找树相同
- 插入:插入操作是最关键的,为了维持平衡和符合红黑树的性质,可以采用两个操作应对:变色和树的旋转(左旋转和右旋转)
- 删除
- 对比:经验指出,平均红黑树大约和平均AVL树一样深,从而查找时间一般接近最优。红黑树的优点是执行插入所需要的开销相对较低,另外就是实践中发生的旋转相对较少。
- 应用
- java中的TreeSet、TreeMap的底层都是红黑树,Java8中的HashMap解决冲突也是采用红黑树。
- 概念
- 伸展树
- K-D树
- 动态树
- 左偏树(可合并堆)
- SB树
- SBT树
- 跳表:二分查找查找快,但是添加和删除速度慢,那么怎样可以做到查找、添加、删除速度都快呢?那么选择跳表
- 树套树
- 可持久化数据结构
- 算法设计技巧
- 贪心算法
- 背包问题
- 基础背包:0-1背包问题
- 问题描述:
现有n件物品和一个容量为c的背包。第i件物品的重量是重量为w[i],价值是v[i]。已知对于一件物品必须选择取(用1表示)或者不取(用0表示),且每件物品只能被取一次(这就是“0-1”的含义)。求放置哪些物品进背包,可使这些物品的重量总和不超过背包容量,且价值总和最大。 - 求解思路:
假设有5件物品,其重量分别是w={2,2,6,5,4},价值分别是v={6,3,5,4,6},背包容量为10。在数学问题中这是典型的线性规划问题,我们可以在线性约束范围内求解目标表达式。但是怎么用计算机语言实现呢?我们可以先这样考虑,当背包容量为1时,如何放置物品才能使背包中价值最大;同样当背包容量为2时,如何放置能使背包中价值最大,以此类推,直到背包容量为10。此时我们需要维护一张二维表m[i][j],其中横坐标i表示物品,纵坐标表示背包容量(1<=j<=10)。
0-1背包问题的递推二维表
m[i][j]表示当可以放入前i件物品且背包容量为j时的最大价值。当只能放入第一件物品即i=0时:若背包容量j<w[0],物品不能够被放入背包;若j>=w[0]时,物品可以放入背包,此时m[0][j]=v[0]。当可以放入前2件物品即i=1时,我们需要进行这样的处理:若j<w[1]时,说明第2件物品不能被放入背包内,此时背包的最大价值为背包中只放入第一件物品的最大价值,即m[1][j]=m[0][j];若j>=w[1]时,假设此时背包容量j=8,第二件物品可以被放入背包内,那么便会出现两种情况:
(1)将第二件物品放入背包,那么背包中物品的最大价值是多少呢?因为第二件物品重量为w[1]=2,在将第二件物品放入背包之前,背包的容量应为j-w[1]=8-2=6,此时背包的最大价值是m[0][6],因此若将第二件物品放入背包,其背包的最大价值m[1][j]=m[0][j-w[1]]+v[1];
(2)不将第二件物品放入背包,那么此时背包中物品的最大价值依然为只放入第一件物品时背包的最大价值,即m[1][j]=m[0][j]; 我们选取(1)(2)中价值的较大者作为i=1,j=8时背包中的最大价值。
i=2,3,4时的分析同上,直到背包的容量为10,此时m[4][10]即为背包中物品的最大价值。有了上面的分析,我们很容易写出下面的递归关系:
(1)i=0 当j<w[0]时,m[0][j]=0;当j>=w[0]时,m[0][j]=v[0]。
(2)i>0 当j<w[i],m[i][j]=m[i-1][j];当j>=w[i],m[i][j]=max{m[i-1][j-w[i]]+v[i],m[i-1][j]}。
得到了满足约束条件的背包中物品的最大价值后,需要知道是哪些物品被放入了背包。观察二维表m[i][j],我们注意到m[i][c]表示当背包重量为题目中要求的c时背包的最大价值,那么在得到m[i][c]之前,我们必然是比较了m[i-1][j-w[i]]+v[i]与m[i-1][j]的大小,从而决定是否将物品放入背包。所以我们可以利用回溯的方法,若m[i][j]=m[i-1][j],那么物品没有放入背包;否则物品一定被放入背包。因此我们可以从最后一件物品开始,一步一步回退到第一件物品,直到找到所有的物品放入背包的情况。本题中物品的装入情况如表中红色和蓝色部分所示,其中红色表示当前物品被装入背包,蓝色表示没有装入背包。
例题:public static void main(String[] args) { int[] w={2,2,6,5,4}; //物品重量 int[] v={6,3,5,4,6}; //物品价值 int c=10; //背包容量 getCost(w,v,c); //背包的最大价值为:15 //装入背包的物品编号是: 1 2 5 } public static void getCost(int[] w,int[] v,int c){ int []x=new int[5]; //记录物品装入情况,0表示不转入,1表示装入 x[0]=1; //初始值表示第一个物品已装入背包 int [][]m=new int[5][c+1];//需要维护的二维表,为了方便计算加入一列,其中第0列表示背包容量为0时背包的最大价值为0 /* * 初始化第一行,即背包中装入第一件物品 * */ for(int j=1;j<=c;j++){ if(j>=w[0]){ m[0][j]=v[0]; } } /* * 背包中依次装入其他的物品 * */ for(int i=1;i<5;i++){ for(int j=1;j<=c;j++){ if(j<w[i])m[i][j]=m[i-1][j]; //不装入背包 else{ if(m[i-1][j-w[i]]+v[i]>m[i-1][j]) m[i][j]=m[i-1][j-w[i]]+v[i]; //选择价值较大者 else m[i][j]=m[i-1][j]; } } } System.out.println("背包的最大价值为:"+m[w.length-1][c]); for(int i=4;i>=1;i--){ if(m[i][c]>m[i-1][c]){ x[i]=1; //装入背包 c-=w[i]; //物品i装入背包之前背包的容量 } else x[i]=0; //没有装入背包 } System.out.print("装入背包的物品编号是:"); for(int i=0;i<5;i++){ if(x[i]==1) System.out.printf("%2d",(i+1)); } }
题目: 输入一个整形数组,数组元素只有正整数,输入一个整形,求元素组合成子数组之和最接近于输入的整形的子数组。(说明:①数列元素不必连续,②最接近包括大于小于等于,③如果存在多种子数组,则取其一即可④输入的整形数组元素不超过20个⑤输入的整形数组中的元素不可重复使用) 实例1:输入“{23,45,12,31,35,40,8,39}” 输入“80” 输出{23,45,12} 实例2:输入“{23,45,12,31,35,40,8,39}” 输入“100” 输出{8,35,12,45}
- 问题描述:
- 完全背包
- 多重背包
- 三种背包
- 二维费用
- 分组背包
- 依赖问题
- 依赖问题
- 问法变化
- 基础背包:0-1背包问题
- 背包问题
- 动态规划DP 动态规划部分简介 - OI Wiki [力扣] DP问题分类汇总 - 知乎
- 线性DP
- 最长上升子序列LIS
题目
给定一个长度为N的序列A,求最长的数值单调递增的子序列的长度。
上升子序列B可表示为B={Ak1,Ak2,···,Akp},其中k1<k2<···<kp。
解析
状态:F[i]表示以A[i]为结尾的最长上升子序列的长度,边界为f[0]=0。
状态转移方程:F[i]=max{F[j]+1}(0≤j<i,A[j]<A[i])。
答案显然为max{F[i]}(1≤i≤N)。
事实上,无论是上升、下降还是不上升等等此类问题,代码都是相似的,唯一的区别只是判断的符号更改罢了。给定一个无序的整数数组,找到其中最长上升子序列的长度。 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 说明: 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n2) 。 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? // O(n²) public static int getLis1(int[] arr) { int maxLen = 1; int[] dp = new int[arr.length]; for (int i = 0; i < arr.length; i++) { dp[i] = 1; for (int j = 0; j < i; j++) { if (arr[i] > arr[j] && dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; } } maxLen = Math.max(maxLen, dp[i]); } return maxLen; } // O(nlogn) public static int getLis2(int[] arr) { int maxLen = 1; int[] dp = new int[arr.length]; int right = 0; int l = 0; int r = 0; int m = 0; dp[0] = arr[0]; for (int i = 1; i < arr.length; i++) { l = 0; r = right; while (l <= r) { m = (l + r) / 2; if (arr[i] > dp[m]) { l = m + 1; } else { r = m - 1; } } right = Math.max(l, right); dp[l] = arr[i]; } maxLen = right + 1; return maxLen; } public static void main(String[] args) { int[] aaa={10,9,2,5,3,7,101,18}; System.out.println(getLis1(aaa)); System.out.println(getLis2(aaa)); }
- 最长公共子序列LCS
- 概念
最长公共子序列(LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。一个数列 ,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则称为已知序列的最长公共子序列。 [1]
最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如Diff工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变。 - 应用
最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。简而言之,百度知道、百度百科都用得上。 - 代码
public static void main(String[] args) { String[]x={"","A","B","C","B","D","A","B"}; String[]y={"","B","D","C","A","B","A"}; int[][]b=getLength(x,y); Display(b,x,x.length-1,y.length-1);//BCBA } public static int[][] getLength(String[] x,String[] y){ int[][] b=new int[x.length][y.length]; int[][] c=new int[x.length][y.length]; for(int i=1;i<x.length;i++){ for(int j=1;j<y.length;j++){ if(x[i]==y[j]){ c[i][j]=c[i-1][j-1]+1; b[i][j]=1; }else if(c[i-1][j]>=c[i][j-1]){ c[i][j]=c[i-1][j]; b[i][j]=0; }else{ c[i][j]=c[i][j-1]; b[i][j]=-1; } } } return b; } public static void Display(int[][] b,String[]x,int i,int j){ if(i==0||j==0) return; if(b[i][j]==1){ Display(b,x,i-1,j-1); System.out.print(x[i]+""); }else if(b[i][j]==0){ Display(b,x,i-1,j); }else if(b[i][j]==-1){ Display(b,x,i,j-1); } }
- 概念
- 最长上升子序列LIS
- 树形DP
- 背包DP
- 区间DP
- 状态压缩DP;
- 数位DP;
- 计数型DP;
- 递推型DP;
- 概率型DP;
- 博弈型DP;
- 记忆化搜索;
- 线性DP
- 动态规划优化
- 倍增优化
- 数据结构优化
- 单调队列优化
- 斜率优化
- 四边不等式优化
- 深度搜索应用
- 回溯法
- 01背包
- 地图着色
- n皇后
- 最优加工顺序
- 广度搜索应用
- 01背包
- 旅行商问题
- 贪心算法
-
高级
- 启发式搜索
- A*搜索
- 最大流
- 最短增广路算法
- Dinic算法
- 最大流改进算法
- 标签算法ISPA
- 二分图最大匹配
- 配对方案
- 匈牙利算法
- 最大流最小割
- 最大收益
- 方格取数
- 最小费用最大流
- 最小费用路算法
- 消圈算法
- 启发式搜索