. w0 B8 u S4 {) h# A" X2 T3 g! ?9 ~" U# R9 f
f、栈 ' [. s5 c% w" B- J+ A内存结构:看用数组实现,还是链表实现 8 z$ B4 C3 Q7 s5 \- z) v9 U4 g实现难度:一般 2 F8 J2 e% i g4 o; V下标访问:不支持 F& `1 l, e) K+ _5 Z分类:FILO、单调栈% S! }# Q! T x+ Z7 ?6 k
插入时间复杂度:O ( 1 ) O(1)O(1) 7 b L/ N( S' j查找时间复杂度:理论上不支持4 g7 V. w" |' I: E6 a1 ?9 K; V" n
删除时间复杂度:O ( 1 ) O(1)O(1) ( ^# S+ H% m1 T9 ]) J, P0 ?' O2 Z A) }6 p0 }" w6 V# O
/ l+ B- q( k0 B- |' _g、树* R7 b5 h* K" P! P; y
内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续 . U) G5 U4 L9 b, c3 r实现难度:较难; N3 L/ E' T1 v) {1 S8 T" _
下标访问:不支持 e. C q" N. R5 E
分类:二叉树 和 多叉树 M. _6 p! p* R) V2 K2 R
插入时间复杂度:看情况而定* p& U. A6 l0 G, A7 {7 N! N
查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log 3 f* ?# j6 m- z( X2 " t3 O. b; }' t( |: T0 B: Y2 Z" ~ 8 x3 n# W" u) h! {+ ~
n) $ p( e; q8 Q! j# h0 w5 n' J, ~删除时间复杂度:看情况而定/ u) n" o: T/ v' X, Q! A1 k1 B& F
: Y; H8 C! S% i9 a2 Y9 @5 q& \ ^/ B: }
1、二叉树 6 Y" r3 U: Q/ n7 ~ x4 ?二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。2 S) \1 ]- Q3 Q0 [
其中,堆也是一种二叉树,也就是我们常说的优先队列。 ' H. J- c* H/ T, W4 C8 ^, h2、多叉树 9 M0 o0 k/ c' V! P, fB树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。; b7 u* j6 o5 E+ v8 B
h、图 ( R9 ?) b7 l# f e* P内存结构:不一定- d1 t* ~; E" b' s: O) ^
实现难度:难 6 @) v U5 O* |+ L# i& V2 P( C下标访问:不支持+ u3 [# T- D6 U" `; o I
分类:有向图、无向图. Z! x9 [8 h8 m% [
插入时间复杂度:根据算法而定8 | c+ [/ f/ u( B7 b* [
查找时间复杂度:根据算法而定1 H# j7 ]/ `% R/ Q, D
删除时间复杂度:根据算法而定 , p# O( }2 p8 b1 t( T" G( u$ J; i2 \9 |. g
* I6 ?! ~1 t$ u
1、图的概念 ( N1 H) E6 _5 @3 Y在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:% {7 i( t/ T8 n" A8 Q/ R& O* w
图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。4 a/ ^& L& X7 H. v- T
对于无权图,边由二元组 ( u , v ) (u,v)(u,v) 表示,其中 u , v ∈ V u, v \in Vu,v∈V。对于带权图,边由三元组 ( u , v , w ) (u,v, w)(u,v,w) 表示,其中 u , v ∈ V u, v \in Vu,v∈V,w ww 为权值,可以是任意类型。 8 j, c6 l+ F5 f图分为有向图和无向图,对于有向图, ( u , v ) (u, v)(u,v) 表示的是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v;对于无向图,( u , v ) (u, v)(u,v) 可以理解成两条边,一条是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v,另一条是从顶点 v vv 到 顶点 u uu 的边,即 v → u v \to uv→u;% Q {3 P+ d3 P: ~; [6 @9 C
2、图的存储. h% M2 p- [$ J7 y
对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。 " D/ C0 H6 E2 M/ d8 C3 e# ?6 g$ ~2 f# I" j3 `6 n
: t5 _2 m$ N' g
1)邻接矩阵* q( D2 }9 p- }1 o7 o# k
邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。0 a b+ ]. X9 V. }$ o% J# [
它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。 ( ~6 k! g% `+ u# G9 ^3 W* [[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[ % O1 b0 t* }0 p8 ~01∞9∞0∞8320∞∞∞30 8 ?$ [3 Z! }/ l/ d' v$ Q0∞3∞102∞∞∞0398∞0 $ H4 s% P6 A: d7 |\right]8 c, T/ H' Z+ y1 U) B$ w
⎣* p% A) W4 O' P8 [9 T
⎢ 7 {0 d( {+ e/ ^ o5 ^, [, @# m! |⎢! U2 [& q9 ~1 Q
⎡- V1 H9 ~& U- ]- _. C& o
$ \" a2 Q& r% ~( F1 M! w: ~( Y : L T5 x0 O& S& R" U. P/ h
0+ R) Y: B: D1 X# F+ Q( R
1' U9 V$ u+ m0 U, G
∞ # `5 d. y7 y( y# c' }( E1 a92 m3 T2 s" M, z) P" x. j) r
$ p: K# v* u' w 4 y6 \. m6 `& N" P W, v- z∞ + A3 i3 I+ S, f: M! ]0) Y) L- J3 ~' l( ^
∞ . i9 ]& V$ b5 p" |* C6 L8, _9 M7 f9 @* F. [' p
: C6 h, y3 V% p0 v: m' ` # r O/ w; z2 Z
3 \: U( u! X# K- B& N. Q2 ' k. U( G9 o3 U( g00 F7 ~. q" a0 R, P
∞ / o0 r; k! I) t8 y # |/ y0 v' R/ \/ a( M. C8 {, c" b; i
; Y. C- W4 W/ z0 s; H2 S( i∞8 L- k# E' V) D
∞ ) I6 @+ |) n9 Q1 x! ^% `9 C; l0 G39 L. p1 T8 {6 S: p3 {+ i, G! D
0+ \3 G5 y2 u3 ~4 ]* H
4 ~! `& Q# `+ E) E
$ e# T- f$ d2 y3 g# R' S) I
⎦0 r' D; j. c# A. ]" ^( |- W
⎥; O) X7 u& a/ V# @
⎥ 5 d0 K4 {' r8 y⎤) H$ {$ _1 T7 F2 c) N
* Q* N) u& x8 t9 m* I 0 x9 M2 V' C8 o) ]% U8 w8 h) C( `
2)邻接表 & x# F6 {5 k4 k0 Z0 V9 d B* |6 }* c邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。. f; l- t- K# W/ n8 r! {4 U3 d
它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。 G, h+ u6 D8 T( P如图所示,d a t a datadata 即 ( v , w ) (v, w)(v,w) 二元组,代表和对应顶点 u uu 直接相连的顶点数据,w ww 代表 u → v u \to vu→v 的边权,n e x t nextnext 是一个指针,指向下一个 ( v , w ) (v, w)(v,w) 二元组。 ! Q& c% @8 O$ x+ l+ R( K4 Q7 i- K 8 _+ w6 d2 f- z) G3 _: b4 `# U" K% H7 ` ^3 C3 L, W/ k6 L
在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;% j0 x! E2 u' F( Q* r
vector<Edge> edges[maxn];% F4 q2 p5 m- ^
1 " U+ W: W0 b# ]1 P' i3)前向星 4 U6 P5 t6 S; j, }; c8 i, G前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。) ?+ C) x4 S; {) D$ e: Z! u
它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。 + G' |' u2 S4 q如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。: ~/ `; J. W* N ^) Y
4 H1 G u9 x4 A7 \. A' h T& g* C' Z. Y
那么用哪种数据结构才能满足所有图的需求呢? - J. l: D; e) |- x% _4 }接下来介绍一种新的数据结构 —— 链式前向星。 * y. j' p7 Z9 O7 ~* o4 x6 f9 V4)链式前向星 ; V) D$ W0 P1 y4 g链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 i ii 都有一个链表,链表的所有数据是从 i ii 出发的所有边的集合(对比邻接表存的是顶点集合),边的表示为一个四元组 ( u , v , w , n e x t ) (u, v, w, next)(u,v,w,next),其中 ( u , v ) (u, v)(u,v) 代表该条边的有向顶点对 u → v u \to vu→v,w ww 代表边上的权值,n e x t nextnext 指向下一条边。 3 o, V `2 S/ L; N8 a+ q9 n2 m具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。 4 @4 N1 I! o0 {' Q边的结构体声明如下: 7 `8 t+ `$ r5 {* g1 J$ istruct Edge { 1 P* L+ Z+ r0 r- E0 ?6 [7 q int u, v, w, next; 8 K- E8 m V8 s- X Edge() {} / _ I2 o+ [ ?# E0 @- R Edge(int _u, int _v, int _w, int _next) :5 a- i* f7 G, A! X" X: Z
u(_u), v(_v), w(_w), next(_next) ( ~ H; f( w$ x+ g3 \$ i { 9 a) u' p; u4 Q6 ^3 V4 Y( } } N8 ~5 ?( i" E( L
}edge[maxm];$ R6 {5 H( h" C9 s4 O1 U$ _
1 % ?$ I4 A% y. o2 M- x: s. M2- M6 c. @ Z" ]
3. \3 }+ y3 Y7 y5 z
49 L- I, U! ~1 @& K
56 O3 L2 z; S. g, q6 O; h' Q9 E) f
6! F( ` N8 M' ]) v% {7 g
78 ]! X3 e$ ]( d3 Y
8 8 G3 }8 S& k ~ o+ r: W初始化所有的head = -1,当前边总数 edgeCount = 0;) F$ R: z1 B' S7 F9 [
每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下: # z& }% c1 S# W+ V5 K/ Zvoid addEdge(int u, int v, int w) { 4 x# e6 L3 `) B( o6 S( u edge[edgeCount] = Edge(u, v, w, head);, p( U2 [$ R3 \7 w0 B9 i- N
head = edgeCount++; ; {9 \7 g, U: G0 P}- z) Y' j) ?+ H
1+ e9 F: L B8 a" ?: L
2 1 n. N: s2 J" V9 X9 f33 P" v! _7 W8 G
4% _! Q0 X- G0 s/ S, s
这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。 * T0 p' I) l; \2 }调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。 * j5 D5 L6 F1 v& Ufor (int e = head; ~e; e = edges[e].next) { / y" d3 `2 V9 p! B9 c0 f9 F$ O int v = edges[e].v; ! } @ ` v/ i, B& \) N3 \0 W ValueType w = edges[e].w; 7 j1 h" E4 j% Y3 q* C; t( ^- ? ... - [, u- g* i. f& {7 Y: O1 Q} }" S" a) D, u' ]$ u
1 7 u& J: d# i6 _6 X. a9 }" ^& P28 g/ }/ J0 w# h4 ^
3 ' d6 |. z6 k& Q2 {6 X4* f0 e( Y3 | C3 s
5 7 S- z, Q- k4 N文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。1 |7 L! F) d. Q
4、算法入门3 Q) k( c9 u0 D* c3 C2 L
算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。 8 C+ N: a5 Q3 c$ p) I( e , Q/ M) Q" d6 n0 Q 2 r1 C1 B) ?! J6 _入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。 7 u0 c; R7 t W! A: I/ u1 }0 y k/ |对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。 ' t( o% D( w9 E1 O. [& r1、枚举 ) c. n% a5 E" }, ] w6 a& X# R6 ~枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。, d# }. [9 V4 |4 o( t: P
对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。 4 ~$ i D# E0 t2、排序' l! Y* ?# @9 G2 y6 R% v; W
既然是入门,千万不要去看快排、希尔排序这种冷门排序。 Z5 p. P) u$ I
冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。" l* f S r1 h; G& e
C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。 x, J. x6 {" \: w2 {: _, B0 p6 e3、模拟$ m/ s: a+ {2 S0 S' M
模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。( _2 c- |# y+ U3 D& L
不管时间复杂度 和 空间复杂度,放手去做!& ]% R* D2 @" n3 H0 E3 @
但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。 / F' V+ U# p! \! I9 O4、二分- m) j O; U2 p R7 K4 k( q
二分一般指二分查找,当然有时候也指代二分枚举。 8 P4 T* ~3 T* M! x( p+ [例如,在一个有序数组中查找值,我们一般这个干: ! `' Y; }7 c @1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1; 9 _# V8 j4 Q$ z( y+ J4 _8 {' ?2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:4 m% @: i$ G4 Q8 ]
2.a)目标值 等于 数组元素,直接返回 m i d midmid; ' J! J2 ]7 s R4 ~ 2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1; / Z" e9 I. v, r 2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1; 8 g A& A5 {) Q/ i+ W3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。' U' M1 s* G9 q3 X$ \& j3 Q7 @
5、双指针 2 S( z( q! ]3 K5 [双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。: u( D$ l3 n. K' u: h( d
2 A0 D3 T3 t" a2 H! C
. V$ w/ N* \7 `" u6、差分法. g, t1 }/ R- P1 F1 |
差分法一般配合前缀和。0 a6 d9 c/ k0 x* ^! Z6 z
对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题; ) `, n& r: `; A/ G假设 [ 0 , x ] [0, x][0,x] 内的 g o o d n u m b e r good \ numbergood number 数量为 g x g_xg ! {( x1 o4 \4 G1 n$ e# [9 X
x ' D+ t6 n* C% ^ + f0 I" y: C7 N( U9 _( s w4 Y
,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g 9 u* A3 u# t( R. _- L
r; p1 S# h4 S; ?3 T4 C
* M$ M: e* T; L5 ^4 n% q* ]$ q −g % K- p; O7 I% y( b. Y- [l−1 . e. h+ t8 b2 U, s1 h ; O& h+ F: E2 D7 s* Y( A( g) |
;分别用同样的方法求出 g r g_rg " |/ _6 @/ W1 V pr - n! c! x4 k. q: ?5 S, J 3 A+ i# Z# s. s' b# g: q 和 g l − 1 g_{l-1}g 6 |' O+ S# C4 A. Dl−19 O5 p6 C, Y. x/ H) S* l
6 R1 r" ?4 O6 S+ F6 t ,再相减即可;2 p& z0 g5 P4 t3 g
. D& }3 K3 V) K% A v% d! p
0 c# d- L5 v" h' h2 k6 E7、位运算" w2 `* w3 R$ b' e6 D# J
位运算可以理解成对二进制数字上的每一个位进行操作的运算。 , l: c8 r- s/ b4 \( P位运算分为 布尔位运算符 和 移位位运算符。 # F p- Q+ n: i6 H. V: o. ~* ]布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。 3 a' R) ~9 H. ?8 {如图所示: 8 O8 `( l5 [9 W1 R q/ @9 V8 t 4 i+ _2 [! P+ A2 s' { * j3 t: I: D) S) R位运算的特点是语句短,但是可以干大事!4 M( L8 T1 P* i$ k
比如,请用一句话来判断一个数是否是2的幂,代码如下:0 d* `+ S6 @9 R
!(x & (x - 1)) 0 y7 W" p+ Z$ u7 y3 v16 ?% S4 p4 I! [8 l- q
8、贪心 8 O3 h" X5 E5 ~; y贪心,一般就是按照当前最优解,去推算全局最优解。 g! }; C T& b) z
所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。 $ Q8 c1 r5 ?; }$ S. W$ n9、迭代: u; M2 g8 I& w8 p1 `* L
每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。 # c0 q+ _' l" x6 a) U4 c# E( N$ @# w; M10、分治: T% V* }, M$ K, {" U' J$ I
分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。 + m2 F7 f) H0 A( j( @) z- ]1 n5、算法进阶 # k" | ~$ k: T0 Y1 Y( Y1 ?! ]算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。& f6 a& y- t5 M! r
如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。 {! k" x5 g3 a/ c
这个系列主要分为以下几个大块内容: & @& ~; ~+ G/ K) y: ] 1)图论 + g+ f3 i. K/ a 2)动态规划 : J' y2 n N, z8 Z% P0 e" J' j 3)计算几何 # ?2 G$ b/ f1 G3 _! V 4)数论+ B6 S, H2 r w" \
5)字符串匹配 % g, ~, Y, p8 n; v' p. V9 F% d 6)高级数据结构(课本上学不到的) Z4 c# F3 U) m/ o. Y" L5 Z8 | 7)杂项算法4 ~# Y0 w+ f/ Q, p
0 E# ]* M1 C2 W9 Z/ F. r7 e3 p: u
% a8 G" E* a# n6 G先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式: ; x1 l9 q* A" O9 f" l8 T k0 p/ Y, w. [& B5 }
- Z! w" X4 o* c* E! Z 1 ~9 V1 r" A7 U1 y; Z+ b! z* W' E3 ]8 w
1)图论 . x+ {- w) _+ O4 K2 l5 h& I$ C3 o1、搜索概览) _" z" e9 a9 T; t3 S+ L3 y
图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。0 P. | p, Y- I" U: p; k
/ u. C) y: G. V$ _6 a
2 q4 r; Q4 k3 }& u- J比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。 6 l8 A" H S- d8 C2、深度优先搜索 1 P# c/ [6 y; r深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;9 J! _: i% ?% a8 M% C7 v
原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。 H. Y H" U; }& v8 ~ C
但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。8 L Q: j+ ~; q1 |4 t& H$ ? Q
如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。 9 ], Z y$ o2 V" n2 j& Q& E8 t# _, P$ D5 g