. h" z% [) n$ Q2 t5 k3 X∞ . T3 f# Q( Z* R7 d. l0 ' k7 V( T. a' L v" ~4 e$ \( [6 H w' x∞ 0 ?( c3 H+ Q* Q) V# {0 O3 Y: y84 u' |% b9 R# n/ n
1 G8 r G" O! O# v# T % _7 T$ e1 \+ S6 R4 \' L
3 9 e8 L5 E, c# t6 O" S* S2 8 t# p% Z! @) n' E( R& J0 7 e! ?' a' d! g0 g" C; B∞ $ s: `. J2 q9 ~& |8 H . C( v9 `" m- r& L4 } 7 u+ M) B X& L+ v- H∞" U9 p8 z3 U2 @' F+ u
∞9 C3 G1 }/ b3 t, y
39 v* @/ q" B4 Q$ p" I
0( o2 y( ^/ X/ g4 m0 c5 B+ |
8 R4 i+ T; W" K4 y, I* k4 \; ^" j * y: s; r3 _) `⎦ + Z8 x/ I6 N$ H% a3 m) y0 T⎥ 4 g! R& d/ p- E⎥ # M' v: b0 e9 u" I' s9 K⎤7 S5 g- S5 B s, B8 \) F6 A; c) a
0 J( I1 {. |& t! M
3 p; g* } E8 A2 W+ m5 H2)邻接表 ) K7 i7 L6 \- b' f1 [邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。 . F3 A& P4 Y; x ]5 n/ E它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。 / q' A$ w+ E |) G% U; Z; H如图所示,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) 二元组。 # f+ Z2 u0 L7 i, z 3 L Y$ n$ g& V/ `4 N0 f, I4 x5 n; g2 X/ }. q) ^: `0 X
在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;( \* D4 Q- G) o
vector<Edge> edges[maxn];& T# Y# p" V7 f) x
11 d$ d9 }& ^0 \# `- y
3)前向星) C: x6 o9 a$ S" B
前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。 L, `4 z9 _* ^' u+ v$ t
它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。) F, q- u$ o6 U1 O
如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。/ y8 t8 x% U, J9 s7 H, C
/ z' i Y8 [) u$ U
' T, x, M8 y' s& S' d# n那么用哪种数据结构才能满足所有图的需求呢?7 X: x* a& u* f" z
接下来介绍一种新的数据结构 —— 链式前向星。 5 o5 {! ^" L, d4)链式前向星 / N+ A R, T6 i( X/ [3 W c链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。. ^% y8 t7 J9 H, s4 [* Q
具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。0 N( [# R5 G* J8 l- D# n0 I3 t( q! G
边的结构体声明如下:* q8 T" R, O/ Y# _% f6 s* Z
struct Edge { / v, D7 l( x" J$ g* _ u& d$ K int u, v, w, next;' a4 C4 s8 G+ X! ]
Edge() {}( O. F. R2 C' x6 \
Edge(int _u, int _v, int _w, int _next) :) p7 F2 u4 ?) k. I8 y3 z
u(_u), v(_v), w(_w), next(_next) 4 S# L1 k$ c* F+ g, G- \- v t {1 j5 [' @3 p/ `2 X
} . L% l5 D& v f1 r0 R}edge[maxm]; + q0 q0 F# X! }$ D% W& e' X1 + Y6 } p1 ]) F2 1 E* u$ P y; p. t3 2 J6 o- e: R! T, s5 A4 8 X4 ~' ^$ W7 _9 F5 ; A6 ?6 D- r- _8 x6: k3 Q5 Y8 I* K9 b5 K5 s; i
7 8 G/ u: S3 ^: [, @8 7 j4 |9 {7 w+ @ T! }6 F' ~初始化所有的head = -1,当前边总数 edgeCount = 0; - a5 ]- c; x$ h& Z每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:1 {0 A7 O5 j: f- Y! s
void addEdge(int u, int v, int w) {( ] H: P( x* Y3 n+ L$ W$ ]
edge[edgeCount] = Edge(u, v, w, head); 4 H3 o( p; l/ Z& k head = edgeCount++;$ e9 P3 r5 z) ]* O' ?+ i. T
}- G) |; x* y& l
1 7 S9 L# K& h5 v' m! ~! C2% Q% s9 X1 B K0 W; Z) Q! u, L
3 ! V' B, ~% Q! j5 g- a" ? o0 B+ s4 ; K, F" w3 V4 ~8 c8 D% K6 h这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。& |7 J" i$ | X" |2 _8 r. i
调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。 ' h3 P* h8 T) a1 yfor (int e = head; ~e; e = edges[e].next) {- e! C" S, R- T, q' P0 x8 t
int v = edges[e].v; * |- s' [9 \* v1 R ValueType w = edges[e].w; # ^! @( F4 v$ y" G0 L ...4 D$ N0 k: x4 N# t+ G1 h3 d0 c
}! U/ ~4 h0 F9 e( ^
1 * ]9 w, J/ `4 K8 A4 v28 ^1 ]7 v5 v T! s$ `4 ?2 y! ~
3 1 X2 }0 f# `: l2 g; C4 % l/ P' Z8 H4 I3 Z3 _# X* \7 ^5 : S' G3 f0 ^+ c# O% c& x( S2 X文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。 / B1 j, M% F6 ^0 C y9 }, [/ M4、算法入门% I! t1 a9 a; k( W4 [8 N
算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。( M4 }+ R4 |2 n- i1 F
% O9 |' y" D) m, P! N: ]. I- |0 b
/ O3 _; D# {. x; N; x( E2 b% m入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。 % B3 U' T8 k/ L8 _2 t: C. U对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。. j* ~: E' w$ g9 H
1、枚举5 H" \! o7 g; e6 @
枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。 * j6 i( Y5 c9 a& C对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。 7 L$ a: {! ~$ r- j% @2、排序 8 z. j1 k1 a" R既然是入门,千万不要去看快排、希尔排序这种冷门排序。- S2 t6 s, M( y9 M! ~
冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。' O4 t6 P' o8 b+ \* N6 P; D% y
C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。* \" ~+ _3 c" F
3、模拟 & I) D5 l" G: i) |2 l6 J0 E模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。 7 s/ r& D9 y; m! z7 | e不管时间复杂度 和 空间复杂度,放手去做!: U! ~* x, d) R" v! l9 @* }
但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。+ o/ d* C6 g7 ^: k
4、二分1 Z% D9 m0 z' k
二分一般指二分查找,当然有时候也指代二分枚举。$ Q) I, D6 n. T9 y* U: i
例如,在一个有序数组中查找值,我们一般这个干: , g: o$ g1 a' }; f0 D1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;8 ^, t% [4 C/ c/ n
2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种: 2 X$ H& B1 w5 W' D* x s 2.a)目标值 等于 数组元素,直接返回 m i d midmid; / V& M: U U! L7 u+ V 2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;$ Y4 M* H M# `9 ^1 d0 J% Q' I
2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;- S' ~- n" D M, I- G f
3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。/ R2 B8 m7 @" v8 w5 P/ n. A
5、双指针: G% R. ?' g, j4 v6 A
双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。0 o3 T' A& l7 s4 k& q
( n9 V S: ]* {$ a: C" O" i0 J 0 S) b8 _/ `; \, L7 S7 {3 x6、差分法0 `* {3 O, U2 r4 \4 f. S$ N- F/ t" t
差分法一般配合前缀和。 ! |% L2 u. V" ~/ s4 d对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题; ) A# [" R6 i* R% j假设 [ 0 , x ] [0, x][0,x] 内的 g o o d n u m b e r good \ numbergood number 数量为 g x g_xg ) v9 B4 V# O6 a2 o1 Xx 5 ?7 z$ D; N# O8 }7 G / s6 B! Z1 i2 e: u6 Z( r ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g 1 I) G8 q, Q4 D' X1 E+ W! ^
r : A' U: }; w9 r9 r6 W C 8 b5 }/ T& ~/ u1 ~. o% W0 t0 u( `+ Y −g , v, M/ o6 g5 Y8 Q# d1 q9 w& j
l−1 + n- E- a) h J+ @& r* |0 F + O" A$ p. K2 F( q
;分别用同样的方法求出 g r g_rg ! Y% b2 O) ?' G' P. y/ N3 h" Gr/ q' B, r+ b0 c1 ? O5 ~5 g
9 O/ J8 r2 ^. C% q4 t
和 g l − 1 g_{l-1}g & O+ J" H5 P. Y. p, [3 V; ml−1" O: E( \( v9 N. |) x7 R1 i
- [9 R- q2 V4 P" V0 |
,再相减即可;! ]; ]1 u6 s: Y: Q* e0 J9 `3 _