QQ登录

只需要一步,快速开始

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

    3 X/ o0 q0 l. p+ b& f❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏). j* w8 m- I% W
    & {3 e' N  W/ c" p
    前言; ?6 U5 [; G: \7 }( R  T! s
      所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。: a5 T* S2 Q$ S$ K. t- H2 v7 F
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。9 j5 F; w. g: G% @2 a! s
      刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。1 j" n* a4 @" ]
      每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。. O: T) c9 p& [+ E

    ! ~3 ]4 J* b- i$ G' M8 b
    ) G* d4 S2 I9 ^* d/ R

    9 ~( W0 u# i, r$ q2 Y3 c* ~6 p4 T
    7 F2 p+ N" k6 q$ `7 C
    4 s) K7 ~$ F# G6 ?0 A- }* y; Q
    0 L0 d( v7 x, r- q1 X; y

    & H5 _( }8 X& Y7 v3 m0 r' H
    % _+ @' A$ V5 m0 y* V
    图片较大,文章中有拆解,需要原图可以留言找我要哈
    & ~5 U6 r, L/ p+ p& w  P' K" r1、基础语法学习
    / u4 z2 W4 L8 t0 j算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    ) A( {$ N/ m1 B6 s) O因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    ' D" b" g! S! {! }8 m0 L: x, a- w3 A8 h5 g5 ^+ r# p
    / _" \* k7 |- G/ Z3 ]) X

    $ Q# Z3 f* |9 ?. r3 r
    ! z9 K/ t0 ?7 H3 \* V" O
    1)HelloWorld
    / |" r% ~& W, [, r8 P无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。
    . S! G8 k% w: a0 Z2 p* T2)让自己产生兴趣/ |% a) s; {" ?/ p8 X- ]4 v$ c
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    8 _  m( ~4 p5 ^, I' i% T  t刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
    ; P' ]+ n* ~9 a7 h0 V7 I3)目录是精髓
    , C, M3 h$ i: A3 o* h% [然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
    : `+ I. S' s& S( F) ?% x& \" X1、第一个C语言程序  g1 y! m5 G4 i: P9 Q2 l' n
    2、搭建本地环境3 M* _) A$ s1 G0 _. u
    3、变量& h3 B3 t5 q1 g+ R+ O$ ^
    4、标准输出7 m1 W( s: s$ C, [* b* ^* L- t
    5、标准输入; Y" O  r! P8 m% R$ z" r4 [; I& N
    6、进制转换入门/ f8 \3 S( A) _% h4 {4 T- B
    7、ASCII字符1 _$ t/ `$ Y4 ^# A
    8、常量
    4 O& c0 g. _8 J. W8 \* o; z) Y
    2 O8 {; q2 N; z* M( L

    ) X2 t! c3 J! ?/ y8 q  ?如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    / E) j1 S$ }2 T5 K: g4)习惯思考并爱上它
    3 q1 u% V; z2 q. b只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。& G& W2 e) U; @9 K  k
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。, n( m9 t6 D0 n5 K" g, b
    5)实践是检验真理的唯一标准& F2 S' n) U7 e% _: Y' R" B5 @
    光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    $ e6 K9 d' A5 @. S3 ?所以,记得多写代码实践哟 (^U^)ノ~YO! I0 ~) U2 e' f1 M( L7 [5 ^
    6)坚持其实并没有那么难
    ) g: B. G  g8 l" t& @. F2 x每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    1 k8 E& D4 B) N. Z1 T2 }1 W7)适当给予正反馈
    2 M# d$ ]" S' V然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    , ?! b0 I2 N: N) b) |& S- ~$ a当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
    % U! H$ W. W, c2 N- G看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。# b$ g: Q$ M, z- A1 u1 ^7 R, u3 @
    8)学习需要有仪式感
    ( ~* l4 m5 A: o) t8 h7 {) F9 E那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
      ?+ e& l* ?. v介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
    1 `" r5 B! p- N8 }/ h1 h& v我也很希望大家的学习速度能够超越我的更新速度。
    6 X, }: t* [: u  M# E. e2、语法配套练习
    3 U0 Y3 v* Y2 g$ T& Z学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。! }- S  S1 H; t& s
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:3 V. q* t+ l  z! V$ d

    ; W3 m, m% s) p" [

    / P& [% o8 ^5 Q& R. W- }  R% t! C7 o
    % ~6 K8 M# O* F2 C" d( `
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
    3 O, d2 C- r# ?$ Z; l这里可以列举几个例子:
    0 ^" a2 g6 p+ d& T" k, O0 u" L$ o1、例题1:交换变量的值
    # l3 C  n  G8 N6 H一、题目描述
    * O( A; z, |0 ^% w. b  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    * u: K8 f& C+ {
    ) a4 E! A" h- z. v+ j

    ! O& n& g' Y; ^2 _2 h
    ; L- R" G- X- j6 u  _
    4 m: N  K% a3 @6 L& j2 w: P9 _1 g
    二、解题思路; z; [0 K- l: @* F, c
    难度:🔴⚪⚪⚪⚪
    $ S! q: {6 q, k# j
    $ ^7 j* o9 w% [8 K4 Y1 \
    . P+ j' r( G3 k6 |7 {# d
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。8 z$ E* D, F  o$ @; q
    a, b = b, a
    & ?# \% U& V1 F10 J  ^$ h" K. G
    在C语言里,这个语法是错误的。0 E! c* t4 u$ D2 b; t! l/ g$ o
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?. C" }( Z1 X3 C2 I* L
    当然是再找来一个临时杯子:1 L" J& B8 V. H  G! S
      1)先把 a aa 杯子的水倒进这个临时的杯子里;) U0 A; d2 c  [) l! s& n$ M  T
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    ; A% ?( f/ j! ]) z  3)最后把临时杯子里的水倒进 b bb 杯子;) [' [! o! g. j7 r, u* `0 b, [

    # k7 a  l+ P9 ~5 i  c0 h5 ?

    4 I9 S8 Y- }& W& L4 S1 m" M这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。  M2 G3 a3 i! T9 P

    ( ]! ?$ @, A4 Q# B2 q
    & q1 e( s- G5 K: x
    三、代码详解
    1 f7 h) R, m+ j) E1、正确解法1:引入临时变量
    / s5 C% G* A$ ^9 Y- g" U0 Z#include <stdio.h>
    * o/ `1 @) l- q2 aint main() {' w" Y" X) z% f
        int a, b, tmp;
    + D/ P0 E; \7 ?0 x: `& M/ J        while (scanf("%d %d", &a, &b) != EOF) {1 t' y3 O8 K, I5 K! N! N) q0 A5 B
                tmp = a;   // (1)
    - q, |3 ~" q5 q( z1 s            a = b;     // (2)3 t1 \/ O9 f. z
                b = tmp;   // (3)
    & u8 P+ A1 X3 [, ?            printf("%d %d\n", a, b);$ H: B: Q$ R: y! Z8 G; Y9 y
            }$ W; k7 f$ Z8 y3 g
            return 0;1 H; I' D. Q% U6 @, f0 T
    }; F& M; ~/ q  r4 T
    1) Q% j8 \- [" H5 P9 g
    2
    1 o4 F1 Y% @7 h- C( |2 M. Q! z33 _( N7 E+ j/ `, m( ?
    4
    6 H! K/ M* K: B5( X1 l+ i  P2 m0 j# ?- n
    6& m! r2 E+ p2 o: I9 i
    7
    + r1 p6 i1 I+ w3 A4 F4 V8
    5 r6 q% B; z6 g1 ]' {4 |7 h9
    5 w, y6 e4 X( ~10
    4 X! ]* U, q0 z6 S6 h, K11: ]4 S6 f+ `1 ~5 d! I
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;- Q5 @4 `5 g) Z: h
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
    6 Q  V5 W' A5 r* W5 _. Z/ i( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    ) e! X3 a" F$ Y% T! [4 E0 @0 C$ f这三步,就实现了变量 a aa 和 b bb 的交换。* T$ O  `  O% D) |' D9 E9 `
    2、正确解法2:引入算术运算8 S- p: H" c. x2 W# i. g" B6 R
    #include <stdio.h>6 A0 V7 r( u8 c" l
    int main() {* G2 N& b, \8 Z! d2 ~# w+ q8 U
        int a, b;
    1 q# T7 @0 K% I# j: ?& q        while (scanf("%d %d", &a, &b) != EOF) {; M6 Y; S& c& H% |" R
                a = a + b;   // (1)
    8 x$ J6 n$ G6 J* t9 n- V3 r            b = a - b;   // (2)
    : \7 j! L/ ^, |, B6 b8 b0 C3 |. y% u            a = a - b;   // (3): K, Y7 ~% x6 c# {
                printf("%d %d\n", a, b);
    , o  _& n- `% [5 F/ i        }8 p5 c  k( j9 {
            return 0;
    " V: Z+ o( \; W( m* X}6 Z5 L! P! d- c5 O5 R
    1
    2 U4 n7 j: q" K' [# U8 P2 ~" W24 W$ j- m4 a+ L3 h5 t
    3+ T& f! N2 V) c7 @0 ^9 u' |
    4
    $ h; U- A5 S; h2 Q- G5
    8 T0 I  a5 G" f6
    1 M. q9 S0 I4 m! H7( w( ]" I% N* |6 A# i% j# F  S+ V
    8- S, T7 i1 e7 i$ r+ k/ K
    99 X( k  g$ q1 D6 c1 v- p# O
    10
    5 B+ c+ o( T9 x. {# T; ?7 Q11/ J( @- i3 M& V0 {% o9 C
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;% h! a. K* m' h% D/ J% b0 q; R
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    0 ]: O/ h5 x! N2 {( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;$ R+ l8 f- v- \" ~5 Q7 i. K
    从而实现了变量a和b的交换。6 \' W" ^- W/ @( Q7 `
    3、正确解法3:引入异或运算
    8 }) t/ T/ C& J$ M首先,介绍一下C语言中的^符号,代表的是异或。
    4 _- s' A  [! C0 B/ s二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:$ w- @' ?9 w4 A+ k: v6 h
    左操作数        右操作数        异或结果5 i$ L0 m/ u/ s7 ^
    0        0        0' P( J% {8 n! a. y7 s/ U/ S
    1        1        0
      p$ f8 Z- o# P1 ^6 d8 k" k0        1        13 `4 I, w1 b# S  x# L8 i" R/ {
    1        0        12 U$ ]& [* Y1 m7 I
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
    4 P$ j" n( n, S5 U0 z5 |! ], l! F4 w这样就有了三个比较清晰的性质:( q( Z( M' \0 h+ {$ z3 ^
    1)两个相同的十进制数异或的结果一定位零。
    # {/ Y. O; N- }# w7 \2)任何一个数和 0 的异或结果一定是它本身。
    3 T( a4 W+ e6 F1 i: z& ^0 M3)异或运算满足结合律和交换律。3 N. ~( o7 d* w
    #include <stdio.h>' `, p0 U- ]- n5 @) N
    int main() {
    4 s+ C" I( q4 d- r( V6 y& s    int a, b;0 J+ f- w. G& y7 ^. H
            while (scanf("%d %d", &a, &b) != EOF) {* m" P- }, w8 b" J: k; C# c; Y
                a = a ^ b;   // (1)
    $ |5 k0 m, l- c, c- F- {2 f3 G            b = a ^ b;   // (2)9 \, \' W4 `/ d) m" D
                a = a ^ b;   // (3)+ y3 d* b. n% C  H" @) N
                printf("%d %d\n", a, b);
    + K& B, R+ f+ n# D        }
    , x% a. D2 w5 d$ H( w        return 0;! P3 M3 x& w4 [
    }$ i4 P& {$ y3 M
    1! c/ R- b3 l: B2 W9 a% e5 s
    21 u. S; i+ M" o* a
    3
    ) p( C/ G1 c1 s4# \; ~+ t, Z1 h- I- P* S
    5
    7 r% N) A) k/ M# m7 z' B67 a/ [$ x* G0 ]/ B) N
    7
    ! l) z9 p6 g* H' D) q* k3 X86 r1 U$ u4 e7 i
    9( A4 {" c6 @) v  \2 d
    10
    0 M: p% H2 l( y* W7 \5 z11  t; P, R8 G" F. H  C/ A% M
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。4 b3 m$ Q/ i- [3 B1 o
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。8 d) e5 [+ Z+ O9 ~  X6 D
    从而实现了变量a和b的交换。0 x. x( n( T% l; p) X+ o+ n
    " Q9 [5 w6 Z" i- v
    ) R( T# A( }* h! ^/ L+ Q3 R
    4、正确解法4:奇淫技巧6 e3 z3 v) Z2 K7 }, K, X
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    6 u* s% z# u" A  q9 _#include <stdio.h>
    ) s4 D' x- _& b5 g9 b% r9 Vint main() {  g4 p$ O7 |- v6 x. a
        int a, b;' Q3 {! r) k! n$ G
            while (scanf("%d %d", &a, &b) != EOF) {
    5 H0 K  K* v5 Y7 Q) _            printf("%d %d\n", b, a);  A* p. V$ v* P" }* n& n
            }: B9 t0 C! C& E, w2 J
            return 0;( K' @7 r& }2 J2 W) R9 z9 U- E
    }
    ' q7 j: C8 T8 Y% h1
    / j' J9 T& e2 Y6 v3 P* \" V2
    2 {  e6 a- @; @4 P( {3+ R0 }8 p: D) B6 q8 e  K/ R
    4
    ; q! b, \( _6 x  a: l' l, X54 C9 ?$ C( L8 o! x
    6
    / n& ~9 P, a9 E2 Y7
    9 P1 A9 W' l& g3 x" Y7 b8" S0 c/ g: c: Y2 }, [0 T
    你学废了吗 &#129315;?
    & Q/ S, K- L  q' L/ v2、例题2:整数溢出9 E7 |  L' Z$ {4 r
    一、题目描述
    ! [' b1 G3 ]2 B: _( u  先输入一个 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
    $ [8 U! v$ h8 M6 A& V7 A3 Z62) e' q( B: w- f' H
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    , [( q9 a# H1 B  D3 i# h0 `# d) K7 s; c9 \

    ! i8 d  v0 Q0 ?/ r* J& o) {二、解题思路& ~/ C) e4 w; c9 z" ^* X
    难度:&#128308;&#128308;⚪⚪⚪
    $ d! R# X8 f: h' k2 G7 e( D1 A" W! r* A2 `3 x9 X

    $ Z1 i. h' x% j: D9 Z" ?这个问题考察的是对补码的理解。; I6 ~( U% H/ p2 ]# n& C
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 " s) K; t6 X$ @# H: n5 Z4 u3 g4 A% _
    62
    4 N! y+ r9 k& i, t; M: t: _ ],这四个数加起来的和最大值为 2 64 2^{64}2
    . w3 _" p: d3 G2 o: K0 `2 r2 g( R/ z64
    & w' J/ }* s* m. ]. H! r6 j 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 1 F" D( G. M; _" _$ k2 i# b4 H' ^
    63; R: B; j2 y  _9 ^! Z
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 2 I: c; m, ^+ i2 b- E% G  q( n
    64* C* S1 O3 u# F+ p+ a
    −1。
    + X4 p. }  [2 k# [/ u% [% F但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 % P; S5 @0 ~8 U
    62
    " i( K4 E5 P" T! i0 \8 M4 a7 ^* k  时,结果才为 2 64 2^{64}2
    6 s8 A& `2 y, M8 O' x0 q9 Y64
    , [% |, ]" G7 o. p+ [3 c ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
    ! a6 g' @- i. Z- p( S% c三、代码详解3 l0 R8 T8 l6 M' ?) P6 |7 g! m
    #include <stdio.h>
    7 x4 @. \+ b* n5 |typedef unsigned long long ull;                           // (1)! Y6 v* A: O" q2 i4 D+ @
    const ull MAX = (((ull)1)<<62);                           // (2)
    3 ^7 j& a( Q, I- I8 H9 T& q, p
    3 K- `% ~6 R$ I
    , x* Q  }; `% z7 E; k
    int main() {
    4 n- a$ J3 d4 J        int t;) {3 e/ i' t7 T) O4 R1 Z! j
            ull a, b, c, d;6 E8 y! u# E  X. a
            scanf("%d", &t);
    / p* y: \1 f* G( z) U1 [        while (t--) {
    , J- t' l1 i8 A                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)5 T2 E5 K9 d& Y5 s
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4): o, z$ p! R& x9 V5 J( Z9 S
                            printf("18446744073709551616\n");             // (5)7 l' H( o. ]- _1 I6 m9 w
                    else! k6 L4 L( P  W7 w4 i9 @$ R" v- M7 m
                            printf("%llu\n", a + b + c + d);              // (6)4 S! _! ~, M7 P* S! m
            }6 B2 }# T* f; S% k% w$ K5 L
            return 0;5 W% K: p1 N5 a1 E4 R0 u
    }
    9 ~$ p$ G( g6 {; {# l3 p- o1
    ! O( b) k; t; p4 ~. k8 w9 a) G  O% @2( i/ n! k! D4 T( j: r! E1 e
    3" }2 u- s1 j  p- ~: I. V% }* D
    40 q: x* `- l' q7 a  S
    53 J5 z. Y& c' A" x; w) d" _
    64 ~& D4 G( Z, J/ K
    7; Z3 y9 N0 J0 n) e+ i/ L" i
    8
    " q( b; T. u' k+ D: y& o5 F9# l. ]! K! G/ U: x4 R' E7 C
    10# y; u0 }6 ~: t) N; {
    11
    - @! |: X% X* c9 g. d12
    2 D$ @' f5 w! F/ {4 Q; ~13: y2 n4 V0 |% ]% K8 t( P% Q
    14
    7 \0 _( D# D6 K+ Q15& f9 A8 P$ \% G8 i9 f, H; t' l
    16* _3 t3 t4 A; b: O! h4 n+ u' s
    17. o1 @  N  p+ X: @* B- Q  P
    ( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;% [4 e7 H) O9 m! Y- @  C
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 % `' D* j; p9 ?# u: ~, [
    62% K' i2 Y' w: I
    ,这里采用左移运算符直接实现 2 22 是幂运算;/ p+ z, I8 b7 W. u, }
    数学        C语言5 ~5 a8 l' l' ~' k7 m5 d
    2 n 2^n2
    # m9 @. h/ i  en
    1 ]( |. s/ S/ B% J4 s         1<<n2 l# L6 A5 j0 j/ X
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    - M- `; U' n. _( 3 ) (3)(3) %llu是无符号64位整型的输入方式;
    1 g/ d) [7 M; L5 M! }) x' y( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    . M: g- u, G! m+ h0 _8 I5 {; b, {; S( 5 ) (5)(5) 由于 2 64 2^{64}2 6 h8 r/ e1 i  q" ?
    645 F+ I( g: z5 }5 t* b! k7 k
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;3 B8 L, v# _! B
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 4 \$ }3 C2 [1 |4 q0 U* n# I
    64& P- Q2 Q7 [8 @! G" B  B
    −1] 范围内,直接相加输出即可。' X# Y6 [0 \4 g; p' v! n
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。4 y* n% S) {3 D) `5 V" N2 `
    为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。, H! Q9 j7 a: j6 Z: z# d! I
    9 N1 i( j# I- B6 O; q! N
    . r1 A* N/ X6 k
    3、数据结构
    % S9 M9 S: |+ Q+ m- g, z《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    # G6 s( M" m0 k( w' X1 R1、什么是数据结构
    ' J; S+ ~% h* r; W  P* L你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。+ j/ K+ ^9 ^. Q# F% y% q
    如果一定要给出一个官方的解释,那么它就是:
    ' c2 L+ |$ t3 l5 r计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    3 K" v; Z5 p/ W' q; q  t
    3 V7 I+ [; e6 h5 F- c
    9 x' s, `4 p& m, _3 S4 {
    是不是还不如说它是堆,是栈,是队列呢?' N% W+ k- \9 Q# _7 b7 y$ c
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    1 H, o8 p, I2 g. f* B0 S2、数据结构和算法的关系' Z4 @! _5 r' Y
    很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    # P4 t  l: |5 [, ~4 i数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
      C0 {6 Z/ I$ W6 b* F而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    7 ?0 W6 J/ X  L; `, I讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    0 |( s+ Q; N4 Z  _3、数据结构概览
    : Q9 M8 |0 e* c% V4 y) F  j; z5 }周末花了一个下午整理的思维导图,数据结构:
    1 ?2 h: Z% S# m+ c7 B
    5 @  V- S7 T5 V! C4 z* N

    3 U3 T; C; a# I% O. u' G常用的一些数据结构,各自有各自的优缺点,总结如下:
    . C% l8 E0 o$ ~# M+ @a、数组
    3 ~4 v2 d; B: K& E4 t% U内存结构:内存空间连续' e* U" o' J4 r5 r. a
    实现难度:简单
    : N, M+ h6 P8 p) @* b下标访问:支持! l9 b  q. V; C& a1 ]
    分类:静态数组、动态数组$ G+ S3 o) h7 w
    插入时间复杂度:O ( n ) O(n)O(n)
    & w' M" _  i$ `3 @8 U查找时间复杂度:O ( n ) O(n)O(n)+ S* X7 W2 f( p, \
    删除时间复杂度:O ( n ) O(n)O(n)
    - |$ H  W3 p# h0 q$ C) s9 V& ?
    # G; ?5 i  Z7 u- J: Z
      t& z/ j0 }3 i5 F& f
    b、字符串
    ' }  k: @' w/ I7 c1 U+ e内存结构:内存空间连续,类似字符数组
    1 ?  T3 f5 Q7 j实现难度:简单,一般系统会提供一些方便的字符串操作函数) h* F' [' `) X3 }7 ]
    下标访问:支持9 C: |% O% r( g2 i' T; _6 ]0 Y2 p
    插入时间复杂度:O ( n ) O(n)O(n)$ j, j; z7 o7 U# C% i- u! `
    查找时间复杂度:O ( n ) O(n)O(n)
    1 v& r, R) V# ?" h1 E7 f% C删除时间复杂度:O ( n ) O(n)O(n)
    . r  N, O4 O- T5 \* ]& w% i* [: e' x

    " R* y) r% P5 O. ~! Y' L' `. C- _( Rc、链表
    ! O8 e8 o. H5 U内存结构:内存空间连续不连续,看具体实现; U6 }. F% D1 `6 W9 I/ \
    实现难度:一般0 V6 O$ j! i7 C  d3 [2 U
    下标访问:不支持
    7 c0 e! p3 \7 H( G* x% O4 X分类:单向链表、双向链表、循环链表、DancingLinks
    0 p* ~$ w1 _/ r; v! b! H插入时间复杂度:O ( 1 ) O(1)O(1): O( k# h; a  B. M- F$ ~
    查找时间复杂度:O ( n ) O(n)O(n)
    , h7 F" U; Z+ C$ Y' k( |# Z删除时间复杂度:O ( 1 ) O(1)O(1)/ E: X. L/ n% }5 V( R6 u
    : C9 f% d  I( Z6 |3 ^

    ) J9 `# Z) R+ O& v* X# Ld、哈希表
    ; }% a9 b. }$ c3 r% s- ~- }  y内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    & D6 B6 U! F' T: ?" X' ?实现难度:一般3 X# L& R3 W% C
    下标访问:不支持+ r. R, }" _1 }1 J- P
    分类:正数哈希、字符串哈希、滚动哈希
    ) D7 P. v# W1 V) j插入时间复杂度:O ( 1 ) O(1)O(1)
      B: ^' q  S7 W) r) a查找时间复杂度:O ( 1 ) O(1)O(1)7 l! X+ p( [2 D5 T
    删除时间复杂度:O ( 1 ) O(1)O(1)
    ' P- T  o+ k8 k1 j* U' G. T  `2 k9 X! Y

    6 \+ M9 @1 o: P, O* ^- G( ve、队列  I2 B5 R3 p: B' N2 B1 V: J
    内存结构:看用数组实现,还是链表实现
    ' U; T% A8 R3 z8 L8 x7 b实现难度:一般
    5 g( z9 R! O  }* o下标访问:不支持
    ; J8 W7 u# t3 i" }  L; e2 o) a分类:FIFO、单调队列、双端队列% U1 q3 o6 K+ i8 n7 o' G
    插入时间复杂度:O ( 1 ) O(1)O(1)
    0 j) y. g& x5 S) f" f1 A& u8 ^查找时间复杂度:理论上不支持3 V, r3 T' ?+ j+ Z+ u5 }$ u$ o
    删除时间复杂度:O ( 1 ) O(1)O(1)0 J! z; n  K& M  B  {* U! c0 {# e, \
    1 b* W& M" H6 D4 e7 b7 l1 T
    % M6 R. S+ m" q# X7 i( Y
    f、栈
    , T, g, l8 q* @; M% @7 C内存结构:看用数组实现,还是链表实现  O: m3 u- y/ |, C
    实现难度:一般  i3 p& N3 X1 F1 H3 O( `* E
    下标访问:不支持
    & `- U  W- ^5 s7 n分类:FILO、单调栈
    0 a( a% o' B* a# n- r插入时间复杂度:O ( 1 ) O(1)O(1)
    2 ^0 e: |4 _6 [% l% [/ h* B查找时间复杂度:理论上不支持
    & S, S" _( h3 v3 d: e0 `2 ?8 A删除时间复杂度:O ( 1 ) O(1)O(1)2 X+ k; V% i$ w( i* O8 C) Z

    1 z  Z5 k+ V# ]( N4 t- M

    : i6 D3 [" E0 W  q# n: {g、树9 [2 v0 F/ V; u" d
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
    : q( y( ?0 |( ?5 E+ ]4 ?实现难度:较难
    7 ^4 y; f0 v  A) J下标访问:不支持* M; K  `7 l" E: W+ O, A9 a% x
    分类:二叉树 和 多叉树
    ; _6 @2 \6 i1 ~, p插入时间复杂度:看情况而定6 _5 E* ]1 p* v
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
    + O- v8 y' r# S. i2 j3 Y2  `. Q7 z6 k5 D2 j: ~; T
    ​        7 u) e8 D' \2 L/ [
    n). l5 l" O6 c$ o; W! n
    删除时间复杂度:看情况而定. J+ N9 ^" W" ?/ Y! u1 r& B' R$ e
    8 s' a, t0 A/ i1 L5 S) n- n% w

    + e) O& h6 Z! u6 R' J1、二叉树, w! U3 C5 |! H& S, g
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    ( M0 s* ~0 c' L其中,堆也是一种二叉树,也就是我们常说的优先队列。
    4 @8 j6 }& W3 G2、多叉树5 w* X; q. y2 w! D; y+ \
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。6 L9 j4 _" g9 n
    h、图6 V; R+ m: t5 X, G/ H+ N
    内存结构:不一定" X6 W9 z' b1 ^7 _$ m% f
    实现难度:难
    $ d6 t0 V7 O4 a# D下标访问:不支持$ V, c! A# f; w- G
    分类:有向图、无向图
      o$ C% t/ a% O插入时间复杂度:根据算法而定
    / Q: q& h. m, \+ v0 h" w0 p6 e9 I: K查找时间复杂度:根据算法而定. U! c% k$ [1 ]) ^- A* {' v
    删除时间复杂度:根据算法而定
    " P8 b: x4 V! u" x: Y6 z0 n, `5 i% S8 _' A# @" v8 O) M0 K
    : I: g6 t  m' j$ \4 o9 p8 J0 Y
    1、图的概念# F8 Z- X5 S" c+ p; K+ g! J- u
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:. ]0 Y; A& c% ~# P) d' z
    图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。, p+ @+ B/ [4 }0 w
    对于无权图,边由二元组 ( 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 为权值,可以是任意类型。: A; [& t% V* X  |% J
    图分为有向图和无向图,对于有向图, ( 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;; X6 ^& g0 c' d$ Y  ?! Q$ K7 x
    2、图的存储
    4 ?) |& g! w7 ?6 G; R3 y( B5 m对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
    3 f. r1 J7 f5 U6 k& W) d3 A+ I) D. W3 d. y1 X0 P

    ) L# `0 x2 W  h1)邻接矩阵1 I! ?- O$ n4 c1 v
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。
      x3 h. a: ~/ H它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。2 c( f- |& {& ?# F, D4 o
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[
    ( T( p8 f, u+ T/ P7 X01∞9∞0∞8320∞∞∞30
    - }- _( ^. X3 E+ z& i0∞3∞102∞∞∞0398∞00 S7 \" n4 P' Z- U: y! ?; Q/ X
    \right]( R; H; I4 @- k5 ?/ c) O
    9 S  q7 W* i' H0 P1 k% H3 J" d& v
    3 B7 ~5 S% x1 h/ L! f) R9 q
    - [# Y" ~4 h! p) L' ^& a! {7 m

    * [& P- f3 G. [8 M​        6 d8 S/ s' M  ?' ?" z3 a1 `( y
      
    / Z5 o! N& ^& C3 J2 L) j0
    ; j0 a2 S0 i0 }+ x$ a- A3 x4 E& a. V1
    # ~" H8 A9 l: e, x! {0 Y9 O8 ^, u1 D/ _8 N. l! u9 s
    95 K; @+ O" m% z/ u4 }+ Z
    ​        " }8 w: V7 u, q2 O- J
      
    ) B4 n# W* W6 e( F. {3 i, Q! j2 k/ t6 z& A
    0
    9 _3 ^& A6 E# r- z8 ~7 a! H# a9 r% ^9 _. E; t% N
    87 ^' A- U0 |9 k( p  b6 m9 `3 [  p
    ​        / z$ Q7 ]6 {! S4 b
      8 C- f$ V. ~. F" p. W
    30 Q- g- {# m# @
    2
    - ]% k7 p5 r7 O& N0
    " s0 E4 X- S  f) f! S6 n9 C, x) X) N
    ​        # f0 \7 ~8 R+ P. A6 N, P4 Y
      # Z; G+ Q  O+ p9 C
    / j  `6 X" C# c' t# o6 x! @

    / @4 V- c* G3 o1 q4 O" t3
    & y$ X* @* m# l" U- O4 _3 s+ d0
    9 u2 ]0 ?/ T' U( d6 K  c" x* x​       
    4 v; P8 S8 D, x( u* E  & c% @( ^- H) L( r
      E: j% [8 f/ ~9 P' c
    . p& t! s! }4 N& G- D4 p: K! e
    - L8 Z% I: ?  m4 V# t: r- Y

    , ~- e5 e8 W2 ^* i/ M# ]​       
    9 j1 g: N; K4 ~; @, J( v, E
      F- _% I- a8 A! d& v2 K4 i/ n2)邻接表+ @2 F8 ]+ u" I% S: l- u
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。3 @/ L+ L5 W+ p' b
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    1 ]$ k& m# K' S! T: A, _如图所示,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) 二元组。+ r. r" g1 H3 J# X: F1 R  h3 {
    / E- i' K9 ]0 V4 {  C  |: p+ s

    0 I. F. t, v# z- G9 v在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    " T- V0 E' L9 E; t; n: o    vector<Edge> edges[maxn];
    + f1 e& [: O$ }" Z1
    % q& U6 i" z% C, f3)前向星
    * k- q8 ^; ^+ r% h; x  L. }前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。) x3 z  {+ W3 P* u2 e( ?
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    + r7 i) H. m1 K, o' O1 z如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。) l, a7 B1 x; [3 t% O0 ^

    0 [7 b* M3 D* u

    / s, D( u7 [  G那么用哪种数据结构才能满足所有图的需求呢?" L# K7 `3 z4 u0 [5 c  y3 I# c
    接下来介绍一种新的数据结构 —— 链式前向星。6 ?1 v5 E; A/ q6 ]/ d
    4)链式前向星
    . ~: [- w) J6 `" t; v2 r链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。0 H9 n) ^8 N8 ^, M5 h, g& J  c9 L
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。/ `4 G/ m6 g! q9 u7 N" Y. }# I; ~
    边的结构体声明如下:5 S# z4 G9 \6 y; j2 L9 R% M
    struct Edge {4 j" ^, n, T: b% Q/ S8 \9 G! R0 g8 S* E
        int u, v, w, next;/ k4 ?4 ]* f% W2 p
        Edge() {}
    . ]( {/ S8 i- \5 d' d6 z    Edge(int _u, int _v, int _w, int _next) :
    ! ]5 e) y7 ]6 ~! R9 `. v. u  [# D        u(_u), v(_v), w(_w), next(_next)
    # l4 Z' X- x; h; g. C6 a' N0 Z! g    {
    : M/ H# M8 o; H    }: ?/ k/ x7 |& P& g8 g  P. u
    }edge[maxm];; \; Y( |/ _3 C3 z0 D. r) X; F! {
    1
    / }' F9 q' G' J1 ]; b0 L. g6 r, W6 f2
    8 @+ W1 O9 D1 c5 ?3
    2 W9 C, o$ V. O% D  D4  k: x3 R# \  n" G& ?' _
    5& _- e; N+ Q) o( C6 A8 L
    6
    $ ^% |0 T) ~5 E8 O2 E+ K0 L7 m7( H8 i  q! }- o1 f0 {2 p- R
    8
    ( n8 A! W0 f! |( p1 ]& k初始化所有的head = -1,当前边总数 edgeCount = 0;
    ( H( }" }2 u3 H9 H* P# v每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    " c4 f7 e7 [9 Q: e" J6 z  ~- _* p2 gvoid addEdge(int u, int v, int w) {
    % p2 f. r+ j# g$ G9 Q5 Y. N+ o    edge[edgeCount] = Edge(u, v, w, head);" w( ], i0 ^8 F, \) `
        head = edgeCount++;
    4 x( b" v  _) p; A6 C}
    8 R4 n: I# @2 o$ e4 k! @  S19 K$ I; F' m9 j1 W8 _
    2
    0 N1 l0 N; e3 u3. y2 y9 D0 k  B3 G. |! c1 a
    4" l( a( B  S& w' I
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    2 w5 d9 J! _! e9 @8 j调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。, E1 `- l8 }0 j( u
    for (int e = head; ~e; e = edges[e].next) {  W3 |9 @1 |1 y9 T/ _( \
        int v = edges[e].v;! [' N- W/ N9 p3 r/ o
        ValueType w = edges[e].w;
    : @5 D) [8 f' F! O    ...
    # r/ ^1 @6 S! u2 o}; B1 t, d- k0 t# M
    1
    ! e9 y4 `$ n( S! K) r2' ^! t. f# B6 s) d& s! l- d+ M6 m
    3
      T1 c' H0 m' x4 N  }# P2 t( r4
    4 u& K& G3 f; |) P& B2 u6 W56 M: E) K* J- V! m
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。- a  ]+ a: p$ r1 w7 _$ M: U
    4、算法入门- J+ o; ^, Q! Q# B' Y5 Y  w  h
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    8 C  `+ V; X% p- x9 [) h% Y
    5 E3 l0 c: z2 [. l5 ~. [

    - N7 J( E2 l+ H; x) ?入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    . [0 p  T/ c1 \# ~5 J' N% c* Y对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。, M5 I2 Z2 i% I
    1、枚举
    / t1 r3 B: h& ^8 }* H( [7 y6 c4 ~枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。* v7 F+ B. Y9 W4 @
    对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。: h  v# Y7 ?5 Q# B3 H: ]: J
    2、排序
    - Q8 J: ]5 U; r& A6 R' ~3 K5 I既然是入门,千万不要去看快排、希尔排序这种冷门排序。7 |: T, u$ s+ m5 G
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    ' l% J3 E% u, b) ~, BC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。
    1 d9 M. ?" E1 q5 s  q9 D/ ?. u3、模拟0 l( H; J9 Q+ Q" T) B
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    3 d* V( A& G# c, |; l) f/ o7 ?1 a* r不管时间复杂度 和 空间复杂度,放手去做!" y7 ~' L- @! T- ]
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    0 v3 |( T6 A7 ^4 I4、二分
    $ B+ y& P: J+ v: ^6 @二分一般指二分查找,当然有时候也指代二分枚举。$ n: n! W; m8 B% ^
    例如,在一个有序数组中查找值,我们一般这个干:
      \- M, ]) v5 q' X. u- B1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;6 x( c! r$ f) x5 y& U
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:7 {4 a2 _9 D, J8 d
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;! |4 {5 e/ a- v% K
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    ( V, C1 {2 q- \) l) s1 z' ?6 A  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    / K& X) Z: ]9 d* k* l$ Z8 _0 k3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    ! o! R8 Y3 `$ y# f, i, |. k5、双指针) J5 g0 s& A) s+ q4 j
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
    ( G, i8 d, F! ^7 W! @( p
    7 D4 l* F9 Q- u; r- i6 u( X+ g

    3 x; X0 K; m% y& X5 x7 x+ R6、差分法
    ) v5 ~1 \; L/ p8 a" e差分法一般配合前缀和。
    ' o# X- C1 C0 `对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;
    % n4 Y+ r/ D0 G: ^9 i' Y假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg + N  |$ V1 z) k% `
    x
    / r4 P" _! L  Y- M6 G​       
    5 e2 x% i1 k( _) G8 e ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
    + r/ ?& L0 I, D% fr
    # ]: m7 j0 b9 S1 [% }​       
    : s, j$ v; X( \, Q −g 4 X4 Q! x' q( X% I1 V# |3 b0 H2 n4 ^  |
    l−15 z2 p4 C) ~( ^1 k$ Y: I
    ​        5 p0 l2 |: L0 B7 F( h. r
    ;分别用同样的方法求出 g r g_rg * u- H* A2 ]3 a2 R# R7 G- u2 D
    r
    6 v1 z' m6 c; F3 _0 ~​        . c2 |9 _. x/ t. y- x6 J0 m5 [" C
      和 g l − 1 g_{l-1}g
    7 i6 d7 g! R) zl−1
    6 T: ]  a# v; ~' Z: P* G​        $ h! _* J" y- z2 H( X" N
    ,再相减即可;( T/ k( J9 O  Q' E# L0 H% {+ l9 p
    ' ~% V3 h- D: ~
    $ S6 n8 C6 v1 G& l6 @/ \
    7、位运算* b9 H( x- I6 N0 w% X
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    , R  U; C0 k1 ^7 |+ Y& V. g位运算分为 布尔位运算符 和 移位位运算符。
    $ D/ x) t; [+ v' O布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。7 |8 P8 Z) h4 q6 B; z& D9 d
    如图所示:
    - H3 h, ^7 \) H# p5 v" x. V9 @; e! _* Z

    & A, U  K& ^5 ~0 ?7 b/ K位运算的特点是语句短,但是可以干大事!- i1 _: g& I6 l5 e( g0 ?6 ^" ]
    比如,请用一句话来判断一个数是否是2的幂,代码如下:0 T- D! x  u- L& k% ^: m
    !(x & (x - 1))- W5 a' s; d) f' Q% b6 a
    1
    0 W" q5 m1 n; F$ t1 V1 r& V2 a8、贪心
    6 z" ]9 {" u4 {4 n3 @贪心,一般就是按照当前最优解,去推算全局最优解。7 |6 H1 o1 I! W! X4 ~- D
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。4 }& _0 d0 N- Z  z* |5 m" J
    9、迭代
    8 D+ P  w6 _% ]( d" ?7 T: r每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。( o  {, j6 n& l  q2 o6 E: o5 n$ G* Q. J
    10、分治' X& \- z& \/ X( U
    分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。" C3 l5 I( \7 `! s- [
    5、算法进阶
    - U! H+ Z2 h) a6 \- \算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。2 _% v% i7 S: m+ ^; Y* Y$ b
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。  u! Q6 y/ D" n& D' v' ?: |) ^
    这个系列主要分为以下几个大块内容:& i( g! K, G, O* H' A2 }/ p+ a
      1)图论
    + m- T' l6 v2 ~, }7 i8 ]3 i  2)动态规划& j" q0 X7 X4 z! T' K8 N( ?/ r
      3)计算几何
    9 k! ~0 H) F  ^* q* j1 B  4)数论$ G6 A; }/ B- `
      5)字符串匹配
    1 _8 V+ `! I; N  6)高级数据结构(课本上学不到的)
    + m, a, v6 `( f  [% B& V; C  7)杂项算法) s8 n0 \) Y, Q* m# ]6 Y
    ' V' s4 o& C/ Y9 t
    & A8 k% \0 `/ @% E! W) ]
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
    - s: e; f- q3 D2 W% b  J9 v) U4 a& L. M

    5 Y4 L3 i  D# I6 Z9 J1 V$ V
    5 W. C, x$ Y- _. V0 [

    % L3 ]( S& N' X1)图论
    : G/ G4 x$ J$ t3 `) w6 R0 }1、搜索概览) \+ Q6 X0 U/ n, L; M4 u( j
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。0 X2 K: E* c& K4 C4 t* r. h1 m
    6 P  Y% T/ S+ k4 y/ @
    : U: a2 x( K8 Z, H: L# L) v9 Y
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    : c- m2 Z3 P$ [2、深度优先搜索7 I) ?9 K1 j! m: B. X0 a. g
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;1 u* J% _2 w" z9 M0 ^4 g: c- C1 H
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。8 w; k) i! y6 |
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。( |; M1 |, K: m& i
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    8 O2 |: u/ Z5 b, `6 f
    # U. r- G' h; H7 d. E4 n; c

    ! X- P* x, e: h  \, W2 z红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:
    ' {/ F6 u9 g" ]        0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6; Y) h6 q0 X8 b" o
    16 t. j" E' Y* {. i7 z- i  f
    同样,搜索的例子还有:: m/ P, K7 Q# |- q" \- Q8 _
    " z/ B& \5 ~# G
    + @# [$ O4 h0 x) W9 L% E6 k7 h# q
    计算的是利用递归实现的 n nn 的阶乘。
    0 m6 O: x6 M/ T6 L( e9 y# @- _* Z3、记忆化搜索
    + N- o# b! D6 ^* N对于斐波那契函数的求解,如下所示:+ \) _( o: t# C# o
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    ; i1 v2 f' n( \$ B( Q3 b; A! ]' Y⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)2 I4 ^5 g) U- g; T
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    . N  g, a; d( l! W6 |- gf(n)= 5 |9 W/ a: E  t5 [

    ) h+ ?% F* e1 l0 _6 W$ c# N9 u) @
    * ~/ o$ {6 _3 [8 d% }/ _! c- p
    ( ]) Z6 M1 T  y0 f# M
    # h) L! K+ N; l
    ​        5 J3 N/ q1 ^1 R/ W) B; w# I
      1 `4 G/ s0 x8 ]  _) r: E
    1
    # u0 l, J# g& }1
    ' ^! {+ k! B+ X* q" E9 z6 H; g/ p$ |f(n−1)+f(n−2)
    8 Z  `% }3 m4 H% K; ?- i​       
    . u7 a, p( ]/ x  
    * F: F1 F, S3 d" s; M: n6 H(n=0)
    0 m2 \* c+ R& Q0 p" c$ z0 I8 m0 X(n=1)6 h2 Q$ q. C5 r7 c8 o# e
    (n>2)
    * w( ^2 {. v8 R% \9 |! C; V​       
    ) `# r, P3 b0 U, ^  }, C
    ! T. m1 c. F) S( h5 B对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    , k+ g. w$ M# s
    ) M/ B- l$ g! J. ?6 \' z

    ' f, D) y4 }% _- F: k5 j, V这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    1 ~  m. q' H; I1 M4 \% ]" ]我们通过一个动图来感受一下:. v+ ]$ O) m) A9 s/ Q

    % b, b, Y& d" Z/ u4 F7 o

    7 {( A, u. q" e0 U, r: W当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    ' f3 B( I, t) a' J% o, }0 J2 z这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    ' g$ `. |( M  a& K7 b, k+ T4、广度优先搜索
    - h3 t, s1 c$ \5 }% t单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。2 m  m: ?+ ?0 |/ |8 \
    我们通过一个动图来对广搜有一个初步的印象。
    + C; Z& n- U8 V* C) Y) e5 p
    & U) H" ^1 c3 L2 [# c: O3 R/ ]; v

    8 N9 M) b% n5 w. O3 _6 q
    3 `7 d( Y* j+ W) f1 b6 S1 A; r
      b/ m4 f1 a, B. W5 e) R  k
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。* U6 [& X! l2 H5 P
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。
    6 {9 w! ~, ~* n- L7 |. @- |这时候,算法和数据结构就完美结合了。
    9 e" `1 [7 l: }( h; `* R3 @+ A2)动态规划  U; G! |$ L; W0 ]
    动态规划算法三要素:1 ]8 \( c/ g# |$ m+ T
      ①所有不同的子问题组成的表;
    7 L( ~" i% ]! W* y) M' A3 F: |  ②解决问题的依赖关系可以看成是一个图;5 q. V3 M: ~/ _5 _2 _4 ^
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
    3 b: R* F/ y( [$ S' w$ C& P2 k% m
    % V$ Z) }6 Z# ?4 i

    0 f4 Q, S0 S) Z+ B5 O2 i如果子问题的数目为 O ( n t ) O(n^t)O(n
    5 j7 N3 Z- v) T% Q$ T- W+ Ct
    0 {1 ~  B5 T0 s. d+ X) T ),每个子问题需要用到 O ( n e ) O(n^e)O(n
    , ^4 X" f* A" m' o! g: Fe. W! g( h1 N9 D: k
    ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    $ `2 r1 Z7 Y0 r$ w# O1 Y  N0 a1、1D/1D
    . t/ q0 S+ R* t; g6 g, p& n4 T3 Wd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    ! \4 g7 C# M7 x8 ^5 ^# vd=opt(d[j]+w(j,i)∣0<=i<j)
    : ^" @6 n% P5 F# i: X) z状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):; E& e* I! P% b2 E& S/ [& d5 F+ }6 F

    9 o+ V, S+ _  ^- m% Y' B5 m: ]' R
    . f5 ~5 P; P: l# L/ M1 {9 M8 V4 _- z
    这类状态转移方程一般出现在线性模型中。
    7 q# v) _+ p  r7 z4 @4 f/ Y2、2D/0D4 i* Y7 g) a% q7 @* t6 M# p
    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} ): d# ?: F/ Z: \0 y- F
    d[j]=opt(d[i−1][j]+x 0 A7 b4 k- y. l( ^6 H  ], U/ n
    i4 _$ `$ A; g; A: M2 o; C
    ​       
    ! z: ?' r6 z4 J ,d[j−1]+y
    5 j% P+ e+ L* mj
    ) S8 R, l% c( l8 l8 ^+ t5 `3 a; |​          G* |0 g. z* B5 u$ k
    ,d[i−1][j−1]+z
    ; N5 h4 W( }6 V, @ij
    7 ^8 r3 k+ ]; a7 G2 Z​       
    / ~+ p% T! X' }$ w! ~  R. X! C& h )
    " q3 n3 t  }: a. K状态转移如图四所示:
    8 T% s" g* a5 z# R5 E+ [& T/ o6 y) r) d% L$ M

    ' J# S# T. Z% O9 f: G5 k: w  I# h- S比较经典的问题是最长公共子序列、最小编辑距离。
    6 U+ a: s: s- Y2 L6 N2 J有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列3 T/ g. g2 q3 h' v4 U
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离9 E' V6 P$ Q+ s: k7 ?. K- G. j
    3、2D/1D4 @7 X6 J5 u8 W3 q% l' s% y
    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] )* g! @, K+ ?* t5 c
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    / D# i1 l4 z6 k' Z) `) l( \区间模型常用方程,如图所示:
    - M7 i# y' d+ X% V
    4 n6 `8 o( B  N3 m; g4 ^! p0 D

    7 }4 J: G. z8 [: x另外一种常用的 2D/1D 的方程为:
    # x7 |8 A0 h9 ~$ A! s' t4 [5 nd [ 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 )! L2 T, m5 I# ]- v& O3 N* Z0 e0 K
    d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
    ( W, m/ d& S% w* f区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    & z2 d4 ?; h! O( H$ e0 l' T6 q4、2D/2D# ~( i# }& p/ q4 ^
    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)
    8 I. C- b4 G2 gd[j]=opt(d[i
    % @6 H7 L' k, ?. G1 ?7 q8 s* ^! f3 {
    ][j 9 j5 G4 }& u( h  w! t" D: `4 S

    ; K( c5 s6 j* L6 }3 A" O: H: F ]+w(i
    , G0 [7 {& M% {/ f, d& {
    ( j* J" @/ e/ ^) |) X ,j 0 W; P  s3 M: Y- v
    9 S/ [( ?3 B6 ?) E" T( f4 O, [
    ,i,j)∣0<=i $ |# b: V4 |' v; \# c* l

    . B1 K6 C6 K2 i' @( s <i,0<=j + ?6 P- ^2 z7 @! x1 E! ~
    ( C- E: s5 j# V( Z5 P, K  `7 |
    <j)/ k4 A" }3 A* T; S, B" S: u& l
    如图所示:9 k& Q3 w# f; s. n5 x1 W

    7 p: E8 g% l6 ]3 f- j: ^

    % O( z# Z: C2 O- _/ s/ `# c% l常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    - A; P4 [" \2 ^. W6 [# y: t对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    ' y$ R  B. U# N7 ^( qt+e& }3 Q+ Z+ l3 S( K! q  d& G
    ),空间复杂度是O ( n t ) O(n^t)O(n ' l- H  Y' |4 c/ I
    t3 E5 w* c6 ~* M, }  \
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。6 B1 _5 D: T% O, z
    3)计算几何
    ; r# q. a' l. z# b$ a计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。0 B; G: s& H/ b
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    " f* @5 P0 W3 q1、double 代替 float
    . [4 D& l" G: i/ m) Kc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;# `1 T7 m2 Z+ U2 ]) o
    2、浮点数判定3 T7 _' g, p' X! P) j
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    . s. n; _" s3 E0 q0 |9 O两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    6 {. Y) T% P0 h8 Q  Lconst double eps = 1e-8;( p9 H% ?0 [5 |  n
    bool EQ(double a, double b) {& s, a: V- A: q7 B; H
        return fabs(a - b) < eps;+ [: Y- Y9 ~6 V2 P& j# K4 t
    }
    ! B" _7 b2 l7 O: W11 d4 b  y) L6 o
    2
    & Z# z0 K/ l1 A; D3) a! O: ^7 P. U
    4  B8 B' u, ~3 t: H3 E: R6 v7 n" Z
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:& R! s; ]: X0 ^4 Y+ V, p. Z6 }
    int threeValue(double d) {
    1 h$ d6 {. C! E# F& V: T/ a2 m    if (fabs(d) < eps)  I4 Y, X7 G7 n) c3 v; j
            return 0;+ {  X# z; |% v, y; h
        return d > 0 ? 1 : -1;6 G* t& @6 n/ _- Z2 V( s$ h
    }
    / C: J* ]* `0 Z# q$ `1
    . `. ~  c9 m6 Q2
    # K& c% c: C, ^2 M+ A. ~( X3
    . k$ x( Q9 c& g. n% ], Q4  F+ y1 K4 Y& W0 Q" x: C1 K
    5
    9 q( v. V; F9 t( i" B4 g: T3、负零判定
    ! X# b8 J+ K9 M5 ?/ p因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:8 v" c: K% m( h7 l$ V
        double v = -0.0000000001;
    # J0 L- m6 A8 q# l6 O% D7 `  R    printf("%.2lf\n", v);, L0 K: Z& C( l) `
    1
    ; d2 E* n( J+ I" f% m2
    4 A: {: f0 l, ?1 W+ o! ^+ j避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    2 w3 V1 a3 \& `$ |3 T' Y    double v = -0.0000000001;9 K- N' [8 S1 R. k7 d
        if(threeValue(v) == 0) {
    7 \- o, Q' g1 s- ?* C$ w+ [% o        v = fabs(v);
    ! a7 x+ v. y- h! X1 K2 `' I+ E    }% D3 r) O% @8 V9 E7 c
        printf("%.2lf\n", v);2 z, @& I4 F3 d, M1 U! w7 p: j4 ]* H
    13 d. e9 y. m$ a$ `
    2+ ?$ T0 I) m5 {& a
    38 k, J! ?& a3 p0 T) h
    4& C1 g( G( V) h# L8 T
    5
    1 N/ Q" [8 i' h) U; ^+ z: t4 \0 ~4、避免三角函数、对数、开方、除法等0 H% l: a9 ~- ]! q
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    - f  ~. H6 i) V7 K除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
      K9 A6 m6 ]7 j# a2 r5、系统性的学习* q( H5 t7 P& m  J. o: s) H* w$ a
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;- v* T; @6 z8 k: a
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;. I1 i" _- F/ e  u
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。
    " ?! J* o* [3 x- `& n& T  _! N3 [
    ( `* p/ E- a3 M/ ~
    1 ]6 B+ x% c; |  n  m9 b4 }; ~
    学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
    . t5 q% O  g4 D$ ^* Y1 O) o  e4)数论7 c8 b& W4 W) ?
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    7 s- X  {0 O& t2 G$ k9 E数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    & Q* k; A7 I; F  W) y当然,数论也有简单问题,一般先做一些入门题提升信心。) |2 e% J. Q8 @7 _
    1、数论入门- q# z& M% C( }7 X& H& ?$ \; s
    主要是一些基本概念,诸如:
    ( N' q% g$ o2 Q整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;- k6 l) f0 u$ m, i! V+ L+ E" J
    2、数论四大定理
    3 `- w# T7 o. c5 n0 p这四个定理学完,可以KO很多题:3 b' v3 O+ ^4 C
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理; o! S& C: _$ t7 @$ R: I
    3、数论进阶
    " o" p9 P% X" c系统性的学习,基本也就这些内容了:
    # K7 g: a% e+ N- Z+ n2 w扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。7 ~$ }! t6 w) t/ u1 f. ]& ], h# h
    5)字符串匹配
    4 ?5 U/ v7 l. U字符串匹配学习路线比较明确。
    - i5 e% \) D" j+ z3 q先学习前缀匹配:字典树。
    7 R' c' g) R" `然后可以简单看一下回文串判定算法:Manacher。# ~" W1 P6 H7 I6 i: `2 P
    以及经典的单字符串匹配算法:KMP。
    8 c9 m: A. ^7 ^) @5 T1 |9 e9 l实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    & l2 h2 ?5 F- z" }6 l然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    9 i0 N5 z: R( t+ \. [7 L# V关于 算法学习路线 的内容到这里就结束了。
    ; W6 Z, r; z  M8 T1 q5 F. e如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。$ _! R2 |3 \8 @, `6 Q  Q$ k
    参考资料
    " x4 f# u+ I8 P# K% D4 |! G【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    ) g" R: ]% H2 a9 E7 s' \【阶段二】C语言例题:《C语言入门100例》(日更)
    9 c: @$ R. B* A5 x3 H【阶段三】算法入门题集:《LeetCode算法全集》(日更)0 Z+ y/ D. w9 P+ R2 h/ d2 b1 _
    【阶段四】算法进阶:《夜深人静写算法》(周更); l: p, q% S+ L" y9 k1 M' }+ j$ @) I
    ————————————————
    1 f' ?! C! m3 i2 a: ^) ^版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。5 z% e* @8 O! F
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/1183822283 A9 A' {& v( Q* R( V! o
    ) I: H! V( ~9 Q. O! Y. f, u5 m5 q; u

    2 _/ _, V) R7 L
    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-6-14 12:54 , Processed in 0.462471 second(s), 56 queries .

    回顶部