QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4474|回复: 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
    ' d9 R% i* v4 o/ z7 d. H$ Z9 d( u' ^! k
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
    3 w2 g: H, s0 W
    0 }9 c/ y$ a0 |. r前言
    * S7 w/ s5 o, j8 q  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    ' E4 f+ ?& A4 }2 d# d9 V  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。; [: w2 S2 l; a9 N
      刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    , i, ]4 D! M4 x% @  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
    0 y: g: }& r' O) W3 a: ?8 o: F- F
    ; K6 j, g1 z& G
    8 p1 Z3 F9 k; ?

    / b% u! N* ~, l

    + z2 _1 m) F2 M: {1 @9 f8 |9 K- ^8 h4 F& o

    ( ?! I3 `5 \7 Q3 u
    : f2 o% g2 B3 i' L- a
    8 p% |2 a  N) d) ?
    图片较大,文章中有拆解,需要原图可以留言找我要哈
    ! N' x) ~& `/ X. @7 E) C5 ~' b1、基础语法学习% r. s9 d/ \, \) V6 H# W
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    - i  I6 m9 a  e  t: @' b因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    0 |/ A+ K8 v* H& u0 Z" P6 {! Q; l# p3 I8 B8 h  b

    , Q5 t/ i+ Z0 B& |  [" @& W) V/ O9 O

    : o/ v+ W6 D* S' H1)HelloWorld
    3 i* [# s4 X. S6 s3 [& N1 L无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    ( N- g2 |- |2 M5 |2)让自己产生兴趣( U! \4 U* Z0 k; d) X9 i# b) V
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    " B& x- L# U' Q. V0 ~8 m8 J  m刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。4 H0 P; H) K3 g
    3)目录是精髓' w4 B$ f% V0 L# R
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:9 p- q+ V4 |/ u6 Y/ B1 e" \0 m
    1、第一个C语言程序
    ! E0 M) q+ b5 P) ^1 b$ z2、搭建本地环境# y' Y/ t  O9 F* {) k
    3、变量: o0 @% \4 k, ~8 K3 `& b; Y: s
    4、标准输出
    $ R  j" M6 f3 a' [9 j/ n# m  ]5、标准输入) |$ U! z" i2 U+ L6 H8 ~
    6、进制转换入门
    1 p5 h1 p  k3 o* P7、ASCII字符7 V$ B( P3 L" X* s
    8、常量$ j+ N/ A2 z! t

    & s+ j* g9 O$ r8 C) g
    5 u2 s  |7 C. w( Z0 j& K4 g! V6 `5 c
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。' l# m( `# m$ z, r( y" [8 c7 s. F3 C1 D
    4)习惯思考并爱上它
    3 Y6 j; Q# I+ M$ r5 }& S只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。( ^5 t$ m1 D; |" U
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。2 f6 A, o/ n0 G' h6 Y/ Q
    5)实践是检验真理的唯一标准
    " j' _/ A% E; n8 N1 n. ]0 ~5 ~光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    ' B* R# a9 N" {' b7 N& S- h# d所以,记得多写代码实践哟 (^U^)ノ~YO
    * j' u# K( |! j  l% W0 y& |% t6)坚持其实并没有那么难' L3 ?! y6 f: F- B) a4 N
    每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。1 E* G" C0 K9 H1 `
    7)适当给予正反馈3 f# C, N. K" l5 S
    然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。! R" G& X. y3 n7 ~/ b
    当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
    . Z- `* F. ]. v! h7 J5 G5 |看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    & |& J4 W0 I" a2 `) _8)学习需要有仪式感: Z- {- k7 n% j# b# w) Y! V- r
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!0 A) L" _2 e% g7 S
    介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
    $ _+ E* q% [) m* m$ |我也很希望大家的学习速度能够超越我的更新速度。
    6 ^1 ~- ?9 M& P- j% h  j2、语法配套练习
    / |) L( ~  V# ^0 K5 z; |4 U, i学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。- F. |# {4 d) s( D$ [
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:
    5 x. ^2 m' F0 @- ?- F  B; @( l# R) \( T9 C) d& y

    + v3 @( \! b* C. b% n, q3 b" c7 L# t- j9 V3 E6 E8 [, F
    ) f( E9 d3 @$ ?2 T, y
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
      i" d) ~! t9 h( E这里可以列举几个例子:
    # |4 }. X( J" `& M% O! \* s1、例题1:交换变量的值
    8 }. {" Q2 q  d1 q' [" K+ X8 U一、题目描述" c$ j9 ?8 e+ d5 Q
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。7 J( h/ e* W2 F* C

    1 ^2 X: F4 W# S9 O
    - ]+ s* W/ m+ N3 t

    - N' S$ @) `+ ]  k5 U# Z/ y

    " k9 w- T& s& b0 E8 Z) U7 l9 l二、解题思路
    ; V3 C) |# U0 W( i难度:🔴⚪⚪⚪⚪
    7 F; J  }0 @" i/ [( V9 c, A2 @; B# t4 \9 [) ^& |

    8 T5 H4 M7 S7 a6 M$ }+ e) L这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。* H4 j8 O" r" d  I
    a, b = b, a
    : Q1 S- l! P# w* H" z9 B0 ?3 r1
    6 Q8 j2 z& H# y' g在C语言里,这个语法是错误的。) l5 c+ R. a6 p% h! a
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?7 x8 I+ B2 `2 A8 J! f( l) O9 M* r- G/ X4 D
    当然是再找来一个临时杯子:8 \$ u" K; U0 Q" D+ i0 I
      1)先把 a aa 杯子的水倒进这个临时的杯子里;' C1 e8 V0 ~* o9 T' C4 L  E0 K
      2)再把 b bb 杯子的水倒进 a aa 杯子里;" c9 @9 O9 b) {: U
      3)最后把临时杯子里的水倒进 b bb 杯子;
    3 C: T. W7 u; S( F8 v$ y- r; v8 b4 w, A4 h8 L$ L; O$ x

    3 F/ B* K: ]7 G5 ?- g这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
    + ?. Z' x  V5 J; a6 Z. _3 P3 j8 ]5 \; K  P# ~$ a" m

    0 Y1 y* D3 K1 _( |三、代码详解
    " k& |9 c$ e+ |" H$ I0 c" b. h1、正确解法1:引入临时变量
    9 b; ?) y, t5 j#include <stdio.h>
    7 ~0 O7 D) ~7 i1 i, C& l7 ]int main() {# B1 {8 s) r: o9 U9 G, e
        int a, b, tmp;
    9 V% ~4 X. D& f$ ^        while (scanf("%d %d", &a, &b) != EOF) {
    $ z$ ^9 j5 Z" u. ]# _            tmp = a;   // (1)& l) r( @! c; B5 L0 P4 x- A
                a = b;     // (2)
    ' M! U) g' v% w! v" `7 @- @! t            b = tmp;   // (3)1 k* {" X2 Y1 `# S& p0 q: @  c
                printf("%d %d\n", a, b);
      [& G4 ?, \: |1 |# y0 m" y        }5 r% A7 E3 N4 e& F/ F; y* G
            return 0;
    8 I0 i9 S+ J7 V0 [9 W6 k% [0 n' y}
    / D& s/ \( m4 e1: D' s, S1 Z# n+ F; C
    2
    ( O! _$ `: u, |3* O: K2 E$ @  a
    4/ @( k7 k5 I3 S
    5
    4 `' r7 @- O# E2 Q6) Y$ I% T- f3 p3 X  |! o+ w. e2 l0 o5 u
    7. k( }$ ], }4 [6 w
    8
    7 t& v( ]# u/ X1 S7 _0 _. p  d9  ~1 t0 a% c1 E) ~% Z+ k" O2 ^
    10, W! ~4 `; {0 J( p3 y
    11( D! H" K( F# C3 j& E) A7 m% w/ e6 {
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;: {& m3 H$ L! O) X9 l
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
    1 `# S# @  i9 P  u% {0 s( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    3 w* `* ^" D/ j' D3 |2 B这三步,就实现了变量 a aa 和 b bb 的交换。! ~# s# p/ q7 K! M5 c
    2、正确解法2:引入算术运算' l, i( g# \% N% T% L+ J1 c  S/ q1 Y
    #include <stdio.h>
    - q$ l& L- l: r1 A/ uint main() {
    , c  z9 ^' c: [" u  o3 r5 O" W! k    int a, b;
    7 N& O$ g  N5 \7 N2 U        while (scanf("%d %d", &a, &b) != EOF) {
    6 s% O: z9 i5 V5 U' M            a = a + b;   // (1)
    8 q$ L) m# D/ L% N/ M) r2 r0 x            b = a - b;   // (2)% \. @9 x; ~$ k- P+ J+ \7 A
                a = a - b;   // (3): M$ z5 N% l1 X% x$ C
                printf("%d %d\n", a, b);
    7 y- k% j; {  d) U        }( s) j! x& D, L7 ^
            return 0;
    7 V; C3 a7 c0 R/ P2 D}' l3 [* x4 \# O; x7 C0 N
    1  k( n3 D9 Z( _* l: u" N% W
    2
    ; x: W! `& _6 S& J* E$ z3
    % c+ Q( _6 W9 {2 p/ U4 N44 g2 z( Z2 g+ J
    5
    4 ?- N2 Q5 n% \# `9 Q3 \6( B6 c* I+ J2 p. I8 R
    7
    1 R1 v, H* x: m8' [7 Q& A) S- v& v% L/ \' r
    9
      [  j& F5 M! R6 T+ t& l! K+ m10
    2 I5 B4 I4 Q* Q$ V% v11
    # {' O! |6 k7 r% D. D( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;- K5 g$ N/ P% ]& [, i$ O
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;& ], t! k8 ^4 t
    ( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    ! F* m% F2 u" q* t( c从而实现了变量a和b的交换。
    4 T% E: D. _0 l. S7 z1 ^# [3、正确解法3:引入异或运算
    ) P4 c6 ~9 I# J6 M首先,介绍一下C语言中的^符号,代表的是异或。
    / e) e2 j- A0 E) J) t* t二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:! l4 _! C3 `$ [5 W
    左操作数        右操作数        异或结果
    * D# E; T3 U* [3 k/ Q, F8 d0 f0        0        0( D* P7 S8 [* w7 H/ I4 L
    1        1        0
    4 P' i6 O, |+ a2 i$ ]% C0        1        1
    6 S2 o# Z! V9 x$ U. ~8 ]1        0        1
    / R+ P! q: C/ N也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。; `' N( w) b% P
    这样就有了三个比较清晰的性质:& D3 k6 q& V- z# ]
    1)两个相同的十进制数异或的结果一定位零。4 e- E0 w0 A; O+ ~
    2)任何一个数和 0 的异或结果一定是它本身。# p: y+ R! F& ?7 a; r, z
    3)异或运算满足结合律和交换律。  }, F0 S; G( s- W$ e7 x/ ?; [. l: T
    #include <stdio.h>
    ' [" P* q/ ]! Jint main() {5 y/ E" o/ I7 O" v- `. M8 @
        int a, b;
    0 W. [( f# J3 c# |9 Y2 r        while (scanf("%d %d", &a, &b) != EOF) {
    9 W) i7 j& x$ q$ c            a = a ^ b;   // (1)# P# C; A7 p  G0 U. s
                b = a ^ b;   // (2)4 O: E; Y1 m/ H6 _: \' f7 y( x
                a = a ^ b;   // (3)1 n9 N5 Z/ n, g% O+ ?( z2 H5 l
                printf("%d %d\n", a, b);
    & I1 B. n, S$ u( \. j( d1 @        }
    ) a9 Q/ Y2 E% r        return 0;
    & a; P$ [; S. o1 K+ h6 |}6 ?) {. {7 H; C. k, t5 W3 P# H
    1
    ; p1 A3 m# N' P! P, v6 m22 r! c8 @5 Z; F8 }5 e  O
    3- l( \2 d& J% g8 A# {" f7 B
    4
    0 Z9 N& H7 |, t. [! Z5
    / N# Q. L4 \# H: G! _9 ^/ D6$ u) u( g6 s* ?- N
    7
    6 Q. ~7 g) G( [8
    - X: w2 d2 B6 \; c0 R7 X91 X) z: ^) T2 B( K7 z
    10" s) t% ~5 N' A- H/ a( y
    11
    : w4 P  Q- A. S: `我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。/ O3 f0 e% ]7 a( y1 _( D
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。) n3 {. `- o# a- m0 s- H
    从而实现了变量a和b的交换。5 ^# {0 L1 R, t+ j7 J

      c+ Z# l. ~9 r& X, b
    . v0 \- k( ]' Q# S* K) t/ J' E
    4、正确解法4:奇淫技巧" y3 ^' P4 \  F
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    " W, h# O; }( R#include <stdio.h>
    ' B) \9 U0 y# T, k9 |, |5 fint main() {
    2 z( R7 B/ j/ I  s! Z8 u* P# P    int a, b;- f5 v  U+ Y) q& w
            while (scanf("%d %d", &a, &b) != EOF) {
    ( l6 I: o/ P* ?7 ?# W            printf("%d %d\n", b, a);
    ) d* i. X; l: M9 G( P# a        }
    ( m, t' N( N. t  [# R        return 0;
    5 }& h" ?( o- a- ]9 t}
    8 E- ^& `: C8 }- @1
    ' ~7 Q1 Q+ h4 ?, h, E2- g) Y* B: v2 b/ W" r# j
    38 v) S- c7 `, }9 ]3 O
    4
    5 F) `7 T5 w; `* q( X5! i. R" U7 c4 L, B# m2 X
    6( F1 F, M! y2 v
    7
    % r6 T4 E- o1 i$ U* i' ]( D8
    9 X2 D9 y( o3 d6 {% N2 ~你学废了吗 &#129315;?4 d! U) ~! n6 t$ N0 [. V* O* _4 j
    2、例题2:整数溢出- a& L- W4 R$ G5 l# N
    一、题目描述; E6 N7 h+ u/ u& D: c/ P* q
      先输入一个 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
    - }. n2 p, B3 Y; M9 U62
    7 Z; ?% m# i- c! M( S" @ ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    " [( I* K+ L  V& J& e, Q* v- t& Q0 |$ R+ g0 F

      x& [( c# I" O二、解题思路( |6 g7 L6 O7 b3 G
    难度:&#128308;&#128308;⚪⚪⚪6 }/ h, L6 o- W* ^/ N; R

    ( |* ]8 l& \) N  g* f, v* s1 Y; x0 b
    " r% i- _9 ~  U9 X! o0 W
    这个问题考察的是对补码的理解。% D" N- W2 `% q
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
    5 _& E& ?! l0 w; c62
    ' K3 \5 I+ u' A* O ],这四个数加起来的和最大值为 2 64 2^{64}2
      `9 _; N$ j4 ]" k9 x/ t64
    0 P: j5 ?  _2 J8 a 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 1 I# N- U2 o; v6 O" d) w& b
    63/ C, O9 x4 m5 R; B3 b& `. p  F
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12
    ! e" H+ L. k; e8 l' b: C5 i0 P64
    4 B$ {( S- S9 ~& y, k# {' E+ J −1。
    , L8 l1 w  S5 g: ?& C- {但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    7 W$ j: o' Q1 l" _62
    . h' L( I1 O6 q) ^. Y! c  时,结果才为 2 64 2^{64}2
    ' p; J  q$ f, z( c# X# v* E" [0 _( [64
    ( r7 E) U5 ~" K% W ,所以可以对这一种情况进行特殊判断,具体参考代码详解。! ]# T* k: ]+ g* r1 m! v( j7 w3 d. B5 l
    三、代码详解
    ! k& {1 G$ P# _, {' Z$ F$ r) M#include <stdio.h># a2 N" P( _' [) I" l, ?/ E
    typedef unsigned long long ull;                           // (1)
    2 C* Q. D2 ]" a8 v5 r0 J+ C& ]) Q) Mconst ull MAX = (((ull)1)<<62);                           // (2)
    & A1 L& ]1 L3 l, I. S9 N
    : t& l& g$ |0 u; ?( ~

    - g- j+ Y5 F0 z: [6 A# b1 \+ fint main() {& E+ \# A9 Z+ Q( t& P
            int t;
    - K3 A. f- z6 u& h  B! Q6 V        ull a, b, c, d;' p, ]# v* _) L# d; O
            scanf("%d", &t);' L; l8 _8 Z  |- n) A
            while (t--) {* I! `. a1 r; [) k* f
                    scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)
    5 T: k* P/ _. Z* ~                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)8 r) s' N3 _3 z9 d
                            printf("18446744073709551616\n");             // (5)6 s2 X8 F# B# s; y% h* }% c, q% v
                    else
    6 z8 D1 o5 o$ y$ A) B' D6 ?                        printf("%llu\n", a + b + c + d);              // (6)
    ) A9 G$ l6 s% g% q1 F7 e        }( ], ]- l5 j$ h. d% c
            return 0;0 \& F6 Z. _! O$ X5 V
    }
    # O% i' u0 x! f; Z+ s1  \. O" L; ^4 B
    2
    9 Y1 Y3 G2 }. h! g( C# ^& S3
    4 L1 ]' e4 J2 P4
    1 j; g5 c, J! D: f6 I8 ]5( v9 Y  H# }3 `3 s, O1 W" n: D
    6
    - o+ p# }7 k5 T7+ z+ b$ m# y$ j# J0 [
    8& Z8 q; ]6 D5 x9 C; ]
    9
    0 r: P3 J5 D' g2 Q; R10
    # o2 c6 g3 s  s  o+ |11
    1 @: R# J; a' j" J! u/ W+ d127 H' ]6 I( X+ |# [: l9 Z. C0 c
    13
    $ ?$ _; E: a  G# f7 N$ K2 i14
    2 u0 U+ U1 ?: ^5 m. L  U15
    $ v; x% \3 g! `- U4 n! P1 t16% m- v* e$ n+ ]! Z; ?: h+ p
    17
    0 G2 Q8 y# n) ]6 s; W4 m. T- c6 h3 F( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;' _6 l% g0 a9 X8 U0 I- t
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    * T8 ~6 b* C9 ^& ]62
    # Q7 j% L  i; Q% v$ j ,这里采用左移运算符直接实现 2 22 是幂运算;  K6 K) a6 F) k& W4 @
    数学        C语言4 j( [$ L, q( i* i+ b+ s; ~+ `! p
    2 n 2^n2 * L9 F" Z$ ]' w3 h% X) S9 \, O
    n; x6 o, q9 t6 W8 o4 ^0 ?) V1 I
            1<<n5 R* k+ B% {3 e  b/ s( H
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;$ v9 _- f3 B7 R  e$ |
    ( 3 ) (3)(3) %llu是无符号64位整型的输入方式;
    ( K5 \" f0 B- j! [( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;' h8 b& w9 E8 b/ z* m- K9 O0 o
    ( 5 ) (5)(5) 由于 2 64 2^{64}2
    3 H9 }! F/ v- L" t  J6 \# \64# ~$ b7 L; n5 \6 z, E" ?
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;! n! y8 W6 f- O! k# @4 J/ j
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    " U6 u" {/ @  Z  z- v64
    * V+ D! l0 |7 F+ f' T* m: J2 |! F −1] 范围内,直接相加输出即可。
    6 T, R# [" p% m3 _0 Y由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    1 A3 |$ `7 u7 \7 P9 T3 X为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。, ~3 z# E* J: m( L) D1 H
    . P2 B/ @$ I' j+ T2 p
    1 @3 _4 y9 c4 ?/ z+ H, E
    3、数据结构
    # `7 E+ C7 E9 h) \) T* b) o《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    9 \* O0 {- w, `) `( y" F4 j0 d1、什么是数据结构
    - `& @& W! r: E8 ?, m0 }# ^1 ~你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    , S+ }& @: ?$ q$ C- I1 E如果一定要给出一个官方的解释,那么它就是:: E. l" t9 E( l1 a5 z/ x
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    : S- B; o( R# ?# d' k( f
    : B8 U$ N4 H  Z

    " [9 B" N2 q! z5 M是不是还不如说它是堆,是栈,是队列呢?' B: m1 s4 k6 H$ J
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    / [0 `, d" `/ k3 I; Y2、数据结构和算法的关系
    1 y% n: d2 L( ]* t$ S$ r& b很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    1 ]) v/ c! ]6 \1 R3 q: h数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。1 I1 N5 Z* x' q  Q
    而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?( h7 t9 R& G  |9 E4 L6 V
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。; t, w/ Q/ c) G  [
    3、数据结构概览
    8 Q8 N( H# D7 q周末花了一个下午整理的思维导图,数据结构:7 @+ g' b2 @" Y4 o9 Z1 ~/ g6 z

    ) k2 b& k/ C9 @3 [' K- t" K) |

    2 R) e6 F$ I& o2 j5 C/ {常用的一些数据结构,各自有各自的优缺点,总结如下:
    9 `7 r8 a' L9 j' |. @+ za、数组5 K" h! k1 y5 K) Q5 h
    内存结构:内存空间连续
    / u. @9 b1 _' D- T实现难度:简单
    * u: u; c! b2 U  t7 U下标访问:支持
    5 K5 x" z' A; c8 F分类:静态数组、动态数组5 g& @, D+ z, c; g$ Z' Y. r
    插入时间复杂度:O ( n ) O(n)O(n)
    + `' ~! s. J3 ]4 l, w0 M查找时间复杂度:O ( n ) O(n)O(n)
    ' N5 g; w3 ~  _4 _9 S& i8 [删除时间复杂度:O ( n ) O(n)O(n)
    / U2 T5 T1 Q; Y9 w3 Q1 o* B) S3 N* [/ K4 N" z/ @

    : v5 j* n6 d. A7 h8 M8 Bb、字符串0 r* f0 G% O2 J5 }* d# p
    内存结构:内存空间连续,类似字符数组: e! }1 M$ {3 |) ~; s
    实现难度:简单,一般系统会提供一些方便的字符串操作函数! x" D5 w1 \- @3 g/ f7 O3 `
    下标访问:支持, o+ p6 @  P% D/ v* w
    插入时间复杂度:O ( n ) O(n)O(n)- ^, J, E) j, d: u, L
    查找时间复杂度:O ( n ) O(n)O(n)( m6 s: ^" o  c- }
    删除时间复杂度:O ( n ) O(n)O(n)
    . H4 D& A7 v5 I- X7 W* ]: }5 u; k" R9 n. M% s( W

    1 h+ i1 u4 R1 \* f7 O+ Kc、链表  e( |0 |  Z4 i. V( D5 J
    内存结构:内存空间连续不连续,看具体实现/ W: T& v# i9 A4 E. O9 R
    实现难度:一般
    , p; W, c/ _1 ]+ q下标访问:不支持. {4 O9 @! G1 r: l# A8 D6 [
    分类:单向链表、双向链表、循环链表、DancingLinks, u. }+ G& I+ Y$ p6 H
    插入时间复杂度:O ( 1 ) O(1)O(1)& C  I% f. o& a% y! u: U
    查找时间复杂度:O ( n ) O(n)O(n)% n/ u- C  p7 J
    删除时间复杂度:O ( 1 ) O(1)O(1)
      b; t/ E/ v+ N9 |4 I$ \! V) Q! Z/ |. u+ V: o$ ~; Q# e
    8 z' y. Q' o7 f
    d、哈希表
    * q8 m( o; Y+ N7 O; a, k内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续8 u3 v. d6 Y1 e, P
    实现难度:一般
    1 |, f: ?. o- V( ?下标访问:不支持
    1 R" h# @2 I7 S; l  M- Q# G+ g  c分类:正数哈希、字符串哈希、滚动哈希: p. o! \) y3 C+ k
    插入时间复杂度:O ( 1 ) O(1)O(1)2 \* t7 _% Q# u% C( w
    查找时间复杂度:O ( 1 ) O(1)O(1)
    + o' `/ q; x) y删除时间复杂度:O ( 1 ) O(1)O(1)( S7 U2 n" D+ s

    ' H0 i# B: c% O/ Y& P: n  v# l
    & y3 G* L5 q, i3 U7 X8 N
    e、队列
    * n6 r' Z% y- i$ _4 H/ x$ K内存结构:看用数组实现,还是链表实现2 P; j; d" ]0 Y* ~: G
    实现难度:一般( N" l  h0 q) y% M! H/ s% V
    下标访问:不支持+ h# o6 `- E1 T( g$ X3 ?* ~: N+ E
    分类:FIFO、单调队列、双端队列5 Q; M$ }: u0 p  c
    插入时间复杂度:O ( 1 ) O(1)O(1)  f) s; _) Y/ H- d, @. h, s- M
    查找时间复杂度:理论上不支持* H" f; b% `4 ?: _! x: d8 q1 q/ p
    删除时间复杂度:O ( 1 ) O(1)O(1)
    ! Q; {' D+ o- v0 Z* R& p- |9 T7 q/ k

    6 w  F/ v( c& a5 r: Sf、栈4 O- c- U$ K' n* E7 U$ {# h
    内存结构:看用数组实现,还是链表实现
      x9 b* l' Z$ i' n/ M实现难度:一般. H0 U* C0 P9 E
    下标访问:不支持
    $ b: h5 M% `& {# x分类:FILO、单调栈
    ; H0 R. c( x4 B* e5 Y. z( z插入时间复杂度:O ( 1 ) O(1)O(1)
    5 w1 L% N- Q: e4 \) D' ~1 \查找时间复杂度:理论上不支持9 a: |+ }' Q  W* \8 }5 \% c. T
    删除时间复杂度:O ( 1 ) O(1)O(1)
    ; D& U7 @3 p4 v; e1 Y/ S( M8 W5 d* j9 m" g; H+ K- V9 g1 z3 \

    - V3 W# p1 J6 E+ H8 Q9 |! Vg、树: s; B' P: |: Q% N; k) w+ R' o
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续' T- f5 p# u1 f# I- i) ?
    实现难度:较难% ^; _4 Y1 V0 F1 P0 G( o* E) u
    下标访问:不支持
    / r' b7 Y5 T- a; X6 v* K分类:二叉树 和 多叉树
    ! z. m5 n" G3 s0 u7 v# U% h/ g插入时间复杂度:看情况而定: `3 W$ J' ^* v/ ]
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log 1 i% p* B( s) f/ }$ j$ W
    2/ G8 |% i$ i6 _' L; d! o- L2 U6 }
    ​        ) r, v2 V9 H# f: \  {* ^+ }4 @
    n)
    : f" O& r2 C( o( v/ Y! t! ^删除时间复杂度:看情况而定
    - n( r% m7 j& ~0 ]0 Z! T" O' x% Y% y+ l

    % u+ a4 }. e$ x1、二叉树
    + x4 r. S, l( d二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。* {6 L' G4 Z9 S8 ^" y* O# t
    其中,堆也是一种二叉树,也就是我们常说的优先队列。
    8 N+ p) ?% d' T' A8 s2、多叉树# p0 A! `6 }5 Y! {
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    / [; o% K* {# I; c/ i2 Q& g+ k% Dh、图
    / @' d9 o2 ]8 P内存结构:不一定
    8 W6 j6 D+ ?+ D. l: N实现难度:难
    4 b5 y. E& N& N, j4 I下标访问:不支持
    0 x: L4 h& I3 J2 B# z  Y  y; p- F分类:有向图、无向图
    ' ^6 m$ o7 A" U' h, B. @插入时间复杂度:根据算法而定- J3 [6 d+ l; N; n, X, S
    查找时间复杂度:根据算法而定
    ) e/ ]6 D6 `" i4 L" J删除时间复杂度:根据算法而定
    5 @9 z, `  R' j1 `- U& I% J  r4 C6 A

    8 r1 A# K) a4 J7 X1、图的概念
    " u, Y. x) u( \; a2 c" k, q在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:; D3 W5 S3 J# h* n" E
    图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。
    # f( s% n- ~, z0 k对于无权图,边由二元组 ( 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 为权值,可以是任意类型。3 v  e. h$ H) W( H- u
    图分为有向图和无向图,对于有向图, ( 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;, Q: C3 @9 G% g7 I
    2、图的存储2 i5 P; d8 Z4 K3 \3 @* S
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。3 \* b& }7 |8 ?$ H* A
    1 }  B7 c8 m8 @. H
      b( |5 M0 C: T+ N7 [# G0 y
    1)邻接矩阵: f4 {' P8 c5 H3 O. e5 b
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。$ o9 J$ |, E$ p" x  B( c! F
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。
    6 ~- P$ H0 D. F& K; Z  x[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[- Z1 u$ J* S/ u5 _9 H( i
    01∞9∞0∞8320∞∞∞300 X% g7 V. d- b+ z6 P) d' s
    0∞3∞102∞∞∞0398∞0
    ' n( G/ H* a7 E\right], ~' Y6 g5 G3 X4 J! i$ S0 K2 N

    # {3 X/ Y( ~7 F" Q2 U3 `7 p1 l% d' t' v' L9 T$ w! C& m0 X; z3 ^

    ( Y! E3 ~% Q9 f, P& R' L8 u3 U4 E/ `/ i
    ​       
    0 X6 J# }$ Q1 V  
    ( q) n9 x# _, L" N0
    5 P+ T0 \& u3 u1 U% O2 {+ e7 z1
    * U7 l4 {4 f! w! {$ m( v
    2 q, A; N* H% ~$ M" m6 {- W* J/ e9" C( d' Y8 o- a9 e4 I$ Z
    ​          H# P0 a/ C6 r1 @" ~% [9 Z
      5 n& v. g% S9 `/ G* x* W% |8 @

    * N( ^3 m* i" g$ i7 K0; Q3 z( N. C* ~, Z4 G4 x( L, g
    # T  H- ]) E, m, s& E/ ^
    8# ~- L5 N0 T3 v9 J2 e
    ​       
    0 Z3 Q4 R# J: g9 L. t4 ]9 P  
    " j6 K- b6 W5 F7 n3
    0 u& h- d8 w0 Y* ~* g1 s- y2
    & {- [2 w% b; V4 m) A9 _0
    ( k+ v6 t' t) J0 T5 X) }
    / C  G7 B1 Y, C5 _  }% F​        % c- u- o( H* P% I1 t+ U1 |
      9 f( o5 a$ M% q& l& i
    + U8 z$ @7 [5 Y6 q
    + b6 L, E, V& ?# C% _2 n" L9 T0 e
    3
    ' b$ n& I- ?1 c* w0
    5 f6 D) i- R& h  j​       
    " Y- G/ H2 `7 V; m9 C1 W  
    " @9 J& e( B) L$ j  b" ?# y2 J6 R- V1 T7 c) S0 V5 f
    / I. Z, {* v6 k6 b! r
    ) F, O+ e  ~6 d- R3 K
    9 R6 E& u% J+ o& x6 W, V
    ​       
      F4 m7 j( U: _0 G / `' W4 X8 I9 {* \2 Z( K5 |
    2)邻接表
    ) m2 \# j& [* q% J2 S' A邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    : o" L  Q6 x2 ]- q" U7 r, n3 B它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    7 i: n; }; R, F3 r# u% h如图所示,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) 二元组。& C% n. `, N8 x9 E

    : _# N3 l, `6 |4 T) s

    4 N* Q; b* j0 C% ~, b* ^0 S在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;; ?- v4 h9 @* c2 A, I  K4 m
        vector<Edge> edges[maxn];
    8 O9 {. S4 s; H2 j: ~  p; C6 J1+ a6 K; \: x8 O) B6 y4 t/ H5 C5 y
    3)前向星" ~. f3 _, L8 w' n0 t6 U, J
    前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    , X9 r3 I# F% A它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。- C' W) @/ {0 q3 S$ s/ C) C
    如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    4 g1 q( P* T) r$ P$ q% C1 E# X7 D
    ; a( P( L) e9 j5 x
    4 N  V. ?  q- ~3 s) {
    那么用哪种数据结构才能满足所有图的需求呢?
    ( a8 O6 Q8 o8 Q$ ]! W接下来介绍一种新的数据结构 —— 链式前向星。  ]" e6 k+ y8 F. q/ i. m
    4)链式前向星  F& j0 r1 [9 V
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。- }( t6 _) U$ e
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。4 M: @; g- r' U3 v1 l- `" z
    边的结构体声明如下:
      R( N0 r) G% \6 l2 W) Z- Ystruct Edge {. J, B, D7 N6 a' J
        int u, v, w, next;
    5 L9 |: T: f, s( T    Edge() {}
    $ i4 g3 h" B( \! a    Edge(int _u, int _v, int _w, int _next) :+ \, j2 y) |" u, q. g
            u(_u), v(_v), w(_w), next(_next) 5 l# V" k& {8 l) v+ u
        {. _4 D  e5 b7 D8 u6 A
        }7 a+ ?9 z' C8 M( f. F; R
    }edge[maxm];
    % q9 ^. c" _6 E0 G% ~. Z1
    ) F1 D0 e( D# V  h2& i( D* x  n% q
    3
    6 |% N7 s3 D: q' I4
    9 w! K+ d. [2 e2 r4 G8 p5
    % l6 z) J8 {% @" y$ g% X* c6
    ' m3 W( Y9 X: n4 M# @/ w7
    - U; c0 G- v7 B0 p- i8
    6 X) ~2 J  T1 Z; K初始化所有的head = -1,当前边总数 edgeCount = 0;  H2 K+ A) A4 I3 F
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    & h3 _  a+ ~1 [1 J7 g, ~void addEdge(int u, int v, int w) {/ @' l5 F' g* u4 w( ~/ E  c
        edge[edgeCount] = Edge(u, v, w, head);3 K( Y% D: k: b) ^
        head = edgeCount++;
    0 \2 `+ N# z+ ?9 V( D! t; v}
    # Z( ~5 J# j2 J5 {1
    7 Y, C4 u" V$ d' v4 R. b( D22 T7 }% ]% }! R! T" Y" h; s" e+ R; A1 ]/ A+ N
    3
    ! @$ M' X* n" `4 D0 D) G/ \4" d9 k- I5 E, p6 O$ G& Q6 g9 E
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。, U5 j5 j$ ~8 |5 X" B! J4 y6 J. I
    调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    : W3 o4 T7 [# b- P5 `0 Ofor (int e = head; ~e; e = edges[e].next) {
    2 L* X- x4 V- Z5 v$ R$ Z    int v = edges[e].v;
    - v  n$ r: `; N7 n, f$ g8 n- y    ValueType w = edges[e].w;  b/ T0 l* h- Q3 t, H9 q! G
        ..." S6 g& ^) R8 F% J
    }
    . F3 b4 F; F9 Y- |9 p13 G7 B8 H, [6 ~' f
    2
    6 u! @9 p1 m! B5 E3/ P7 J  |1 L! H. W
    4
    - s& u& P2 c% p7 a$ j53 e  ~( e- k5 g4 p0 K
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
    . A: _2 Q/ S# l# C) \# Y4、算法入门
    . n4 q8 D6 R- a$ M2 e$ q算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    : I) F4 H7 j1 K5 B$ p  C4 e5 m" N

    * j; P) \. p; ?! ^入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。0 w: G% B5 @* x9 x% x" \0 n
    对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    ( o' @* w- T5 Q% F2 i! h0 T1、枚举; D+ b+ Z3 D2 D. R. o) H
    枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。0 M* L+ b# W8 V2 q% ~2 }
    对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。' s0 l2 e1 A" G
    2、排序
    " H# k3 u% U, E; O既然是入门,千万不要去看快排、希尔排序这种冷门排序。4 q) U) @+ D* q) ]
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。: Z  E5 ~* a% |  r2 G3 A
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。' g  j% v$ ]; Q+ p, H
    3、模拟# \2 @0 S( i2 F  i: l
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    ' F% R- @  a! u  i& Z不管时间复杂度 和 空间复杂度,放手去做!
    2 [, ?& k) P4 o% U" V2 N但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。% w/ }# i6 y" e6 m* u
    4、二分1 U8 p: V9 Z! w. n- D& k, u
    二分一般指二分查找,当然有时候也指代二分枚举。
    0 }5 \& c6 w- l1 e例如,在一个有序数组中查找值,我们一般这个干:
    ! \" c  [% D$ N( T' E$ b0 A% Y1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;! r  D6 I; l2 G5 c2 X
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    * I. H/ g$ `* |. i' J$ B) e$ v  2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    - V3 j1 |7 p; p5 [  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    " k$ ]0 |' d" y4 f9 L! F8 ]9 {1 I  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;* Q; l+ w6 O& w/ [
    3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    ) I0 `7 \8 h: x* p& H5 O5、双指针$ n% e: i" q0 a: n6 M
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
    ( s% n' O! ^. K9 j8 m" f. K0 Y) p9 n. v. B9 H  X  A+ A; E7 Z

    " y) U5 H6 z4 [: a/ l4 \. Y6 t7 q4 w6、差分法
    7 V5 ]( c% ]* b) ~$ V差分法一般配合前缀和。* R) m6 j  `8 A
    对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;
    1 _+ u( D6 `7 L- k( A# R4 u7 b% S假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    & Z2 @" \! m7 K5 W' X8 fx
    " r' j7 ?* V( t+ j9 @( V. Q​        % W$ c4 D' G2 I0 o4 q( V
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
    4 B5 K, |) @* t5 g6 {# P: hr
    7 _9 O- u- F8 n$ r7 F2 n! E​       
    , p( @; I0 c0 L" j2 ^. X) g. W −g
    ' a  S; U: j3 p. |9 k! ?l−1* E0 U8 m( U! K4 d
    ​       
    + j% R3 X2 Q" [; s0 ]9 ^ ;分别用同样的方法求出 g r g_rg 5 v) y; w& F( v) C8 u
    r# I9 o- p. v  p6 y3 z1 J
    ​       
    + A7 V9 |" @$ G2 M' d  C  和 g l − 1 g_{l-1}g
    + w1 t$ J7 {: {5 V6 m( L6 tl−1
    6 D" P; O* |. g2 X: O* l​        8 q  P5 L6 h3 ^3 w9 @! W0 ?  _4 k& n
    ,再相减即可;
    / s  N1 e% N6 e9 p9 b1 Y9 a! w7 s
    % {, c6 u) t2 ?- F, H7 J
    7、位运算9 E, L1 w4 Z5 p8 P5 a
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    # I8 j! @% e. V; f. [位运算分为 布尔位运算符 和 移位位运算符。
    0 v1 m' t# y2 Z* ?布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。% @# j  C9 [( b% z% ~- x: e
    如图所示:% J) ~3 n  m2 \4 j( F2 V8 d/ ^

    / p3 I0 \( X- |' X+ i, p3 b. j
    0 j8 h2 p+ p6 Z! l
    位运算的特点是语句短,但是可以干大事!
    . C- Y+ ^7 x& q. v比如,请用一句话来判断一个数是否是2的幂,代码如下:0 [0 ^* a3 l) Q6 |1 d
    !(x & (x - 1))
    . D; h2 O/ F# }1+ h3 ^- ~6 e5 Z
    8、贪心
    # L5 T" g/ z8 Y' e: d+ W贪心,一般就是按照当前最优解,去推算全局最优解。5 k7 Y$ q1 @5 u" Z! \5 ]- r
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    : c& A- m) f) l! @9、迭代( H2 T& \$ P2 v$ Z8 |
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。  C& r3 O9 h: ^" N% d6 |5 ~- |
    10、分治! H$ A/ G  u9 M
    分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。9 T0 j+ m1 \/ Q9 L8 L9 I. U3 z
    5、算法进阶4 M5 \1 y- Z) f( A' y! X
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。* ^$ m6 Y0 M0 R/ S& ]& m' |
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。
    1 t& a! v6 c7 V# v9 V: t这个系列主要分为以下几个大块内容:2 K; U5 l  K9 g& q
      1)图论
    , \; c4 m: G! u  2)动态规划% X: c; L+ V+ J. e  c3 f% @/ G
      3)计算几何
    # ^3 Q7 L! f! t5 m: e  ~9 ?  4)数论$ H8 N" [2 V, T6 y" Z; L  Z; E4 Q
      5)字符串匹配1 u6 l: y( k: H7 i/ d! E7 J
      6)高级数据结构(课本上学不到的)
    9 T# Q9 N# p7 K( Z  7)杂项算法6 R) V) F; F& V, L7 Z. g

    # y% w5 s9 a7 G7 o% ~( z' e4 u# ?& b
    0 G7 S! y7 H2 t5 q6 q8 H
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:+ z1 g4 r+ n; l( P1 C8 J
    1 a8 s5 _2 K5 I4 k) F) o
    ' C; l: i- g' a

    & r# x8 F1 \6 G5 `4 Q

    ) [' `9 u, [. P. O- V! O1)图论* n9 q3 H" k. d; e
    1、搜索概览
    # |7 t! G6 L9 l, J4 C图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。) ]0 l! v) z( n& z& `. T
    / ]8 T! R& w0 T9 u, q$ d
    ) E; ~2 @+ P$ V% Z9 _
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    + g- T1 @) U! _& H  h) n2、深度优先搜索! Q+ g; A% d+ S4 @2 F
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;/ N/ z5 _( P$ c  X. J2 i6 B$ c
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。$ q2 H+ s$ ^6 k" Q/ n6 b
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。2 r9 w* I" c( O) C. u% I9 w
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    1 r( s, Z: p7 P  f
    ! a& {: }% ~/ N* A

    9 ~' w( w( k0 A! m红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:) k/ }& a+ ^3 Z  T
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6( \6 G3 ~7 F: y4 O/ I
    19 @+ p0 T- i1 v
    同样,搜索的例子还有:
      \" ?, G8 e6 A6 T- K* g( ]: i* f1 x+ v- f6 Q( W  d  {& b* F

    . y  L# ~2 L* I& M' b$ Q2 o9 m计算的是利用递归实现的 n nn 的阶乘。
    8 }& J2 ~5 A# N8 \" F  K3、记忆化搜索* ^( _3 t% ?) \- G- {& S( K
    对于斐波那契函数的求解,如下所示:& V2 F4 [7 f( S: Y
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =% ^, L! k, N" b& O" H3 }
    ⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)% g- @" i3 Z6 ^% h  I
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)+ `' a4 }' V4 V: g( G
    f(n)= 5 I; w0 P7 e% D% u( M+ f- }! Q% ?$ d
    ; J6 r: x- C6 F3 V" T
    6 G1 p) C( u1 H, {2 c
    % s1 G% f6 `( a  m9 u) W1 i

    9 i  C1 z/ R6 t2 J/ {1 J5 p
    ; S" C( B1 w7 t0 U0 t​       
    7 i4 X# ~+ H  X+ l' K) y7 o  
    9 m" |' Y! A( V9 N* H9 E6 u1
    4 @; m  Y% T: r1
    ' W5 M) k$ _( @9 E6 q& Y# O' Nf(n−1)+f(n−2)$ H8 l3 s2 \2 E
    ​       
    " t) W6 k; i1 I. W7 u2 P5 C+ b  ) c1 E  I8 z7 A" W: K; X
    (n=0)2 X" ]/ n/ U- n
    (n=1)8 o0 c1 ]. D4 t8 g/ X) B3 a
    (n>2)1 j7 |8 e  }- `: `" L
    ​       
    & g. w1 |* \# _  [
    ) T4 J% @6 R! l, |) t对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    3 g$ r# Y, N2 Q" K% [9 C& }* G  t, E& V; J4 g4 P

    ; }/ @! E; E2 r1 B$ {2 I+ b这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。9 `* r4 @5 h5 V4 Y+ X1 `- T% U0 R
    我们通过一个动图来感受一下:
    , o) D$ r! @5 O6 Q4 l9 X" m5 J* x" v. G

    . d4 X8 {5 A" I* B6 C) W# o6 H当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。! t% S2 n; h3 |$ }. n
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    , o$ f' j# e" I4、广度优先搜索$ A5 p7 w! z9 `% }
    单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。0 H, ~2 u( o) d' ]1 t9 d2 L+ D3 V
    我们通过一个动图来对广搜有一个初步的印象。$ t5 `" B# e6 l; B3 {, r

    3 f# X4 o4 F3 E, z2 c

    * A. I: _* x4 H4 m+ S8 G$ r# i8 O+ _6 X* G8 p

    4 K+ U% Q8 U( y从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
    / Y3 j  F1 l6 d' o' w7 K! a+ Z; a那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
      `; X/ f; l8 S" X7 G8 u这时候,算法和数据结构就完美结合了。
    2 f6 e8 ^/ `4 M5 ?- P2)动态规划
    & K% v$ v8 f- B  S! E" W动态规划算法三要素:" W4 m: z( x  |
      ①所有不同的子问题组成的表;/ g# t) s7 {  d# y% A  s' Q) M( ^
      ②解决问题的依赖关系可以看成是一个图;9 T/ O/ h5 B0 v2 O! a, O
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);; D  g+ T3 y% q/ S! [7 S' B
    - ^% |' b0 N7 \) ]  L" a7 D" x
    , y6 \3 @' S! X3 D: k
    如果子问题的数目为 O ( n t ) O(n^t)O(n 4 s1 Z. Z, a% Q* R
    t; W7 K7 R. s" G0 I
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n 6 t  r5 ?3 W3 ]9 N4 j# f8 y/ E+ S
    e
    1 ]$ E/ m# i. z& ]: ~ ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。8 N7 U" R: S) L  y0 M; H# F% T
    1、1D/1D
    + _1 D* _9 |! K4 `d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )' Z4 L. u5 E( _7 |! x* G0 i
    d=opt(d[j]+w(j,i)∣0<=i<j)5 P: U' F8 K* \4 _8 S4 c
    状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    * l' `6 @% J) l3 g3 c8 i" k7 d
    1 r  U+ k# c) T/ \' N

    & f5 m% ?% ?) X: N6 J+ K5 ~4 }这类状态转移方程一般出现在线性模型中。/ O9 `6 A. \/ ]# _3 D3 N
    2、2D/0D: I$ j  s) ~9 r
    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} )- p1 c: w$ k5 X' ?- g  ?
    d[j]=opt(d[i−1][j]+x
    / a+ q, k& A! v1 h( f8 pi0 T4 Z: F) T2 f/ G/ j- i; ]1 t
    ​       
    5 J4 j% ^7 L5 ~) O ,d[j−1]+y
    ) ?" U. i+ z/ W. {+ r6 p5 o/ j9 wj
    1 g' c. H5 [4 E1 T$ w​       
    : v2 m& Q& L7 o1 n6 M& x$ [ ,d[i−1][j−1]+z
    3 K- t: I% v9 E0 Dij
    2 H; x4 y; B" l​        / f5 q) _* [2 c
    )* ^* j7 n( R; E! w
    状态转移如图四所示:5 G2 u5 P/ m% Z1 s+ z6 F4 H

    : t& r; N( t4 {( y! C1 s
    & @, r: B" X/ T% u' u
    比较经典的问题是最长公共子序列、最小编辑距离。) M9 r4 P) ]5 _/ [/ r& {8 a
    有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    / i; E' o( w# p+ M# M( B有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离% v' X: P/ t: W
    3、2D/1D/ E7 @4 K# U! X$ c- E* j- W! u
    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] )
    0 o- v9 H( W. K" Y2 wd[j]=w(i,j)+opt(d[k−1]+d[k][j])% X  O5 U# ?) G8 y; ?' w. W* i4 S
    区间模型常用方程,如图所示:
    ' @- A& }" H+ Y" ?( ^* g: p4 H
    / O: L; Y/ t8 f
    $ t9 n$ B0 L1 C2 S5 j
    另外一种常用的 2D/1D 的方程为:0 v- Y" f7 w: w% u
    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 )
    7 q6 O. ~1 D! E0 \9 G4 Qd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)! R; _4 Z8 r  w) X9 E( L+ I
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    $ H; U3 ]1 @% i. }4、2D/2D
    3 Y% H; r9 {, V# ld [ 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)$ M; _! U+ m: J7 M/ u
    d[j]=opt(d[i
    8 \; l, u: ^, V9 U& R9 s& l* i* u3 f8 v
    ][j : ~- }9 y% ^9 C9 x: u
    . F6 A& u- H+ P0 _
    ]+w(i ) s/ k& ?3 M7 [3 v
    8 ^+ u  _$ P6 z  e/ K) _
    ,j ! f8 V1 A+ o1 K7 C/ ~( a: r
    " g0 f3 W9 m3 {, I5 d4 [7 i: `4 @
    ,i,j)∣0<=i
    % q6 Y/ q; Y! f- F; ^7 F# j' g6 K# v6 f1 k$ F& Y$ X* v7 \( f+ L
    <i,0<=j 0 o0 y0 I+ p6 r$ v' r

    0 Q" b* j! T, Z' A1 e' s! n& S( {8 W <j)
    * n5 W  G3 M/ g" b如图所示:
    , P$ I- N& H) n  R& Z" e  x: a0 \. U( \: E

    : P7 [: o" K+ J7 q3 \* @常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。# {8 M6 f- i" c. T
    对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n ( W- A2 b/ B! m' R8 N
    t+e
    5 u7 F3 i) t/ |% S* a; K ),空间复杂度是O ( n t ) O(n^t)O(n 0 j8 m( q% @5 m3 Y8 s+ c) l  C
    t
    ) X. H1 |+ R* d7 ?) } ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。
    9 W* O# \) f4 d: A; h/ b3)计算几何
    0 i5 X& e, v9 O: w' j' R* p计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。  d' g4 _' {. ~& M
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。  u- Q0 B5 Q8 J/ |) _! `
    1、double 代替 float
    8 h' B8 ~. b9 G3 H4 ~$ B* Uc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;: V/ U( W$ p9 X: H
    2、浮点数判定
      ?$ i4 }" [+ {3 p$ w由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);5 @& M4 @3 M9 L& [! c
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:$ b$ h- u& z2 D$ a* @, v
    const double eps = 1e-8;
    : t2 j1 N% i4 [bool EQ(double a, double b) {
    + G2 e& m" A% D8 z9 I+ i    return fabs(a - b) < eps;* O1 v  f" k9 Q8 W  T5 G$ I7 B
    }% O, ]2 L8 z6 [" }! q" e2 E
    1% T5 G, b- {6 E9 W6 v
    2
    5 P$ s; _/ Y# j3 T4 a& i7 {3: y( L, q7 G9 O. |5 C
    4& Y3 L) K0 r1 E1 g. [$ r: f
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:4 H# D0 i7 ]& _0 \" r6 v8 K* P: ]: C
    int threeValue(double d) {5 z& v0 X7 z' c. K6 K' W3 O
        if (fabs(d) < eps)1 V2 u+ D; C4 t* g/ p# J1 W' W
            return 0;
    + c1 g# f# H+ T    return d > 0 ? 1 : -1;0 H. B( M* L1 a3 M. \3 H; F( \
    }6 Z- n, p5 ?  v
    1! P+ y/ S% l4 v
    29 E. L- V% @7 G, B
    3" `: @* R7 n. C1 @
    4
    + ?8 {& m* m5 Q5
    ' ~$ h, P" w6 i# r7 C* F3、负零判定8 \- E# F) Z" F7 y0 ^
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    " Q6 F0 ^( s0 o- a6 |' Q    double v = -0.0000000001;
    0 D  G% o. P8 h5 W7 |+ Q  m    printf("%.2lf\n", v);
    / E0 }' V& Y5 Z: B2 C& y1 x5 O19 D9 i: C+ e2 z
    2
    ) g& F! i' A& Z2 C6 S8 g) A5 h5 ^避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    8 l" b- u2 O/ G8 A: |    double v = -0.0000000001;7 N2 f: O$ |6 b! H9 R, U9 }
        if(threeValue(v) == 0) {
    7 p2 F8 I: n1 w        v = fabs(v);
    4 `. w, x& q0 Z    }6 r- m4 E, g) A5 s9 n! W  }! E6 j
        printf("%.2lf\n", v);& \$ p3 A' f: R- X: h; m( }% U
    1! z8 Q! r0 p# N- b  F1 {* S
    2" u( ^# @7 T* _$ m# H- F
    3
    6 n, k) ~, j. s+ I3 Y" G4
    / {( ?) w3 O- q% r& q; x7 h5
    ) [- d/ g# N. R7 Z+ ?- O4、避免三角函数、对数、开方、除法等
    ) S' a7 ~5 ]4 L1 ]+ O' [c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。# t5 u6 M  k! d, M  k, @& \6 `# Q
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
    4 q( n! R1 b  d5、系统性的学习
    + o, U. I& ^2 O( g% T0 t基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
    $ H& i6 n4 K' h/ x  K进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    & x1 r+ ?( a9 j/ D相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。4 B$ G* P& J9 Z0 I) c% L% v
    ' E7 S2 U7 Y9 w
    ' v; Q; b, u' d( q9 I
    学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
    ( O" s/ r' I2 K  n4)数论
    + Q  I, h! v  O6 `' ?刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    2 h7 z5 h% w6 l  z: J数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。0 c2 t) M% z9 R/ P; \$ ^. ~6 s
    当然,数论也有简单问题,一般先做一些入门题提升信心。' m7 y/ ?% E* ]' e) I5 Q. h) ^1 v
    1、数论入门% b% `: o0 @) S  k
    主要是一些基本概念,诸如:
    ! b5 J. r5 O" B% c5 h  _整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;8 |0 ?" J1 p5 W( F/ M+ n- R( n7 W
    2、数论四大定理
    ) a7 P+ R+ o5 r/ c7 E1 O这四个定理学完,可以KO很多题:
    , z" Z7 q7 J* c7 c欧拉定理、中国剩余定理、费马小定理、威尔逊定理4 z- O, S7 d6 {3 A. E- T6 j- S. F6 K
    3、数论进阶
    0 |9 G. G7 l" J3 Q9 N  x0 x系统性的学习,基本也就这些内容了:
    7 D& A3 T* _3 K8 V# [. t扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。
    9 s9 C& v! I% I/ j) V% w9 u! ?/ S0 Q5)字符串匹配* T4 E3 M- `4 h# b0 p% Z
    字符串匹配学习路线比较明确。
    $ G3 `- l7 ?0 m' R1 @: S先学习前缀匹配:字典树。
    : u0 H2 w9 b- Y# S, v然后可以简单看一下回文串判定算法:Manacher。5 ?& r; T" Q* H9 ?# V
    以及经典的单字符串匹配算法:KMP。
    " ]6 k; e" Q3 M# j* Z/ a' V实际上平时最常用的还是 BM 算法,而ACM中基本不考察。2 d- j6 }. F/ B' Z7 C$ O0 z# m- m, u
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。. M1 B! E) Q8 Y7 e) }
    关于 算法学习路线 的内容到这里就结束了。  N, e- r( W. L
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。# g1 ?! d( g  d+ I( |. s! }
    参考资料3 A* p% @& B" a& a1 Q
    【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    # P3 \, F) f( m, }【阶段二】C语言例题:《C语言入门100例》(日更)
    $ y1 A$ l+ |& I【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    1 g$ Y" i2 F, H# |( h【阶段四】算法进阶:《夜深人静写算法》(周更)
    1 U' ~. _1 ], r$ x————————————————( Q$ a1 A% e, W9 \6 z- H4 i, \8 T
    版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    5 q+ T2 t  ~- q/ E9 C原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228
    $ l) B; L3 c$ _9 X! }
    2 w1 `" L. {$ p: J. v
    " L$ y6 i5 [4 J2 w4 j
    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-9-13 15:29 , Processed in 0.629355 second(s), 56 queries .

    回顶部