QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4444|回复: 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

    ; b, \9 M  J4 ^3 h❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏), \5 t" a, f& ]. `- e  \, J

    ) n8 G% j" |1 y" l# q* A前言
    7 \7 m; D4 |7 L% I  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    2 S: e" _* R2 A$ D  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    7 i! [9 l& w7 O2 E. f  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    : ~8 H, S5 [1 p3 r  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
    . `2 `# ]* R! s7 y9 I2 X  g  j! b9 K4 _2 B) S2 I: w
    / o- q7 i1 H8 q' u
      Y' u4 |2 W* ^4 B  L5 F2 A5 Q( }
    4 _  d% s) |+ S, {, @  V
    & q. X: v1 [1 J) B2 [

    9 s4 y7 Y, |6 ^* V2 Y" J, h
    - o6 _/ j2 m3 a2 P, c+ p; E
    1 `7 f9 y; V( `/ O6 L" A- b2 H
    图片较大,文章中有拆解,需要原图可以留言找我要哈
    * h0 m% ^2 v% U, T1、基础语法学习
    2 V8 K3 D) G4 z1 ~4 \; C算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    8 I1 n# C' A  ~- |; Q& X因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。+ q/ `8 r9 m5 j" ?
    / _) d: L& Y: `& d3 L; G; M

    7 S& w, W- i  j1 j( X1 N3 ~& e
    3 ?1 M. }0 \0 _" j

    3 n4 d' O: N0 V* s4 z! W* G+ v: X$ \1)HelloWorld
    " b/ [% ]0 ?/ t0 e% b9 p/ s% @5 o无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    + h+ k, e+ O4 B+ V2)让自己产生兴趣  u: d  |# J" @/ S. u3 c
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    5 u; [- U) N( {# n. F& H! q) n$ \刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。# F( n+ r  r& J( T" N+ E6 e* [& N
    3)目录是精髓4 G2 ^) S* p3 J+ ~- d
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:# n1 R2 p; s  n
    1、第一个C语言程序% s  t" h# C9 ~, ]% V! O
    2、搭建本地环境" D9 \) @! B+ x3 w9 d5 Z
    3、变量8 G/ D) d5 D# C6 t
    4、标准输出
    * Z$ R5 i4 n1 q. P8 C5、标准输入# K% ]( k% I4 c9 g- H0 d! ~
    6、进制转换入门; s+ E5 `1 d" y5 N' J8 a- W/ f
    7、ASCII字符9 ^. _3 T2 C; b/ j: f
    8、常量
    3 S4 e5 O0 D  l4 d
    0 p' j3 P- _1 E6 k$ H) D9 {, i
    " \5 [9 ~/ d5 m, N
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    9 ~& Y+ N4 c8 y0 C& r( ~- M4)习惯思考并爱上它1 I8 A4 y  g/ \4 A; [; P
    只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。# [; n) j" I+ \9 d/ Q+ f9 [. \
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。5 j7 s9 ~( d4 f$ u4 _0 a
    5)实践是检验真理的唯一标准
    * `# ?+ R* v% S光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。" F7 O6 ?- e# ]1 u) J6 E# q
    所以,记得多写代码实践哟 (^U^)ノ~YO) D7 w: R; {. d! a% p% B( i
    6)坚持其实并没有那么难
    & s) X. [& x# \9 F每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    4 X8 {( [4 V6 e, ?' }% s* h7)适当给予正反馈1 l) Q) X! b8 r' S
    然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
      X& w- y8 ^8 t6 c当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。1 b! E- X$ p0 c
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。/ k* p! `8 T' T& U8 }3 G
    8)学习需要有仪式感
    5 ^4 \2 \3 I6 |4 `那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    - m- U3 r" E, X; g7 ~介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!+ \  ?5 i9 E) R* \% r
    我也很希望大家的学习速度能够超越我的更新速度。
    * y4 a4 A  c, N% M" C  M2、语法配套练习0 d5 w; J& @$ h% G
    学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
    8 g3 F* `5 m6 C, ]/ I2 c而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:4 F: C2 S! d7 O$ u

    ) X. _$ M( ~3 i7 }: d& r2 f! ]1 D
    ( C% h6 r7 n( R! G& ^* u  f3 P/ n* p

    ) X" l2 j, ]7 H. k# T- U

    , g- ^* z; j; i从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。' @& F$ B7 R$ K: a5 u, E. V" r; Q
    这里可以列举几个例子:
    # F2 D& [" g) O# u5 r/ R1、例题1:交换变量的值- f, I. t4 R  u! d" V7 i
    一、题目描述
    0 L3 S6 `; K+ U' P+ h  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。3 k3 v0 h0 ^1 L6 |/ R

    % Y8 h7 C2 k; R$ R9 L

    0 Q* g4 J# ]4 F1 f. i/ w+ K1 a0 j0 L5 h3 r1 v& M2 h- p
    - r/ z/ n5 A  F
    二、解题思路" h& n1 N: B" ~( z* p/ t
    难度:🔴⚪⚪⚪⚪
    4 |9 m8 S9 v( c# V( j$ u5 R  E
    $ c4 Z& k+ o. h

    9 p, i; U" C* ~这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
    7 m) f3 K( `$ v' t" I! s+ y& Ja, b = b, a
    ( N9 p5 o# f' U1  n9 X) t: q0 x( S0 K  _
    在C语言里,这个语法是错误的。: N' v" P6 }9 W1 E# i" `) p
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?5 ^- z' j/ {: N
    当然是再找来一个临时杯子:: C2 G3 ]  |3 b! p; I9 v
      1)先把 a aa 杯子的水倒进这个临时的杯子里;: K  ~9 I8 k9 X/ K
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
      V$ _/ |  G6 h' @( q' U6 P  3)最后把临时杯子里的水倒进 b bb 杯子;! _4 b) K$ N' v  s9 y; Q9 ^* k. g
    6 C& i- v' w2 V. Q+ l# l

    6 m2 `$ u+ W, q# I: Q0 j这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。, V* \  R& P% u6 d6 c2 ~4 W; |

    0 q; D0 V0 n# o5 A; Y9 R6 c

    , R1 ?: ~8 z% R$ w/ [9 f: u三、代码详解
    : g' e0 l+ G  r' F1、正确解法1:引入临时变量
    4 s* t9 J7 q- l# a8 g# q#include <stdio.h>
    # M6 a$ F( M" ?6 O+ ?int main() {( s. b4 ^9 o* V5 {
        int a, b, tmp;% ~  ^8 L, [' k/ y/ P! U
            while (scanf("%d %d", &a, &b) != EOF) {
    # ~: a2 P$ r8 U' f# a6 g8 z! y            tmp = a;   // (1)" F! P8 {9 r3 G6 D4 o& k8 [4 u
                a = b;     // (2)
    - E6 }% F6 ?! \& h& n            b = tmp;   // (3)2 `% C9 p4 }7 N1 ^
                printf("%d %d\n", a, b);' @, |( q4 Y. S5 J
            }' M5 G. M1 y& Z- {- {
            return 0;
    4 @' s2 X  u) i' x2 \}; x7 E  s! i2 O, e
    1
    , v8 L8 f+ R' A' e0 @1 f. S2
    8 L5 I' k+ v6 D4 e; M3, f! x% V, P. y, @
    4
    " |# k$ q( `4 z3 U3 I5
    5 R  t. p2 m0 H" `3 c( h) X6 b$ w6
    # H% S8 B" [& T8 M7. d) ~" Z, K- k% t: k: P
    8' s6 x; ^$ \$ n$ G# B0 V. ]
    9# U' M6 u  ?. z  Y/ t2 N
    10
    7 m, ~* G. l- n* ^11$ F7 X* T0 j4 E, F" E5 l- k
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;
    % W- L% B0 Y( u7 Y" r( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;$ D/ I& Y0 j$ v" z
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;! d$ @) D' e+ u5 s6 S7 \$ Z, ^
    这三步,就实现了变量 a aa 和 b bb 的交换。$ A  _* o4 L" Y1 }# }* e
    2、正确解法2:引入算术运算
    5 n, Z3 {2 F! }( i6 R+ L#include <stdio.h>0 z6 G8 L! Z9 u; V+ s, k! S4 z
    int main() {6 R& z9 l  d" v" ?7 {) T3 J3 j
        int a, b;
    & L' ?+ |$ A1 k, F        while (scanf("%d %d", &a, &b) != EOF) {
    0 w" o2 p7 M' D" M+ f" a            a = a + b;   // (1)8 X9 w" |/ |. z, L+ C1 O
                b = a - b;   // (2)
    % ]) R% {2 ?/ S            a = a - b;   // (3)5 [. P' l/ i" D
                printf("%d %d\n", a, b);* R6 U: T+ p1 y/ H" `- f5 `
            }
    + ]  z. Y+ Y5 C  Q8 }# b        return 0;
    + s1 S- f; ]6 w: V}
    + a: E. k" \* U" S8 h8 u9 f) _8 M7 C1$ {4 A0 J3 j; u; W1 N) }
    2
    7 b3 s+ q# K; g; j) L3) d8 o& e6 l7 e8 c" A1 b* {
    4+ V: G, o, D7 c$ p3 M& r) c3 b
    5
    ; O/ D6 _# S- i- h# d/ P. |- g1 [6
    5 k, v6 E8 ~  {. N6 |0 M. V% x7
    7 Q* t; K3 b7 j: C9 `86 Y! a/ @6 T1 Q2 T
    9% x! q$ [  N/ n3 `" ~
    10
    3 w2 ]9 L/ l9 Z11" U2 |) c' d3 W$ T- ~" k. F
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    $ W' u, ~" M; |1 F# d/ j- ~$ ~! t( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    $ E3 ?' s7 N3 |0 e! ^+ E! }( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    0 z- _& {0 g4 l6 Y3 S0 C6 |, W: a从而实现了变量a和b的交换。
    " m0 w. `' D% I/ V" C8 x# P3、正确解法3:引入异或运算+ c# C" F; ]2 i* O6 e: Y: d
    首先,介绍一下C语言中的^符号,代表的是异或。
    - I2 i) u# d$ h5 C! L, c/ M1 z二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:4 S6 k) w' w- `1 q; A5 K( o
    左操作数        右操作数        异或结果
    ( a8 ?& L& g$ Q  H0        0        0; D4 W9 ~# G. U
    1        1        0; o( b9 H) w7 `% z
    0        1        12 x' c" W. q8 i( m$ B( U; e
    1        0        19 z5 d' {1 Y, X
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。1 y( f# k' R; w
    这样就有了三个比较清晰的性质:
    % _  r& M+ L  t* w  ~# q: T1)两个相同的十进制数异或的结果一定位零。9 F/ w# Y2 V+ ~/ K3 o
    2)任何一个数和 0 的异或结果一定是它本身。  h8 I$ n& M3 e% ]+ F/ X, c
    3)异或运算满足结合律和交换律。: i/ k* l" W: E& M
    #include <stdio.h>
    . a4 v  \" T( k% T" y4 V) z! xint main() {
    ! F- v) I5 b5 L    int a, b;$ g, r  B/ Z' F
            while (scanf("%d %d", &a, &b) != EOF) {! ?  A# h: x3 J2 Z, h
                a = a ^ b;   // (1)
    9 E  Y& X8 U# W) o8 v            b = a ^ b;   // (2)
    $ ^) V7 T/ J: s" J  U            a = a ^ b;   // (3)
    8 a3 }# m% ^7 _$ P! t) H            printf("%d %d\n", a, b);
    8 W0 c- Q. V  r9 V" X) k6 O) [        }) R. B" a" T& `
            return 0;( d; ^* J, }0 k% N& T  l, M9 s
    }3 D. ~5 y& p( P0 Z; ?8 |# [1 H
    1
    5 K6 d/ j/ y# f* U4 u6 T26 U5 k& Z* j! k; w
    39 U8 I; Q; F3 r$ c2 e
    4, I4 k: `" O: j( U2 w% [" {
    5
    ! m; F, K9 b9 S7 G; ~6
    7 M: Q7 f& Y8 i  e+ {& o# C7
    # z8 g" J& c; F  q5 ^8' ^+ n: n! u5 W( w0 s, s
    9
    3 l) z, C) @: e8 T4 s, Q10
    - f1 T2 r6 T. I& ]) T7 _11
    & M# V6 j3 X9 e% Z我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。$ g' o2 s0 {* K) _
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
    & R2 F1 @  D1 n: p) {6 [+ _从而实现了变量a和b的交换。
    * {4 |, D9 R8 E
    8 o5 @4 Y( i1 P& Y6 y

    ' n( @& U+ N- M1 K: ^% P9 X& K7 z4、正确解法4:奇淫技巧
    , H0 ?6 j3 [$ O1 k当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:/ _% j- f) a3 T7 i- [# E
    #include <stdio.h>
    3 c2 p% u3 p) W! iint main() {' c; m9 J) o3 l/ h
        int a, b;5 p" t# |7 `& ^+ C( C( ~+ ]! [
            while (scanf("%d %d", &a, &b) != EOF) {1 C; T2 L4 O; k7 g! x
                printf("%d %d\n", b, a);
    $ X0 \, R% U- V1 ^' X4 |$ c4 v        }
    ! T/ f# G. i# P8 z1 _* t        return 0;
    ' j9 Q0 D. X1 b2 |3 M}
    + B. g' B# q% w! V1
    ; c7 o% Q$ i4 U2 k1 W2- q1 p: y8 I- q7 i1 Z
    3
    2 M! i/ k7 ]3 s; h( F4/ A3 s3 K; D, z) R0 A
    54 r% c8 C: e$ J6 f7 Z* i) F( L
    6
    # o* b  [5 v; ?3 t4 d# K7
    2 y& a  I( l+ x8
    9 _# Y: Q' I/ n. t, I) O5 x你学废了吗 &#129315;?( `# m4 \. {6 G+ V* O. \1 _' P
    2、例题2:整数溢出
    ) Y2 O' Y' k" W" q- X$ s一、题目描述
    - Y$ E4 F, h. A& \, A& M- t9 l  先输入一个 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 , u3 ?9 g* V# ]8 \% J" ?
    62
    * B% j' S. O+ p  `" f' S3 l ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    4 _9 |. Y8 \- B3 k7 o/ f5 b' d( u, m2 j( H
    9 {% K2 \7 x9 l6 |- q: l6 M1 |
    二、解题思路
    0 d: D* i6 `/ j1 t# O8 z难度:&#128308;&#128308;⚪⚪⚪0 e2 b( U- W6 I* j

    : E2 B- w: c2 p0 U1 O" C
    ) h$ s' Q. R  l! }
    这个问题考察的是对补码的理解。) j2 a0 Y8 z- w4 y; t5 P! X
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 + x* j: _" F% s9 m- S) G
    62# u/ w' V( m" `# D
    ],这四个数加起来的和最大值为 2 64 2^{64}2
    8 t1 L, m. e/ P9 X# z64: T, ]& S. }, Y6 X' p8 P# N7 f
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 7 C4 @  ^& D1 u9 K, d
    63
    % M2 n9 p/ i+ w5 | −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12
    ! x! a8 l# y# b  N; o; P& d8 y% n64+ r2 B+ ^% o+ c) u
    −1。
    * H- v# b4 S) u但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 , n# i$ R) v& |0 e6 |! C
    62% \4 ]: x. c0 P% s( N
      时,结果才为 2 64 2^{64}2 6 K- t6 U1 o7 u" ]* I8 \6 E" y! r4 X
    640 Y5 g  }& @2 V: {& j
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
    6 m+ z* X, U- v三、代码详解
    - \& Y/ a% z; f, B4 |6 R# e#include <stdio.h>
    : `' v( N0 i) jtypedef unsigned long long ull;                           // (1), H9 Q0 M, Y3 N* ]7 v# A$ F3 D
    const ull MAX = (((ull)1)<<62);                           // (2)
    - ?- j0 a& q, K8 s. Q3 X% C% r
    5 m! W4 j. R3 b# J
    & B9 F% c& z4 e2 J- g4 u( E& l
    int main() {# G( z. T6 k1 X$ I
            int t;1 E+ y% ~( B8 d5 C
            ull a, b, c, d;
      H3 U9 w0 C% {% Z: ]        scanf("%d", &t);
    : u: R5 j& A( u4 N        while (t--) {9 j6 Y; i& W2 Q2 q# \! ~9 Q
                    scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)& ?; o9 b: |1 d) m  f
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)! s1 H/ h7 Q1 z* P! Y/ u
                            printf("18446744073709551616\n");             // (5)
    ) ^  O4 I/ e/ D' y1 \9 W                else
    ' B$ J: w) g: s# ]                        printf("%llu\n", a + b + c + d);              // (6)
    , [& k6 z: Q4 m2 l: Y) X: h( ]; K        }
    1 {3 D6 j/ q5 c! g" T        return 0;
    ( }+ m- u4 B; H6 J9 `6 ?( q}
    2 M2 J, n) x: c; v  I1
    * b' i/ j+ O8 t0 R- l- d2
    8 k6 ?. K  K4 I3 V3! C4 `7 h# {4 ]; d7 R% ^. o! Q
    4" e) C2 t* \8 i+ U
    5
    4 _9 J$ c$ c, M0 ^; w, ]( r* w68 Z6 G) B5 ~# b7 U5 v4 |
    7# ]0 `: {* Y& @( {2 P/ I) F$ u2 N
    84 s4 R1 \9 T% G) \  U
    93 s$ o( V( f1 n# R( v; y9 ~
    107 n' \) E4 x* t8 h
    11
    8 P4 Y" ^: ~, x2 g12
    4 J4 A0 O" `4 K8 o+ J6 R13
    7 V6 l; }, g1 x; X9 h7 k14
    6 E% ~% }6 Z: _7 {" A* B  D15
    ! H& H9 L4 ^6 W2 x5 C160 w6 j6 Y( \# C3 Z$ ^8 L. E' U( l! A( d
    17
    + v" y8 s+ y- T( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;
    + e2 L# k8 `& L, |. ]) H( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    , ^* R' ^% w- K: M! k62
    % H: L; Q9 {) G# q8 B* o, @; u ,这里采用左移运算符直接实现 2 22 是幂运算;
    1 T5 C" |  X4 K1 I7 \: R数学        C语言- s- g9 A1 l$ K1 e" g& P0 P% |$ M
    2 n 2^n2
    , z$ s: F/ O3 B/ d" [. `- Y: q* wn( q+ x, `# |3 j, ^$ [+ n3 f
            1<<n$ f) e5 g; P4 [5 o1 m5 N2 ]7 s; C( B
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    ' `2 V: D* f$ P3 T  J( T( 3 ) (3)(3) %llu是无符号64位整型的输入方式;# H2 B+ x+ Z2 O* @$ o/ r3 C5 @  ?3 V/ W
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    + |2 C) h( k8 c5 r2 _4 H3 I! ?% Y( 5 ) (5)(5) 由于 2 64 2^{64}2 ( D8 _8 b0 ^* j3 s
    64# u: B! u- I" w) v& ^* i
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;. M  B* W7 O7 O7 }6 H
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 5 @$ n' ]8 X- w- p8 W
    641 G! u2 X) ]' h5 e) l* [+ U/ [8 d
    −1] 范围内,直接相加输出即可。
    # l* F1 c* |, F# L由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。- }; E0 N1 C: D' j0 w2 h
    为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    4 w+ _2 r. x! B2 M; c: C" ~2 e7 W. J  \# j' w
    3 q$ x# I" n% [2 J) q
    3、数据结构
    ! P" M. a. N" `( Z) y  Q! a: x7 A《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    - \- W0 A/ Q9 f) B3 q1 g+ O1、什么是数据结构# |" ^1 S5 ?/ u5 @# K) L# _$ D! b
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    - [4 _5 Z' q" u6 E: L7 e5 S如果一定要给出一个官方的解释,那么它就是:
    1 u! }- I. U, j/ h6 ?7 c- b计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    " E' l5 ]6 K& L0 V: R( f$ a" k7 J7 q, t% C3 Q/ b7 g

    $ [* \8 ~( L- G: @) g+ A( l是不是还不如说它是堆,是栈,是队列呢?4 B  x9 ^. }9 l1 j  H* U
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    % L8 Q7 d0 y/ Q3 e2、数据结构和算法的关系
    7 N; T& o0 S+ a" g/ p) E! S* g, X很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。+ y, \6 y* y- \' \' ]
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。, |1 b* o2 \3 X+ L" v6 b
    而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    # b2 ]6 J% Y; x- _讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。6 w& V. X) L7 w7 n# M2 R) e; d
    3、数据结构概览) V0 R0 Y( \$ i* L: u7 h( r) d# _* u
    周末花了一个下午整理的思维导图,数据结构:
    2 m0 y* p, c- J; Q0 C+ d' r1 S7 T) @5 _$ r
    ' |6 q' x5 k" S3 i# P4 ~
    常用的一些数据结构,各自有各自的优缺点,总结如下:
    6 L) W) |4 M' |) }7 K- Aa、数组
    1 {8 A. @7 f+ P% ^2 F内存结构:内存空间连续1 ^% H$ S" `9 D7 d! y: ?+ s# Y
    实现难度:简单) m' p! q. [: t: z- K
    下标访问:支持
    " m2 z3 G; p. B; A& j1 T. ?' N分类:静态数组、动态数组
    / S. {% p% i# o- `& H. A: g- N插入时间复杂度:O ( n ) O(n)O(n)0 s4 _3 F. ?" u. {1 ^) N- f
    查找时间复杂度:O ( n ) O(n)O(n)4 f6 N# J  ~( l$ v4 X
    删除时间复杂度:O ( n ) O(n)O(n)
    3 C0 q2 n  n# h- {1 J; l  Q
    . ?4 `( H2 b5 c' ]: }$ T

    9 B! I5 _- ^6 M) |) ^b、字符串+ h% {8 h$ B" R, ]
    内存结构:内存空间连续,类似字符数组( Y' c+ i6 {$ c. e2 H
    实现难度:简单,一般系统会提供一些方便的字符串操作函数) ?* s' D- `8 @3 u& m& H& C+ @3 ?
    下标访问:支持
    9 P! A9 ]- w+ q插入时间复杂度:O ( n ) O(n)O(n)
    ' C' E. J+ @8 J, @# t& G查找时间复杂度:O ( n ) O(n)O(n)% K9 P6 E: x( J; j1 d5 A! z
    删除时间复杂度:O ( n ) O(n)O(n)
    6 Q7 I. ?0 Y9 y. {# W2 Z* u
    , }8 v* c& V2 h9 E8 {6 t8 n

    2 C& R, U3 t( y. W  c* ic、链表
    + w0 I, s; ?& G2 t内存结构:内存空间连续不连续,看具体实现
    ' i9 e- Q/ T$ I9 I1 M实现难度:一般
    5 Z5 [1 x+ ?0 u: ~下标访问:不支持* j- J0 e% N: D( V4 \
    分类:单向链表、双向链表、循环链表、DancingLinks; m: `, l  g9 D- b, ^- M
    插入时间复杂度:O ( 1 ) O(1)O(1)
    9 y+ P+ Y* X8 ?- g查找时间复杂度:O ( n ) O(n)O(n)/ g6 L3 E. T1 B$ q
    删除时间复杂度:O ( 1 ) O(1)O(1)
    , ]- _% B0 [2 y0 y
    3 L4 _6 V- D7 j  X& j5 E) J1 J
    ! U$ o# P( Z8 x+ Y& K
    d、哈希表( r) M* V' i1 Y( H1 v, a5 m( K
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    . r( V% J, W2 a2 J1 d实现难度:一般
    ' I/ ?7 |. [5 }4 }下标访问:不支持
    7 v: k6 w) V6 r# z# p分类:正数哈希、字符串哈希、滚动哈希
    4 ^6 p$ ~" f. g1 {5 }插入时间复杂度:O ( 1 ) O(1)O(1)
    " ]1 q& T  O0 f4 y查找时间复杂度:O ( 1 ) O(1)O(1)& ?  c+ }( g- A: F: @/ a! y1 d
    删除时间复杂度:O ( 1 ) O(1)O(1)3 q( D: T& F, c3 n% X; T/ ?
    6 m9 T& H8 _6 d5 N' c

    & `$ e8 _% d# ~6 S0 n% Se、队列
    * Z9 ?- Y: y; ]) |% I内存结构:看用数组实现,还是链表实现
    + r5 i3 s* K  |, c8 W实现难度:一般
    + L3 o) M( {7 o8 d下标访问:不支持' C' R6 J3 I3 C6 z, Q) z1 d
    分类:FIFO、单调队列、双端队列
    ) x7 m2 X) D  v( ^( B插入时间复杂度:O ( 1 ) O(1)O(1)% V# B  g! w* z
    查找时间复杂度:理论上不支持
    * f" ]: P8 Y6 ]删除时间复杂度:O ( 1 ) O(1)O(1): h0 j2 Y+ S( N  a, z9 Z

    " L/ x* `/ I4 }$ x: M* M/ T  s

    ' B# I, Z1 I  M$ mf、栈
    : Y+ c: L& Z) x3 c( H/ w内存结构:看用数组实现,还是链表实现
    . S9 T  j* h1 x! t1 ]( W! S3 T& B实现难度:一般' Q# [- C" @& Y' t' g
    下标访问:不支持3 K  Y  ^4 [, a4 T7 @3 ]
    分类:FILO、单调栈# g/ @6 o  s# m! v3 ?+ P+ {6 U* c+ V$ G
    插入时间复杂度:O ( 1 ) O(1)O(1)! c# a& |: K% K$ m1 x/ [
    查找时间复杂度:理论上不支持* x9 K( p$ I/ y, g
    删除时间复杂度:O ( 1 ) O(1)O(1)) x# f, T* u# ?. J5 }( w6 G
    . L, m; T8 D. Y) s( ^, M) z! V9 H! B
    7 ]$ l2 e) g$ m
    g、树
    / x8 a+ d: }+ l0 L; X8 C8 E% s内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
      C, A3 E6 z7 N) T8 K: g$ f实现难度:较难, u+ q- N1 W- \% ]7 B/ y
    下标访问:不支持9 R  J' S; ~) a. ^/ O
    分类:二叉树 和 多叉树9 {8 K: }# Y: y$ P8 r9 ?
    插入时间复杂度:看情况而定" O/ u4 }* ?, z) }
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log ( T) Q: e" a2 V: N: ?
    2
    , w8 M) o1 }: |. ^' d* B( H​       
    4 Z' y3 }' C  n) z n)
    8 c9 @& ~& B( r8 \8 A删除时间复杂度:看情况而定
    % j7 G$ d! c/ ?# ~) S
      ], x! P* Y3 X+ ^
    : v" n  P) @3 o; C
    1、二叉树" V( i  J# m) k# c6 W5 N
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    , x# I7 G/ ~/ G8 ]3 ]1 x其中,堆也是一种二叉树,也就是我们常说的优先队列。
    6 m7 a4 A/ C* I2、多叉树
    9 ^: D- t# k; e8 }B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。/ P. O9 S1 K9 P4 C* C$ m2 E
    h、图
    + W3 n; H: k% J3 O; A4 N  W内存结构:不一定, `1 l2 _2 G! C# S
    实现难度:难" U: U0 u( H& ?9 ^3 c
    下标访问:不支持
    , J, f- {$ n: X8 }; e分类:有向图、无向图3 {+ L5 c7 q3 M& i1 h
    插入时间复杂度:根据算法而定
    . B$ U: {: C- _查找时间复杂度:根据算法而定
    . S* P' ~# q; ~删除时间复杂度:根据算法而定- D2 _6 x1 X3 `0 U" L/ ^
    , ?: q( m& a$ z: Q( o9 m( L$ X: C

    * v9 q  c; Z9 ~1、图的概念
    5 f# Q5 W' b& q- s6 t在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    2 e( K. g4 r8 a: s+ \图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。
    ' f1 y+ H6 y1 B; U对于无权图,边由二元组 ( 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 为权值,可以是任意类型。7 p8 F$ ]- }& ^" L
    图分为有向图和无向图,对于有向图, ( 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;6 `! S/ A- J9 x$ B6 @+ q- K- d
    2、图的存储
    " o0 B$ M* F) n# p  Q对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。; p3 y0 L( s0 ~8 ^( C$ |& |9 }' a

    $ p5 t) e( S7 Y, R% Q+ R- v
    6 C7 r# y2 N+ q/ W, A: y. g) `$ D
    1)邻接矩阵9 z; M1 X6 V6 F# a& |; h" d) G2 k
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。
    $ |+ {+ G) K( O' B它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。
    8 y. E/ h& [$ p6 v+ l6 f! X9 s[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[9 G4 G2 m6 _, C' U  Z5 O; F6 b
    01∞9∞0∞8320∞∞∞30& F* C. ?1 F$ L$ O& F9 q+ O
    0∞3∞102∞∞∞0398∞0
    9 M$ e; Y" Y7 R8 ?0 _, d\right]. y- K$ }: R# f0 ^; c
    7 a" d5 y+ s: ~2 |

    . a: s0 |' Q* {& z" L+ f! j; K
    8 _; U  X+ K8 q9 f/ f5 P% c. {2 [
    2 W" T* J, C2 s' m( l+ x3 j​        3 H3 [# V! P1 i( G  K
      
    $ z' t/ O( T/ j08 P1 [; o5 Q9 i$ B( \
    1& r0 o6 s; u9 Q' j
    / \$ m3 O9 ~  ?2 y& @
    9; L) \* C6 K. I# @7 Q& ~2 M4 Z
    ​       
    & N+ I& B) V3 {+ O' [! j6 ^  % f) I: b2 h) l$ J
    7 m/ |- Q* x" m8 L+ |; L" b
    0% o0 B0 x) B3 `; Q
    7 l) h; J/ Q3 g: \$ ]; x7 |8 S
    8' P: o) j+ A  {: ~, ?. o# L
    ​        0 i" U2 `$ d& }% G5 L
      - P" x! Y. {, v$ |: @
    3
      s6 p% }. k6 r4 i/ I  N* U2
    ' |& k5 u+ n  ?9 v# u04 e8 N/ g2 }+ \$ U/ T

    , U! F* A1 l2 c% d4 k( z​       
    ( X8 q/ z5 u7 ]. `  8 X; Z6 j9 v. O" G+ P
    & ?4 `/ Z. Y" A
    ; [6 j: P& r1 a* p4 S* y' q
    3
    0 [. j5 |0 {+ r04 x1 b* Z; d! `. H3 o
    ​        0 B# W) `( w. k; b3 q& R! E
      0 k$ Y4 @& G  [0 M

      e+ A  z5 p8 ^- Q1 `$ e
    , b, o) W, |1 O9 j' Z7 m3 I1 ~8 I0 m( a

    / `* v, B2 ~+ @3 n​        6 U/ a: N, O/ u9 @

    8 k* |/ t4 k& Y; H2 `: Y2)邻接表
    + V% x, S! D! {& h# i. i& U邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    8 e# |6 J8 X: x, `/ y" L, h+ s. C- U它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    " J5 n+ U" E, h0 I* w" ?2 V如图所示,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) 二元组。/ v1 Y* @; l% R& D3 p9 F: g, r6 n/ @
    2 C- t! c; H$ O+ w7 a9 n
    6 v# N: Z. d4 C2 I
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;5 U9 x6 X: o5 }5 h  ^
        vector<Edge> edges[maxn];6 j" V2 a9 @( Q" p( A
    1( K2 ~4 T0 ^( j/ v1 ~/ c! k
    3)前向星/ m5 h: c/ r' T4 ?# s/ `! ~
    前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。: s, J* \: z1 S. |' n/ a* E
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    ( q0 Z; K* B8 f: [% f如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    # b, q! k5 Z1 u& W/ ?( P) j0 t; Y0 j& N
    / S1 T+ w7 p, N) A' L7 o
    那么用哪种数据结构才能满足所有图的需求呢?& u  A- f- _  D5 `2 J! T
    接下来介绍一种新的数据结构 —— 链式前向星。
    : j: t' ?9 |1 E: p, e4)链式前向星
    2 |  R8 ]- z7 x5 O& L6 Y链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。" j1 f0 {' c) f1 ?
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。' A+ o" l6 ]; }: i- e
    边的结构体声明如下:
    9 P$ Q" H& z' g/ G- n6 Ystruct Edge {& x$ j- O0 G2 ]4 e1 h& B) g% L
        int u, v, w, next;
    ' n* n7 j1 u9 X    Edge() {}' @' G/ T3 G3 g& k
        Edge(int _u, int _v, int _w, int _next) :
    / \) x# `6 w2 C' f# J        u(_u), v(_v), w(_w), next(_next)
    4 k0 X4 k! o* R5 V% [$ i    {: |1 t' G6 {6 Q7 k1 P3 g# ~
        }5 i( T& O5 c4 k# {& i& }, f# ~. l" o
    }edge[maxm];8 ?  p' P4 b: r4 r
    1
    3 d( I2 x- [( H$ \25 T0 d2 I- R2 ^
    3; x2 I  j' ^, h4 E) v% a" ^
    4! s1 m1 `' Z$ u+ Z) a
    5, ~. g; o- N* O' y, ^7 A
    6
    , t3 F% r7 B5 H3 ]! d/ l! N77 Z4 E% k# F/ e+ R' U
    8
    : I9 H2 I: K0 ?* V" C9 `初始化所有的head = -1,当前边总数 edgeCount = 0;4 h1 o; C6 @- N; I
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:1 R4 k' z* m( q, \. r
    void addEdge(int u, int v, int w) {
    - C5 Y) f; C" {( Q" K    edge[edgeCount] = Edge(u, v, w, head);1 I* i5 u2 @0 h4 C; @
        head = edgeCount++;
    & K5 o! f# U' D1 `7 u. T}
    ) H( e6 B- d+ ?4 Y0 Z: C1  h" r6 n/ F: Q$ [/ W& v
    2
    * u/ z1 U* Z% j0 a35 x; N  D* K  \% C
    44 H7 S/ [0 Y. I4 s/ N9 o0 b
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    3 E. {& ]8 J" T  H9 \调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    + U) t2 F& O) ?/ t) [for (int e = head; ~e; e = edges[e].next) {, _; a4 Q( r. b  p! Q5 L" r
        int v = edges[e].v;
    ; C# S/ g7 R, |    ValueType w = edges[e].w;
    # K9 Z6 H6 Y8 h4 M* o% @( A9 u    .... A. l0 b- j: Z! K2 d
    }
    : z9 ?- z/ _% t11 J5 e1 h6 r# G" h  c& ~( {- s
    2% J5 K' H& s3 B
    3
    - }! p0 @! n$ N3 i4
    3 ^: j0 V: r( m: O* O5
    & g' k! U7 X' D- ^' a文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。9 j. h3 s8 i, Q% Q
    4、算法入门5 s* ]; }+ g( u
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。# S9 c0 w7 g) D0 n+ f
    0 y; V1 e: f1 \. H: t; g3 d

    . k( z3 [% l* ~入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    ) |4 A- Z# ~, ]3 P对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    3 ?! y! N% ?) T, Q1 y# ^1、枚举
    5 a+ M& T/ }) T+ T枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    7 J+ I7 S  O0 z5 E  z" W! t对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
    + t# x! R# b, ~. E2、排序6 z6 y, L; n; Q8 U
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    3 z6 C0 g( c* s2 k$ Z+ G3 J冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    / s2 A! t6 y& f* QC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。
    2 g( e( I# {- `, Y3、模拟0 `4 r8 m0 G; w, M5 k" x7 d' p" Z
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。1 o: _! W' w' h) q. o& Q$ D
    不管时间复杂度 和 空间复杂度,放手去做!" \. {- Z) Y7 [7 F( e
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。' U- C9 t- M" G
    4、二分7 H/ C3 c  _, r- g0 Z% Z
    二分一般指二分查找,当然有时候也指代二分枚举。2 y% L9 E" W/ c! J. b
    例如,在一个有序数组中查找值,我们一般这个干:
    . G# y' E% I8 O$ r7 {+ z' g1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    3 U) j8 O: M) S. T2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    ( l, ~; `2 p$ J  2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    " }+ u+ j3 B: f4 R+ R  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    + j& L. y' K1 j& C  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    : ?8 j; g  R  }- n+ a+ E0 a3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。! ]) s/ ?% c5 k+ a9 Q2 F
    5、双指针' R% U* m7 i. ?9 @& q! T# t
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
    + G* K6 z+ Y( Y" y* ?  `. u8 V3 k$ P) e' ]+ f' q! G) _
    9 P4 j9 P* A+ _) G5 w
    6、差分法. M6 k. I# Y7 }8 D( f' Q
    差分法一般配合前缀和。
    : W1 o' X8 ?( [! e对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;; _% d) N0 P+ u5 H$ L/ ?" W
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg : o8 W: G; h! k
    x
    3 T4 d* t: o! {! O9 c​        2 F8 Y3 I& g4 A) p1 U' M4 \: Y' \
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g ) a: I6 y  p" B% i, ]: D1 w! z
    r
    3 M& {  \7 a* Q! J4 q2 P) X  h​       
    ; Q  B& f5 R$ c3 ?1 w- C −g 0 u! _: ~0 V$ j( B% A6 i+ G9 v
    l−1/ ?% y0 ~! |" u3 |+ K3 q/ l4 I5 T0 z3 G
    ​        5 P6 D9 t9 h3 H6 E: q3 E
    ;分别用同样的方法求出 g r g_rg $ ~  G* [, m$ i$ m% _1 |
    r- R! p' k( W' ]7 u. O
    ​       
    / _' ?4 h' y) L0 k7 N6 Y  和 g l − 1 g_{l-1}g   K0 ?& p; X; D: a4 I; v/ B
    l−1% G; V1 \6 o6 J- \7 Q. Y
    ​       
    : D/ e+ _% O! H) Q% c, Z ,再相减即可;$ H. t' Q& J& H
    ) J/ K! A. K6 o# k5 J0 p
    - b2 G# m, a8 ?. `% Y
    7、位运算1 K. R8 ~' @; A) T
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。6 K9 Z" k( w" r' ]
    位运算分为 布尔位运算符 和 移位位运算符。
    9 z) }' U; ]+ x# D. ]布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
    + d* O9 M5 X9 ^6 x4 }# ?如图所示:& }. Q4 [  y9 \/ Z" E5 B) z
    ' |2 |0 Q/ B% O2 q. x% K

    # D) `5 A. P) _位运算的特点是语句短,但是可以干大事!
    " u& k& A: b1 W3 e: }比如,请用一句话来判断一个数是否是2的幂,代码如下:0 L8 o9 {9 Q# n1 f4 v/ q7 V- X
    !(x & (x - 1))$ G" R) T1 I' h/ I5 C7 c+ j
    1
    . F0 E% C. w$ f4 Z( S  g8、贪心
    ! P* F; n- x: N+ E0 c! ^贪心,一般就是按照当前最优解,去推算全局最优解。
    : n0 z- R; Y. N  _' a5 p所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    + H  u! Q  P, F9、迭代
    # W. k+ G* ?' ?+ o0 R0 Y/ ^5 c每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
    ) o  Z5 \: k# R" a" L" m; j2 |10、分治
    . D; ~: t! K& Q9 m分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。- M& z8 P2 x/ P  F  G
    5、算法进阶
      t( [/ Z8 y. p) R2 A; e/ N算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
    8 X! y5 ~+ s. F; T如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。2 Y+ R5 N4 i7 {  w1 H! H: _3 m
    这个系列主要分为以下几个大块内容:: i6 R' F5 H+ E
      1)图论3 R, e2 \, W, L, k' B6 r3 m; K
      2)动态规划! n# x' @% Q4 J4 z% C2 R9 }$ L1 g
      3)计算几何
    5 [/ p! R1 t: d6 W- R  4)数论
      Y4 ^7 g4 e. p4 I2 f9 t- @  5)字符串匹配/ ?; y% o7 T! X& |* G0 y+ d
      6)高级数据结构(课本上学不到的)) B' h2 j/ O3 ]; r4 E( v6 J! `5 p
      7)杂项算法
    $ Z, q8 v4 ]+ m! l/ b. j  o2 c9 \' c: G. V0 L- H9 |8 S" d2 d

    3 D; z; w2 Y* ^) x% q2 J+ o) t先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
    / G7 f% D$ T3 q: Q
    ' S" H- ?* ?2 ^5 i
    % Z+ z, T2 K: P

    " u; h0 h5 O0 X: |

    4 |) G3 I, v5 c  c9 X* Q1)图论
    & _7 m! }' y/ Q+ `6 J1、搜索概览
    , n' y" a( _) `1 v: Y4 p2 \图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。
    ( w# H5 e5 F/ E( J7 m7 L" e0 W; l  u/ }7 u5 S
    5 w+ _! e1 x' |0 ^0 Q( p* U% P/ B2 j
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。' Y& `7 e7 R; n+ E
    2、深度优先搜索
    ' g! O6 |6 w5 v& l深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    ; s( l( ]) C! V原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。1 f! s: G4 m( h7 {" E+ T; K6 {; ?% l7 z. _
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。7 j  g% O% ~7 h" P9 F# N
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    5 O7 Y  m1 Z& X' a2 \7 O* A7 [) q! g$ c9 S' {/ S+ d

    9 k2 Q% R8 J8 O3 N( U$ G红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:5 Z- _/ ]0 O! H2 n8 e" {1 R
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    % z+ v: U* C0 W, y9 S1
    0 c( }) R" \1 Y4 {! Q' J0 y0 y2 W同样,搜索的例子还有:
    6 {1 r% q/ ]0 n6 Y3 O% n* a9 q# u. z7 U$ a8 {: B

    6 {) C7 P: }* a- f计算的是利用递归实现的 n nn 的阶乘。' ]3 F, ]5 E6 ?: U+ _
    3、记忆化搜索
    ( [- D/ W% f# i( d对于斐波那契函数的求解,如下所示:
    ; A+ M0 f1 H6 ~; `' T: jf ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    . {( l1 L; t* }- D⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
    " v' F8 J% Y6 m4 C0 u{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    3 e+ t2 w9 k* X3 f$ f3 a4 p; jf(n)= # @( D8 k( i! b' R8 X/ [
    , Q1 b  h. a7 D6 a; K) c; P+ _

    + U# `$ F' r- M' x- ]0 [2 A8 j
    9 Y/ t) N8 r; z( _$ H
    ) h% x9 y, P/ ^) a! [" W0 s5 q& x
      N+ N/ S9 E1 r9 t& I/ h​       
    ) g) w) h8 v- l- J/ \  i  
    ( z6 F* K/ G- x1
      I- a2 N) S, q. X- B  x- y, u4 A10 \/ u% r1 J+ P2 v3 R
    f(n−1)+f(n−2)! M* b( p' [5 I6 w* A- r) D
    ​        $ P! I3 q9 a+ @3 N
      
    / E  a2 z% w; O7 W% d(n=0)
    8 `2 E" X, J& Q$ i* d+ R& G& Q(n=1)
      r! ]$ h9 a0 w. i, C* ~(n>2)3 Y1 S5 V8 M( ]( H/ N. y0 \$ |/ s
    ​       
      y9 L! t5 y8 ~7 V- x
    ( R/ H0 v0 D3 j( ~- Q6 O$ ?% Q* e对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    * A9 v9 G# ?3 T5 Z* B9 q: ^9 f7 E. x7 _  m( U# {! D; Q* [" b1 D/ E
    ' E: @1 s- ^+ Q, C4 A1 v) i
    这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    9 r/ j% X2 T9 R  X我们通过一个动图来感受一下:
    0 p; P5 [. \. ^6 k# |
    4 _8 \( ?4 K( t* o/ M! l" K

    * y' b! ]5 o& Z4 B+ p% v当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    & D. a! H) ~# {: D这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    7 @/ f" [% T6 z! `4、广度优先搜索
    ( S. M$ P9 h) ~' M  S2 W; e5 `单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。) C. V. A) W4 V' g# g( R% n
    我们通过一个动图来对广搜有一个初步的印象。7 C8 _4 e4 p* H) N8 N

    # K5 s( l2 u: K
    * E" e* X* `) l' T; S

    9 G! Y' n- V7 t3 j6 s8 \

      v1 b  a+ f# Q, A( q- d6 c从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。1 V2 L% B8 `) z8 Y
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
    : T; E: l5 j5 H0 c" x这时候,算法和数据结构就完美结合了。- p/ s* }9 K# Z# u4 }' |2 U
    2)动态规划
    * t/ N% X0 I; @+ x$ k/ P动态规划算法三要素:& T9 r9 l* q2 {
      ①所有不同的子问题组成的表;: H3 H2 Y1 B. A. E7 @. g
      ②解决问题的依赖关系可以看成是一个图;
    1 i) d; F& g  K  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);2 w8 a( \2 f, z5 b/ l& U: `
    - L4 b" D8 `: q; h0 P0 m# k
    , c. R4 @1 d$ C( {, E; J8 z
    如果子问题的数目为 O ( n t ) O(n^t)O(n
    : @  T" Y( H! O3 ~t
    8 |2 s* d) M7 y$ M! \" z: j ),每个子问题需要用到 O ( n e ) O(n^e)O(n + r/ }; h2 h, D1 I; N
    e! ^9 O6 F+ p, g. ~2 o
    ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    ! l8 F* W, l& A0 d1、1D/1D, n8 V0 e7 p8 N7 B' P
    d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )' Z# ?7 m, t# }% D6 Q3 ]! z
    d=opt(d[j]+w(j,i)∣0<=i<j)2 Q: L( R. I" J% P! f
    状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    5 b% G4 v$ c* V* i/ w9 w* E; v: t, f
    ; B! a/ C7 ?% H! i- B/ c  X% E

    & n6 |. s, N/ o1 p  \这类状态转移方程一般出现在线性模型中。
    , i1 r! p2 h  P: ~2、2D/0D$ T6 Z, I2 {0 L7 h! q# ~9 P+ c$ w/ A
    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} )' U8 b, t1 @4 w# s7 B
    d[j]=opt(d[i−1][j]+x # R' z2 [: I$ {0 C' l# l
    i5 J3 w7 e+ g# ]& A) ~: R; x5 Z
    ​        $ `' x/ J. i5 H0 h) X+ D
    ,d[j−1]+y
    3 N6 X  H  A9 M. gj
    / ^4 y) ^! U, _6 O9 L( A; H, R​       
      @  y' l/ v$ z8 C9 ^ ,d[i−1][j−1]+z   L4 C# p" n3 f
    ij
    ! N5 K, w1 K' N2 |/ V- x# [1 f​       
    + g) J( ^; O! {+ w( \! | ), ]+ g# B' g# m; k! m( Z; `: L
    状态转移如图四所示:
    & G2 C; K' Q7 L) R
    0 f) \" D1 z5 ]+ d2 H

    4 Y. X0 Y, l7 p比较经典的问题是最长公共子序列、最小编辑距离。! ]8 A% l7 o9 d% O
    有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    % }$ i5 t6 i6 g5 g3 i. J: t有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    / D. A6 p. Z8 i( Q3、2D/1D
    " c5 G3 U6 h! f: b; X) ]7 Vd [ 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] )7 f7 s) l: }7 v- Z5 e. s# j
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    8 i8 w, S" Z; e0 [区间模型常用方程,如图所示:
    . K0 q$ i" s5 d9 Q  R/ G% \9 A2 Q+ H) T5 A$ ]" X6 U, ]) p
    6 ]3 T* l! L7 R7 o6 |+ K
    另外一种常用的 2D/1D 的方程为:
    & |& L5 p$ u7 a8 y0 Jd [ 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 )
    ' G- v: Q6 B- I( T6 Ad[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)% k8 X' Q2 W5 }1 l# C- Z7 l
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    : s# K- x; I4 e- E* S, Y4 y; i4、2D/2D4 F  k. V) \9 b: E/ c+ X$ S
    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)
    " ^* f5 D  ]$ H& Zd[j]=opt(d[i
    " }& |% A7 T: a' K) R
    ) p. `$ T( d) O7 j ][j ; y* y+ s' g( S/ t+ ~1 B$ B& ~
    ' e5 W6 H  m4 Z2 a
    ]+w(i
    4 B, R$ S! X1 ]
    9 H2 J7 h" t: W ,j
    , C& M2 l# E; D
    8 q6 b- a  O+ Y; b( p) G7 e ,i,j)∣0<=i 7 b4 {! I1 T) y3 B3 P+ B
    / q1 I! Y- P; ^- H
    <i,0<=j
    8 s' s0 c+ j3 k/ T1 g+ n
    3 Y7 u( x3 A* f9 s; J& l- T/ V; [1 @ <j): y/ J2 h; U: u( ^. U1 g: H4 }
    如图所示:
    0 L- n" w; V! u: j, \2 N
    3 \* M: C! a0 F) o- P. S
    " v  f0 n0 f& n
    常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    & E1 g! V* z  e对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    & y6 o. Y. e# j3 V) N6 At+e
    4 R4 V  E4 Y) }+ T; H ),空间复杂度是O ( n t ) O(n^t)O(n & J, N; O) ^/ M1 e# B* O. [
    t
    " s. Y# k$ {8 ]$ h ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。
    . |- L3 E& o( h: w3)计算几何
    1 `  J- B" ]; b6 d* t  a- J计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    * i: a, m7 l) v7 G5 `' W% _7 j如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    . F6 w! n  j1 r! x1、double 代替 float' m; J8 @; T% G; O) i
    c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;' k8 v& ^% G2 j# l3 A" x0 v
    2、浮点数判定5 Q. y. Z5 |; R+ @! P2 b' n7 J' x
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    0 z# S3 C  S! ?7 Y# {" U两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:/ X6 ^# }5 `1 C" {6 `% R( P
    const double eps = 1e-8;' ^/ W- f+ o& D
    bool EQ(double a, double b) {' r, p  q2 I/ a8 p
        return fabs(a - b) < eps;
      S7 v; Y; v" g- t: e- j/ @; [}
    3 ], r# B3 W( K6 M( f0 Y8 i1% B6 c2 g9 U& U
    2
    9 G, {& m' d& _; W2 K) L9 b3; R8 F: {; c, W' d8 B* G* l
    4; P% }2 l5 J) w& t, C
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:8 z, @6 k/ q$ x6 C; f
    int threeValue(double d) {
    3 {0 J: U1 |: \' }0 L- h; w    if (fabs(d) < eps)
    ; W" ^. k0 G5 V) t        return 0;9 ]8 o3 }, v! @) r
        return d > 0 ? 1 : -1;
    0 m3 `( |+ {1 g# `' |2 {}
    % H9 T2 X7 {) U% Z9 y* Y+ N1$ H$ w$ I$ n( Q8 I2 H% b% T
    27 ~1 s( G' X' |1 K" v
    3
    - O4 W, U% T# Z4
    & S; L' |2 a, H3 `, x5
    8 q6 ?/ h1 p9 {( b$ o3、负零判定
    . {% V* i* Y: S6 G+ Y9 d& {因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    ' J( H- m. a0 `, l    double v = -0.0000000001;
    8 r8 {1 x* L) o8 S1 D    printf("%.2lf\n", v);
    ( n2 r" d4 c6 r5 K! D18 I' J2 G2 j2 ~9 V: S9 ~1 _# y, I, i
    2
    # A! n, w: P* R- I1 n4 [避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:' g" b! q$ r+ G3 C, ?$ w
        double v = -0.0000000001;
    / H3 q- p% @+ u) H, M# {    if(threeValue(v) == 0) {
    ; Y  [4 N9 A, n$ \& }        v = fabs(v);
    % Q1 W' _' e0 e# `5 l    }
    $ C: `$ e& s: n4 p( [    printf("%.2lf\n", v);7 r5 N9 l  D6 u4 Z0 ]9 q" K
    1  y8 y6 k2 v0 E7 e, ]- L% L8 \' n
    2. _/ c$ Q  [! R5 L3 F* H' x
    30 ^) t$ L5 j% z
    4
    , t1 q8 e! l/ G1 {! \- w) \: J0 j5
    % M. V: _& K" W8 u4 n- M' Q4 Q4、避免三角函数、对数、开方、除法等  ?( ~. Q2 I7 o" ?% s, q! u1 b( |
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。1 {+ N) u2 d. h& h. c
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。. i6 `6 J, |. n/ w) ?
    5、系统性的学习
    : L( W* s9 g) V8 @7 E基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;: ^% U9 @  H  ]. e; d. d6 L
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    5 e/ N" i0 O- x# v" ]' F3 [7 q8 Q0 v相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。
    5 z. K/ H7 p- A4 e& O- f# _7 W
    * e; R$ q2 Q" m3 I% p1 H( w

    + C$ `& \5 {' D/ r学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。1 G% Z8 |, g- \
    4)数论* x& ~. b) L) L0 H. }7 z( C4 C
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    1 q. L. y' H+ D6 b4 {数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。, r! x: `! J" S+ x# P0 z
    当然,数论也有简单问题,一般先做一些入门题提升信心。
    . T5 b+ d$ [4 ^) v1、数论入门! G/ V2 h# u+ r, g, A% k
    主要是一些基本概念,诸如:
    2 o; t. B  e9 }整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;3 Q+ H/ h( V7 h/ W7 {# U, _3 o- X
    2、数论四大定理) L7 H; J9 k# @5 P5 {  H! r' q
    这四个定理学完,可以KO很多题:$ n- [/ ~5 @; q1 i: h
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理0 n$ F; ]5 r) y& h3 d. |! ?
    3、数论进阶
    ' G5 Z* B+ _, I6 @4 |$ D( s! ~系统性的学习,基本也就这些内容了:3 M$ t. o8 \, g" B$ x' H: J4 E: x1 D
    扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。6 T1 J6 ~( Y' h' M$ V
    5)字符串匹配
    & ]0 j; o  Y7 X) Y. X) V, O8 ~字符串匹配学习路线比较明确。
    ; s2 `  \% r$ Z  X/ |& G# _先学习前缀匹配:字典树。
    * Q1 Z. g& c& K. V6 T然后可以简单看一下回文串判定算法:Manacher。# S' o" \/ H  Z6 @! L$ Q) G
    以及经典的单字符串匹配算法:KMP。  x$ x/ g8 \/ L. ?0 w: s
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    + x# J  b  ~) o: b0 e! z然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    ! j" F% k, O1 b6 I; q0 l& Y+ P关于 算法学习路线 的内容到这里就结束了。9 ^( L* F8 i/ Z, P8 r8 {
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
    # ^  C- M2 h" `/ Z$ l参考资料
    / X- _$ G1 g: q! d【阶段一】C语言学习资料:《光天化日学C语言》(日更)6 ]! i1 u1 Z$ r% x" A
    【阶段二】C语言例题:《C语言入门100例》(日更)
    % p: F/ m7 @4 j: F! q8 |, V* i2 a【阶段三】算法入门题集:《LeetCode算法全集》(日更)9 [6 q* B6 ?- N  X  I
    【阶段四】算法进阶:《夜深人静写算法》(周更)
    1 C* z3 h* C( {- P————————————————
    7 g7 w- N. Z4 m版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    / R1 k$ S9 k; G' O% K原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228% o0 s) x6 Q0 i# `
    4 D5 D  R; F6 R
    + T1 v& [/ N; K0 w
    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-7-31 23:48 , Processed in 0.514291 second(s), 56 queries .

    回顶部