QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4451|回复: 1
打印 上一主题 下一主题

❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-7-8 15:06 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    $ n: I0 S  v* A
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏); y# s- _4 c3 r, G  y
    ; f7 g5 z$ D9 N* i: G
    前言- h. o- C0 [: Y: T3 R5 I8 h0 p
      所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    ; l- K* U2 S6 U4 d0 ^/ ^  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    3 \+ k2 J0 J- H' Q* g0 n; g  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    4 i" A" \4 Q$ ]& Z  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
    / L4 r& h, j# P6 W8 F5 R' \0 f: E; U- ^, j: J0 [
    ' j6 G$ F+ O* b' b, e9 F; [/ O

    2 |! V$ ?- Y# m

    ; W  y5 |" U% s5 p7 E$ k- l- z8 G# y8 c5 C, E2 S$ D

    3 j' F) R+ ?! d) X3 @
    3 q7 T3 g2 p7 ?; g

    " t  G. s7 J4 V/ f图片较大,文章中有拆解,需要原图可以留言找我要哈& N3 e/ \* k8 D: v/ r' y& ?- U9 q& `
    1、基础语法学习
    . p+ B1 N! ?0 q* t$ n  l算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。# v# T/ j- j; t% [0 x+ u) O
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    : M; `/ R& ^7 N3 r
    . W. ^6 d; w) x! [
    / S( ^" {" n( Q! A3 G

    - p0 [3 s' x5 j, D; E0 G& {

    . ?$ r* `( A$ ]4 j6 f1)HelloWorld' h. @; A& z/ i, A- {$ n# v
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    4 V" j0 I/ w3 u2)让自己产生兴趣! t9 g9 o  Y5 o5 J" k
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。) F9 _2 d  ^- b2 D$ x
    刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。8 L" u" o& }2 u. z# h
    3)目录是精髓
    7 }7 d4 z9 ^# |3 w" R: G然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:* _* L+ Y! x/ r9 f
    1、第一个C语言程序7 v3 W( T& I7 F) u, ~
    2、搭建本地环境0 r2 [; m; m) M  Q; k! J
    3、变量
    ' {; t6 y4 J' A7 k& Z1 S4、标准输出' k& d% ^9 m* i3 {9 S1 L# ?1 [8 \" G/ t
    5、标准输入" M/ ~9 ?2 A) x& c$ K6 I$ j
    6、进制转换入门7 ^# u3 U# x( a; h+ L  f/ a5 d
    7、ASCII字符9 U  h& C" a' P. @: x) i
    8、常量
    ! h: S7 S* z8 O+ a# v0 ^
    , o  J; b2 D& n2 q9 I! Q' I; {9 k

    ) v4 {7 U! d9 b- ?如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    . r3 i8 h  _: y  \* b9 F: ^/ f! r3 J4)习惯思考并爱上它4 H- A5 O+ Z3 s* `. u
    只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。9 M% L. y5 v% ]* `8 x
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。: j. G; ^) Z) y2 g# l; M$ v
    5)实践是检验真理的唯一标准
    $ m7 y& @/ m# R/ Z光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。  V4 U. T8 a, K  \0 U( _
    所以,记得多写代码实践哟 (^U^)ノ~YO
    9 u; J1 c/ a6 B/ _# f: g6)坚持其实并没有那么难
    9 H" d8 F9 F+ E1 f9 z/ @0 q每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    2 N% |2 |+ b$ p9 V. X5 j7)适当给予正反馈
    9 r! X+ y6 D8 C4 h! q6 T然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。, b& w/ e; X% q
    当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。* x2 M& U: i0 c. |8 l$ J" I* {
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    ; s* c$ R$ T7 i8)学习需要有仪式感
    4 m+ e: w; m: u8 p2 Q# a6 ]那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    ( o1 A7 B/ o" f! h介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
      w3 ~: `% y0 K3 |9 q6 Y7 F$ q/ K1 X我也很希望大家的学习速度能够超越我的更新速度。
    ( p0 u# q: q8 R" A( f8 l2、语法配套练习
    : `) g4 }6 R/ V# g6 L. M4 s* w学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
    . g  x  y, e. ?( R7 Z而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:
    7 u: ~9 k% H* h4 u2 Q3 P4 I: O4 P9 z% h5 M
    % S- d7 g) Z. [# W

    4 G* N, @$ C1 b3 n. s

    ( x% G% M% ^% i$ x从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。9 i: i- A3 \+ j: Z' \
    这里可以列举几个例子:
    . q8 B+ ]$ [- S" k7 i; {1、例题1:交换变量的值$ Q  T) }. l  w( d( E$ t5 ]
    一、题目描述6 X, O  h  \/ |2 b" i
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    2 t5 y7 p# }! b
    + q/ j* S  x+ j) ~

    % ]/ G) [  s. W5 K
    : t  |, T7 t" y7 e5 h; s$ u

    0 N. S5 d( w% w0 K二、解题思路
    * \0 P/ I! ^) a$ f$ {难度:🔴⚪⚪⚪⚪: k& ^& g+ U& @" w, F
    2 s1 L% ^/ p& \$ C+ a

    : _1 p6 _6 r& p; l这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。: z8 P, D- N+ j8 j$ {4 i; {
    a, b = b, a2 ]2 C4 f/ ^1 P6 [: s
    1
    " u, D' |; Q0 \' z$ B6 e8 h. D. o在C语言里,这个语法是错误的。9 q/ J! T# E) ^6 D- P) Z+ e9 h
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?4 b9 {$ V$ \( n' |) L( D
    当然是再找来一个临时杯子:
    . W( _) {0 U, z# B  1)先把 a aa 杯子的水倒进这个临时的杯子里;5 @/ [. C1 M3 _1 s8 ^, l  {2 }
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    ; V3 \' R2 m0 ]( l. s  3)最后把临时杯子里的水倒进 b bb 杯子;7 h2 ]$ j* J2 |/ J* w. ]# ^9 ^9 H

    1 t1 [0 b6 P  V0 q, ?' v/ l% r# S

    6 ~' @7 c# p  _这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
    5 V+ I$ z# J: g; f) e* n
    3 U% t+ |. L4 ~. M$ x9 G9 g) _0 Y2 s
    " m5 m. ]1 E2 @, \
    三、代码详解: w  |! D& d& ]8 v
    1、正确解法1:引入临时变量8 o7 s* _$ N6 x) R5 N+ g
    #include <stdio.h>
    6 j$ @/ P1 m: f+ g5 M( Pint main() {
    : S  `; X4 L$ [9 b' M    int a, b, tmp;
    % B( ~$ s- O( [! S8 b2 `        while (scanf("%d %d", &a, &b) != EOF) {& H- \) [6 c1 j
                tmp = a;   // (1)
    ( E9 m& Z# }( X+ Z            a = b;     // (2)$ u# @% K& X4 U; c  R8 D2 L
                b = tmp;   // (3)
    + N4 h3 ?2 N: w" x            printf("%d %d\n", a, b);
    0 J( l: Z! v4 Z$ G$ o! Q5 D6 E3 g        }
    8 W0 _/ R& H9 J+ g# t; S        return 0;4 S3 ~, l7 p4 C( F+ P  M
    }
    " L: l8 h( c: W# ~  L: T: H1
      [6 H" W7 O8 b( e3 G! `29 H  K' s3 Q1 ?8 s- ~( ]
    3) P$ o# ^% ~% j+ Z! `
    4
    7 N/ @! E; v/ P5 R* S# r5
    3 |9 z3 }7 L4 P* Y1 O69 {" `0 N( |; P5 Y8 v5 W
    7
    ) n& r8 d8 ~; m) Y8
    1 Y, }: K. n  F/ Q4 N: T9
    2 V4 a$ C! {* J! D) @# d+ \10
    ; \( ^& v5 u3 J6 a11
    - i" c$ ~# Z& \8 V. b* K( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;  C% c+ D9 n7 ^8 M
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
    - Y! M4 ~7 b0 ^6 T( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;; t! d/ L' Z6 L! t% {3 _* J1 P
    这三步,就实现了变量 a aa 和 b bb 的交换。
    " N  `: q1 W6 c* J2、正确解法2:引入算术运算
    ' Z) B0 D8 g' t1 L2 N2 H/ Y#include <stdio.h>, O  R" Z" W( l9 Y
    int main() {6 s4 Q) _6 g, o
        int a, b;' e* p7 J( D1 s8 Y0 B# w" w/ ?
            while (scanf("%d %d", &a, &b) != EOF) {2 U7 M# R0 b" d5 F9 r* L) p6 G
                a = a + b;   // (1)  X! N5 r) H5 ]* Z
                b = a - b;   // (2)
    # V, b# }8 Z/ y+ L# A3 f            a = a - b;   // (3): n* l- z' p, J3 B
                printf("%d %d\n", a, b);
    ) J9 X5 e4 S6 S$ P        }
    % h; R! P1 b1 u        return 0;( G0 }: b$ T, G$ U4 P
    }2 y& I% O! H7 }) L  V6 k" a- B
    1
    * ]% R, T. Y" p' Z* {' I6 r2
    : u7 T8 a4 U4 s. b3
    # A. d8 M* W* C3 w. O) ?5 p+ I4( m0 U) _7 f. D9 u9 _# X4 @
    52 V  A/ V$ H! S  F/ R
    6( J3 p+ U; U- y+ S9 S
    71 e7 H+ M9 a0 D* }
    8
    ! C% R7 b8 n: V9& O0 h, _( X% U
    10
    0 {7 R' H: |% k" ^11. c( i; K5 J0 w3 K0 I
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;  q" `( ]- T2 t! H& {5 r3 ?. O
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    ) I6 L  r% i: I- o  i( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;/ G! \  W  w. m6 k: {, }* N' F/ A
    从而实现了变量a和b的交换。
    : S  v  G' t9 V2 v$ A! b3、正确解法3:引入异或运算9 Z9 x2 N6 i3 \3 W' I; g, I
    首先,介绍一下C语言中的^符号,代表的是异或。- D. {, L( r1 b7 H- G& M
    二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:! b* k+ L6 }7 h' M3 O* W; \  o$ e" K
    左操作数        右操作数        异或结果0 ^" ~" t. O: ?; E
    0        0        0" Z8 s# v6 _& E/ z' o) i
    1        1        0
    / m# Y. c! v$ a' p: _" l4 L0        1        1
    8 z7 K5 Z  c* u: n1        0        19 {. z5 z3 U7 L- A
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。' g" J" _% g4 u" D
    这样就有了三个比较清晰的性质:# T* n8 [3 q1 ^7 U1 v4 g' o
    1)两个相同的十进制数异或的结果一定位零。
    * O2 N, S. w! P" m! |1 [  n0 S. O2)任何一个数和 0 的异或结果一定是它本身。5 A9 R8 O8 P3 e9 |' y) ]4 a
    3)异或运算满足结合律和交换律。
    0 z& H$ n% ?7 n% t#include <stdio.h>
    : |7 h6 h* ]+ p; N+ O6 Iint main() {. s- I+ Y" L1 t. B8 \  M  z
        int a, b;
    " l/ O/ r3 U4 }        while (scanf("%d %d", &a, &b) != EOF) {
    & u% o4 \$ v5 x& f1 {            a = a ^ b;   // (1)* X" u$ _- t  |) T* Y- a
                b = a ^ b;   // (2)" r$ a! }6 T  Z2 m
                a = a ^ b;   // (3)
    # G. a9 d! Y- D; n" Y            printf("%d %d\n", a, b);
    1 n1 J7 Q1 f7 c6 I- [        }$ a5 R; i1 u3 K- [' g. X* d
            return 0;
    1 w: D. E0 J5 X$ X# a( b: }}
    ) k8 k# n. i" m! e1; j1 i/ F, b+ t) h: `
    2
    , q# u6 |, I/ U: l5 ?! q3
    9 G, _" }. g! |4 f: k4 v4, G" R& C! T, g3 _5 v  v% V
    5
    ' x' }$ g* S/ K/ i6
    3 e6 J0 ]7 o  k9 e) m7
    ; D4 z/ Q& \% O0 e& I4 _8
    / V# O: a3 g* Q: u3 w90 {0 h% u" _0 m, }
    10
    5 o" D& Z6 e; G* N11" b7 y! X0 k" C- A7 d
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。& _, E  y- [) e$ }, e- }$ {
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
    " ^- U( j. L, [3 L1 e) z; E从而实现了变量a和b的交换。# d  ]. k6 j" y  W* Q/ Z$ n

    # U& ~' `) y; w$ c4 l2 k/ Z2 S) m
    - U% C2 [" K9 D4 T
    4、正确解法4:奇淫技巧$ J* Y3 X* |  s4 Q) O
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:$ _# v( }0 }2 h* E9 A) w
    #include <stdio.h>; ^3 {% [! m, j4 ?' [
    int main() {
    # z+ Q7 l; e3 F4 o! f5 V, B* i    int a, b;2 S8 W: l# a+ `7 M2 b* x
            while (scanf("%d %d", &a, &b) != EOF) {
    * m* Q( j- U& D1 @- u9 k* D            printf("%d %d\n", b, a);
    % P; K0 a% v) `        }% g" R. F& }4 k( K( _8 \
            return 0;
    8 y4 I- w; p1 P( L; K}# Q  |6 \1 b% y9 O; C( X
    1/ g" m9 Q0 o8 Z* _
    2
    2 j  _% G% l: ~+ J34 d3 \' Z  G0 P) `7 ?
    4. G  Y- I  |1 o% _5 Z: @/ a7 K
    5% E" ?2 c+ Z: T& Y
    6# W3 R( i1 u: w5 a
    7
    5 [) d$ Y, I! h3 k# j) M+ W9 p$ i84 h/ I; D* L/ A$ i0 P+ @
    你学废了吗 &#129315;?
    + p! M0 x4 Z. {1 G. s; F% w7 M3 B2、例题2:整数溢出3 T9 t( T: j& r. ~: V
    一、题目描述) c; F3 l/ ^4 i
      先输入一个 t ( t ≤ 100 ) t (t \le 100)t(t≤100),然后输入 t tt 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62})a,b,c,d(0≤a,b,c,d≤2
    " z9 W% t- p0 e/ n& G; q8 f* I62
    # i7 s# v: D+ n ),输出 a + b + c + d a+b+c+da+b+c+d 的值。$ ?, J0 N2 F7 {& E

      E, }7 h' K4 I% q7 C4 n! C1 M* w

    0 N* ?4 X( ]. y) I! E二、解题思路
    0 w( Y1 c9 z4 ~7 h, J难度:&#128308;&#128308;⚪⚪⚪
    $ B: x8 c8 C5 C$ y$ l
    ) Z% p$ k6 U9 Y$ Q/ Q
    : f" W3 m' F4 A& A2 k
    这个问题考察的是对补码的理解。
    0 F* _4 ^' w6 [5 P+ `0 y仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
    $ l+ I+ Z9 q+ O0 B/ y/ P7 }62' T$ |& q) z. f' L. W# E3 X% s* S: d, J
    ],这四个数加起来的和最大值为 2 64 2^{64}2
    " c$ M2 n, C; f1 _; J8 q640 F/ k' V' C1 e& g  P7 D7 j; ]
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
    & e& ?4 Q! A9 p/ f; U639 E5 V3 y- Y# v1 F1 X
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12
    ; q6 h5 x+ T3 t- W8 m! E. y64
    ; `3 l% e7 D7 N( q+ u −1。' W: |  q2 ?9 P, E
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    ; r' ^0 Z/ g# C( F62
    % ^% {. P6 @- |& W" T  时,结果才为 2 64 2^{64}2 6 z/ B3 O  F: O
    64
    $ p7 v7 N( s4 G2 z2 E ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
    6 h6 Y2 q2 x" `% |/ c三、代码详解
    ( F/ S% C' e: y#include <stdio.h>
    % ?+ n" x. D1 s' b; ?3 r. G7 Qtypedef unsigned long long ull;                           // (1)
    1 b6 d3 i7 |3 K8 ?/ T1 b' Cconst ull MAX = (((ull)1)<<62);                           // (2)
    * D! p/ M' N# H- z9 S% _/ [) \7 r: [, x6 D$ ?

    6 h  f9 H/ W& ]int main() {4 {3 @* S( d8 d) f3 D. o
            int t;# z1 t, U4 N  }" P3 D1 y
            ull a, b, c, d;* f5 D7 s7 f+ G) Z% N& m, R
            scanf("%d", &t);$ z* J# b9 C3 H0 F$ x
            while (t--) {6 E$ ^- g# e6 I6 X$ F+ k# S' W
                    scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)  z, y9 h( r8 m* a4 |
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)
    2 h1 G3 ?. ]- j( l: M                        printf("18446744073709551616\n");             // (5)
    # g  Z: d0 z$ ]3 C* Y! m1 I                else
    3 O# F  S) I* y6 ?0 b* i                        printf("%llu\n", a + b + c + d);              // (6)
    7 x, ~& m7 e( D- [# s8 X3 L        }+ S2 m$ l0 C, I, k5 N
            return 0;
    ) n+ S8 p8 O/ A! p8 ]- Z}. S8 [4 \+ \: ?9 _
    1
    0 J. I, q9 \: N4 ~; c) x+ a- B2# x' u. h6 O* D3 A- o+ M
    3
    8 r6 ^, K+ Z$ n3 w7 P0 E' K3 m4
    " |; }3 M* l/ a% ~52 B$ ^. {6 O2 K1 Q( o
    68 I0 S  v; {9 [: D% g; @" H& }* ^
    7
    5 r6 A9 }. _! m, t$ M8/ ^" i$ F% M- {- @
    9% M6 s; {. P6 [" v8 ?6 N
    10
    8 X* h$ B! J5 j6 F/ x1 q* ?11
    - A. @1 D- p& H$ M" J12( s  O: ]2 r! O- j# s
    13! y7 w: g3 j/ q( c" m
    14- {; ~. D, Y* Z) s, [* M1 C
    15
    1 R, n# T/ a( Q' C& v16
      J$ D1 s) w+ L8 ~/ e4 d: q; T7 d17
    0 z  s0 @: h6 _1 W8 a, d8 y( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;+ ~3 Q& w* n1 E& ^- n9 s" f
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 ! B9 a  c3 M0 X. Z; v7 x2 d. Y& P( p
    62  }4 Z  N% j+ {8 c4 G) g1 V
    ,这里采用左移运算符直接实现 2 22 是幂运算;* D6 O5 Y7 R6 t8 C8 _* [' r& ^6 w
    数学        C语言5 C7 t9 L, M/ |8 J- h  C1 s
    2 n 2^n2 " S6 B. v0 ~9 y" ^- S% b: ]
    n$ n2 P0 {* w+ ~( p6 O+ O& M$ {- k& ?
            1<<n
    2 M2 ~* f+ ^- @% u需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    ( t, W! B9 T, o" P( 3 ) (3)(3) %llu是无符号64位整型的输入方式;/ n/ z4 P8 v; K% y
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;4 b; \- Z5 }! V. o/ Y4 i. `* C
    ( 5 ) (5)(5) 由于 2 64 2^{64}2
    # d- h; f9 {2 ^% t( V, z, f64
    % {# b# _. y6 e: v4 B* ?' \  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;4 p" w: t$ ]8 f7 i: E$ e1 g
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    ) J  L7 m4 [  n  O% D  e$ v64& m  y  t! ], Z5 h8 k' X6 s8 p
    −1] 范围内,直接相加输出即可。3 j, Y1 h1 X2 f/ _  y" i
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。: A. W: Y: T1 O3 Y6 e6 c, K
    为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    ; X  _  T/ W" K3 ?7 B  \; |" F7 L. a/ w) K
    9 a' G0 M. q/ r  q8 j5 L
    3、数据结构- m8 ~3 _8 R) h. R
    《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。- H5 v0 \6 n+ Y+ [
    1、什么是数据结构. H2 a1 y! E# Q1 p" ?
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。' C9 v* r9 E3 [0 _% ?& p
    如果一定要给出一个官方的解释,那么它就是:- `4 D6 X( H6 H
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。# H2 P/ H2 u, c9 F" O& Z

    + K; j) L2 M& L: V  c& M

    0 ]" d$ D/ D; C! O! [是不是还不如说它是堆,是栈,是队列呢?2 G, j$ m( d* q8 i( _
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    ! f5 D* C( D9 m. o" g. x" d* ~; r3 l( E2、数据结构和算法的关系
    ) t4 h$ D! G& D' L8 I很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    7 l! H7 j1 S4 t1 R4 Q数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    3 k: \5 Z3 H% F: n" d而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    0 a& [% l. h3 l讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。( B9 \6 v, z0 \
    3、数据结构概览* H2 ]/ B2 M9 ^4 `$ ?' X0 `& G
    周末花了一个下午整理的思维导图,数据结构:: _- R6 ~' ?, G
    1 t4 ~# v4 O. n  U
    * V- h8 f. r6 x$ S  n
    常用的一些数据结构,各自有各自的优缺点,总结如下:
    ; C' X) l- n$ ~4 I0 `a、数组
    ; t: D6 y0 ~$ H5 n* N4 b3 E+ u内存结构:内存空间连续
    7 s: u3 U- f: o' _实现难度:简单/ Z' y' @7 w1 y) H) c5 D' f3 a$ O1 y
    下标访问:支持
    % ~& W1 i  a0 c! H) k% ~1 w分类:静态数组、动态数组
    3 m  ?3 H7 P- U2 Y9 T插入时间复杂度:O ( n ) O(n)O(n)
    1 T% f+ N4 P9 I: m1 }/ a3 u: O查找时间复杂度:O ( n ) O(n)O(n)4 `" r7 d+ @( G+ H  X
    删除时间复杂度:O ( n ) O(n)O(n)
    ; g  N3 F, S" L$ Y2 E; N
    ' q7 C; B# ^: h" Y

    2 l" v, w. G1 l6 L. I' Lb、字符串
    , T; A! c& R# r0 C2 B内存结构:内存空间连续,类似字符数组( D# {. a+ F6 c1 T8 Y- @; V
    实现难度:简单,一般系统会提供一些方便的字符串操作函数" C  l$ A4 @3 a9 ~
    下标访问:支持
    ; \4 U: M3 `0 H1 B" A# H2 d插入时间复杂度:O ( n ) O(n)O(n)7 A* `' R# p& ~! I6 B
    查找时间复杂度:O ( n ) O(n)O(n)5 u2 g/ `# R$ P# j
    删除时间复杂度:O ( n ) O(n)O(n)
    3 D, [4 u$ L; Q- Y$ I& Y4 V
    3 `$ t+ ^9 P& a
    ' b6 b- O+ }# T: M0 B
    c、链表
    0 H' ?; K* ^' J: e$ n2 f内存结构:内存空间连续不连续,看具体实现$ x7 r' d+ W3 H* `0 y
    实现难度:一般
    9 ]- x) U* W3 J* n6 J2 O下标访问:不支持
    5 x) a; ]5 W/ @' j" h分类:单向链表、双向链表、循环链表、DancingLinks5 M8 \" c1 _( d" Y1 k& i
    插入时间复杂度:O ( 1 ) O(1)O(1); e; m- w; E6 o2 v* n
    查找时间复杂度:O ( n ) O(n)O(n)
    5 v* {1 j  F. E! i$ Y; s删除时间复杂度:O ( 1 ) O(1)O(1)
    ; r8 P1 ?/ |. {6 V
    / h# Z* Z& n& t9 x9 s

    1 g; @9 P- ~2 O  v1 f4 f  t- j! S! dd、哈希表' ^' B' |5 h$ f# V3 M, Y+ k
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    % G+ l3 B' C0 j9 c8 M5 Z/ p' s( S实现难度:一般
      J9 o) H1 J0 j4 v4 `( b下标访问:不支持
    2 u# o! @5 q% K; Y# P7 M分类:正数哈希、字符串哈希、滚动哈希
    , D: `% Q+ F; O插入时间复杂度:O ( 1 ) O(1)O(1)
    ) @# y" p- _$ P) a9 D5 A查找时间复杂度:O ( 1 ) O(1)O(1)
    ! n0 X7 O8 u2 ?5 K7 _( v删除时间复杂度:O ( 1 ) O(1)O(1)
    / o5 ~( X% Z2 }! E1 G! ~9 N, S+ {7 `0 x' j+ `* l) ?4 G
    - w! t: q' n+ _/ y
    e、队列( I% |* M4 W6 M% T1 ]+ c! e2 m& |5 o
    内存结构:看用数组实现,还是链表实现
    : l7 M, g% f2 D! w实现难度:一般
    . {# Z- o4 A9 c下标访问:不支持0 \1 c/ a1 ]0 n  V% E0 I, e" j
    分类:FIFO、单调队列、双端队列; n, z$ m% Y. s0 I- X
    插入时间复杂度:O ( 1 ) O(1)O(1)
    , [3 [& Q4 \1 t, R* g查找时间复杂度:理论上不支持4 U% ~3 Z; t' v8 ~; m& F0 M
    删除时间复杂度:O ( 1 ) O(1)O(1)
    * V: d' a1 m8 ]9 |4 b. `* h- V! W2 t9 b; f* \! \
    % L) U4 D# g  @! X
    f、栈- h4 Y' K8 I3 U# u
    内存结构:看用数组实现,还是链表实现; v/ c" ~2 \! b
    实现难度:一般; x! {8 h  }% ]
    下标访问:不支持0 K, b  m7 z- M* F9 d1 k
    分类:FILO、单调栈
    % k9 R# o  L& L0 D插入时间复杂度:O ( 1 ) O(1)O(1)
    3 T  `) @4 {' y3 Q查找时间复杂度:理论上不支持" D% U) X, s/ p# Z- L7 c  l6 S
    删除时间复杂度:O ( 1 ) O(1)O(1)
    , z) e6 N* m+ h, \. B5 N% n7 D: U' w" k! |4 G2 z7 e) H  `+ b( b

    2 c( k3 m' ^' N& M* C9 h$ Rg、树
    4 k5 q8 W  L8 V" B+ l4 P内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
    ( m' K' ~2 }6 o% ]% d. Y" _6 @, q实现难度:较难3 {7 g; b+ z" T
    下标访问:不支持
    2 C3 V- {* \; F分类:二叉树 和 多叉树" n+ S0 ~" s9 j& y; C
    插入时间复杂度:看情况而定
    2 R& z! u: Y! c0 \" K4 m+ o查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log 5 Z8 Z# f( W1 z
    2
    7 g" H, r4 y7 A+ w( s​        4 J3 X7 z/ I) y7 B
    n)
    ; }8 s: |. q8 T9 x. `; [删除时间复杂度:看情况而定" T% B+ F6 v# L: ?

    & n" L8 _1 N! ^  T

    . F9 [& [+ T6 f" H1、二叉树
    $ Z$ K$ d! B& U0 S7 m5 ?二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。8 N! {! L, s" f3 x6 L. W
    其中,堆也是一种二叉树,也就是我们常说的优先队列。! X6 ?+ B) @- H( ]0 L, J/ ]$ n! f+ M
    2、多叉树
    8 s) u  G' E; }% _+ z! H4 bB树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。6 J( a6 L, q; n! n2 N
    h、图
    ) Y  l- v6 F& s6 J内存结构:不一定: B! ]  V, v0 a
    实现难度:难( u" J" s! g4 X9 {9 `! T
    下标访问:不支持' w* X. e# X: N
    分类:有向图、无向图
    4 A. W1 Z2 o& X. R% _" ]插入时间复杂度:根据算法而定
    / a1 z  D% S/ G: p9 M7 C2 h查找时间复杂度:根据算法而定/ c- W1 s$ x9 C" K  ]+ W6 C
    删除时间复杂度:根据算法而定" ]% B8 P" S! k3 z* a

    , r! r2 \% A7 D- ^# H* E8 E

    - F  }$ G4 q, m! w2 N! `/ W6 p1、图的概念
    2 T3 \8 T, n2 J" e0 A在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    9 c* O! i5 g5 M9 d% K+ y1 D# G图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。4 ^4 K% `8 }3 X9 x9 j$ q1 d
    对于无权图,边由二元组 ( 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 为权值,可以是任意类型。
    1 @! B0 L) F4 p7 ~7 q图分为有向图和无向图,对于有向图, ( 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;* G! A2 V' y+ _& j5 [" C9 w- P0 j- i
    2、图的存储& C2 G4 C( |! Q" @$ b2 v6 Z' ]
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。3 P; z2 ?  F& `% W- i5 s
    & o7 n% s$ j7 A2 R( L4 ]% t
    5 T9 }+ D& ~5 d5 ?$ k$ `* A: R
    1)邻接矩阵8 v( _. `3 L* k- V
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。
    6 K4 e' y+ C! T, E) c: H它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。) D! ~  v6 P  o! O% G7 m$ j2 O
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[" Q4 \! B  v  q3 f
    01∞9∞0∞8320∞∞∞30' S! K% @2 I# P- i& s
    0∞3∞102∞∞∞0398∞08 _6 H5 V3 `) l
    \right]
    7 u+ B- a6 v2 L3 q# x
    9 O1 {3 q) o0 Q; r* f( Z
      n) m, c) I& J1 i1 a8 m& A
    " d/ Y% ~0 }# u
    8 l0 B: K; P/ {2 B7 {" u6 e​        , C/ R1 e3 P- E( a
      4 D, K0 J8 ^2 [- P& T+ n
    0
    1 K1 j* L7 s; c3 K( [7 L# J1
    & R* _7 ?2 z. c( E6 E
    / b8 G9 Q) `' r$ ]( }+ U, d+ g9 M99 L# p* [6 c& K- M& e% x1 D
    ​       
    5 R" ^/ H8 {( E/ t2 C( O  
    ( P2 S7 {2 S% ^' D" j$ Y% s0 j- |8 t! I$ q
    09 u: n6 f+ x) J3 f/ j! g$ B
    1 ]( {; S" b# V4 F/ C
    8
    4 [/ \- u9 e+ ]8 W​        " n7 n, ]0 ]: |' \  i: ?4 M
      . z4 t  n* p3 _9 g4 F- S* g" h
    3
    ! X  \: h% ^$ x2
    " U! T/ b7 s' D0
    - s% K5 r+ i( R( ]+ N; {7 Z* i6 y" q, t3 q9 l$ a
    ​          M* w! P/ Q3 a0 R. b
      
    4 D9 ]6 ?# Y  D; y
    ! `+ E( A/ {4 c3 f/ w/ u. L9 ~
    ( b3 U5 U  ]5 T/ I/ R3 ~34 k9 I7 N" D$ s6 T
    0
      T) h' \! v8 o  |7 g2 `5 E; H+ ~​        4 i9 w! i8 e3 ~4 e" w9 w
      & U$ w7 g$ f5 x8 t) s' }; z0 C. u

    ; {: ?6 H0 C$ Y5 Z7 g! [9 a% Q
      r1 s% y1 L8 W$ @2 q3 K- o' V) p+ I0 U5 u' a5 s
    6 A* R& o" F! O, S  H# a5 G
    ​       
    % }/ t9 Y" \! |/ ^6 q- E% t& a  s- ?. a + ~9 x0 E! @5 x! U$ Z
    2)邻接表7 n; `$ b9 G5 ^5 a5 V4 D9 G
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。0 ~; P* V& P. e- c) q
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    - a' @+ \& @% [) Z) i  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) 二元组。  X4 s# Z4 M) J( a# C

    8 M" _2 {, l1 m  G2 m
    9 }/ {$ Z+ `  j
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;9 Y4 F. X$ x* d) I1 V
        vector<Edge> edges[maxn];$ G$ e' h' F3 X) S
    1
    % M$ n$ `& f" O' H: z3)前向星! }; B- O- ~6 b- O
    前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    3 E8 J5 n& S& n& N: ]7 Z  ~它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    5 ~  Q% P; S' Y! g, C. z如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。7 S. l) J' p, I/ a0 K% F3 z4 i
    % ~& E6 L% E& [
    + `, e& f( D. w1 V7 R3 H8 w2 X
    那么用哪种数据结构才能满足所有图的需求呢?: g: f. @+ ]" x+ n6 o4 Y
    接下来介绍一种新的数据结构 —— 链式前向星。
    9 o9 L# n# e- @. w) E4)链式前向星( u2 S5 w/ o$ h6 ^
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。
      Z7 {" F# O. Z3 m5 \具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。# L% ?6 G  j6 @, d. u
    边的结构体声明如下:
    / a( c7 D0 i- z$ X6 W& L/ Jstruct Edge {5 _2 C+ M; A; N  B
        int u, v, w, next;$ T+ x7 E$ k& f! M* F+ H2 d2 v
        Edge() {}
      z; a  s% G  S! T! B' R9 n2 t    Edge(int _u, int _v, int _w, int _next) :
    1 u7 _7 n" O0 u$ n* i9 X+ R8 O        u(_u), v(_v), w(_w), next(_next)
    : a1 N; q" V. \1 `/ G    {
    & H/ x# Q; |4 r    }( }% X: h7 j6 D  t
    }edge[maxm];' w6 V0 b: i) Z8 I& d7 |
    1
    & v, T0 z+ _  V- _# J7 z, _0 ~+ B2$ A3 I7 W/ U/ o2 Q4 W1 \: K
    3
    ; x5 P% n) ]" x+ Z6 _4
    8 N3 G# n& J1 b' w% C/ ^( A5: U4 I7 T: r, C# J, C% X
    6- v# t, h1 M4 l$ C/ @( v' u
    7
    2 h9 l: c" A. I2 O8 X6 _6 P8
    + b7 h/ t' Y8 u9 |( G, U初始化所有的head = -1,当前边总数 edgeCount = 0;' V! T- U1 u' Q4 }1 O( P8 g
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    * I( q7 i, n2 R2 F9 j. f5 Nvoid addEdge(int u, int v, int w) {
    * u; x- ~& t- C( |. l    edge[edgeCount] = Edge(u, v, w, head);
    5 a" ?5 ~& G! F* ?# ~. _- h    head = edgeCount++;$ S5 G+ R# n3 F* W4 c, k6 C
    }
    7 j) O+ n% [/ U! {1 Z7 m1
    ) x+ `( y. t) I( k28 ~* p! z$ g' h% W  Y+ R% j
    31 f+ n( o4 N0 l+ L9 d9 @. k0 s) q
    4
    & Y! z9 J# f8 ], W1 o这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    : B9 a6 Y9 H; t' O7 f! G  r调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    8 b% B" g7 b7 j: N: g+ afor (int e = head; ~e; e = edges[e].next) {
    * _  d9 S1 U; U7 l    int v = edges[e].v;4 M$ p5 n5 p. c: x- \( \
        ValueType w = edges[e].w;; z, M6 u2 F( {
        ...
    # o% V" @$ b  h* P5 W3 {1 T}2 A) Q' {8 p2 Y1 K! s
    1
    " T" I8 f9 [' ]( e5 b3 m$ V2 d29 O& W" u6 O  @; Y  q
    3
    1 `) k* }5 a3 z49 y* S1 }* F" Q+ g7 e
    5
    " G4 x  L' f- X+ U( d1 M文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。& Z4 `9 N0 G' f2 M& U; X  z3 `; m
    4、算法入门. c; o. b5 p1 v) B, L
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。/ E( V+ _9 y- B/ {$ j
    ) ~# X4 F2 R% l0 W
    & i( w: q1 y6 A, n# W
    入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    - o3 c! w' O' ]对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    6 _; p5 u" @3 y: q" r1、枚举
    # A) z! R" n2 G6 W7 \3 i枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    7 k; v% e: A8 O对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
    ! \0 c5 g; u3 Y* L% Q. R2、排序
    0 W' r1 p. g* k既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    ) ~" ^: r0 o* k5 ~/ ^冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。, n7 v4 h- L2 \& ]) h
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。* R2 {8 C8 H' k3 ^9 u/ n8 V% `
    3、模拟2 m5 N% `& g3 H1 F& j
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    . M2 Y% }# N( r' v, V' i不管时间复杂度 和 空间复杂度,放手去做!
    ! \! c) y2 z/ ?* }1 u# X但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。8 m. ~9 i8 l- v6 \
    4、二分
      F3 Y% B  o$ p: ]* t二分一般指二分查找,当然有时候也指代二分枚举。* N' h6 ?2 d6 u- ^! R) D
    例如,在一个有序数组中查找值,我们一般这个干:5 Z5 i% r! x; v6 t: @" G
    1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    . ~1 [6 K; e; l- s& i2 e2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:- b+ U. J: [! n+ t8 {: f
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;. f/ C( d* N9 W  D+ Q
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    $ b: |( h+ l6 C# s  C+ c  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    - q& N; K% X, g: H1 V( n3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。% \& {* ^% W$ [! P: t9 o" {
    5、双指针" w, Z8 Q- b1 s( _- o/ w! y7 X
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。* E9 w2 k4 {8 N) d( D$ A

    " J: Y& {( P7 n: I6 W
    5 [  {& O9 h% _: U. V
    6、差分法
    8 q* }4 Z- w" W- Z3 F7 v差分法一般配合前缀和。
    ' a$ ?: C; Y4 k, q9 G对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;7 R' h; T+ n4 J% M  L+ o$ b9 m
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    1 b' F% i& b0 Q; L8 sx
    5 d: M; K1 D" b% b​       
    2 ]' l# ^% \6 ~# c ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g " O# D& N% ~7 E9 b( p. ~1 ~: ]0 V
    r
      |9 K% J% B6 V2 S* I8 @$ c​        ; l7 K# Z; C5 ]1 ^, h: J6 I
    −g 8 X0 l, }" R& s) `) O8 K1 B
    l−1
    . M4 f7 K* q+ b- C% A​       
    5 f/ w8 C9 {: f0 `1 s" W5 Y ;分别用同样的方法求出 g r g_rg
    ! u: r# K& c$ W6 H" E6 _1 F$ k0 Fr
    % g  B) B/ Y" H" |5 O6 E- Z​        $ X4 F  J/ Z  z
      和 g l − 1 g_{l-1}g 7 I- t5 {' Y! z  x% p6 m7 ~
    l−1
    - G7 T% r1 v7 h$ z: x* Z​          I$ I( W6 ^7 g8 {4 |7 p
    ,再相减即可;: X4 f7 t$ S" y4 e; k- ^* ^% T( t
    % b) A* `- ^/ d4 H8 L4 i

    " F% z& v- o- f5 @* Q! K7、位运算* r) o0 W* g5 A: `1 D
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。- q* }8 f; R' g6 S$ H7 Y$ }
    位运算分为 布尔位运算符 和 移位位运算符。
    5 l5 Z$ p1 n. D: l布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。* B( [5 _3 b* ^6 F! ~9 w
    如图所示:# g8 m' [+ G' R8 o# f' q! N
    ( ]5 H' g6 t2 w) G7 y* r' e3 M% U

    ' Q; P7 E7 ?# x7 U3 N% U% M: ~位运算的特点是语句短,但是可以干大事!2 x. C2 w% p1 }. f3 s
    比如,请用一句话来判断一个数是否是2的幂,代码如下:
    ; ^& i2 P) l* Y9 d' q1 G2 C/ R!(x & (x - 1))0 }+ U- a" n- ]% R, \
    1& v* ]0 i0 |/ _/ p7 p% q+ ~
    8、贪心9 q' O9 K( l7 b0 g. G" v7 G8 y
    贪心,一般就是按照当前最优解,去推算全局最优解。4 T1 G. @6 {* d/ p# [6 y4 O3 Q8 _- }5 j
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    ( U- `- i6 l: O9 ~2 V9、迭代- I' @4 p3 l9 l9 _7 Z
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
    6 T* r0 K, H. f$ x6 e! ?1 e, G10、分治& O+ u0 B( k5 m) o4 K
    分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。( P! T; r6 n+ [+ x0 t! C' V4 p& h
    5、算法进阶# }! J7 v/ R- j" K. E# Y( D; z: |2 C
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。( w: `, W4 j- d' v  T( {
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。" t$ @3 i$ e4 c3 M5 s1 J
    这个系列主要分为以下几个大块内容:2 g9 k* [  l$ c" @3 q. s
      1)图论
    6 o% E0 X1 m8 L  2)动态规划
    7 z: H! s( C! ?( L  3)计算几何! `7 r" P' C3 J; [
      4)数论
    ) O! T: S+ L$ [8 ]; S9 x  5)字符串匹配% [7 X( S6 t' a
      6)高级数据结构(课本上学不到的)
    6 h' v7 O6 f  a  R6 L  7)杂项算法
    ; B+ C- J6 c1 k. D' y) m
    1 l8 q4 r, m8 B6 i& d

    ) R& h- ^4 N: \- |" ~' _先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:) X* I* J% c" U- X

    & b+ u5 r1 e, M  k# f6 T; q
    5 X8 y$ A1 L# M- l' J& i
    ! d* c; z  @& V

    ! [+ l0 @. b* f$ k( v1)图论2 ^0 R' t8 V; [
    1、搜索概览
    " T3 L5 b7 ^$ f; {& y图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。" v% B& t- s* z* ]
    % H% y( Y0 H* U

    * d2 ?9 H. Y: D% V8 M比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    4 B8 K( m+ y) Q2、深度优先搜索! h- I& K" d' {1 O% S# {1 s
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    5 D/ X) M  V' K3 m! Y- Z原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。9 c. f% E$ M" g' q' Q
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。$ Q* y1 G# x: X2 ?+ Q. Z4 A
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    1 T& Q$ f9 ~1 c5 b6 W) r2 a4 l( _- y& d
    1 m& `. V8 n; V3 A# i
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:- m/ j; S7 E" A3 o& N+ t
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    1 o+ }6 _7 m( z% }2 \$ a1
    - f3 T+ R( t" I) G# j同样,搜索的例子还有:9 Y7 d* c8 Q8 ~& _" Z
    3 l8 c. k" q; j: M  e

    + v) [; [- f. i$ Y& p' z计算的是利用递归实现的 n nn 的阶乘。
    5 v  R2 b: {( S$ K! u2 h3、记忆化搜索
    9 F; y7 r( Y) D' o: \4 R1 v/ `对于斐波那契函数的求解,如下所示:$ C/ E, X( g1 s3 T% q# t  X
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =. \, @4 }  e: z4 \/ s
    ⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)1 @6 j- N/ p7 F
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2), m; L0 F7 s+ d& [
    f(n)=
    ; ^9 w2 V0 n. [/ r8 `. j8 a3 ^" T; Z/ v$ I) v" C4 T/ c2 W
    # G; q* Y7 @) N. O: M
    3 N" M0 D6 ]% N* W4 f
    ( b0 Y2 Z% G& Y  ]' b: L

    ! q% h  P1 c+ ]6 i​       
    / R# r( l! Y5 j1 h, z  
    5 M; z$ z* t. ~/ Z' G1
    9 T' _  p- x% i/ h7 h1 p1
    7 C: a: n: w; A4 wf(n−1)+f(n−2)
    + O$ P. m- L4 P; G7 k, d+ v​       
    " k5 ]; }3 ^$ f0 G, B  
    2 ~% Z" F) q$ ?  |2 I" d; o(n=0)
    ( ?+ Z/ q$ t1 J(n=1)
    8 p4 K8 ^" D2 l& `- _' a(n>2)
    . U0 l+ m! Y4 N5 W9 U! ]​       
    9 j6 }7 q6 I2 a: S0 }$ I+ W ) X* r6 W+ w) P5 o. q
    对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    5 `& T8 p- }" X# ~2 b" |' V" t- J; M: ^& q+ d  K& h9 C# C9 m% r

    - ~* m+ ]; r- J5 ?这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    * C8 D' h, A( G/ ?& o$ T我们通过一个动图来感受一下:
    ( O: b9 F9 C- h7 x* ]* ]1 j' @2 H. z5 T) z1 P1 d
    # U; s; O8 X- t% ~% H" r* N- g
    当第二次需要计算 f ( 2 ) f(2)f(2) 和 f ( 3 ) f(3)f(3) 时,由于结果已经计算出来并且存储在 h [ 2 ] h[2]h[2] 和 h [ 3 ] h[3]h[3] 中,所以上面这段代码的fib != inf表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。% [6 J$ K! u( ^0 U# Q) W& U3 U
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    $ C) d' h/ S" ^9 e+ z  _- {1 M4、广度优先搜索4 a8 R7 \" e& T
    单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
    . E8 M: x0 q. _4 @+ k- s我们通过一个动图来对广搜有一个初步的印象。
    / h: n8 x2 v: d. X+ v, x5 M) E) ^7 M- A! ]
    % A  U1 a! n6 V0 z

    0 i& [! j* f1 a5 y& ~

    % x: v  N2 t0 u; H# }- b# O+ v1 k从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
    , c3 }: w# {9 z那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
    ' T, y* N" j0 s/ {4 f这时候,算法和数据结构就完美结合了。  j$ p$ N  x* o. O9 X8 _
    2)动态规划
    1 S  H* }* B/ G& Z! O2 K, f动态规划算法三要素:" r/ L+ G$ u- @/ {& s
      ①所有不同的子问题组成的表;
    , T6 M2 z( a- `  ②解决问题的依赖关系可以看成是一个图;
    % Z" u% S% h1 t  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);8 o) V! G3 L8 x- I) E
    , S. Z) u& l" o, _" N
    4 I$ @, ?  V. [  e% U# K
    如果子问题的数目为 O ( n t ) O(n^t)O(n 0 T  r' a6 f& @' d4 i
    t
    ! I% V3 k9 s$ \- O$ @% E ),每个子问题需要用到 O ( n e ) O(n^e)O(n   X. H7 T- x" O; \4 j! }0 {
    e
    + O6 P) S6 {. E! A# K ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    % l* @, E% ?# e+ X1 N2 {/ b; n! N6 [1、1D/1D
    2 ~( I# A2 m. G5 ]- gd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )) \3 Y- D3 C1 G3 O2 L0 b2 T7 W) R8 }
    d=opt(d[j]+w(j,i)∣0<=i<j)
    3 a. W7 l; C4 J6 V状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    # h- d* G7 C3 e* }- I( b0 z: [1 l3 }4 D, I$ i
    ( q! Z% A6 Z' p# J' @5 p' ^
    这类状态转移方程一般出现在线性模型中。) R6 u1 S) |0 W7 n8 \- V
    2、2D/0D
    1 E5 |! X5 X3 P, q* {d [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[j] = opt( d[i-1][j] + x_i, d[j-1] + y_j, d[i-1][j-1] + z_{ij} )
    ; Y; c7 \1 C# Q$ `d[j]=opt(d[i−1][j]+x 4 F9 {8 s# r5 T6 j
    i
      S6 O* ~/ w3 Z5 b​          l: Y4 t2 J+ D2 D
    ,d[j−1]+y ' l4 A# J/ `6 m4 }( W8 e& C
    j( a9 d% A& P' q  ?
    ​       
    $ P5 T4 g) n5 c7 Y ,d[i−1][j−1]+z
    / D7 ]% j4 m8 Qij, t& H: X9 T. z1 W- I6 h
    ​        9 r8 O5 ]4 J! {
    )
    4 H. c& f3 n. k2 J* o7 {1 Q状态转移如图四所示:* j( D; R+ W5 N# ]+ i- L0 m

    ) l5 }, t9 c' Q# B/ t
    - a% X, O6 E9 o2 Q
    比较经典的问题是最长公共子序列、最小编辑距离。
    5 \% ^* R$ C+ d' d5 n有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    9 O6 y$ o: e) w, k6 b% l1 |有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    2 }. X( g) q8 k" a5 v6 [3 D3、2D/1D
    + ^( _0 [! o4 @) l7 T. S# Pd [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[j] = w(i, j) + opt( d[k-1] + d[k][j] ), f# \/ Q( o1 d" Z. u- w
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    0 B0 O) H9 O: G, B区间模型常用方程,如图所示:5 u$ V$ A  q3 N3 @  o) T( \0 x

      s/ M+ z1 E% g( z* B  g
    6 p2 w4 z' g7 i8 R# J5 A
    另外一种常用的 2D/1D 的方程为:; i- m- W. m- m2 _0 w$ H3 E
    d [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[j] = opt( d[i-1][k] + w(i, j, k) | k < j )
    ( V. u* C' d1 f+ s" }d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j), s4 N. S& E, u/ m. c: @
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    % q# z8 i; [8 P* s4、2D/2D  C, G3 Y! U# y
    d [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[j] = opt( d[i'][j'] + w(i', j', i, j) | 0 <= i' < i, 0 <= j' < j)
    ) O( @. k. z9 N9 nd[j]=opt(d[i * V7 w- e6 s( j& o. ?
    % a6 k9 D% R5 d
    ][j
    0 F! V7 U7 ^3 e4 p. o+ L% D# Q" B( L. A& {% I
    ]+w(i ! g9 v4 O1 Y/ w2 ^3 j

    # U+ ~+ l! {" ^! _- K ,j
    5 I7 P3 P" T: @
    6 v2 G# k# o7 Z' j ,i,j)∣0<=i
    ( o" p( P! K& @& |
    ) x5 m6 P+ ]7 L  i& ` <i,0<=j . I6 t* H, p1 `! `0 h

    * \* E& Q& a+ w) \5 p: f8 {. ] <j)
    5 g' Y( i6 m- a, T2 |2 l  u. Z如图所示:/ e4 R# p0 R7 Q* k* R
    6 n7 G1 L% M; V- Q9 ~9 _$ ~
    7 w, E# [+ ^  N2 J/ O* D: H
    常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    * [$ x: w3 {$ \' [5 R对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n ; ]- e! E+ b7 M* K
    t+e! z' P5 y" z1 F  [6 E
    ),空间复杂度是O ( n t ) O(n^t)O(n 0 c5 Z- f& `7 u* g3 u! g
    t
    ' I4 J: ~7 K, P% D0 O, @% J ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。5 f( [5 u2 c3 i) k+ d' r
    3)计算几何, v6 v8 ]' I" O
    计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    * G( V9 I- {' L如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    . v0 \# H: b1 C: X7 Q" J1、double 代替 float
    - J- E0 M- o( `! Dc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;
    ' _9 Z, \7 n+ K2、浮点数判定
    - ~% p! x5 Y# l' I2 Q- P由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);* Z* b( O0 L; l% C8 P
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:9 i* R6 }( z5 W3 Y
    const double eps = 1e-8;
    ( E3 _; @# g! q  y/ {" J" @bool EQ(double a, double b) {' y! o  s' N8 c* D2 ?! z1 `
        return fabs(a - b) < eps;  |& [+ `; a6 y3 f. }6 s$ o7 I
    }9 i( Y# r6 I1 D9 w/ q; v
    1% b- }) n7 l. s% Z  K- q
    23 z. Y% ?& b: J: N: i2 ?& g4 ^/ [
    30 q' S1 K. t- }$ b1 y6 G7 d8 U
    4
    . h( b( L# f/ N8 T1 }并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    7 I7 K; u# O' \int threeValue(double d) {
    8 O& _" e6 p: d! f- z/ ?3 E; B0 ~    if (fabs(d) < eps)
    - W3 s' C  `0 g, {& M8 V  d        return 0;2 R$ ]4 x! H$ K" ^6 d6 Y
        return d > 0 ? 1 : -1;' }- r% y2 y' T; o( i' P
    }& j/ h7 ~  D+ i! d
    1
    ! \3 M8 U4 n) j  U# m2% C5 q8 C& H( a( T+ t, e
    38 f7 b& N. U! g  x7 Z- i
    4: d6 v  g" {/ x& C
    5- R1 Q% o+ i4 G  V# C3 P$ m/ x0 x. b% X
    3、负零判定
    2 Z+ E0 K! I4 i, t$ T  `因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:! R* [8 P- j; Q% k& f" L; [& k
        double v = -0.0000000001;
    # x! M- F# M, C+ H    printf("%.2lf\n", v);
    . P0 ^" W" @7 c! N18 W: K2 J# E4 }9 s  z! p) N
    2) f; o$ @; Q) {; n
    避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:& B' G9 O  `5 U  U+ V$ J
        double v = -0.0000000001;
    8 K( k& P$ Q" v3 ]6 z2 r7 V    if(threeValue(v) == 0) {( {; ?. r5 _) M! f8 C' D
            v = fabs(v);
    9 U! P3 [( J8 R6 p6 M, D    }1 r0 _7 f, g3 r5 `+ B; u) y
        printf("%.2lf\n", v);% ]+ X) m; |# ], v; X2 q3 G
    1
    , O4 I# \! B9 Y# c* q+ ?0 c" P25 ?. Y$ B% q1 E) v5 }
    3' |# L3 ]8 m2 J; z+ |8 W' o1 b
    4
    " i1 R8 T; e# d) w& V) |" X) E- I9 F. S58 f" }  }/ m1 i  L$ y
    4、避免三角函数、对数、开方、除法等
    6 G' o9 a$ Q) J% L7 r1 Jc++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    % g& S" B+ ?) y) U  x除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。7 X2 I# ?! p' L- |% A
    5、系统性的学习- K* O' r/ c& X- E
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;* ?$ j/ f) W$ ]8 l) a) ]
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;; d" h* G- M4 L+ L, d6 u! v
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。
    # y; W1 b: s- a) G
    * o/ o# |3 ]/ T3 S; Z% Z0 m
    , f$ D/ G/ f/ g2 A# e! A
    学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
    ; H& Y7 F4 _9 t- N8 ^4)数论% u1 B! E4 O3 i
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    ! O% Y3 I$ }& n$ f! Z" s& C数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    ' K  v7 ~3 a& z/ G! |* j( r) Q当然,数论也有简单问题,一般先做一些入门题提升信心。
    ; A2 J, |& r2 o. H8 d1、数论入门# @& u* p+ Y9 h/ R
    主要是一些基本概念,诸如:/ p. \# A8 _8 s/ X& N3 A- s* a0 Z
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;+ l% M" h, U" j1 k) x; Z6 z
    2、数论四大定理# h  B! [( |# a# h7 I
    这四个定理学完,可以KO很多题:  V% \: `+ O" Y  S* O. K# W
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理
    , |; x# B+ b2 ~+ v, }$ G9 F3、数论进阶
    ) G, q! V  U$ j3 Y+ V9 E) R+ f系统性的学习,基本也就这些内容了:
    ) a9 d5 B0 \7 O' `' x扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。% e& p1 G7 {+ U7 [6 {
    5)字符串匹配
    # q% _; I5 b5 g5 B字符串匹配学习路线比较明确。
    1 v0 c( B8 C, S# W: l9 ]先学习前缀匹配:字典树。
    0 U8 T. D9 {  x5 W$ b' T) W然后可以简单看一下回文串判定算法:Manacher。! L" M& }" L0 K4 o
    以及经典的单字符串匹配算法:KMP。$ @( g$ }1 W: J
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    $ Z; G5 B; W2 L& {然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
      a) U" p  G- A" Z关于 算法学习路线 的内容到这里就结束了。0 f6 [6 F1 C5 ]8 t- c2 f$ F( {
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。# g0 a; x: ~* E) o7 R% w
    参考资料
    ; ?: Z; p2 U' s! C【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    3 c$ @7 E+ _$ Z. l4 W) q【阶段二】C语言例题:《C语言入门100例》(日更)
    % y; K% p, ~  V5 j' _/ R0 G【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    / ?5 C& N9 T$ @. B$ p【阶段四】算法进阶:《夜深人静写算法》(周更)
    $ w' B; f3 t: A————————————————* Y6 |9 Y, R2 {& y! [0 T3 Q& J
    版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。8 S! V' f( B$ |, Q6 }" M# k
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228# R0 y. r! K  W8 t( P/ v
    " e$ P1 T" g# t+ h1 z2 ]' d# @8 c5 S

    . S6 {0 m/ ]1 l: _/ t, O2 Y$ \
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-8-1 05:17 , Processed in 0.381811 second(s), 56 queries .

    回顶部