QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4376|回复: 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
    # Z) L' S+ e( a* A- b
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
    . Q, h  }8 i* K; \% u
    ! k1 Q& F4 o) {, R: Q前言
    % r6 G& L& B$ `5 Q2 ^' h  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。! l  h& _1 H! D7 N; u0 m
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。& S) @: B9 R3 U  g; q
      刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。/ J. P- B: |. O: d0 O
      每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
    2 ]" n2 B  a4 J+ d# M! T
    # {$ b9 H3 _2 b* s$ ]+ j* l
    1 v" \9 {" X1 ]  r3 S( d2 e
    : W; @; F0 {% X# E/ s8 m- F8 ]5 g

    / w2 x' P+ y' C1 s* P; O1 S7 X- |$ P3 \: t8 x, p

      G& P5 G' p2 W, r8 ~+ Z. x* c8 \: J& C0 V! E0 ?/ j6 R9 {

    ( s# `8 n0 d) {5 y7 l& A图片较大,文章中有拆解,需要原图可以留言找我要哈# @2 A, _3 J+ w1 X# b9 T
    1、基础语法学习
    , g( Q1 F, O# e算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    ! l8 A( X) y0 t4 Y: K因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    9 [2 k3 }, l% S7 {# y) H3 l/ W, ^) E7 W+ O! N5 Z8 m% `

    . i/ Z: \( R% K! s+ m. x& W9 R) W5 i( b% \

    : Q5 X, t- w- r: X5 S& ]% S1)HelloWorld0 D+ E2 v( [0 v. S- i3 c
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    2 D/ _* s- |3 Y. ^3 M4 b2)让自己产生兴趣
    4 `) x4 v0 T% Z8 `; P所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    # S% _2 {2 R3 P7 y刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。/ g4 X0 `# k: }/ ]: s2 L
    3)目录是精髓
    ( A7 t* W. C4 T/ _+ m8 d9 c: r然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:7 ?8 B* n, f3 j( O( Q4 `5 k- r
    1、第一个C语言程序& {2 ^' a- }( o; h2 V! h; }
    2、搭建本地环境
    ; ^1 Z- |9 O) Q& `$ n' o4 t. p0 o3、变量' \6 s% a( q! ?2 p1 e" p3 _
    4、标准输出
    , C) y4 j% X) }3 j0 f  ~) R4 u8 a- i5、标准输入+ z$ F  i! B; b
    6、进制转换入门9 L' z% D- F" ?- a* I
    7、ASCII字符
    $ q3 \7 H% u% a" y8、常量+ R- F- E5 \* n1 L. |, d
    7 S6 n! P# Q. Q% e& X% ^/ J" d
    * G' l' V; d9 }. c! n9 D& S
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。, t  v$ P3 p5 K& X, x7 y! |
    4)习惯思考并爱上它1 N* ~5 g; R2 B, x! }1 K
    只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
    / L! E1 m: M0 g! U  `就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
    ' O8 C8 K! B! N; z4 w4 |! u5)实践是检验真理的唯一标准5 `+ T' U6 v* C* O2 e$ j
    光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    4 Y( E: e/ z1 N' E; l所以,记得多写代码实践哟 (^U^)ノ~YO
    - H' t7 ]+ @4 ^) @0 C1 [+ U& ]6)坚持其实并没有那么难5 {( A, Z8 a" U9 i
    每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    ) G/ W  Q6 B- z6 }+ F' U7)适当给予正反馈
    9 t/ u/ V5 `( J" `! N然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    $ F4 |* C- i7 ]1 j. \当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
    1 q7 g) L) x: K5 {5 B- r. K看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    4 L. j* F, a" e. ]6 }8 `$ D8)学习需要有仪式感6 F! I* ?5 i( Q% p) B
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    5 {% [) e1 P* q) @介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!" {! _# ^% E# K7 s; O" Z
    我也很希望大家的学习速度能够超越我的更新速度。
    ( S# M6 U9 R6 E3 T9 U+ w5 z% P2 ?  J2、语法配套练习
    / {3 H3 t6 T* x. j$ ]学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
    # F0 K: T1 m  k) ^" t# F5 E而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:" G+ E0 a7 j# j: ^
    6 N, p8 G5 l+ ^/ S; w6 x

    ( j- x! v2 H: P8 a: ]$ X9 A3 o0 V$ A* A9 g! G& x) b& c

    ' a( t  X+ C5 d! E0 z从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。* y  B, j! F! P
    这里可以列举几个例子:
    4 A8 w+ p) }' K. H8 {. ^5 h; [1、例题1:交换变量的值
    " X* V& y% a+ M% V一、题目描述: Y4 w( m1 `' D! {. d* Q  I
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。! }7 W  F# F/ F
    - i+ b! Y6 j+ W  o* {

    & t# E, N# K, N0 B4 V# H' \0 l. B2 i2 K2 _  F4 j" ^
    / J/ }2 O5 z# ~& n0 g
    二、解题思路
    $ Y6 n' {  B4 P% Y4 |2 F5 ]0 h难度:🔴⚪⚪⚪⚪
    . r7 w7 m1 P8 i* |; u3 x0 Q
    3 @; h+ x  g- r3 Z- F6 F# [- d
    6 c- I# c- b, A( O% l* X2 A& _
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。, i6 \& [) {$ w$ V" W
    a, b = b, a
    * |* d$ d$ P# m1$ k) {  k  {( i
    在C语言里,这个语法是错误的。  W, d" j' m, |) }7 I9 H
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?
    ' h6 u: e, S9 p当然是再找来一个临时杯子:
    7 G& R5 T6 ~. i  H6 k9 D  1)先把 a aa 杯子的水倒进这个临时的杯子里;. H$ V& b' i: n2 K) w
      2)再把 b bb 杯子的水倒进 a aa 杯子里;5 H' V' K$ _) ?# b
      3)最后把临时杯子里的水倒进 b bb 杯子;4 f% ]2 m( P! P4 P- n

    4 p2 Z. z6 z& ]6 G* P# M
    , J9 v- _# `/ j4 J  H" n+ c
    这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。+ V- m% l! Y- S
    ) _7 j! n6 R( f9 B5 |9 A; F

    % b2 o, s0 s6 X- H5 w& I三、代码详解; r: H4 z4 A! g5 o. E
    1、正确解法1:引入临时变量6 L! M7 p" B/ k5 `" u% S- X5 T
    #include <stdio.h>0 R, f8 O- U2 ?! ?3 m6 s3 ^
    int main() {
    6 s+ R3 {4 Y4 ]1 A9 t    int a, b, tmp;
      O. a8 b( Z% J0 Z' u) I        while (scanf("%d %d", &a, &b) != EOF) {
    3 G8 ~) ^. N, Z' T0 G8 {5 w' J            tmp = a;   // (1)
    , y# A* ^3 R3 j: v# W  x            a = b;     // (2)3 r' w* x( `, g2 a( D
                b = tmp;   // (3)
    . ]) S- E! D1 P. U, m* e; R            printf("%d %d\n", a, b);
    + T, R" _4 Z, T+ G) g        }. e7 {$ a7 r6 r3 o
            return 0;* \8 n" f' J( p$ q( D; j
    }
    $ J4 ]! e3 k) m+ {1
    " m9 A" i' S$ p, c: B2
    ' g& a1 u; X6 [3 r- p36 o; P- Y% {8 L0 ]" ]
    41 \! e) l6 d+ G( R; H' c; G
    51 q+ `& G  U& ^5 O
    6
    $ z2 y, ^- A# }/ H( k* b: P7/ `# Q- e8 k- c$ b- Q
    8
    * d! C8 h1 W, O# c% N: b9 t9
    0 w3 X5 A, P9 K" {0 H" B  a109 C) ?0 S$ \5 F% ?6 y* `
    112 H( k# b; p9 Y, L
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;  D- S$ K" n/ r3 K
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;; N# N+ c; ^9 W4 l- P3 q# D# K
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    8 T' N* H3 s! ]2 w这三步,就实现了变量 a aa 和 b bb 的交换。
    7 u+ n0 `' q6 K  m$ O$ H2、正确解法2:引入算术运算% _! k# ?; L2 Y3 t/ ^* U: q
    #include <stdio.h>& @3 p" v- O0 r5 F/ g) k1 s
    int main() {. G+ t4 a1 e7 Z' p. }9 c% m
        int a, b;
    7 D5 p0 D* `4 Y  V, E8 ^        while (scanf("%d %d", &a, &b) != EOF) {5 ]# g3 K& N" M8 T
                a = a + b;   // (1); Q$ a) q# L* d3 t+ B1 G' @6 L
                b = a - b;   // (2)% ]5 J, p: |$ V$ ^$ u
                a = a - b;   // (3)$ ~3 U6 O7 C! f, e
                printf("%d %d\n", a, b);
    8 c4 g& b! L  a3 z! g+ J4 Q        }
    , T0 N/ y" y4 s6 u& z6 L        return 0;& q" N; F- I- S) \8 ^/ h
    }
    6 t* J$ |# m  i1
    6 p7 |; z4 Y% A! x2
    ) W; Y% q1 I0 S# z6 h0 u0 u2 ?3
    ( y" t+ U" O1 R8 _$ i6 |4' `! A8 P! F" T
    5
    8 Q8 K: i/ d; t1 [5 E. {% x6 T6: ?, T8 ^" W% W7 |
    7" P' Z' m0 X4 p" Z
    8
    ( D1 H4 C% y  _- A. V9
    , p7 {# v+ o# o  S# n  a1 N8 c10
    * M5 @+ {1 G- U5 `% T11
    9 D. y2 p: \/ T1 J( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    0 `3 M. D! \2 y0 k4 I8 C" D( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    : w6 |& i7 d# j% h( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    2 h; r+ S! Z8 i从而实现了变量a和b的交换。/ w5 ]  C8 I: C4 p, X: k, V
    3、正确解法3:引入异或运算, u2 G& B9 Z$ _
    首先,介绍一下C语言中的^符号,代表的是异或。; u# L2 y% A+ a9 x7 ^
    二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    2 M# n; j  Y' H# g5 n左操作数        右操作数        异或结果" _) [6 ~6 b2 J4 a; W
    0        0        0$ V) F7 C0 `( ^# i3 e3 v( L( @! o
    1        1        0, B, p+ \1 a# @  D
    0        1        1
    9 J* ~/ K2 x  p2 {* j% G1        0        1
    * B* A+ ]- J+ b+ F# P) J# e也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。- |8 ^4 \9 ]% f( L; K& z! m
    这样就有了三个比较清晰的性质:# T! F! ~. C5 k$ R7 f. i
    1)两个相同的十进制数异或的结果一定位零。
    - c4 l5 M  _3 O* @% Z7 x" U; D2)任何一个数和 0 的异或结果一定是它本身。
    0 g" w4 E2 Q7 _" k3)异或运算满足结合律和交换律。! D% F2 v5 t: z2 y
    #include <stdio.h>
    " G& n' c& k9 N4 d8 ~8 d  Fint main() {4 S- r; y' u% X% x
        int a, b;
    & H4 U/ H. a  K5 F5 U        while (scanf("%d %d", &a, &b) != EOF) {7 v3 _, S) R; M8 i: u+ T7 `  |
                a = a ^ b;   // (1)
    : x8 w7 ?7 }- c" @5 V- {4 u            b = a ^ b;   // (2)
    ' P5 |3 _. w* q            a = a ^ b;   // (3)
    $ `$ I% B, N: N( W% O3 E            printf("%d %d\n", a, b);4 d# o' M. F% F, A" _' R- ]* W* O& k
            }
    $ U) @+ l0 ~6 c        return 0;  f" i9 P8 C9 K- l7 i  F) D. m
    }& y9 _& t- Q- m+ Z! J! Z4 s
    1# u1 G) O! G7 I; r1 w1 P6 i
    2
    % {' Y+ h  c* g0 s- b3+ W4 R. n2 b' C0 ~
    4
    # i7 }) ^/ H5 S5$ P( {/ V9 ~. U: x$ q3 s) @
    69 W& F5 d) ?4 M* H: d$ e
    78 H, K" W* `( R. N
    8$ H7 `1 v6 ^+ ^, }' X- {
    94 `+ j7 V: V5 {3 \/ n5 ?
    10( n+ s9 U" J- ?
    11' r# ]: R( S# z$ |% B7 z
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
    " m/ Q. U% l) E7 c; f而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。  p+ A$ Q7 w2 ?
    从而实现了变量a和b的交换。
    ' k8 @0 _" k! ^7 x$ e6 h9 L6 b4 @  S$ H. P! ^
    6 E( O+ z9 p8 }
    4、正确解法4:奇淫技巧
    $ s( ], W( c. Z* d* B当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    . h' t5 Q: @+ n* b#include <stdio.h>
    ! M' \" Q/ M* \, `9 Iint main() {
    " ^* e: B8 Z- b% _, J/ `  ?- V! l    int a, b;
    4 }" g: Y$ ]3 s/ o$ [        while (scanf("%d %d", &a, &b) != EOF) {
    9 i( J* q9 L9 ?$ M: [) e; p            printf("%d %d\n", b, a);
    + [! d0 I5 p  l9 B        }
    % w/ L0 ?/ h5 `5 z1 k0 X& t        return 0;
    6 G& }  D6 _  ?  A0 J* X& u0 _}
    4 C2 }6 y" ~" H) C14 D9 P" }1 J6 ~# v8 u, Q
    2
      R. g0 x4 \  n' s( X; y) H1 \3
    ) w( o/ K( j5 m$ E& C" [4& D8 U& J+ _" S1 s
    5
      c8 D4 @* t  d7 C6
    6 S8 X6 n% U3 p  G7 \. M7
    # i5 o; T4 {; k4 J! b( {8
    ( I1 s0 y, v+ n/ l! A你学废了吗 &#129315;?
    ; T( k+ v3 w) @0 f, C$ I2、例题2:整数溢出  O  H" l2 S- \  H
    一、题目描述
    1 z& [" z" v; u& p  先输入一个 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
    - X$ U" F. Y7 `% T62- r6 ]3 ?4 [" a- [
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。& Z; s% e3 q/ I; ], f, z) N: b" J
    % H3 o. T4 E) b* Y
    ! l* Y2 B- q: X/ o; |* `
    二、解题思路* ]4 N5 g* P8 p( z8 g
    难度:&#128308;&#128308;⚪⚪⚪( m$ ?* |( ^/ I2 `; R5 H% n" R" }
    0 @5 C/ A, U' B/ \8 [, a% @
    ' ^6 Y; W" C7 }6 C% Y
    这个问题考察的是对补码的理解。# f( X% R* f6 U, c/ C+ p
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 & _6 ?) L4 _; H& q
    62
    4 W: x0 I! J' i ],这四个数加起来的和最大值为 2 64 2^{64}2 * x/ r& i9 G* n) ^- I
    64
    9 _8 Q* I7 ?) ^! u$ d$ `4 E 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 2 |# h( P$ p' r6 x/ c0 B
    63+ x* g: C; ^$ X
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 0 {0 q) u2 j9 I7 _0 U) d) o
    64% N7 L# q9 a' L& ?
    −1。, ]' \: e* m. B$ i  m) e& o8 B' ~# |4 \
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 1 I9 o8 V/ ^+ i: K" ?$ G" [" F
    628 m4 I  U* q2 O; J1 R+ F# {
      时,结果才为 2 64 2^{64}2
    % a' n; T# ^6 b0 Z; y! I$ S644 H  N# h" A! E; m  }- t; M/ z
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。0 m7 B: `! h% A( O
    三、代码详解8 `8 t. q1 d- I9 I! l1 Y
    #include <stdio.h>) S+ ?7 T$ d0 H& f9 m
    typedef unsigned long long ull;                           // (1)
    ) x2 f, c$ M9 L! O4 h  l  G1 Dconst ull MAX = (((ull)1)<<62);                           // (2)
    " J  |9 i& h0 O9 P. H# }* q4 C- E
    * ]9 @) H' ~9 ]  x! |

    $ L1 p' M/ C% `int main() {
    ( p/ b3 L9 j. ]; i  [        int t;7 M; T& d4 z* R' D" h8 g$ @4 X
            ull a, b, c, d;
    ( X& h- K% ?$ S, `% i' J        scanf("%d", &t);% n! c& T' n0 y
            while (t--) {
    ' r9 ]. T* L6 x3 V; c/ ?                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)4 @& l% }$ H# ^$ Q% k' d
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4): k) Z0 A9 G' |5 C+ Q' {2 C" q
                            printf("18446744073709551616\n");             // (5)
    - \* ]: \4 L$ W- W8 o9 l. r                else. F/ b' N9 N& V- u8 {
                            printf("%llu\n", a + b + c + d);              // (6)4 u  T" a+ v  g; Z
            }- X8 e0 C# u6 f, _
            return 0;
    . ~0 W& K. y  F% l}4 h, U# `4 m" t+ G/ D  P6 c+ c
    1
      H1 q$ @; l! ?& F2
    5 ^& X4 O" j! i8 w5 L' S5 ~: `+ W3
    % W' a* a% ?* S% x" \4
    ; r+ D+ G1 T4 z0 l0 [5 h0 f% z5
    + p& M$ \! n* }/ |/ J61 g9 w( L) t" R( t8 H: P
    7
    6 `/ E: i2 l# \- K' \8# A. a# D# F/ ^* k0 M! m) b1 H
    9
    : \7 t5 I* a5 E9 J, b# i: S$ l10
    ' V( A7 I. }% Z% `5 x- o11
    ' C0 t, H8 {! \# s8 I12
    + z; c+ T2 z# x. _13
    $ b; {" Z# t- ?7 F14
    6 s, t4 y' D3 @: J* q2 F. f/ }153 ^! b# Q8 U) Z& T5 ~
    16; l& W" q4 V) ?- W) D! q. ~
    17( c' R% e  C/ s
    ( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;) m0 Y! N1 J8 ?7 S
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    8 z3 N1 H) F+ Y8 v8 S62# C; G) |' F. L! g8 _: Z. U6 m# q
    ,这里采用左移运算符直接实现 2 22 是幂运算;
    " l, ~& s, g) N数学        C语言
    7 H# L* K" v& p3 j; G8 i2 n 2^n2
    3 S; K* a  s7 Cn
    & ?% B+ ], m4 b; c# d, }# h         1<<n* }5 }0 E0 \3 L9 F/ e
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    ' ]! m' y" l9 m. k/ q" e( 3 ) (3)(3) %llu是无符号64位整型的输入方式;
    8 N) b0 p. M; O, n" C3 B* Z( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    ' D3 Y# L1 r& @2 J- q! ?2 q0 ~( 5 ) (5)(5) 由于 2 64 2^{64}2 & y2 N3 h; \: C  [  @
    64
    ; e+ r' e6 V5 v" m! l0 C5 Z  p' w  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;8 i* w) |& e" W" V( t7 ~5 i  [2 a( {
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 ; u/ t( V. F3 l/ K7 s3 S
    64
    2 g* x# f5 Q$ d) W+ ~; D −1] 范围内,直接相加输出即可。) {! f" C( p# s3 M/ o  C& h
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
      o  w2 ^7 `+ i为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    ) Z6 z9 _2 R( e* X
    1 L; r; W4 W& X; l4 Q, J+ L

    2 r& B1 S1 C2 q  D! f) d3 X8 o/ w3、数据结构
    7 V4 d" i7 w8 s  {1 {5 S. ?: x) o《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。4 @2 e2 u5 a( K* z9 E7 E: \
    1、什么是数据结构
    $ y7 N4 b4 G! I# H5 M- t你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    ! Y* u. _+ e. P' V# u. a5 z5 t4 i如果一定要给出一个官方的解释,那么它就是:0 J, k" v' M2 @% @0 ?4 M
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。% I  M8 y- o4 k6 J$ B
    + I# ]! u. k; o2 P4 G) h3 C

    ( Q: z! ]& |0 F5 i# C9 q8 m是不是还不如说它是堆,是栈,是队列呢?
    " w% w! P) P2 p9 U是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    , A% L/ K7 R7 L+ V  ~) L1 W! p2、数据结构和算法的关系! `/ c" V" c5 S1 t
    很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。; Q9 o& P0 \) h/ b
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。) m; m: H( w: w! t% y
    而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    7 \' t  [  m5 W4 t( N讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。6 h; }+ D4 {% d2 G/ {( i. g0 u7 L
    3、数据结构概览
    2 I% e. m; W# R6 ]周末花了一个下午整理的思维导图,数据结构:& D+ o7 U! I: F: r  |9 J. j; h

    9 e" M+ k" W* R) ?3 o" y
    , Y8 F) P( D+ j' i' ~
    常用的一些数据结构,各自有各自的优缺点,总结如下:! G; _. e4 G. ?- A' ?& v9 r- D
    a、数组
    8 K/ `& B# z% d5 X! g! e内存结构:内存空间连续
    2 S+ L- s0 |6 Q3 b5 U实现难度:简单- ^4 f" p4 p2 S5 k$ W5 v) b' w& q
    下标访问:支持
    9 O5 E! P! P3 j& ~1 F! e, Z分类:静态数组、动态数组1 I) X) m# G+ X: {. P5 A
    插入时间复杂度:O ( n ) O(n)O(n)
    7 z+ Q3 ~; A" x# X6 S& U8 x9 F查找时间复杂度:O ( n ) O(n)O(n)% j0 W9 @( L, O$ d  Q) t% K
    删除时间复杂度:O ( n ) O(n)O(n)
    ; Q7 S' p  x- C, u
    ' D1 Y/ S: }( k, }* x
    $ g+ W. A8 A" i) o7 E. t' I
    b、字符串# g2 R! @+ k! S# E1 s$ k
    内存结构:内存空间连续,类似字符数组  P% R* A" f3 D8 q4 k$ m
    实现难度:简单,一般系统会提供一些方便的字符串操作函数* Y8 r0 r) H* m; T+ V
    下标访问:支持4 o3 i+ w& h# [5 z5 n
    插入时间复杂度:O ( n ) O(n)O(n)) y7 V  t# B+ W9 V* Z/ g* L
    查找时间复杂度:O ( n ) O(n)O(n)  F1 a9 s: x% O# Z
    删除时间复杂度:O ( n ) O(n)O(n)4 l8 P# K# C) H# d; B% J

    , _, p4 c, _; N. D) J% I- T  J3 R$ O
    3 T( b; B0 R+ t: {
    c、链表0 ^& n7 k; e# H2 c* ]* d. b) G3 n: C
    内存结构:内存空间连续不连续,看具体实现
    6 \: a5 U" q, x) s6 m) c& A8 A+ ]实现难度:一般3 Q1 O* E( i3 P. i. {6 u/ ~( r
    下标访问:不支持
    & x& t# b& c" |6 x分类:单向链表、双向链表、循环链表、DancingLinks  n/ s: i* ~% y
    插入时间复杂度:O ( 1 ) O(1)O(1)
    / d& z' o3 Y, ?9 k: B  o  d查找时间复杂度:O ( n ) O(n)O(n)
    ; `: l3 x6 |- I删除时间复杂度:O ( 1 ) O(1)O(1)
    4 e  {$ l* @7 X! n( E! s
    ; Y0 T0 }/ D+ ]
    " x  P0 F# L7 U4 ~4 s% u
    d、哈希表
    6 ?9 `/ i/ X' T, }1 c5 t/ j' Z内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续5 e2 U) Z1 P" f
    实现难度:一般$ h- {  y/ v  \5 q* x9 G
    下标访问:不支持# i- F3 @! p( I3 Z$ C; F( @
    分类:正数哈希、字符串哈希、滚动哈希
    0 j0 ?1 F7 z3 X; R# X$ S: }插入时间复杂度:O ( 1 ) O(1)O(1)* p$ G1 m' M; T6 W. _0 ?2 H
    查找时间复杂度:O ( 1 ) O(1)O(1)' h' N7 A' L* A2 ~
    删除时间复杂度:O ( 1 ) O(1)O(1)9 V, K- L; o- ~; h) q
    6 ^% A$ S2 d0 b7 l9 M6 |

    ( d: s  |2 F( X* i6 x* Oe、队列4 j6 F, c3 |$ {5 f  O3 p
    内存结构:看用数组实现,还是链表实现
    $ c$ y7 H, N+ X  p! ]. V- v% q8 S1 A实现难度:一般
    # f$ l+ k& e1 W% \( b下标访问:不支持- Y2 M8 l& f9 }6 y3 z0 Q
    分类:FIFO、单调队列、双端队列
    8 Q3 Y' V5 [, i1 y; r) l插入时间复杂度:O ( 1 ) O(1)O(1)1 ?7 X" A) }: |& I8 [  L
    查找时间复杂度:理论上不支持; f9 K0 ^* I" [: s; a$ b
    删除时间复杂度:O ( 1 ) O(1)O(1)* e8 e2 Y1 {. I+ i4 c
    " E/ Q0 s6 ?- H7 s/ w$ n$ e

    ! X& i9 H* m! s2 }f、栈
    % G6 x& j% j  ^' c5 \( h. [& O内存结构:看用数组实现,还是链表实现: q2 |3 s4 C5 y2 Q
    实现难度:一般
    : f- {2 A) |6 }9 x下标访问:不支持- u- U' E2 x4 d! ]
    分类:FILO、单调栈
    5 d3 k  H( ^6 L* \0 h- L. L) P0 G插入时间复杂度:O ( 1 ) O(1)O(1)
    ' ]$ F0 V3 v- Y; P查找时间复杂度:理论上不支持- Z# ~2 }- _6 ~/ T) ?" _1 T
    删除时间复杂度:O ( 1 ) O(1)O(1)
    7 j$ R4 @3 T  p' N8 r& y% C/ [
    5 Q0 b9 T; r* W4 m# j
    3 e) C5 u& w& E) g/ n
    g、树
    $ k5 H* C  |4 g8 o% t内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续. l' f( d- f/ p; Y
    实现难度:较难
    . U- j9 _5 n5 z$ B下标访问:不支持
    % W1 }- W8 Z+ s1 n" {分类:二叉树 和 多叉树
    0 \) u4 f% y' j  K, C插入时间复杂度:看情况而定
    ' A% v& s) \6 l' P6 Z9 g; V0 ^* H查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
    " c( g6 a- Y& g; U2" u) u+ ~" R/ o8 B
    ​        ! d9 a- |9 K, Y* w
    n)/ |4 A& \3 O0 ^2 X0 B* V
    删除时间复杂度:看情况而定+ c9 }/ @  M0 V

    9 b1 S: |4 N# ]) k# o0 I" w$ b% B

    * D: ^: Y' r# x2 n3 u1、二叉树
    4 h. _; A4 W. o- m3 Q9 ]; B; N二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。9 Y  P, V) Y3 }  L' A
    其中,堆也是一种二叉树,也就是我们常说的优先队列。
    & l; x: y: \% f0 S9 U* `& e" B8 S2、多叉树7 f/ @0 }. f9 S2 |
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
      U) U  @6 @- S6 @0 Jh、图
    : N* @9 k; b/ ^$ ~内存结构:不一定
    & ]: J, m7 A7 O! `. d/ B实现难度:难
    " k. P8 K$ {* j. F0 J下标访问:不支持) ^2 Y# ~8 Y, M1 K9 X# _% {
    分类:有向图、无向图
    2 ]: v% C$ ]% @2 B4 g! u插入时间复杂度:根据算法而定  X5 s8 c3 M9 B! a* T& t
    查找时间复杂度:根据算法而定
    7 a- h" J4 @2 h) \4 o2 ~删除时间复杂度:根据算法而定; l! G! w. ?. }& a9 }$ a# _

    - x4 ~8 A  }# L7 Z% D

    - u  t% ~6 \# p' G1 T5 r6 m2 C2 w1、图的概念# ~/ j" s; A, c
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:( L# d+ j& ?& W& n; B
    图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。
    - v) e  Q8 x9 E; _! 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 为权值,可以是任意类型。8 J3 I8 Z% S! F
    图分为有向图和无向图,对于有向图, ( 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;
    # V9 D& g, I2 @  n' o3 @2、图的存储; O  \4 S) U; u9 x" [
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
    5 a3 p8 `/ G8 Y" A3 Z! N6 ^( e0 R" F& ^
    1 ~5 a6 J4 f+ G! i4 m( K& ~! d
    1)邻接矩阵3 r( Y" |# h7 e! r
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。: A. H" Y+ c9 s0 c
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。
    " o' `$ h8 V' Q' S4 d[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[
    0 q9 B- i' J  Q3 A" I- f01∞9∞0∞8320∞∞∞30
    : }6 {9 J4 L" F1 b2 E0∞3∞102∞∞∞0398∞0
    , L: Y6 |' Y. N; S\right]# M- [' f8 H6 v* X# o

    4 \+ O- D% m! P0 L0 d" t; b: x8 m7 O( |5 B! h$ F2 Z* E

    2 }$ k% _8 E. ^! e2 Q0 I/ K( e! {; ^  v6 V) K7 T+ L7 e; T1 I
    ​        $ G1 f0 r4 ^  H2 h6 v! e
      
    9 o/ z9 b9 t2 R) o- ?1 \0
    - R( F5 ]5 j* E3 j* t- M1: o5 E8 q$ l7 Q, K1 b
    0 S/ P& z. U/ o9 }0 L0 D+ @
    99 k; y# m! x, k' D6 u
    ​        . n% u2 K0 \  f9 ?
      2 X! I  M! \4 K- i% F) D& K
    0 [8 S& X. J9 S8 s) t0 Z& ^
    0
    4 T- T% k+ r& Y' I  ~/ \" U: K8 Z
    , ]1 q8 Z* j& D, M) R2 a- r82 x6 ~5 I1 T5 w8 Q6 A3 K# J8 y, |
    ​        2 y7 L" z# O0 L
      & }7 S; D( y# `. m
    3
    ; {  b0 D( s4 c( V- X9 n( n3 d23 b6 y7 a. E/ ]+ S4 D& E6 m/ j$ S
    0: U, g. k/ Q& b" g3 h5 T2 }8 V0 S

    ; \2 A/ h9 u: a0 R8 `0 M7 m$ e+ e$ G​        # _( ~: w; U+ [  V% ^3 c
      , L" ^; U  Z1 \; e

    9 z9 j' ?3 k# P. m" H2 ^; M- D: k7 p% |; g+ [, V
    3
    # E8 Q! a' p; I0 ^- I- ]7 V0
    0 G# _9 g4 x; Z" x0 I8 L​       
    1 w, s) n) e+ p7 u5 O' w  
    5 o& C( ?/ |" v0 j* }/ q+ t1 a4 x, B' n7 B

    ( j/ j) D9 N: A( l& R  |7 I9 F( R8 H% l1 v4 u6 E4 g3 A
    + W+ u$ o+ D/ z2 W. R+ f  S
    ​       
    . L# A- b5 q1 P- }+ i' z2 N
    8 r# c8 Y: k' {; J; m0 b5 O9 r- m8 w2)邻接表  S- N! C: h2 L5 K; M1 Q
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    $ S% b6 a$ W( B5 b1 _2 p它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。2 I. p. Y5 X: |1 O  Z9 |9 L
    如图所示,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) 二元组。$ O  u( D- O' }3 V% Z8 C( w
    : k' ]  x: A+ f  Q, o  I* O

    0 D: {0 Q! x2 P在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;; A1 D6 Q! E# s: B7 c
        vector<Edge> edges[maxn];
    # H7 m6 O% V2 C7 j* m& v18 Z# L( ?+ D9 X
    3)前向星
    " }$ N" _& M8 q( G$ {9 ~4 i前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    4 S9 i# W# x1 X它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    ! I5 H! r. U5 @5 s如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。. W" ~, \+ L, x0 Y, W# u

      q/ H& \& M/ N: n# u+ v/ H4 S
    + w1 Z, P. {6 h: L, T8 ?
    那么用哪种数据结构才能满足所有图的需求呢?
    . K1 P+ @  W; o$ @( r7 z! N接下来介绍一种新的数据结构 —— 链式前向星。
    . k# Z5 }  k$ e% z4)链式前向星
    + e; U5 G! {6 S4 j链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。. a& z' q) `5 z0 T
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
    " Y7 x" f$ R. `3 p/ p: }3 L7 {边的结构体声明如下:
    ; p4 \3 G! s+ y; O9 u4 ?) D, Xstruct Edge {. w# R9 l4 D: }4 ^) t9 J
        int u, v, w, next;
    # y$ M7 p1 _7 X; z. k# G0 x    Edge() {}) x$ @8 z* y/ C9 n
        Edge(int _u, int _v, int _w, int _next) :4 w6 H$ R3 @% ^8 n7 @4 [; m1 Q
            u(_u), v(_v), w(_w), next(_next)
    " ?  X, d* `7 O# o6 Z; d( z: f    {; v( E+ E6 i& O" M) ^
        }* B% O) J! f3 C# |' Z0 {
    }edge[maxm];+ y' U  g- G! h) f( [: w1 B1 E
    1
    5 K7 K# @0 J( m2' n6 n4 f9 Z( c  J  H6 N) ^4 c
    3
    ' N$ |6 Y/ C- s4
    " A5 a; j+ B/ U# Z& a9 H5: a6 y8 z+ D$ u3 _' a/ w! c
    69 X6 h- f2 I' ]" Z$ ~$ x% q
    7$ |9 E! }" g/ D) B
    8
    2 S  @$ ?; ~6 r6 N初始化所有的head = -1,当前边总数 edgeCount = 0;
    ; d) Z& D4 p$ m3 X7 h* Q  z. Q5 \. {每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    9 K8 M5 Z+ q! S2 ~* Avoid addEdge(int u, int v, int w) {3 X7 I8 u0 K3 m* u$ p0 t  |
        edge[edgeCount] = Edge(u, v, w, head);4 s# ]6 O; F+ X! O$ p( u6 P
        head = edgeCount++;
    & q7 N1 \5 k5 S- L2 }0 y8 _5 m}' ^& T5 I" l, Y; E8 [& r/ V' ~/ x
    1$ u9 m: D+ ~& O" Z( a
    2+ }0 p' Q. f! i) ^  b
    3
    + I# f* }- ]* `4 s  X49 ^* X* l6 o7 N
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。) L. g5 p/ g* n6 M6 ]
    调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。" F+ d- L- c2 `7 J" T
    for (int e = head; ~e; e = edges[e].next) {
    0 o& K+ Y  v0 {3 N+ l6 i* ~8 z# A# W    int v = edges[e].v;' }7 [' O2 S' ?3 f5 U
        ValueType w = edges[e].w;* ]  `  h) J5 T, D- p& F# E
        ...
    5 J! l) }5 e* C# C. u# U7 M}; s6 z: g) S9 i7 }; ~0 U, j3 k
    1% }# V( c- J! F& H  H, e
    2" B, X/ r9 s6 o
    31 k5 U7 t. J* \+ \; w
    4+ K) s, A: y& T9 Q% x& C, L
    5
    , a5 }( b! w1 v9 I6 \2 p3 m9 f5 u文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。9 a( v# o$ B& ?/ y
    4、算法入门
    . u4 R3 x4 T9 w& h  @" @2 \算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。5 e" F$ [6 @. C0 v6 N" s7 T

    ) W! S+ [2 u" W0 Y; h( V$ W9 V

    ! ]% `; L% P$ }9 n3 `6 \& O: n入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。- [7 l: t% F1 d' v; M! Z$ B
    对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    5 M8 ~9 m9 [' G: D9 O$ e/ |6 _1、枚举) l* z7 d# d, N
    枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    3 Q0 x# g7 T# F对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
    4 y$ z' E$ X0 |" V: h1 B/ l1 q2、排序
    # j8 X- s! O, [既然是入门,千万不要去看快排、希尔排序这种冷门排序。$ x. ^  [8 g4 N& ~2 _. t# @( O0 q
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    8 k  l$ {7 E$ oC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。5 i; u- o- v) d5 [
    3、模拟( }! S- f/ H0 H" i
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。  r" f% M! z# v/ ?
    不管时间复杂度 和 空间复杂度,放手去做!' s; E% B0 b; A9 N1 e, _
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    9 u3 {2 ]8 n' J# {5 ~1 P4、二分
    , L! a- p( H. e* f二分一般指二分查找,当然有时候也指代二分枚举。
    / T1 R  R5 t# `; U7 ]1 B例如,在一个有序数组中查找值,我们一般这个干:5 \* a6 I" N; M& g" @
    1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    ( W+ H! t1 I5 a- I; w* S2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:6 v$ j, |: `3 {! A4 d5 O
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;4 q% I' {% N3 _- Y* u( W" u( t
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    7 b# ^" d5 y2 L7 @' I, u  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    ; w. X# u# x* s8 v& k  L3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。4 a3 A) {! ~5 V5 r8 I
    5、双指针
    ( ^7 ]4 F; w8 U: _* n双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。* J' Z1 v  ]  I8 C9 ?* y
    ) s+ M+ Q) M! L; z
    ! J, w/ u0 k8 o7 r2 g) Y
    6、差分法  R4 ~$ l7 P' J/ R' c" F
    差分法一般配合前缀和。5 Y' J7 [4 r2 k3 ]) Q$ k, T8 h
    对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;% c$ \+ R9 P/ t5 d
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg # V& d/ c- c' |3 h* t8 ~9 }
    x
    ! \3 q! [( {8 }​       
    7 V5 @8 E$ q) U+ e3 m! } ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g ) y/ F9 s$ k+ B1 c7 U8 }# V
    r. U+ T# ?5 Y7 ?
    ​        : D  ~2 K# n, k' m9 m
    −g + I7 s3 Q8 f0 g. Z$ A, y4 E
    l−1
    0 ^% E8 w- d  t# M& x! z; ]  B​        0 @! z/ b1 X* ?- J
    ;分别用同样的方法求出 g r g_rg
    ) Y: p8 \, g4 Z( E! e& @r  W6 w$ ^- G) \6 G1 A
    ​       
    ; q, d$ x0 |, v3 y+ g8 n  和 g l − 1 g_{l-1}g
    ; y3 N; t- C! n& p) h/ T# S$ o& dl−18 b& N2 R6 P+ r- _/ z+ m
    ​        ' \7 E) o' U% f: r4 _1 H! H
    ,再相减即可;
    & f* I9 d# H! v
    1 K) {  e+ n% D: H

    ; a% |3 p) l: H& t1 y2 P7、位运算, b7 _6 I! o; k2 f1 T# p# u. o
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    & y  |9 r) E- S6 S5 k5 g/ [  |, l: J6 R位运算分为 布尔位运算符 和 移位位运算符。
    " f9 I1 g" j" t( `* G布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。5 a/ J% D  [' G; V
    如图所示:7 }2 m0 N+ u3 T/ l7 T

    1 ?& p& _3 E% a. N! F% e) n
      R4 F6 Q8 r: w1 u7 t9 k7 \
    位运算的特点是语句短,但是可以干大事!
    3 f" w  u- ~: T' y) a3 I比如,请用一句话来判断一个数是否是2的幂,代码如下:
    1 r& G+ p' q6 N7 J!(x & (x - 1))& N( h' b$ n0 @
    1
    $ ?3 v' x6 R3 X8 \! m8、贪心  T4 |9 a$ O" X9 ?2 W
    贪心,一般就是按照当前最优解,去推算全局最优解。5 F4 Y% Y) k; g
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。# V1 U# A1 |& }" D
    9、迭代
    ! i$ F- \/ A' _% V3 L$ N1 }每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。, t1 n0 _, z; E- k6 d) B, O1 ]
    10、分治! C% ~3 v- C% e$ G: h
    分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
    6 v# J) X+ D$ x- u9 L8 g  X; X5、算法进阶0 o# ^) }  a* H2 P5 t. z7 o% l. a0 X
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
    1 A" V" c: |1 J! ~7 a如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。6 u9 s6 K  p5 u2 S3 C6 |
    这个系列主要分为以下几个大块内容:
    ; \# B3 s$ r9 s6 n9 ]6 i  1)图论, P( D+ \7 ?# O8 [- S, P  ^
      2)动态规划! v! F' b6 G8 G. J* j
      3)计算几何
    6 s7 T" ^6 Y; z* M' ^  4)数论
    : j( w+ Z7 Y# _' t! ]- u  5)字符串匹配
    6 g& F7 K: i. z$ g4 o! N  6)高级数据结构(课本上学不到的)
    ( f0 b; G+ V6 ?- y8 e( n  7)杂项算法
    % U$ Z5 f3 f, ?! Z1 F% g2 h
    ; b  D" v" i# l. I' d+ a$ R1 D6 S
    1 @2 E' q0 _2 b& ^, W4 h
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:6 D: q& r( V* \' k4 I5 Z! J
    0 R/ p3 l! o: J& W$ e

    2 o  k* n' A/ W0 e# G3 d( P) x' D8 F* Y! X0 r
    / w) o$ K2 p2 Y, L; M
    1)图论
    7 O6 U, u' S2 K" `2 ~1 B4 z1、搜索概览2 H+ I0 W7 V& ?  y2 Y* c
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。
    1 R4 L$ K- B" q6 T1 s
    3 R, j+ p3 O2 H% I7 a

    * {5 a. \* m+ q: S. r比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。$ E) N1 \5 q$ k  \, @# |6 }  Q- e
    2、深度优先搜索- x7 l, g4 j8 |& v. [8 ~
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;2 V! K9 |( `2 G; e* r1 z. \6 R3 R
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。8 j: M, C* a' A. t6 ^( U
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。- d. }4 T. t' f0 Z7 u# Z
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。, f0 O2 D& s9 p- X( L2 }

    - q' K, t, ~$ s! V4 E* S7 C

    : V( w7 [0 W; G+ G% B0 s4 k& |7 E2 J红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:" L$ C% Q  Y$ a
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6  N: h3 T8 n8 m8 v6 L
    1
    . u4 S; [5 M/ E/ e. \: J2 c2 r同样,搜索的例子还有:
    4 ?) F' @. C6 J: z8 c7 \
    + g5 Y+ X# S" j- ?$ D4 W' X

    : u" B7 g: Q2 U3 P计算的是利用递归实现的 n nn 的阶乘。1 S+ |# X6 n% m9 t
    3、记忆化搜索
    8 J/ p9 \1 [5 J对于斐波那契函数的求解,如下所示:4 U% n/ G. r& Y
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =7 v2 }8 a1 W% \
    ⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)6 ]! P. D% Y: s  J+ `: J# {
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    - t) {! k- q0 a, e/ cf(n)= & I2 l9 J  |% ^  q% ^' j
    $ d. `  ]2 C& u: E3 x4 V9 P& y
    ' `8 s0 N2 {9 E9 A4 P) T

    $ n, d- O! A" P% B* h6 [7 L0 n! @" v+ S3 T( Z

    - b) p( y+ _- o/ ^; g* w6 Q) s​        0 A& u1 S! ~' e" i0 W0 `5 B
      
      f; y9 g- S  Q/ l1
    & z- t% ?$ |' y9 I1 T  K1% x2 a' w* n; I/ F- K* k' |6 a- Y
    f(n−1)+f(n−2)' q5 o3 q7 I+ o1 G/ f" T
    ​        " G% W7 v  c" @* Q
      
    9 C% K+ G1 o1 G# {, l(n=0)
    & u7 o2 J# {9 L( G* u7 U# ]/ W(n=1)
    , c1 U% P8 C  h( _: }3 G(n>2)
    3 {% t# j0 E5 s4 ?​       
    % ?  W! v" F6 q% S. v% F $ @: {/ T7 Q3 E7 E3 W' J
    对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:9 N* j, c8 |+ H
    # f# n) A, u& R  J" x: M$ {

    9 j4 F: j' U3 V1 K这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    6 R) Z3 g7 ]. N" u! T我们通过一个动图来感受一下:& w; Q/ u$ e; }" \$ ]

    " w% |4 Y6 x8 J8 P( M
    $ u8 G# E: R6 Y% F" ]
    当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    8 q) i. s: f1 H这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    2 P5 n( O( M* C1 y3 K9 T# _4、广度优先搜索2 T. B* }  T# D% a
    单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
    + l" J% o# ]; `( D5 f我们通过一个动图来对广搜有一个初步的印象。
    / y1 |+ Y8 b4 a; ?2 Q7 t  j* W7 Q! c9 Z' }" F3 a1 Y

    , F! [2 E9 R7 x1 a" {5 p# Z  [! K2 ^$ |7 s3 u0 J

    ! y0 p1 r* I! x) Z! b, a, |从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。" h5 O  `2 h. Q1 L) B
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。/ c2 h2 n/ O1 j
    这时候,算法和数据结构就完美结合了。
    : i, B* y9 U% L' _4 S$ {% Y2 S2)动态规划% u- e; Z0 z+ F) `1 Q
    动态规划算法三要素:
    $ c# {$ _* f" C. Y  ①所有不同的子问题组成的表;$ l" c) p- W5 k6 g9 i
      ②解决问题的依赖关系可以看成是一个图;9 Q- w$ O! {! {3 W+ ~
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
    7 [: e# ?# |; _* f- u+ {) p9 r7 K# W

    7 |1 @5 z- v! Y" P: j; w) S如果子问题的数目为 O ( n t ) O(n^t)O(n
    / }9 t- y# s1 ^* vt8 c7 ?! N7 v: Q
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n
    - U% Y3 t" e5 t8 ee5 A+ p; y/ @! _0 n: e6 G9 A
    ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    / z0 ]9 ]. u& D; T1、1D/1D
    % y( v( _% U7 M' l" Gd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    9 E; c# j6 K, H; ]% F6 Xd=opt(d[j]+w(j,i)∣0<=i<j)" |# [. \* B, W7 B2 ~& S/ k
    状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    4 ?: u! H. A& l# p4 d8 n
    # T& i2 y6 B- H' q+ \4 Q8 @

    & k* Y4 p' h# K这类状态转移方程一般出现在线性模型中。! Y1 C, _% g$ E; {- i3 d6 V
    2、2D/0D
    ) i, v* M- ^6 @* E. Sd [ 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} ), S0 G- Y! v. P0 u- P; {
    d[j]=opt(d[i−1][j]+x
    , g& P# c, K3 v# _0 U2 ti+ q  T" A6 d5 k& V, P1 Y; B- W
    ​          u" s: n9 E" f, d+ P3 l
    ,d[j−1]+y
    ) Q# [1 v& b5 O/ h2 ~* Pj
    : |5 o8 c6 F: F3 b; t​       
    7 Y3 f6 k9 x: S! [+ w9 O ,d[i−1][j−1]+z
    . o1 m6 L6 {; g+ C+ v8 Kij$ z8 K2 z' \! O/ Z- p* D6 `0 A/ f
    ​        3 k% l8 V- J6 v  X4 O' h: o
    )* {, s+ Z' L3 \' e8 H, ~( B
    状态转移如图四所示:
    - D  R, x) g( m6 V
    * E) E2 b, M2 S1 l5 e" K

    + m0 `- l% ^" ^& _' c- {/ f" c/ ?$ l- q比较经典的问题是最长公共子序列、最小编辑距离。
    " z6 o0 X2 ]' P! C2 @* I7 i有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列6 `6 P( N6 o* s% L% ]: G& R: L
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    0 {# o& U9 S3 `* k0 @4 z3、2D/1D
    & J. u, ?# v: L& x- {d [ 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] )
    " A: u( F' U  ^+ Z0 ~d[j]=w(i,j)+opt(d[k−1]+d[k][j])8 v" ], k/ }# o6 m' ]( c3 Q
    区间模型常用方程,如图所示:% o  P( I) g" i' d7 H; U/ q

    " a$ v- ^* i  C% `; B, X

    - {2 N% a5 C7 t% M4 b  h$ {1 ~另外一种常用的 2D/1D 的方程为:& W& n. |8 F* O8 r
    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 )
    5 N; I6 m( |' p" K( c" @: ]/ q- Qd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j); I' p' @1 r5 ^& D
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
      U, i+ N" D! l4 I  ]4、2D/2D1 r0 f, E- y6 x: J3 j
    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)4 a1 M. r2 \3 @& N4 x! z4 A0 O4 `5 E
    d[j]=opt(d[i
    3 d, |) V/ z7 h/ Z" |* ^' G7 a2 O  @% C% j: l
    ][j
    0 c& c; [) e$ T- q' C) p' z, u! p5 A, C: `6 ^" }
    ]+w(i
    4 N: \2 w1 S. h  b% }: t/ B( H  Q8 Z, l0 w- g% B1 n
    ,j ) R9 p% _6 e& @# O- U, U: f

    9 ^' |; g! U0 F# G( ~ ,i,j)∣0<=i
    4 I7 o1 L4 K7 }; U) |* K% x1 P% }; X+ d% Y. [4 Q/ L+ k+ V2 Y1 C
    <i,0<=j
    ! B8 ]+ m! Z; v) [3 I, D
    4 S& g: w$ J% r. h, @9 x* | <j)
    % o& Y. j; u1 @& N7 I8 M/ [如图所示:
    . V! Y+ f  i( g3 |) M( L( T5 K
    $ n& @+ Y  Z( T" `% Z, s

    5 b6 k0 [6 I& t2 T0 ]2 a  Z常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    9 s2 X- {. R6 d0 e对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    + `4 p2 x* T* p, Kt+e# D. T, k! ]. V8 |0 _
    ),空间复杂度是O ( n t ) O(n^t)O(n
    ; |: b3 S" P: d2 V, Q% rt
    : L, j6 ?: m3 r) ] ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。
    ' h/ z, N4 W" l3 h: S% D3)计算几何$ J! \: K- X5 b4 ?' P) n6 ~& O0 _
    计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。) g/ l* S  F  [
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。4 e. K! Y4 q' B( @2 h) H
    1、double 代替 float% i/ k7 O9 J. G7 j7 p. `
    c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;5 w  G8 t- f0 }" W3 t
    2、浮点数判定
    ) v6 S7 v# l; p' {+ ~由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);# N5 r% G, q# V0 v: f" O
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:! F% a) ^$ f5 r- P
    const double eps = 1e-8;
    : W4 Y1 k. m& m7 ?- `+ r6 M3 Mbool EQ(double a, double b) {
    3 S  A$ i! O1 K3 \0 s, o    return fabs(a - b) < eps;  O: V6 Z7 ?1 N' T
    }0 _) R7 U& f8 n, Q7 ^5 d- [
    1
    ! T' X! G, i+ R22 d& m7 \- l! x  p
    3, N3 D& u/ Q* Z4 `0 u% k/ o
    4
    ; |% N! L: J/ c6 A3 h5 w并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    ' k5 G& f$ v: u6 h) f3 F3 Gint threeValue(double d) {
    ( i7 z+ u& p+ N% i" A    if (fabs(d) < eps)
    " t7 }; D# {# \$ h! j) @5 q        return 0;3 t$ z) u( [& g9 S- W
        return d > 0 ? 1 : -1;
    8 {& N- |- H  x, c( P' W5 _6 b}( ?) z1 q  r: M# p
    1$ v9 O( ~- |% I" J/ K: e! {0 O
    2
    6 v; q$ ^+ j+ p, m5 b; J: }30 m5 H) F/ J' S4 {2 v
    4
    ! T7 F* _8 T# [% P. v: G5
    ) X& v4 t6 t1 r9 M9 l3、负零判定- `: [: z3 [: n
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:) ]. S- r6 w; t
        double v = -0.0000000001;
    - ?% r$ \2 S" Q  W' l) z+ r( L' D7 C    printf("%.2lf\n", v);
    3 n1 Z2 y* B8 n' X$ Y1
    ) H0 V& E- K4 J8 c5 z9 A5 `/ M2
    * w+ Q" E2 i# G避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    2 _4 V) I, v$ J# M& o    double v = -0.0000000001;4 l$ Y7 U+ o  @: J/ h
        if(threeValue(v) == 0) {
    . Q' J- B: n5 v! h& B        v = fabs(v);
    ' Y7 J" E1 j0 q! M! V$ J" L% y8 z    }
    7 Z4 b+ a# R- j    printf("%.2lf\n", v);
    8 [9 d2 ^1 a' y3 J% B4 c3 e* G10 {8 Z* i5 s/ l' k2 W- Q' P
    2* E: G) V1 W2 r- o# r8 m( N2 K
    3
    $ Q/ Y- a1 a8 d$ T+ \4" l/ `/ f" o, k0 F
    5
    3 g3 ~8 N; D  H, E3 H4、避免三角函数、对数、开方、除法等
    ) D( }" u+ ]$ v, F( Y! zc++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。0 H' m7 Y7 J3 K# }: u- c' ]
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。; \3 m! p" y8 A& H/ l# }. e# e2 t
    5、系统性的学习
    , _; j8 @) }9 Q基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
    % @+ m* P: I( X4 J% q7 n7 C7 T进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    $ n1 P: k, b$ W相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。7 s- \1 Y" e( Y; o$ S& i
    3 q& L. a1 {- g+ C$ ?- s1 U

    & ?6 L; v# E! ?0 o. y$ L学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。! o# i7 R& b8 c: n
    4)数论! m* V- v' w- d& P, U2 F8 @9 T
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    8 K: V" r5 m( E& r% h9 `数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。" e6 j8 X" W+ ]2 x$ Z7 M2 k# j- D
    当然,数论也有简单问题,一般先做一些入门题提升信心。
    " p( _- [0 @4 d1、数论入门) g: x: Y  ]: X8 h
    主要是一些基本概念,诸如:, O0 s, V" l8 T5 |# R
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;0 p4 F& J( ?8 A9 a, |" H
    2、数论四大定理
    + a: w; n: K7 e# \6 ?( z, C* c3 a( ]这四个定理学完,可以KO很多题:0 X& H. F% J2 k+ W% r+ z' h
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理4 K. M0 S) F& J, @+ R
    3、数论进阶" t1 _3 K7 r# I6 X1 v, N
    系统性的学习,基本也就这些内容了:
    : M& `# O! Z' x3 S3 {扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。6 D4 ^8 |$ @% N! k0 y9 A. ^
    5)字符串匹配
    * L7 Y; H1 a1 K9 v5 y/ c3 J6 C字符串匹配学习路线比较明确。" @0 r% ^( ]' w7 S% Y& ~
    先学习前缀匹配:字典树。/ F) y' c9 r* t$ U1 b" x
    然后可以简单看一下回文串判定算法:Manacher。. f, U+ B5 \/ f/ d
    以及经典的单字符串匹配算法:KMP。
    2 \. Y; K5 C3 @/ n实际上平时最常用的还是 BM 算法,而ACM中基本不考察。. w, o2 D# S. y: J4 [
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。" Z7 k* I8 {6 J, M% O
    关于 算法学习路线 的内容到这里就结束了。& u: s2 F) k1 Q& e& W% r2 |
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。8 C7 g( Z- b  }& [* W0 @
    参考资料: R: Q) G. W/ r! Z# i$ \4 z5 Y
    【阶段一】C语言学习资料:《光天化日学C语言》(日更): P. y( |, E6 L3 Z8 V6 r' n
    【阶段二】C语言例题:《C语言入门100例》(日更)) z( G6 x' m, H5 G
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    4 n. U, `# \* B5 U/ r【阶段四】算法进阶:《夜深人静写算法》(周更)$ R/ W% Q& |( k3 G3 F% Z% D
    ————————————————
    ; x) w* N; Z/ D1 H版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    4 f$ _' U* l8 ?# |0 u% a0 s+ z% {" F  Y原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228/ Z/ }  i( c  V8 _+ \- W6 g
    0 ~# f2 l2 M0 B! q0 S

    - X" E: _- u8 k) F
    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-4-14 01:21 , Processed in 0.476666 second(s), 56 queries .

    回顶部