6 O; a" |. U# i: J+ |. U/ `6 r5 M
这个问题考察的是对补码的理解。 , V0 h" t: C) [! I仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 & d" O" D$ _/ n w% y
62 * M4 q. `2 w$ ]! Y+ T; V ],这四个数加起来的和最大值为 2 64 2^{64}2 ; @$ T9 q# O2 S64% E, [( }4 Q4 l- ~; N
。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 ; K% o* J0 w" q% q
633 ^, W& I w4 r, ~% p8 |
−1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 - B, L N( `* y8 |* H0 f$ b4 I64 " e1 e2 E( M' S7 f9 L −1。 3 O4 T) Y/ C! M. h! l, w: Z但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 3 h, P9 R+ q5 b0 q- |627 s$ q* }6 E' y; _
时,结果才为 2 64 2^{64}2 5 ^) c% H; {: {; z% U; D+ \8 G64 8 L( t6 l- Y1 s3 R2 J ,所以可以对这一种情况进行特殊判断,具体参考代码详解。' [5 @7 R' J9 p! d# O: \, s% ]( I4 P
三、代码详解5 w( h$ d' C a; W; C( [- m
#include <stdio.h>/ g7 M8 [: X) @7 }# w7 m- x: s: p# P
typedef unsigned long long ull; // (1) / z" b8 d. l& g7 a3 Mconst ull MAX = (((ull)1)<<62); // (2)+ b' |2 v: ^2 Q- t0 ?+ H
8 ?2 a& n! ?2 l0 p; l- O
* D" r9 O2 ]" {. |& s- Gint main() { 1 E7 A. B; s/ l$ ^; A/ O [ int t; / o- M7 W, N# n# k1 C p( R8 o ull a, b, c, d; ( O' y! N) l. ~ scanf("%d", &t);) T# p( Q* e: U- P
while (t--) { ' k, b8 _( H# z+ F9 a scanf("%llu %llu %llu %llu", &a, &b, &c, &d); // (3) & }* {3 v! S s' U2 C2 O9 W% ? if (a == MAX && b == MAX && c == MAX && d == MAX) // (4) 1 H- n' Q8 G; x. Z. ~* c( n7 k( o printf("18446744073709551616\n"); // (5)1 X# ? c) w* O$ m( P" Z
else / s7 M, o/ F$ m' j printf("%llu\n", a + b + c + d); // (6) 5 |/ j" y. D! F2 q }9 R$ [# z/ ?/ _
return 0; - w% O8 P8 O9 q7 n5 u8 R" I; g; o5 g}, V" P9 \3 x+ `
1 " X3 ]) F9 o5 v2 - i: b. g' v( f+ p8 K31 b9 ]; o" t" q" x+ M$ N8 P: ~4 s9 s
4' d! L2 g6 h: w6 \
5 ! M9 Z! J y9 r, ]3 G6 6 B; y( z% d* `% D" T7 $ i. V# I0 J: _1 |8 d% [- z8 * s: `$ N# j2 ?2 O6 P, q2 q$ u8 h% }& O9. E% }. F. D# U) b
10 ! ?' ^4 g M' R& T6 t2 l3 J$ `& q7 J11( P5 z7 B- g* N/ [4 S
129 j* p% D/ q9 x% H, c
13 6 I1 u! c% ]8 ~14 $ M/ ~7 N& D7 s, t% ~8 v* K15) J) B1 Q% h! l) l% j
16/ X- V% W0 R) N8 c V
17 ; S& u; d# `7 I9 E7 v( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名; ! L0 x2 j0 ?6 H, O5 m2 h( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 - `( d V' d2 L
62 G2 ]. X" e6 m; l
,这里采用左移运算符直接实现 2 22 是幂运算;5 x" L! F( _$ o' I* R* D; A5 [
数学 C语言 C0 S% o5 _! X" |! l& H
2 n 2^n2 0 A& K, ?& u4 B& t( |0 d% |# ]1 [0 nn2 Z& i+ Q' j# C
1<<n ; x" ]! Q) s7 s6 m需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1; 6 Z( k% o) F# e; y( w# }( 3 ) (3)(3) %llu是无符号64位整型的输入方式;2 _5 ~0 m2 o- F# |: O
( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;/ b3 E& E1 c1 ~, l6 i- q" n3 C2 s" x
( 5 ) (5)(5) 由于 2 64 2^{64}2 : G* w+ G0 X# {1 c( }64 3 c) N/ i% ]6 o5 F( V9 o5 a5 f 是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出; 4 V1 O0 y) [+ `9 o- j) S* V( e. [, v g- q( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 / v0 a2 ?7 d, l7 h( v; z) _
648 h) t4 u1 |$ l9 n
−1] 范围内,直接相加输出即可。 # w- o9 S6 i* C X& o5 W' o& F2 L由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。 & r& m l0 R1 c) _! Q4 _为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 🤣。 0 D+ A' p; U6 w* n0 q9 m 7 K# I. B+ I+ e5 C. B. j/ P2 R5 \# m6 g: k ^8 M7 [% k
3、数据结构 t+ B- f, s& v6 w* u3 J% M
《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。 ! Q8 b" o9 z# X" \1、什么是数据结构 - B6 K% y: P( d; X" g& k你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。3 d7 f3 d- T; z1 u9 y
如果一定要给出一个官方的解释,那么它就是: 1 F0 [1 I; Y8 v计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。4 k/ w9 }( @) F1 M
9 ^% L+ Z+ w' V1 A6 b; X
" o# Q0 _, x2 C% ]
是不是还不如说它是堆,是栈,是队列呢? 0 A. R9 w' C7 a是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。 9 M/ @" g* Z, J9 b8 s7 i2、数据结构和算法的关系 $ D, U P5 Z3 J很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。# @) e1 |5 W, X9 n5 b( i2 Z
数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。 Q) G" v1 a" S- H" Y
而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出? 6 A; A7 G& E# p: |; J讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。 , Y# l0 a0 o1 J& a; L3、数据结构概览/ y4 a/ b; Z* |, L
周末花了一个下午整理的思维导图,数据结构:8 p- e- n( @ m* \. O9 U# k4 n
# i |6 q5 v" U
1 j& v* d8 U% M* g5 l* Y' a: X5 `9 y
常用的一些数据结构,各自有各自的优缺点,总结如下:. z% U, i/ x7 M9 D
a、数组 # I: y# C6 z3 [1 Z6 }' C8 B内存结构:内存空间连续# e' H) m- Y5 W4 V) I
实现难度:简单 % O( A( Y* @ P; E" t3 _- L下标访问:支持 : [3 i2 [1 x! f' u+ T分类:静态数组、动态数组 4 [: h7 w" [+ u ~- M插入时间复杂度:O ( n ) O(n)O(n) 9 a/ N' l1 W" Q$ y' a* @查找时间复杂度:O ( n ) O(n)O(n), o G" }6 P4 A5 y" s! ^
删除时间复杂度:O ( n ) O(n)O(n)$ |/ Z: p/ j. p7 F
5 m: U: ]9 G7 j! i
- R. l! |) { T& G
b、字符串- K- _- s4 x( z
内存结构:内存空间连续,类似字符数组/ ?5 c$ L* g& `
实现难度:简单,一般系统会提供一些方便的字符串操作函数 * a) j/ W) z& H; }# ?2 N下标访问:支持 % q' z; J7 U1 u2 I, \插入时间复杂度:O ( n ) O(n)O(n), k- c- M9 O+ {0 J
查找时间复杂度:O ( n ) O(n)O(n)- E% Z/ ]' {+ v6 C: Q' x
删除时间复杂度:O ( n ) O(n)O(n)# [4 ?5 G R0 R! S1 k+ P
* p" }! V2 t3 W, G) [7 H9 J
: H2 j- Y! C( z5 Y2 c
c、链表; U2 J' o0 [/ I/ D) p! U
内存结构:内存空间连续不连续,看具体实现8 a+ _" x1 ?2 @( J; P4 F
实现难度:一般" N [4 Z5 v" I+ i0 f" x
下标访问:不支持 : t* F; F: p3 |+ @" e) M分类:单向链表、双向链表、循环链表、DancingLinks3 t" ~4 M* g l4 }. p
插入时间复杂度:O ( 1 ) O(1)O(1)0 p. s% h( @0 f5 k5 o1 E5 |* {
查找时间复杂度:O ( n ) O(n)O(n) ( L5 v6 k; v, F5 ?# o删除时间复杂度:O ( 1 ) O(1)O(1)9 e+ I5 j" r" Q, m* N) e
5 A7 T s( i$ k- U. m/ I m* p. c0 `4 j
d、哈希表6 d/ ~$ T; o( W7 ~6 {
内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续# x# F6 I( s: H4 }0 K
实现难度:一般 1 t) O4 K3 _- Q2 @! u1 w: ]6 c下标访问:不支持# {; E g% _& p& z, ^6 |2 ?( e
分类:正数哈希、字符串哈希、滚动哈希 + a2 t/ d: g. l0 Q0 ~插入时间复杂度:O ( 1 ) O(1)O(1) + ~' C: I9 l7 u9 d查找时间复杂度:O ( 1 ) O(1)O(1)' z5 J# o8 v% k) T7 N
删除时间复杂度:O ( 1 ) O(1)O(1), h; D& U: J3 Q7 ?) s" a
# a$ \( H' q9 y* [6 g$ w: r% H1 @( n" X" h
e、队列; s" [# A% p. m# u5 X" \% E
内存结构:看用数组实现,还是链表实现8 C4 s" _# J+ R! F; H! I0 f3 L8 f
实现难度:一般( d2 U3 c) H& r3 h" e" a
下标访问:不支持 : C0 c6 U7 K U! C+ `分类:FIFO、单调队列、双端队列' R2 o/ Q3 E3 l, \2 C4 q$ ^
插入时间复杂度:O ( 1 ) O(1)O(1) ! G( D; k/ q: b% {. |5 z: \5 i查找时间复杂度:理论上不支持4 N+ C/ ^: Q7 Y: Q' t1 z
删除时间复杂度:O ( 1 ) O(1)O(1) # a: n' X- F$ c. L* p% X( G4 `! ?0 l$ L ! J: C3 ~2 Q. F0 D1 e- I & F$ m8 L# M( @4 j% Y( D% [. } P: Nf、栈 " v( s# z( [3 ?2 }2 e- D内存结构:看用数组实现,还是链表实现 : i; A2 P/ u' [& g/ d# P实现难度:一般. ]1 [1 p, n# ^8 ~5 B
下标访问:不支持 : o7 P* C V+ a9 W" U# g# p c分类:FILO、单调栈- ^8 `8 P( x; U+ [( m" s" y5 Z
插入时间复杂度:O ( 1 ) O(1)O(1) P4 R' l. t. Z1 ^+ N2 q% `+ d6 O查找时间复杂度:理论上不支持' \3 Y" b" V( _: Y5 B5 E
删除时间复杂度:O ( 1 ) O(1)O(1)0 H7 u( p3 ?0 ? a: A" d
# z$ Z; o, f6 ?/ b
7 j [# D) {& k& o* r1 i9 fg、树 ! c) ~+ [- p6 x内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续 $ b/ b& i9 v+ j5 d, q实现难度:较难 1 j7 a1 T, \& H( y2 E下标访问:不支持( J8 R- R# W5 x ^7 W' M
分类:二叉树 和 多叉树& o, |. h+ M; O9 ^: |$ p
插入时间复杂度:看情况而定# y& P) w9 ]5 ~
查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log . B# E5 t j* |: H0 r1 w2& i4 U- t. {) [! \
H6 R" M: n& R B" d, w9 B n)* {+ e9 S* j$ x# u
删除时间复杂度:看情况而定 ( j+ ] q; ~$ m, L" b - x* f& q3 K4 k+ |' W! [8 N. H5 Y* N# G y2 E
1、二叉树 ; V! m5 W# X8 r7 X+ I3 L! H( G二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。 % }. m% y5 T+ k Z+ S其中,堆也是一种二叉树,也就是我们常说的优先队列。 1 @1 Y; G& @! n2、多叉树4 d8 Z1 [ @) B3 E5 Z7 o, V P* ^1 p6 d
B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。7 d5 p; l) h& Y1 T2 C& _
h、图 q7 J- [ |- g' P/ ~( j6 V内存结构:不一定( q% `+ A% B" ~, Z4 e: d5 D1 P1 X; L
实现难度:难 0 A8 d) G! T+ x m+ a下标访问:不支持 2 o" m6 L& p8 c; ~: ~! P( B& H分类:有向图、无向图 : r5 J9 u$ c" h+ L8 t插入时间复杂度:根据算法而定/ X" `# c3 R. L5 n0 D- C
查找时间复杂度:根据算法而定 ! G& L s$ Y8 o* z删除时间复杂度:根据算法而定. @9 u$ x; Z/ A7 e) E" H" C1 Y# \1 U
i& J7 n) \0 q& H2 |. i; f / h/ e) u; F0 E# z' D8 c) a1 R1、图的概念5 a$ O7 w8 O6 s. B5 E% [ x# e( K
在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下: 8 w7 P- ~3 Y4 A' D图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。 * w& {$ _6 _7 F对于无权图,边由二元组 ( 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 为权值,可以是任意类型。! v/ b5 v# o& X% l2 I6 @0 G" k( g
图分为有向图和无向图,对于有向图, ( 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;) [" {0 I! @! r) @( [$ r) O" y
2、图的存储0 c* m* y0 Y% ?2 O1 }: s
对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。 6 e1 _5 h8 C* n1 f2 e- m6 c$ c/ W- @) ^3 w5 g
1 U2 _2 w/ {% I4 k" Z8 A
1)邻接矩阵; w- J7 ]- J }- z
邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。/ f8 b* c e2 g; P
它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。 4 T. ]: R, ~# B2 H8 y$ k; h0 y[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[4 U& n9 V; P4 L1 A, w! Q5 w
01∞9∞0∞8320∞∞∞30' x F" ?5 N/ Z6 o' O y
0∞3∞102∞∞∞0398∞0 * s$ t7 u7 o4 N\right]8 @' Q! ^1 A. D$ {' k N" l
⎣0 }, @" O: q/ v, v" q7 K
⎢* A4 E( Z; U8 I0 F- [
⎢ # v: T/ [7 F. a# @9 T5 X⎡# Y+ b8 Z# O; l ]( k
0 j% U4 `0 C- G# b0 ^# C6 C; E: u ; g! e+ t/ V+ A x
0 0 L" W# n8 K. ^7 w1 Y V1" \& F4 J; R( @; K4 r5 z% a
∞ 2 k. A3 K. [* V9 # M$ S# i A' h0 O# M / I' s/ W! ?+ Z
- b3 S" a! w: a |∞ : Z0 G5 l; K% \& A6 c0 7 p: K! X3 [7 n, i; E- o∞# u) v) w; W# ^
8 ' `7 Y d7 [# l8 N9 h0 b 8 v+ n4 z; y; J( L. F5 Q 0 q) Z; p; c: s/ I32 I: D0 [: p7 {" j- C) |
2# p, e5 A1 Q* Z! t% \1 w
0, L4 F3 g5 ]3 ]3 O: Y2 n
∞! |# q5 U" }( C6 E5 b
) V( a1 a: w, p5 } D% z
$ ?, v7 V$ x6 \. l* `
∞% V i& r* W; O9 d5 x/ ^) T+ h O
∞ C$ r1 j. {9 l# w& i
3" }: D1 W! O/ a" g. n
0% B6 l9 Y9 F' O* e
+ b( I, [1 D) W3 F: s , Y. G2 a( u: V& p# Y4 C
⎦ ( u# m/ g2 Z$ `⎥ ; [1 ~) P4 o X) O* n⎥ 6 H& J7 ?- L+ r6 u⎤ , {$ R! a0 | ^ n% c2 Y& G ; u$ b5 H! S0 A, P( ^ * q, ?6 I: s; {% ^
2)邻接表' k& ^6 S# a4 b8 Z7 v! W
邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。 . d6 h1 d$ d L) J5 M它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。 9 x8 h2 l) c& q$ t: }如图所示,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) 二元组。 i% z7 H h- N# E1 ]2 I " h* S- t; \7 B" G3 _ / r; S) P3 {5 W8 G M在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;+ K; D" F+ h! [$ Z; m. J
vector<Edge> edges[maxn]; # c- I# O# h2 U1 " A4 Z' x# x& E$ w3)前向星/ N* |$ K1 d8 d+ b- ]* n
前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。 # I3 }. t8 n3 s它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。 , U: `& |: L9 ^! W% ?& t1 q9 x( i如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。! e. r& w W5 W. S9 l) _% |
% l- j3 J' ?5 l* T
9 J3 G$ k, X& L& C P
那么用哪种数据结构才能满足所有图的需求呢? 8 @1 ?; R9 ?$ B+ R; {! e接下来介绍一种新的数据结构 —— 链式前向星。 # y' z3 O- H; b( N% ?4)链式前向星 6 ~% ^0 \7 ?- A4 b链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。* O9 E; ]. D+ L b' D6 C; T' B' q
具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。 * i5 b! W7 m- P4 s9 ^( q# L3 j' X边的结构体声明如下:, ~7 E. J$ _. _' x: W7 _& }9 e- A
struct Edge {- n1 u# T5 U, ^1 S
int u, v, w, next; ) H$ h/ X3 I1 L' a6 ` Z4 v- R- D Edge() {}' [9 e& z9 l- a Y6 s
Edge(int _u, int _v, int _w, int _next) : % W# P# u! s, {/ D8 r u(_u), v(_v), w(_w), next(_next) # i* P/ E# D5 w! O( {' p* D {1 P" H- S7 f1 `7 r8 g
} + e2 ^# i% |- t8 F3 |}edge[maxm];7 u! B2 Z. }0 j6 O8 {! X# z0 x# V
1+ Q9 Y2 N3 w( {% H$ i, B$ m. i
2 ' m; P: |6 A, c1 c% a3 3 @8 H u i2 `) e5 ^* f4- D. l! U1 T. q* y
5 ! [8 L" V' h/ r& X6 F$ D8 v6; A" e# q* Q0 B: F$ c
7 3 C9 F$ K% j& `- G6 c8 y8$ r* M% x, I; Y1 [/ b! e
初始化所有的head = -1,当前边总数 edgeCount = 0; ( n5 d J6 W% |& e每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下: # S. q8 }$ d2 e( t F/ ovoid addEdge(int u, int v, int w) { 6 Z# ?3 g" Z) v4 i" k edge[edgeCount] = Edge(u, v, w, head);8 r2 C0 q. s+ `2 r# M9 A
head = edgeCount++;* @* { @" m- l4 N/ @
} & Y- u# e3 \& y- z& P8 {0 W( r& p/ o1 & l& T6 R4 Z; H* a W! Q28 h: f* H7 u# d7 F0 k2 Q2 w9 f M' R
37 y2 |3 {* \: b1 i
4' X2 d p0 L2 P& ~' [6 N$ J$ o
这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。9 x$ k* F( D0 w6 m* z
调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。 8 W* l% H& b: ?5 n4 [# rfor (int e = head; ~e; e = edges[e].next) { % M$ e% r7 E) }- O1 Y int v = edges[e].v; , P7 ^' H6 }0 {5 ~( l1 L" Q9 m ValueType w = edges[e].w; ! h# ~5 [2 X' u T. G$ {9 ` ... ! g7 F, _- F1 x; T- { D}# y) ?$ f" X3 |6 u
1 + p$ d- X# n9 {0 Z+ Q! k2# ]$ r* a. c9 Z8 E
3( @, d. R; w I6 [+ y
4 * v/ D& F3 ~& ~! a! M R5( ]4 x I8 o0 g' N! H
文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。1 d4 i- y3 Q- p; a
4、算法入门 3 C5 X3 e4 O5 ?- c5 _算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。8 n0 r w6 D9 e/ n
) z0 j, w2 p- `: h) w! H# w
7 `! {1 Z- p* R9 {7 z* Z- F' {& R o入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。3 r8 m' n# h4 _) n' Y0 ]0 F2 V
对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。; s4 Z* y+ G! H; }' v7 D8 B
1、枚举. `4 R5 ^5 v5 E/ _
枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。: Z }* B% c& D, c* M9 e; y7 [9 P
对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。0 F6 y q/ Q2 q) k" w% M
2、排序+ R7 Z* w6 d! N8 e W) I
既然是入门,千万不要去看快排、希尔排序这种冷门排序。 @2 C r4 Z' k; M
冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。 , G9 }8 F8 V5 Q" jC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。 ' b6 I$ ?. x' U9 `3、模拟 " b5 e+ d- K9 p$ \' D( f模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。9 e' N+ o0 I' ~( X* w+ B9 y
不管时间复杂度 和 空间复杂度,放手去做!4 ^ q( C V, _, H7 e# _ U
但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。 8 G8 L: _1 Q f. x3 }3 ~: D4、二分) S# {; r8 ~; r: c
二分一般指二分查找,当然有时候也指代二分枚举。 - z8 P$ \& f F; V例如,在一个有序数组中查找值,我们一般这个干: 8 Y {( k0 |$ {) W, x; L1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1; 1 d; g" x$ p# c7 q$ N: Y2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种: " c5 s1 Q$ X" e6 v0 t' @ 2.a)目标值 等于 数组元素,直接返回 m i d midmid;8 g6 n$ x1 ~* E* r* k8 K* J
2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1; & j/ p# h) ]! c0 T 2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;* f3 t- g: U" m& C R _. s7 H, U
3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。 . ]0 T1 H; C3 |1 c9 k5 M, q/ N s5、双指针1 \8 T+ Y$ U7 ]3 c
双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。! Z8 J7 L$ A( E/ S
* J7 W0 K7 ?! G q% ~
6 a0 w, {: K% l% D" a+ g6、差分法 ' R9 k6 |8 N! x6 i差分法一般配合前缀和。9 D0 ]+ p) c W2 F
对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;8 {4 Y$ ]: I8 \: x% K" E8 D1 m
假设 [ 0 , x ] [0, x][0,x] 内的 g o o d n u m b e r good \ numbergood number 数量为 g x g_xg ( p2 ]( f/ Y* M J$ I
x8 X0 ~% P' c4 n% r
. I+ g) z S G2 g( ~ v9 ]8 I7 m
,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g + L+ _7 W1 Y, L
r& h6 R$ l; { U
: c0 L! a6 P3 K$ U' g9 I% a −g - H$ H) F& g: ?% {$ Wl−1 # u! y$ ~, W* F4 V( j- @( g 8 b- C! I. y1 y0 X. M2 |
;分别用同样的方法求出 g r g_rg 2 N5 j: A5 [# V
r * ?8 B; e; l4 Z4 n , v$ T% z- I9 s3 F# x$ U
和 g l − 1 g_{l-1}g 7 g0 u3 M6 c7 H# J: N% f( C
l−1 3 J8 p/ d5 Q; u / g6 m; J2 u: \4 j) c/ H ,再相减即可; + h% D' F: ?5 O8 G 8 ~2 a( u& z6 r, g$ m; x3 `9 P# ?: _ / ^: J: J4 z* @$ ]' j7、位运算( {" n# O+ K8 R8 v
位运算可以理解成对二进制数字上的每一个位进行操作的运算。 2 O0 c m5 b$ b位运算分为 布尔位运算符 和 移位位运算符。 : E: c! I4 ?( S' L8 K4 l# }布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。: q$ _8 S/ E( y& ~2 P
如图所示: 7 H( c1 P3 m1 L/ N % j3 c; c8 |5 D7 g. S' ]) S/ Z \2 h% M8 A. H3 B' [$ `
位运算的特点是语句短,但是可以干大事! $ B l4 }0 s. u+ Q9 G比如,请用一句话来判断一个数是否是2的幂,代码如下:2 S* E7 G3 k& g( [
!(x & (x - 1)); E6 ]% u, z, q1 s1 b8 Q
12 L6 _( @: o6 ]# N) Y
8、贪心9 a) K" B: p- V% |/ U+ ?6 F7 v
贪心,一般就是按照当前最优解,去推算全局最优解。8 t9 W, H0 l4 h; j- X$ P3 P
所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。 ) p7 y- {5 _+ R: A. C. a9、迭代+ X0 k, U3 ~# s" |! h; S9 ^# e& ?
每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。 . F! [; K. F, d- t6 A: c: F10、分治 5 P! x5 a! w* _1 o$ E' J3 b分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。 ; P0 N+ V" ^3 R0 [. n! h5、算法进阶 ! e0 w% s, h2 Q9 S3 d9 F1 M算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。 0 \ }+ \, q1 I9 l8 d如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。 @: K6 z# G* t4 T& F) I) N( V这个系列主要分为以下几个大块内容:1 |( T, M* X+ P2 t+ F" p
1)图论 # r/ t2 r- b7 S8 q7 w 2)动态规划 7 ^# M2 [- F$ O8 l. e! i 3)计算几何 2 S3 ]9 P: S& q( @" | 4)数论 $ K9 j* q+ C! g u5 n- N( z 5)字符串匹配( U$ C$ y4 b% J. p: @
6)高级数据结构(课本上学不到的) 6 E: o1 }) T7 _ 7)杂项算法 , u( w5 _6 o, K6 ? X2 b B0 o$ M4 D1 z* X
8 M$ X" c2 K# e
先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式: $ z( b- s8 ?+ i6 w- |; z 8 V; J \1 N, V0 j+ z# l9 O8 l( `! ^9 e* ^2 w$ V
' N0 u) T. P4 f6 L. y) X" Q
4 \5 U9 n+ _; `/ Y* Z
1)图论 0 C9 `, H& ]: c5 X) K* t5 U6 o1、搜索概览 9 r3 Y' h `+ j% O9 F; z9 }! K图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。- F/ r: k4 C; u4 w- z8 [! m' j* }