7 Q- X6 _3 ]( E这个问题考察的是对补码的理解。" w9 L; E- R2 z. X+ \
仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 ( W2 C _1 f% z
62+ v! ^& V3 b6 F) ~$ W
],这四个数加起来的和最大值为 2 64 2^{64}2 + o7 R+ q7 [/ i/ o9 D
640 |3 ]+ Q1 R# o- {$ a0 r
。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 % G6 q5 ~; x2 S. d, U) y1 X9 Y0 d. c63 # Z8 L _ H- T −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 " N3 n8 n4 |1 ~- }2 N1 m64 3 }! B+ N) i" M. C- N7 Z9 N- K −1。0 N- ^8 E# F1 X! e* U
但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 # q: V, d$ C5 ?! v2 g6 l) ~- ]62 & ?# h% M, O H+ R6 R& p; Z 时,结果才为 2 64 2^{64}2 - E. j+ e( k4 ^5 ]* S9 ^2 P( V- r# o
64 A5 Q0 N( P+ U1 c
,所以可以对这一种情况进行特殊判断,具体参考代码详解。; {, \& \) `! v; I: u2 d
三、代码详解 ' T6 O; @$ M' r#include <stdio.h># u2 V; J( q, T* m, E' D# p. \
typedef unsigned long long ull; // (1) . J% f$ `( D& `- b/ y$ q" Hconst ull MAX = (((ull)1)<<62); // (2)2 n4 h. \* O& c& w" W+ \
6 p: d1 [+ f; ^( r' y, B6 M
6 {" L, @: y$ E( C4 h5 Tint main() { \" \8 e4 I: t8 d
int t;! A& w; z+ q8 {/ F/ N: I' d) j
ull a, b, c, d;; l3 G/ q9 S/ A8 |8 h# _' |% ^1 e8 o
scanf("%d", &t); 2 G1 l9 h* A/ h while (t--) {0 M* {, P( C s( W' t
scanf("%llu %llu %llu %llu", &a, &b, &c, &d); // (3)7 b2 a. X. [+ M3 V) S& {% L
if (a == MAX && b == MAX && c == MAX && d == MAX) // (4) $ n% E% n! ^5 C6 E printf("18446744073709551616\n"); // (5) + j& t* Q# t& n! s; ? else ) h2 l: p- A2 e9 G printf("%llu\n", a + b + c + d); // (6) & W/ j G$ M. ]7 I }! F# G3 r; {* A0 k0 P4 J
return 0; + f% ~/ ]# K) A0 }" I7 A}$ l4 E( a% J1 x5 D8 P: \1 I
1 e& n0 R; v" |
27 Y7 T9 I0 ?( Q8 w# L3 A
3 ' X! e% L5 Q/ ?' K4 : H6 s1 P' d- p$ U6 J5 H59 | ?3 H8 r6 T/ Y5 l2 K
6 * m( g9 [5 ~8 Y+ W7% r9 }) A$ y/ u& Z F4 h
8- x8 Z3 W( V7 z- O+ y. o8 d! a- e
9! _, b) L9 I, A- n7 e. W, V1 x! {
101 d/ F% h6 R; l% g# `' F( n
118 m! U5 H$ T: Z+ _! c2 A% E* h9 _
12 1 |4 W0 \& V& I$ B1 i134 O2 M) i1 q+ c) m/ C$ b# f
14 & x2 U; z7 i$ g, T! X7 v+ @4 v15 7 Y: B1 c( ^ F; W16$ k' D7 X0 H# d5 x' u1 d, v! [& L3 C9 q
17* E- ], k- I8 |: U# e! \3 q+ i8 ^
( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名; " p! ]8 E# o# g3 N. h5 }( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 4 Q$ R$ r6 f. ` v3 I4 P627 F( l! |* A4 {6 d0 E2 L U6 b# y! s
,这里采用左移运算符直接实现 2 22 是幂运算;! z% S Y; c1 R
数学 C语言 4 f9 m# Z, F/ V2 n 2^n2 3 z3 S; s j5 y: m
n) k( h h( Y6 ~' }, y; m9 m% K
1<<n- j- j9 o! x+ c' v* f% q0 T, o
需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1; ; I1 `# l9 B) U2 M! W/ T2 m* F5 |! g9 \( 3 ) (3)(3) %llu是无符号64位整型的输入方式; 3 ~$ K' W# }$ w7 d8 B( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事; b, G! t" I: k% r: g' @( 5 ) (5)(5) 由于 2 64 2^{64}2 8 M' d3 N. q* z0 C64 , P! v$ E: V( t3 k+ }+ f 是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;* \& I- E f0 V0 y* r% w/ q
( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 + q' p; u: Q1 w! [( E8 z64 % t G! N' B/ h, W+ m. x −1] 范围内,直接相加输出即可。 0 z1 _" F6 ]* h! P由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。 + p7 F- I1 B2 G为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 🤣。4 e* d4 g$ K2 H1 z$ V0 f
( ^; L) h% z7 k7 w
* r1 _, x: S" m0 s/ z$ E( Q3、数据结构# |/ q( V3 u4 C* t
《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。 ) \% K2 L- H5 |( j) p6 T1、什么是数据结构' q7 c' N7 \4 l- F, _; T2 @9 t
你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。7 p, f0 }( f; p6 R1 {5 C
如果一定要给出一个官方的解释,那么它就是:, ?. V2 T/ `3 X. h3 |! W
计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。 f" m. p! J& x) p
. h& v0 Q$ K) ]% G; b
3 W, e7 s' p/ @, N. n' d' J是不是还不如说它是堆,是栈,是队列呢? 8 s) i; v- P3 m# ~" o; M5 X( G/ u8 i1 r是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。! D! K- w0 K6 p4 y A
2、数据结构和算法的关系 # Y r* I5 c! J, w1 J9 J很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。- U: x8 d' {) Z
数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。, u4 g- l9 [# u
而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出? 8 D( u }2 p+ U& j" `讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。 2 X7 W+ c9 R/ N8 w! Q' S6 N3、数据结构概览6 w( L% ?, ^8 W) N" h& v
周末花了一个下午整理的思维导图,数据结构:5 n2 a( j5 C, |( w1 O4 ^
& P/ i; p3 Q( [& N: f3 l% j& U
# n7 l9 o- g: h- P V
常用的一些数据结构,各自有各自的优缺点,总结如下: + z5 T6 R0 X. H# ?2 z2 Pa、数组 , {9 u i* z; F内存结构:内存空间连续 2 w2 U& r6 Y$ w9 Y: x实现难度:简单 6 o% A" z; F# E" Y8 }下标访问:支持 4 Q5 I6 W, P: i! ?分类:静态数组、动态数组 I- {2 B; F: b/ O U# v; Q
插入时间复杂度:O ( n ) O(n)O(n) & L/ I x5 ?# m9 M' J+ ?查找时间复杂度:O ( n ) O(n)O(n) % K6 y7 K( Q8 G4 `5 @- K( R. x. M2 @# K删除时间复杂度:O ( n ) O(n)O(n) " j( _; E" e7 H $ a2 X( B3 ?/ k" `) h/ t. Q, k2 m1 S4 H! q* Q1 i2 c! O% B0 B) d8 u
b、字符串; R g! T4 x6 L& h1 b
内存结构:内存空间连续,类似字符数组 : \4 V: E* G+ w, U+ j实现难度:简单,一般系统会提供一些方便的字符串操作函数% q$ @% x$ R$ w. b$ e6 t
下标访问:支持9 [4 j# t' M5 n
插入时间复杂度:O ( n ) O(n)O(n) / u) L7 B( W0 V9 I查找时间复杂度:O ( n ) O(n)O(n)/ q7 C7 e5 s( f6 e1 z
删除时间复杂度:O ( n ) O(n)O(n)1 K* ?3 `! L4 }- s' b! z1 ^! j
7 C2 b& K. e M+ E! |' T
, v& s' c/ C4 h8 U! v! L! G; O. B
c、链表7 K' Y! l; v8 c( ]9 G. d
内存结构:内存空间连续不连续,看具体实现6 r) b9 G, U9 c/ R
实现难度:一般 : u$ J* H D/ T# t B" h7 U# f下标访问:不支持+ F, C6 J3 ?5 B: U9 i
分类:单向链表、双向链表、循环链表、DancingLinks : U0 \0 _9 ]3 ~9 f" M插入时间复杂度:O ( 1 ) O(1)O(1) . f5 z3 `# t: o查找时间复杂度:O ( n ) O(n)O(n)9 R; J- C3 A& j8 J' q$ \7 L8 F
删除时间复杂度:O ( 1 ) O(1)O(1)8 A8 } l, J1 K+ F' o
d" _% W2 Q0 L" p/ T' B' o8 G/ L! t+ t! g+ c' ^ i3 D2 c8 B
d、哈希表1 \, E& g" C+ y+ i
内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续; ?& _" D' _' g7 T9 f% v
实现难度:一般 $ ~+ X! l! h. m7 q下标访问:不支持" R+ Y1 f$ d& O, d+ i5 Z( l
分类:正数哈希、字符串哈希、滚动哈希" e0 P5 n% ]8 [; ]- Q; b
插入时间复杂度:O ( 1 ) O(1)O(1) 2 {% G7 L4 V. h( k/ F: h% |查找时间复杂度:O ( 1 ) O(1)O(1)# Q5 b- I/ w" e# k/ |$ j" U
删除时间复杂度:O ( 1 ) O(1)O(1) + a9 v4 A J% i * k6 M* p7 C3 o; h. x* Y i4 P4 O, i! E! T+ ~3 Q1 _
e、队列8 L" Y* y* r. Q( `
内存结构:看用数组实现,还是链表实现 + K; D- o" d, J实现难度:一般 ; t/ C( n/ Y0 A& N下标访问:不支持/ E. F0 d. U/ m# a; B
分类:FIFO、单调队列、双端队列 9 j) _( O Z6 x" E插入时间复杂度:O ( 1 ) O(1)O(1) 1 Y* O* D8 v2 q2 R: ^4 F5 x查找时间复杂度:理论上不支持 % \" K' l+ {) y3 j# A删除时间复杂度:O ( 1 ) O(1)O(1)( _7 j0 U9 D% p; ~2 ?* q7 r5 ?
/ Z7 ]! O3 X' ~/ S( M# `
; y7 `0 D. b! Vf、栈: d( C3 M# G/ u2 @5 K
内存结构:看用数组实现,还是链表实现# Q2 X; B- M. \' h0 J1 ~& W4 [# n
实现难度:一般8 W; O0 J3 F, o8 q
下标访问:不支持0 [, d1 w7 p% V
分类:FILO、单调栈 $ D: L6 w, k0 e5 s* R6 r% A插入时间复杂度:O ( 1 ) O(1)O(1)! r# t- \3 }: ^* Z. c' M, b
查找时间复杂度:理论上不支持: P. ?! c8 _/ i+ u' [' H) {
删除时间复杂度:O ( 1 ) O(1)O(1) 2 E: ~+ x4 I0 @0 Y7 B7 E% H+ {9 B0 F [
, s6 }: W+ U4 i$ a6 A3 k- G1 {; Zg、树 6 s( l( f" C" c" V$ H/ U0 u内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续" _4 K2 d2 S* j% C& u4 L! M2 Z; M
实现难度:较难- K0 y3 J( M! P% W
下标访问:不支持6 Y$ B* [( x4 E$ K* D
分类:二叉树 和 多叉树 " k7 Y2 T2 i# H/ b2 ^& X8 d6 K" q插入时间复杂度:看情况而定' T- |6 e: @& @0 b- F& p
查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log $ ]" G# M% u" ~2 o! J4 `! g23 n+ n/ [2 S, l5 N/ M* a0 U1 o/ O
& [7 F* V3 H# u; y
n). \2 J: ~" Y) A
删除时间复杂度:看情况而定 7 K' i5 t! c' C$ K9 c! R' } 4 a4 m% J$ C! |& m, V3 h. t+ \9 {- D; X# i( C# S5 `
1、二叉树& \2 z- [4 z+ L' y. f
二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。 $ } B8 l+ k* m其中,堆也是一种二叉树,也就是我们常说的优先队列。- P9 }+ H2 t+ K* D8 u
2、多叉树 , [8 A) ?: |/ s/ @B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。 : m" J& o3 ^* B: g3 h) Qh、图. g* v+ Y# u7 @
内存结构:不一定! l B7 f7 \; p8 w
实现难度:难4 {% @0 w- i. b! i7 g
下标访问:不支持 {8 |! K* H4 h5 Q/ H分类:有向图、无向图 _, a$ a3 l2 ?插入时间复杂度:根据算法而定5 e$ t3 r" A% G3 s8 ?
查找时间复杂度:根据算法而定 3 c: z) m# L% F8 ~* M删除时间复杂度:根据算法而定) T& q# r( M$ G& z0 c( Y+ \
4 |: R' x4 V- p; n% N2 M : H) e& P ^! R# \+ P/ Q4 r4 L1、图的概念 & h( Z( d- V b; x2 Q' k$ w. E在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:# V# P2 I; h$ ~+ W" I; c
图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。 % Y$ n% i9 Z" `: `0 A% N7 m对于无权图,边由二元组 ( 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 为权值,可以是任意类型。( L* f% U" o; p0 ^) D
图分为有向图和无向图,对于有向图, ( 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; / p5 f3 C9 I R1 v- B2、图的存储 , d" o7 B1 I" y, j. H$ h对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。 1 F! y% |* L7 @2 t$ M% Q8 _+ W& k" a3 M5 }2 n6 s
3 f9 t+ |) `1 t! i
1)邻接矩阵 + L! s# F9 s, M L邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。( @' i' V! h5 l: a, T
它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。 5 m: t) ]0 N+ V c* B9 X[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[ : y" n( r5 l4 d- u9 I# N% b01∞9∞0∞8320∞∞∞30! ]. [7 F/ P' T- }
0∞3∞102∞∞∞0398∞0 7 Y. [: K/ Z% Z0 n! }& _. K' W\right]( Z- y" N& O4 k& p7 q" l
⎣6 Q; H! S0 d, `- u* Q: V+ |
⎢ & V9 U! q6 t n6 x9 g4 c: F⎢, e1 k, J5 I) y7 T& b& t
⎡5 f7 L! d: F x; R( x9 d
R7 A/ _8 G; Y$ P( [ S) T e$ V
0 G2 l8 v0 F, M4 a% A! @
0 6 X4 K' A$ K( t1 t+ f# X; V3 U8 k! p" m∞3 L7 Y/ {6 Y/ I; o& j
9 : G( Q2 s; S. k0 m; \( ^4 N/ D 2 j* c* R1 S4 y4 r1 T/ l6 M / X' {- S7 {; l# g. q4 t* [) g2 g
∞ 9 t0 x5 f0 D4 g6 z% s" }! b. v; v0 9 j; L5 ]( S6 m [* r∞ 0 y9 E1 X; x# ]. g! `8% Z8 [ J/ M4 o* s9 O: s5 M
0 Y7 h* G, e$ `" F # x4 y5 S6 U5 I& R; I, y
32 i2 F2 F: q; {4 d# W
2 6 x* Y8 E h# p: J- l) Z0 $ |( M6 A0 g" z' B5 S1 c∞6 m4 d; B% A2 Z. @, _1 K4 F& D( C
/ t8 ~( d7 ^# x: n! ^6 k ! N# X+ S9 q k3 d# G! [3 H& ?8 v∞7 x, U. ]- s/ Z6 Q5 E L
∞ : A1 m# W2 h, W9 n30 b) U0 B r" [2 s$ D, I' r7 S
0 # V! y1 F+ s& I2 K7 H% q, P7 h ; E& ^9 m2 I1 r3 x/ w4 K2 L% \ 1 T# m- c, S$ l& o' m4 ?⎦ |* U6 U' {" G' A⎥" g1 u# K. K% k
⎥* P' L0 |% V/ R3 E# T/ _5 K
⎤1 k, U% L* W% z# \2 N: B
% _: [: F2 C7 }' }8 { W / } x9 E' J+ h) i) a+ u6 R% ^
2)邻接表) [5 q5 d5 \3 |6 z- ]( L6 Q
邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。# }! W9 b! \- a9 F
它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。4 r/ @8 v E! Z% ^
如图所示,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) 二元组。' @+ M. y: Q0 ^8 N7 {
2 b% H) S- i+ | r2 g5 ?) U8 E% Q" p6 |! e8 ]& h) x0 c% r
在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;1 b: O# S; k. k5 a
vector<Edge> edges[maxn];( T7 G' I O0 ]/ `4 y7 o, }
1% n! ?, K. [6 G% _& a
3)前向星 - N$ x; V6 s! G- {. g前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。8 j- L6 o. c; R7 d" }( W8 n; s
它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。: c) Z- I u0 p4 {# J4 _3 G0 e k
如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。 - M6 R2 F; f$ u) Z7 N" Q. ? }. ]& u/ V3 C/ p) Z. \. @: w' ^ r) H, m
那么用哪种数据结构才能满足所有图的需求呢? 4 P8 I; p' S1 D* C7 _" ~, Z" ?$ b接下来介绍一种新的数据结构 —— 链式前向星。6 `5 ]8 }4 l! R' v
4)链式前向星5 I6 G& A1 V( ?+ N+ {
链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。 ( b2 G; [/ u' c! w' X* ?% G具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。 3 h0 Z+ a3 [& c x边的结构体声明如下: & u: y/ x6 @5 p1 L- K, @struct Edge {* g: u/ R6 j( W T- p! P! A3 l
int u, v, w, next; 7 w- s% \) ~, H Edge() {}% s! f J* i$ c0 T. K/ d: Z
Edge(int _u, int _v, int _w, int _next) :3 b& _5 g5 x& F; N- v& w1 R) _
u(_u), v(_v), w(_w), next(_next) ! |4 R3 j; k X9 ]+ U0 Q) e
{ 8 u# J9 s' M$ [+ _! U P }' u( P y4 z" t2 s& O2 v8 N; `
}edge[maxm];/ x# u4 p/ G( z' m. u% m' U
1 5 n' H0 E, } i' o2% _- R0 r8 g& |1 T! T
37 T% T5 g4 _# l6 K# \% d
4& O& J1 H: t1 }: a5 K: P, d
5. L* N# o* E, Q& d5 h. ~$ v0 H# o
6 \% _, c8 d/ Q U' y V5 E; A; M7: Y, |1 ?9 R1 ]9 U7 C( j% d
82 c0 B. [$ d( l2 w
初始化所有的head = -1,当前边总数 edgeCount = 0; ! q: i1 |* K U* ?0 T s" e. }$ T每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下: ; k6 D7 d4 J' ^9 U7 {void addEdge(int u, int v, int w) { 8 v q4 M( m$ \ edge[edgeCount] = Edge(u, v, w, head);& f! O- s6 f8 K$ W9 M0 ~
head = edgeCount++; 1 `6 P1 |3 ~" |: `5 u2 `# w p}+ } l5 j8 O2 v: q8 R. ~. K
13 L# h- M. @$ J0 c6 @ ]! a! \9 p
2 * w7 C) Q" a. |' A I" m& }: r38 J5 l a( L! ?9 K7 [
4 / K. A' m- b# {2 e/ A- l7 S这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。 % N# r8 l. A1 S$ D+ \" y Y调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。 # `" }9 B( U* Bfor (int e = head; ~e; e = edges[e].next) { ( z a2 l3 y& `# I int v = edges[e].v;2 y+ p: p1 v1 t: ]/ P) z8 a
ValueType w = edges[e].w; + p* b, A6 X/ f ...& P! J! V+ Y* ^- E+ {* X6 ^7 T
} " u. L( u. T! V2 c1 W1 U( F# K; `5 B0 v) Q J# R2 8 O( N+ |7 Z9 W1 h3 0 {4 Q. d* G* S% T) J4 * V C9 q+ ]3 N! M6 p- |5 ) Q2 f4 m" H6 y' h" {8 q文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。' m6 V7 q- q( P! a: x
4、算法入门 ; [$ i; X1 `# u$ ?5 U算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。 0 y' P) ]8 p' \* ]/ [: V + I S4 {! E6 N V2 r 5 u( P. e4 t) z5 L入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。 ' r$ l& V3 T; r# v& S; x对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。 4 ]( L$ d+ [/ q3 f* j1、枚举 8 N E9 @/ W6 x: V枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。 ' S& T- S( ?1 L9 T对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。4 v; }) e8 p1 \2 t) g! f
2、排序3 c9 y1 `( ]! X6 a6 u( Q7 G; q
既然是入门,千万不要去看快排、希尔排序这种冷门排序。 " T0 m: w3 N: H' h, C. ?+ }冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。 " U0 d. ?: ~. s7 Z8 e) ~9 MC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。$ r3 [! c5 v' v2 L- m7 g0 U3 f2 \+ P
3、模拟. |) B) w3 J- N; Q9 q, n! ]2 \& _
模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。$ b( }/ {( d- K" p
不管时间复杂度 和 空间复杂度,放手去做! ; i( V& ?! o r3 W% Z但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。 4 Q" g6 @! r2 O4、二分 5 `3 E# X; V% A$ H" z& B! c2 x二分一般指二分查找,当然有时候也指代二分枚举。% U/ Y7 @; ?" m% n- M$ C
例如,在一个有序数组中查找值,我们一般这个干:) v1 `( w# N |. C& _
1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;& k& g$ V1 O$ V9 I4 Q
2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种: ) T5 W6 V# t+ A0 l6 {" T, P! _ 2.a)目标值 等于 数组元素,直接返回 m i d midmid; ) }5 D8 C" |1 U& }2 d 2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;2 W0 \. A) M, \% ?* U
2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1; 1 t. F: L5 |5 O3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。 . P# m; m# g. }* L0 \; V5、双指针3 Z' t* d3 g, h4 b& [2 j
双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。 2 [: _- U4 E( @% [: g; E; i6 ]' t2 s( n. X% t
$ ~2 L( f( w$ \( `6、差分法 $ n. i6 ~, n! U6 o1 Q7 s0 F t& k9 r差分法一般配合前缀和。 & a. N, t! R q4 | a对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;4 ~! U- }4 b4 b" c6 Z2 @! v
假设 [ 0 , x ] [0, x][0,x] 内的 g o o d n u m b e r good \ numbergood number 数量为 g x g_xg ( N2 }, y. {) ^ M0 Yx7 Z. f5 s/ z- x$ w# U
( e( r$ Z1 Y5 a. g2 b ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g 7 w1 T2 j5 Z# _/ B' O6 \0 d
r # b/ s9 t) d+ Y, Q2 M / K+ S2 [/ q) f −g - j5 j8 c( L7 m) S' ] t* U' ^! M8 |
l−1 * O: h! m( n- S& W) ^3 k, L5 M ; W \# C7 H! S- L, m9 ^ ;分别用同样的方法求出 g r g_rg - A1 A i6 i+ ], N; n* F) u- h& j
r9 b* \8 n6 D' p
; x' ~% n; b5 o
和 g l − 1 g_{l-1}g ( |& b, w5 J, L7 xl−1 . \" t( `" `& _7 v2 | * I( }- M, n! D. n ,再相减即可;0 s5 f& s; ]& A( Z- Z$ b