QQ登录

只需要一步,快速开始

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

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

    ) _$ r) q$ X& y6 d1 d0 d$ t
    / Y/ _& V; H& I6 c, b  c
    % d5 |( v, q5 P% X9 h
    ) |, a, Q# {+ o& H& i6 G
    % _" v% s% h, h6 R; ]* ^
    % M' o0 x" q( s3 C6 }. i
    - w- u- k4 ?! G& V( X3 y
      D" n* x9 |6 F5 c: i
    图片较大,文章中有拆解,需要原图可以留言找我要哈
    . k" @* w0 }$ `( ^% d! t# {' m4 ^1、基础语法学习
    & w6 u# ~) H' q) n3 @. f: w/ W& p7 Q算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。4 U$ l# g: c' u: H: l/ n! ]
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。& l0 S" ?3 q# D9 v& _0 ]- M

    : ~/ s" g& O# Z0 l
    ; B) R7 G7 l6 c1 i9 e

    3 F7 [# \! e* }+ {& P: {" i. s/ u
    3 T+ a) c3 q, @
    1)HelloWorld: K) W1 w: P/ A1 W0 F7 N' d  j
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。( @# l& A6 K2 C. K. Q
    2)让自己产生兴趣! E9 u4 ]) n, _2 P# o5 K5 |6 [: H$ v
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。  ~! H# `) M. w
    刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。8 _8 E$ M# y* F- Y' m
    3)目录是精髓
    , z8 ~8 b$ b* C然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
    , h. h6 e+ b2 L) y! B' x' h1、第一个C语言程序# O! v; l) S) J/ C
    2、搭建本地环境
    - h$ w1 B& T+ g: h% x0 q3、变量
      b8 E- g7 I) C1 S6 {% n4、标准输出8 M* C5 U- a1 P  E2 z
    5、标准输入/ U/ S: l+ {# y, a4 }
    6、进制转换入门  i( x- ?% F) b5 \  l. A" t
    7、ASCII字符
    / U3 O0 j9 P- p) l8、常量) x) U  i' S- A8 [1 |9 J9 a' L

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

    " b% }$ D- v; [/ F7 n4 X: c: ]: C* o" p9 N  r0 Y
    2 t) Q2 \% X  |& i$ m3 f
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。3 T) t$ l) F) ^+ A
    这里可以列举几个例子:
    & `! i0 D# t. ~3 `1 G: s# T! N8 n1、例题1:交换变量的值
    * _% ?0 y4 u, n+ e# I+ ]( J0 `一、题目描述. d0 R/ c$ b. R  \/ j; N
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    ) r$ D+ r* R4 ?) ?+ Q8 C/ S0 F2 E& q! S, |1 f8 F! P

    ) O" D' K' V7 _2 C& \8 I; y# N3 V$ s
    9 d8 c8 ]% q2 O$ N- q4 |
    二、解题思路
    * g! d  j  @" q, ~% \$ ?难度:🔴⚪⚪⚪⚪) v% x0 n7 g! Y# N* H

    8 z9 t2 Z" P6 s1 n$ \# |! o) x+ I

    6 R% ~+ `1 I7 k* s' a6 H3 z& n5 Q+ |这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
    1 c$ L+ \, M- d  ]# F! x9 n. q1 a& za, b = b, a
    . m: r8 m8 I% y/ W; _18 ?, a: z# w* e
    在C语言里,这个语法是错误的。* [1 R$ v6 g! i7 ]6 c
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?
    * X0 w' V3 v& m! c* s* P9 e当然是再找来一个临时杯子:( X% g! V8 |% p
      1)先把 a aa 杯子的水倒进这个临时的杯子里;% M3 a" j* H$ Z1 ~0 }
      2)再把 b bb 杯子的水倒进 a aa 杯子里;) @3 @. E' [' \: f- `
      3)最后把临时杯子里的水倒进 b bb 杯子;0 i2 n/ g5 M2 ?  y6 o8 @, q, x
    7 _3 F8 L% H& Y! M" J9 ^

    - W& S/ I6 t( `' ^这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。0 h6 J% v# k& H7 q
    % y7 b1 |0 R6 q& L( z( h  l3 P; S

    # h# |* L) n1 j三、代码详解1 ~1 u$ k5 n$ s( L
    1、正确解法1:引入临时变量# D5 R5 S3 u$ i% l* T1 S# }
    #include <stdio.h>
    6 N4 ^! x) X* |% P% {) d2 @int main() {+ v' r- p* i# u
        int a, b, tmp;0 B8 n4 c6 v* B( y4 x
            while (scanf("%d %d", &a, &b) != EOF) {% @, P: D/ o8 t, C  }" Y) z
                tmp = a;   // (1)1 ^1 {4 H$ @  w3 a- ]
                a = b;     // (2)
    6 w  ?( W7 h6 z1 N3 i" j            b = tmp;   // (3)+ l  t! b2 m0 w* ~3 \& a" x
                printf("%d %d\n", a, b);3 B* ~7 r. O8 \  q2 f$ M5 {( X
            }
    # Y& I" R- K, ^8 \        return 0;
    * r6 c. I* g9 E! p1 \  F}
    8 T; c4 V: _7 r" A6 {% u% f+ ^7 m! d1
    ' c: u) j, u$ H* w/ [. e, t2
    % _% I; I" w7 x3 Q$ o' ?/ ~, F" @+ x3
    : Q' b; z# C! ]* J4
    : U1 c# w. _' q! Y5
    ( L1 L) q5 s6 C& F+ n6
    * |) e. I. B6 h( M' f1 V7
    4 ?8 G4 e3 I8 V. B8
      T* g( F# U- T) e$ Y2 N% a9
    ' P8 f7 x0 A3 X; w1 F) Q10
    - ~. c5 \0 i- K# S. R+ T2 y110 h" M+ |6 f5 g1 B
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;. \' P! B: `; j; i- I# l/ H$ f
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;7 P5 }  a" E9 y
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;; a' m4 q* p5 I) k* e
    这三步,就实现了变量 a aa 和 b bb 的交换。
    8 w9 |" F4 o1 w" e1 U2、正确解法2:引入算术运算- s; ?$ R% k* B7 @6 G
    #include <stdio.h>& I8 x9 k$ j7 _5 C& a# L2 K' h+ z
    int main() {/ H6 O- S+ m; P6 a* [! }! U: {
        int a, b;
    9 k) Y' S( T9 M        while (scanf("%d %d", &a, &b) != EOF) {
    ( Q, T7 p5 D# t6 c& c- B" ^            a = a + b;   // (1). b* Z5 g+ w& \  [6 A
                b = a - b;   // (2)
    7 ~: F- N/ x; v            a = a - b;   // (3)
    ' a( N. y/ `( g. ]            printf("%d %d\n", a, b);2 D0 h8 q  I% f& Q
            }$ q4 ~( P" H2 j* n1 v& X# q2 x% r5 @- l
            return 0;
    / N& [3 i; b; [( f' i9 v$ }}
    3 _& E; f/ ^7 M! [* R; c1
    ! a# {+ T$ @/ f( U, I* D& v* K- v4 q2$ d4 b7 F5 y" ]2 o4 D
    3) n: [. ~1 D2 g8 i( ]& W& I
    4
    7 f7 C. ]! k: w- z# d& O% R54 R2 P( o) ~7 G4 j1 Z8 [
    61 i7 p( d$ l1 y0 ]% j
    7
    1 U" ]: J( Z" F$ }8
    7 F- s6 t1 q  R% ^$ z% N94 @. Y! h( {( U: w  J+ }
    10
    . W: m' _/ g- H+ K115 P, |2 _/ A  m1 A, b: t0 p
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;+ C% A8 i% r$ S8 O- w
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    ; g$ }! |2 T" S+ P! ^$ Z4 u  X) Z  U( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;; E+ i: C0 Q9 o& s0 L
    从而实现了变量a和b的交换。
    ' v! ~  T8 [4 |/ q  t2 {3、正确解法3:引入异或运算$ l6 o. [! T) D5 ]) ^% N( l( ~' @
    首先,介绍一下C语言中的^符号,代表的是异或。% ^* W* Z  L- L
    二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    0 t' ]2 {4 z6 v2 W/ k左操作数        右操作数        异或结果: n3 v( q2 X3 z% r' k5 Z3 i
    0        0        0
    0 {  C9 I4 O1 Y3 Q- X% e7 {1        1        03 d) L$ X8 i( M5 L
    0        1        1
    , @8 V( S/ W8 e. k# I3 l# r- H/ f1        0        1, S9 E0 [4 T* D2 N
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
    3 }; {2 U0 p3 X4 X/ O这样就有了三个比较清晰的性质:9 b* x# U; q; g. |' K
    1)两个相同的十进制数异或的结果一定位零。; b+ [! q2 \( M* O: ]4 x9 w
    2)任何一个数和 0 的异或结果一定是它本身。
    1 O  E8 l7 }" f+ d  r. i1 {3)异或运算满足结合律和交换律。
    # X& z8 ~' g; B0 b+ j7 Y#include <stdio.h>
    6 s8 t% h0 L/ Q- ]# y/ N5 @3 sint main() {
    ! O" i. {5 B0 t, a2 o4 ?7 y2 k    int a, b;
    ( z# u9 d6 A' `% a        while (scanf("%d %d", &a, &b) != EOF) {2 H$ p2 U, J/ [7 K! V8 t# Q
                a = a ^ b;   // (1)
    4 f  n( q2 m" ]! g2 {            b = a ^ b;   // (2)
    , z! x5 K, ~) U- R" y            a = a ^ b;   // (3)
    ' E2 O, e/ K/ p& C4 Z            printf("%d %d\n", a, b);) I+ @3 R) ~7 t6 m4 B
            }
      o4 X0 K6 N* P8 f# @; \8 }        return 0;
    " A  e" d/ u, ]}0 v3 B+ a% b3 N9 T" m
    1& F! ~3 |! }6 c  ]' }" h' T
    2( f( w' \' E) \8 o
    37 h; b! `9 d5 w$ u
    4
    0 ~5 n" v3 I6 A: [+ A5
    ) A( z; x: L4 D% P) X60 R! ~- k! I- ]
    70 e! Q7 Y9 E3 N" f* f" f" `  ~2 t
    8
    & n+ ?1 u1 F3 q+ A9 \9
    & V# u1 @! G( f* y5 Q10$ v+ \9 U$ g; w7 k) ~
    11/ ^: M  v% [4 [2 A% t/ I3 b' [
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
    ! F0 ^* ~7 N( a+ b8 W# J! w而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。( ?# c3 x3 ?( o7 X
    从而实现了变量a和b的交换。
    2 e% r$ F0 W7 y* Z  ^5 `  g' M' g* w1 o0 j. N

    7 ~! _& B, G, G5 n. ]* l" s: t4、正确解法4:奇淫技巧
    / x$ X! c4 a$ Q/ y: U1 V当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    8 M1 ^( K$ v# M, H* ]2 v5 }#include <stdio.h>
    * w" P5 K+ J5 h3 Aint main() {7 V$ |8 E+ T( ?, z+ M: I4 J
        int a, b;& c" h6 W5 t, x% l' \* P
            while (scanf("%d %d", &a, &b) != EOF) {  h- |/ m7 |/ ^2 x& l
                printf("%d %d\n", b, a);
    # |/ i6 c7 {# ]# _        }: p5 k7 M) v& \) G' v& ^6 W
            return 0;
    7 s3 A% E9 O0 l5 b" d% s: _}3 Y+ c0 E. |& q; p
    10 R+ u3 M9 R& z8 J5 _
    2
    4 D# R& t/ B) M: {3
    7 q9 A& c: j' v; z9 Y4$ k. j& {4 T- N$ u* |9 K* p
    5
    # C; H) t2 ]7 y7 M% K# _' l* o6
    7 m6 T7 O" h$ _6 R& l9 D7
    5 T- y- |8 W' T2 J# i! @8
    7 y+ g0 l; T# B$ h9 L你学废了吗 &#129315;?
    / a8 t  J' ]+ s7 p9 m# U. J4 g2、例题2:整数溢出3 c1 F( }" i5 H& o9 y
    一、题目描述* n( y2 W- ~% |! v, h" |
      先输入一个 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
    & `: j. Q8 V: C  H5 R. Y! a  b62
    . y/ }5 R) d: s0 F4 P5 C# |( w ),输出 a + b + c + d a+b+c+da+b+c+d 的值。6 n% M5 F+ E% Q/ `$ x0 K+ g) r

    % }$ ]6 }6 u! N% M8 x! G6 q

    " ?* Q' {0 w( f$ a. e  K二、解题思路: E+ w! o& c3 L6 z' |$ C4 c" M
    难度:&#128308;&#128308;⚪⚪⚪
    . q- F* v  q! ~) V; P- ~8 i3 @* l  x3 E0 V# x" b( A

    & [: ~% _3 D( ~% o/ a这个问题考察的是对补码的理解。
    : o6 K* |% u( {: X$ H) j9 \  x仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 ; B% m  A( Q5 y$ S/ ?0 P1 A7 k
    620 E, V3 F, D: \/ I' L3 u
    ],这四个数加起来的和最大值为 2 64 2^{64}2
    : T; W8 v( V+ J' u/ q1 d642 _' z4 b: t: K" V4 D. o1 a( d
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
    * {' C# I- o. j" j, R63. z  |; j2 {4 Z! S! Z9 ^
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 5 V% ]" a2 ^' E
    64
    2 u$ v: d$ J' g4 @ −1。8 N9 Z+ h; G/ Z- S: Y$ h& A( ?) \
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
      n" p, V5 i/ u. Y62
    $ G/ @9 f! c* {) T& r  时,结果才为 2 64 2^{64}2
    $ ?+ O4 u# W1 J  U2 M4 W* T% y64
    5 a6 g, j5 E* w6 ~ ,所以可以对这一种情况进行特殊判断,具体参考代码详解。* T" t3 }/ T# @( L/ e4 l
    三、代码详解
    / l0 [+ t7 Z  C. ]! E- |#include <stdio.h>5 ]2 v8 t! i' ^" v; q3 X
    typedef unsigned long long ull;                           // (1)
    8 l0 S4 ^  L/ Nconst ull MAX = (((ull)1)<<62);                           // (2)& I  \$ o* b, N- W4 l" `+ S$ T
    ( r, S2 a1 q0 R

    ( w, o4 f* \7 }9 mint main() {. v! s7 \* s2 U% G
            int t;
    " x  u, b# _4 S7 I7 j        ull a, b, c, d;
    , e! @3 i. o0 y        scanf("%d", &t);
    , U; g6 y/ A- [0 }* h        while (t--) {
    & L; D( j4 U5 ]0 J' Y                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)
    % q2 M/ _4 ~4 \/ f: A3 w1 P- x                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)
    " Z: ~5 G& v, \3 |3 F                        printf("18446744073709551616\n");             // (5)
    ; _' P0 Z1 `! [6 v. G# d9 ^8 H  @* F                else
    . {, n2 o$ N* W9 \                        printf("%llu\n", a + b + c + d);              // (6)
    4 K  i' n- a& j        }
    3 m( B7 L# E. {# |9 s  C1 @" }        return 0;
    . G) u7 ?7 _9 q" |" q}
    # ?/ \- S0 r$ P$ F' _( p( ]17 w1 N1 T1 ]( g$ c$ R
    2
    - a8 n, T/ |+ g, ^+ m# f* p) [3, J2 K/ R  s7 Q
    4
    0 g2 }+ @5 g% `, C& D55 G$ e) y6 J+ t3 T% g, E( Z( o* q
    67 b+ G/ }2 M% _$ L# _
    7
    + Y3 E/ P3 w  \6 n1 ~( i$ q% W8
    $ c# I0 Y" ^& ^- }. n- {& J9
    / B5 p; c8 z* }- [10/ H" I: {7 M. H9 X
    11
    ( _& P: T4 q/ o% y12
    , G9 b2 G' e( ?& q. @6 j136 U) U; g% C9 i. @  M
    14
    , t( _5 H& R  h/ g15$ B; b  N$ O6 B
    16
    1 L. \5 {! }( h17
    & X6 f& N0 h; ^- e( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;, S$ R; w& Z" u) i8 }' _3 n7 x! u
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 " M, |, {: E3 t) v" u8 F- K
    62
    0 k0 B' R9 x+ ~1 A0 `+ w  i( r. m ,这里采用左移运算符直接实现 2 22 是幂运算;5 f/ M& L' s) R" D# W0 d% Y
    数学        C语言
    & O% J3 ^/ Q* Z( t2 n 2^n2
    - _/ B. s! b+ c" q% z% G4 O9 \! on; g! H1 ~% w9 M! _
            1<<n* l1 M- A$ z  O1 X
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;( c1 S/ s5 Y- l$ I; n4 G& K& i- g
    ( 3 ) (3)(3) %llu是无符号64位整型的输入方式;: g( R! w6 I' |- L6 O5 n
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;2 \# Y  l3 T" J( k+ S5 V  G
    ( 5 ) (5)(5) 由于 2 64 2^{64}2 * Q+ z7 l1 ~5 g2 d1 L& ~- J
    64
    4 Q. Y. C5 c- O* }/ }8 {  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
    ; B2 Q7 S( A" k- ]% \8 f4 G( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    / Z, @9 [' r! r( `; N64
    1 l# U+ R2 n5 |- e −1] 范围内,直接相加输出即可。$ z8 m. @( i; L4 _
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。: `6 Z' z4 D/ q% G& Y
    为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    4 I7 X3 S3 L$ ~' s7 y. S6 S" Z; b- z; w. l' b" Y: u- }
    . r% \  u1 `/ f; N. b! \8 k! t. b
    3、数据结构
    6 L, L$ Z' l5 [. i《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    , `' F2 e8 P; X4 f, z1、什么是数据结构
    # i7 k+ `5 W: ?3 ^0 Q, h你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。: Y) x" D0 j" y  V
    如果一定要给出一个官方的解释,那么它就是:* S! u4 R) Q; f6 b" {5 T
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。* b" T2 ~9 ~7 b: y4 J8 g

    $ A5 f1 }! R, U
    6 E' K3 z9 ~% ~  A) R! S
    是不是还不如说它是堆,是栈,是队列呢?
    3 r6 r8 [  @) u% T' y6 ~8 H. k是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。' s2 V! Q' R/ k- b* \
    2、数据结构和算法的关系
    " Y1 R4 K* T+ Y+ \0 Y很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。5 o: d. A% g6 H2 G; ]
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。- S. V7 P& e% m2 A
    而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?- u$ \& q& [5 F; i$ t+ ?( n" B
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    $ f% ^. R3 x0 W% E. M3、数据结构概览
    ! ]: R- E1 I, j3 Y& Z8 n- n周末花了一个下午整理的思维导图,数据结构:
    ) [" Z# m. d) s2 n$ w  m) B4 Y
    : P! Z0 L; B. a# `* t. ]4 L5 y
    1 t1 J1 S- ]$ l" ?
    常用的一些数据结构,各自有各自的优缺点,总结如下:
    4 E6 m' T' o; z+ o" Q, t" F  za、数组, P% e/ m. a* y/ j
    内存结构:内存空间连续7 L& F. G  D& B. S# o4 T5 F
    实现难度:简单6 ~% I. V2 P5 K; G4 @
    下标访问:支持
    9 X* r- t" n+ W+ ]分类:静态数组、动态数组2 D8 G: a7 s3 }' S0 e/ Q
    插入时间复杂度:O ( n ) O(n)O(n)& w/ i5 }) p" \7 T. ^  w1 k1 t) s
    查找时间复杂度:O ( n ) O(n)O(n)1 h0 }8 R6 p3 d8 X6 C! c
    删除时间复杂度:O ( n ) O(n)O(n)
    ' E2 x  w4 G0 |5 a) C9 V% P4 `
    2 x1 T* J* M# k
    2 w& r3 N, G0 F6 j
    b、字符串4 m" V3 V; T) x  J- I/ o
    内存结构:内存空间连续,类似字符数组* F4 L" f6 }& {3 E, F8 K- z  C2 p
    实现难度:简单,一般系统会提供一些方便的字符串操作函数
    ; {) Z% X) Z- l8 A下标访问:支持
    % @( r- r! q$ J3 g2 k' O+ v插入时间复杂度:O ( n ) O(n)O(n)6 O' G& `5 |4 h1 X, Q# W9 ^. h
    查找时间复杂度:O ( n ) O(n)O(n)
    5 k6 c& J" D7 J  U3 G删除时间复杂度:O ( n ) O(n)O(n)
    + l( K1 Q# V# B# j9 n/ s) {+ V- t1 Q3 w& _" y6 s

    % T+ q% t4 T/ hc、链表" g3 S: {# {- g9 D& ~! E8 t
    内存结构:内存空间连续不连续,看具体实现
    ) U* a  B, ^1 D: j) ]实现难度:一般
    2 @% T% F7 n2 ^% Z4 O下标访问:不支持: H& {7 Y) F% U9 X6 l3 D4 i
    分类:单向链表、双向链表、循环链表、DancingLinks
    + E# Z) n% f1 M, x! Q4 O插入时间复杂度:O ( 1 ) O(1)O(1)
    0 V2 D7 q, s! X! Y: \查找时间复杂度:O ( n ) O(n)O(n)
    ! \+ s: F% z  W( S* S删除时间复杂度:O ( 1 ) O(1)O(1)1 t  o2 }! V" j; x3 g4 {4 X
    2 Z5 x0 [# ?4 F
    , K  g# q; S3 i8 M/ K
    d、哈希表
    5 c7 q+ f2 w4 h内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    : B9 K% c' [1 h& }* M0 k6 X实现难度:一般
    : \5 L1 L# o5 t" P( F0 w下标访问:不支持
    2 P1 E; J4 z+ Z! I8 b# o分类:正数哈希、字符串哈希、滚动哈希* }, D  l2 C0 `- E$ B# G" T
    插入时间复杂度:O ( 1 ) O(1)O(1)
    ' j) _  F& U' Y. ?查找时间复杂度:O ( 1 ) O(1)O(1)$ N& ^8 ~( h# J
    删除时间复杂度:O ( 1 ) O(1)O(1)
      x+ U8 m# z: m5 P" l/ \* Z/ }
    . Q" `- X# y# g5 F; s0 i0 Q* U

    % u( A- }! r8 N) H( k8 e1 Ke、队列
    , f" r2 ^1 w$ ]6 m& L内存结构:看用数组实现,还是链表实现
    : F( D( v7 P2 i5 s实现难度:一般
    + @) ]# g7 [5 g( Q下标访问:不支持
    0 m/ J( T# r( n分类:FIFO、单调队列、双端队列1 Q; `6 H& ^! {
    插入时间复杂度:O ( 1 ) O(1)O(1)
    7 D/ x1 Q. f% Y( m& r查找时间复杂度:理论上不支持/ O+ o# W+ H5 r: R8 E, E0 d+ z! @: w
    删除时间复杂度:O ( 1 ) O(1)O(1), m* ^  \- q0 S2 Z3 Q) _* g

    0 d0 ~0 T( l8 ?" x1 D: O# y9 C

    ' B& }: E! T& P# w- s/ J$ A3 P, ]f、栈
    8 Q! s' M8 M8 j- s内存结构:看用数组实现,还是链表实现
    9 X0 v1 a' Z) w实现难度:一般
    / l( r/ T8 W# ]9 n8 \下标访问:不支持
    ; r: i+ V/ R9 `0 Z分类:FILO、单调栈
    # `9 X5 y+ I1 g- H- W& D- c5 B插入时间复杂度:O ( 1 ) O(1)O(1)
    . @2 c8 {& L& \& _- N9 v查找时间复杂度:理论上不支持9 `" Y, a$ W; n- q
    删除时间复杂度:O ( 1 ) O(1)O(1)- b1 P. d% k5 [; r
    + Y. F% `! s" C2 ]6 g
    - \7 _2 j% _- Y! }, b, [/ |
    g、树% D  \% y2 s2 F! S
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续6 @- p/ n# |) Y  d7 z
    实现难度:较难
      q7 N% Q* L4 k. T' X9 ^$ }" F: H下标访问:不支持: w8 Z' Y9 J7 O' a2 g4 O" l
    分类:二叉树 和 多叉树
    0 a9 A4 j' I3 m: m7 f# h( k插入时间复杂度:看情况而定5 `1 ~( R/ C' m( r
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
    6 D+ p( J# i) g( h2
    9 u$ x7 \  W+ e7 j% }1 b& h​       
    $ H& e7 a- r8 J! G- Q8 r n)
    # B' ]% e8 G8 l; w0 Q3 p删除时间复杂度:看情况而定5 b* l4 P/ [+ c- p/ B$ R. S" L

    * j; w0 |7 x$ f
    / s( B! F" B+ U; T8 ~9 q, r' j
    1、二叉树
    ' j8 I, R' l( k- b5 L3 @二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    4 b2 Z8 k' @. j0 T其中,堆也是一种二叉树,也就是我们常说的优先队列。* V7 e5 g8 L& m; [- j
    2、多叉树
    * G4 J0 e0 i4 m3 bB树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    0 m& V: N. A! ~; o" ch、图
    * C. A2 G, o5 \. X内存结构:不一定4 e2 R& m( ?+ q9 d- \3 z
    实现难度:难2 E$ Q6 c1 K9 B2 S1 c4 m2 h/ |5 U
    下标访问:不支持
    ! l, t4 H, v( z$ R" t分类:有向图、无向图, S9 ~3 _# U: n+ H* r) H( _
    插入时间复杂度:根据算法而定. M9 E* {3 _. x$ L
    查找时间复杂度:根据算法而定3 i4 O1 N' p8 A& j& d' c. x
    删除时间复杂度:根据算法而定5 Y0 s0 X: Z$ o. [7 E% V" v6 ~; L7 Y
    1 z  c7 m5 c9 V+ d

    2 e7 ]- H6 W6 K3 a) B: ~7 O, @1、图的概念; a0 B' o' X! g0 h+ h
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    ; _# j, T$ ?) K2 L: H' P图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。: T. e  h2 s( h7 M" t2 c3 p# z
    对于无权图,边由二元组 ( 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 为权值,可以是任意类型。% [; w1 ^: P# W0 h, Z
    图分为有向图和无向图,对于有向图, ( 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;5 E  w7 ?( U1 _4 i7 @/ ]6 ?: h  F
    2、图的存储
    2 G! S8 u9 p0 `  [& u/ ^对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。5 w* B0 a3 o8 F: i# Z, j# O1 O
    % T7 V  E& V( r3 [0 D- O

    + O7 K8 e  j" g! a* ]& }1)邻接矩阵
    & j) x* x# q$ H# @- `" U邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。3 M1 o) r4 m) ^! ^# S
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。: k, `" M- @, s) z1 @) S7 M
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[
    5 T* B8 i% g, u& h% c8 }6 w01∞9∞0∞8320∞∞∞30
    ' U+ Z. c0 A2 o+ i  A6 {: r0∞3∞102∞∞∞0398∞00 W! A7 K. x/ p- U
    \right]
    " D: x; g. N5 x2 I) u* Y5 t% [. I6 C1 N

    . z3 l; S# R1 X1 ^' Y" F+ `: w6 @; Q
    2 z- W+ |2 {2 N+ d4 J
    ( u; q# `) D* T) [​       
    3 H& A& h. g0 F5 d  u$ z7 j  6 I1 a* [) p1 ]+ B& `- u$ Q/ u
    0
    0 V" P& N+ N8 G" u1
    & ?7 S, ]! Y7 _' G6 j+ `2 Z+ Z& i7 J; v: \6 Z9 ~6 S
    9
    9 h# |7 F: t! S, p; T/ J' o4 {​        2 s) T& \3 H4 n  k% w2 o* E8 I
      
    2 A# C% `* {: r1 [$ P- H6 L: f
    * V$ R$ Y7 T6 d4 @, O6 A0
    5 |0 o0 Q& N5 H2 I3 o
    " _+ T3 L' r6 G# O* V8
    4 G8 n3 A6 h% J​        5 Q  @. g- I- v+ S
      ) ]& k: t0 F/ A4 n
    3
    0 u1 a: P  s3 n0 t  |2
    : X5 }0 b; |# |0; p& Y- O5 a/ d6 N  ?

    ' `& j. N2 b3 _9 @​       
    0 ~6 ]3 A6 p2 }) o  
    0 ^# t* Y2 E/ a5 X- v$ U/ z7 i# Y. E2 \9 t( P6 P

    * R7 _( d1 h) E9 a3' o5 u% W7 ^' A, o0 v) e4 G; `
    0" i0 O+ l  i% V" |& l
    ​        3 w' ^8 D8 [0 j8 g) F! i: H8 _
      - d2 g9 ]5 a4 z, ?) `4 P

    % n' m3 ?1 p0 K" ^# H
    - ^+ F! k3 i9 |! l+ `2 F3 m0 ?
    5 v. ]4 R/ R, e2 x2 O6 C8 b2 y8 O
    6 r) a" e( J0 l& }* G​       
    / r6 A6 ^- n( p/ p' s) ]$ z
    / j1 b5 w8 ~4 w% K2)邻接表
    9 U* Y# T( J8 T1 s, }0 n- N" t2 V3 T" s邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    + T5 J" R7 a" S; D+ y# o6 Q4 U6 A它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。; ?0 Y2 C; o( J
    如图所示,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) 二元组。
    7 Q$ k/ g7 s; p" E- V
    8 E+ B# k* k7 F+ O$ d

    8 t' t4 I9 f1 \* y6 m" \在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    : w7 ^7 D3 S, R* ^5 s( X9 Z    vector<Edge> edges[maxn];
    ; r" I3 t; E3 C6 w% u1
    2 b0 X- d" |$ L3)前向星9 ?5 N0 X0 m2 Q: f
    前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。7 ^  E0 P& G  ~7 K$ _( L( v
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    ( ~; D" F6 j/ ]8 _! O如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。+ H! K- B4 X7 N# o6 P; \
    ( N& T& i" n8 B' d' K& c

    & S8 H2 ^# ?$ N( m那么用哪种数据结构才能满足所有图的需求呢?
    4 J. W; V! w1 }2 {' q2 d8 k接下来介绍一种新的数据结构 —— 链式前向星。' r: Y- k& J* S+ @/ Z' z: ~8 `
    4)链式前向星5 y) M7 `  W! x/ ?0 P/ _; t7 g
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。
    , U8 ~! m$ B) _7 |# X: c具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
    4 |/ l( T, j# V; l; I边的结构体声明如下:- |3 D- Y& ?( B4 T6 n
    struct Edge {, z& W- l% b* b1 r
        int u, v, w, next;
    * d; |; ~8 \, |) ^' K, y# s8 i/ A9 Z    Edge() {}/ ?1 y: t: K- Q* j( h# F$ m
        Edge(int _u, int _v, int _w, int _next) :9 {0 ^  x& t/ A. `
            u(_u), v(_v), w(_w), next(_next) " y- ?! N' ?+ ~1 G4 x; g1 _
        {
    6 z6 q0 g" G1 y' B$ T# K    }
    2 V6 U5 l, S: A6 z; e: H* a9 _$ X}edge[maxm];( c8 {2 }- X2 B9 l' z
    1
    2 _  r+ q; n% d: o& T8 X5 K2# ], l! J) c4 h0 {7 v
    3
    # }7 M. D2 q" W4
    1 n  U& ^7 c; P' P" n5. j% Z0 I5 E- \" g. z
    6
    3 L% i8 f3 r5 X5 M% a- T7
    ; |7 z: O' ]# C% Y4 o( \% ~80 u/ l% _8 i8 G  I& d
    初始化所有的head = -1,当前边总数 edgeCount = 0;- q  k* U9 [2 E
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:4 Y0 f2 [9 _& o6 e' j, D
    void addEdge(int u, int v, int w) {
    3 W5 X* e" J/ M3 M4 c  ^    edge[edgeCount] = Edge(u, v, w, head);
    4 I) D+ Q! J0 b6 R' z, R    head = edgeCount++;
    , i7 h) z5 q9 K: I$ o: [7 k}
    ; h( U+ c$ W8 n: k1
    9 d3 |8 K: u6 F. w+ `2) g" P2 F4 M/ b* _
    3
    ' j$ E% c$ K- N) C8 ?4# {7 H% G; H8 i4 s: @3 L
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    * Y0 L* V5 A& B/ l+ _6 K! A: O  c7 G调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。0 {9 M" |" i8 W8 u2 M+ `5 z
    for (int e = head; ~e; e = edges[e].next) {$ `+ D8 [0 J* ]1 y* w
        int v = edges[e].v;( e; p. X) y( [7 s9 J
        ValueType w = edges[e].w;2 ^4 d2 ?0 g2 t# R; f% b1 |
        ...
    % Y, K0 l. z; o! i4 a}: J" [% r8 i0 z
    1
    5 h8 i' `" Z( R9 D0 Q2 n2' j; i* W5 L( [1 J
    3
    # |: z7 X. T* J4
    * M/ U3 Q0 x; w3 V  t0 N/ m58 ?$ M6 V& `; M* I
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
    0 o7 G3 a6 f2 H; e& L& \4 `; Q* }4、算法入门$ G! M8 O) K6 Y0 y6 E* _
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    8 n2 P; R* P6 }# m3 j, k8 Z% o
    - L+ S) l9 ?$ ~
    , p9 L! u! T- G$ s
    入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    6 f# _3 H" z# C3 Q5 m$ N3 g2 ]对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。3 s/ K2 O' S8 h; c! x: d; ]3 d
    1、枚举; s8 H- n5 f5 J* W; v0 m6 w
    枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    , V" p: v$ v! }) R$ _+ h1 O) Z对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
      g, I" H( G5 |) D6 G2、排序
    3 G$ B! B3 O7 |1 Z既然是入门,千万不要去看快排、希尔排序这种冷门排序。+ ]2 _4 P( @4 K" F! |  E, U
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。) C! J* ^$ C+ `
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。4 W1 r8 z! D7 C6 e
    3、模拟
    5 d8 c5 k6 q' y& C模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。- [* |( s7 j, a
    不管时间复杂度 和 空间复杂度,放手去做!
    : ~. _( m2 S! A9 T1 H但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    ( ?8 ]! v* f) B1 [4、二分: n* w  L$ U) n' k
    二分一般指二分查找,当然有时候也指代二分枚举。: ]+ W6 `* m2 m  v7 L
    例如,在一个有序数组中查找值,我们一般这个干:3 {  R& V- |" s$ R. M
    1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;8 a, m2 d: h( s% m. U7 Z9 M- D
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    ) s& [( h6 a% ~6 H: t* T) {  2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    - N0 [+ k4 u7 U0 [* i  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;. f( J% A2 a' o  p
      2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    2 o/ E* f) H) w$ m3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    " ?# w% w, {5 F- [7 Z9 m% s- Z5、双指针
    $ C; w( V8 K: s9 M7 {' _双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。6 Y8 v+ l! U) S
    0 F8 I+ m4 F! a0 D+ |; t- U3 ?7 k
    9 f0 O" O& c! F* e% j
    6、差分法+ l9 z2 d1 Y" i9 `
    差分法一般配合前缀和。
    4 U. \' R  {" X1 s+ G对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;3 ~- y( p# R: v4 A* Q% s+ v
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    % h) q7 T8 C6 D% ]* r2 A7 P+ \x
    ! n8 P# P5 S& X- ^  T9 b) s​        8 w8 U+ d: ~* E( x* e# e) \4 B
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g 6 z" C: M3 p- ^+ \4 U7 l; L9 P) I
    r
    + B% q- l' H. j) i+ _6 [​       
    0 f3 i" n/ T- T& o4 p- q −g
    . ?5 e( R, n( S' L; X' i4 i0 Y8 Yl−1& Z3 l/ l/ C+ `- Z. g
    ​       
    2 [" U: q& i: c3 o ;分别用同样的方法求出 g r g_rg 4 E7 s, @; l" r' D9 B/ q
    r0 r1 i+ E/ A: s* c% r
    ​        ; w; [7 Y$ h1 }
      和 g l − 1 g_{l-1}g 2 i, T! p* ^1 x- L
    l−18 ^  U) \$ T% y; F' T1 V7 W
    ​        . J- p3 j- @! `! n4 p
    ,再相减即可;
    * C8 Y! C5 u' L7 V
    : [3 k7 R& I& K8 N: s& n+ ?) a% r3 G

    $ H5 ]0 Z; w$ d! l9 L. W" ~7、位运算
    ; L3 F. c& @0 t; d0 n位运算可以理解成对二进制数字上的每一个位进行操作的运算。2 M* K" z* ~5 Q5 C
    位运算分为 布尔位运算符 和 移位位运算符。, \) _* U0 M8 H, a! F
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
      i& g# C% k1 y& Y# M如图所示:* E6 u# T! V$ ^5 i7 V5 B

    # P+ {7 K6 }" g' \/ G

    6 f( t( v$ b0 Y( ?位运算的特点是语句短,但是可以干大事!$ p' c. y3 f; _' S
    比如,请用一句话来判断一个数是否是2的幂,代码如下:
    : a# ^$ _% e6 Y4 k3 ?) d, a!(x & (x - 1))- Y  {# @1 C' |" U- Z
    1
    # T' n! f7 Z$ G8、贪心* r. T  Y3 Q4 N9 D( ?6 i* X
    贪心,一般就是按照当前最优解,去推算全局最优解。- k2 V6 g4 `$ {& R6 n
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    $ |2 l/ `" }! s1 n( j& p- g9、迭代+ Z8 X5 ~3 K, ~! h& o
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。* \! f; k& B+ ~$ f9 C4 U
    10、分治
    / a9 Y3 B2 C, y; _/ W分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。; B- e5 H" U. X- K& ~
    5、算法进阶7 F! q2 ^& F9 s! `# y2 x: e( K
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。8 P' P5 n: t) r8 X6 q
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。( j5 p+ X, u8 i! V7 c" i
    这个系列主要分为以下几个大块内容:& }) u1 S  |+ t: T+ I( t
      1)图论3 t& Z) E5 W8 u* ]5 p3 E
      2)动态规划5 h4 {) T8 ~( m$ x1 f
      3)计算几何
    ! D4 k" a& Y& m) B- Z  4)数论# E4 n& q; y: a0 S2 f% R
      5)字符串匹配1 M9 U- k2 {8 E) {3 P& ~' g
      6)高级数据结构(课本上学不到的)
    ( b6 Q- Q3 ?$ R) T1 }( s  7)杂项算法+ z9 f# O" D- r, ]$ [
    2 w1 e. G- a1 _- W+ C6 a, q" h
    / Q: P; \9 K0 C1 s! N
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
    9 T7 V  U3 ]% Y% L# G/ c
    5 [; x0 H! D0 z! V

    + z  _/ e5 [4 b# v0 n. D, K% V' P. o7 t" E" O! c! q  v2 ~, N

    - Z6 v5 _( E5 V" {( U9 V1)图论
    " g; p, ]1 a7 L7 I3 A, P; N1、搜索概览
    + e+ e% I( J8 E6 z0 _/ b$ J图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。* k! s) o9 q0 V5 Y' `
    3 v8 s/ P+ E4 k$ i' x: H, t
    0 O% H8 J3 D- Q
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    0 c$ o# u5 O4 @3 x# ?! W- Y6 r7 C2、深度优先搜索5 K$ ?/ b8 K' N
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    4 L8 \6 l. J- }% \* f原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。; X$ h$ l. N& j
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
    ! F- L1 D3 a! [3 I1 c8 B9 g& z如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    ( p+ `( U( W7 Q. C( n3 ^" |. x+ T. X. U* I0 Q: U0 f8 D
    7 E- ~4 r' J9 ?+ d/ ~. i
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:
    8 f# t( U8 V$ Q' A* Z4 @2 q' E        0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    " L" s: v( e& s$ T/ |. v* a; z1
    ; F0 N  Z) @5 j" N7 q8 k同样,搜索的例子还有:: k5 ]5 H! f6 K: \
    7 @. H/ |8 `. i9 I
    0 {' K" r. r* B- F! T
    计算的是利用递归实现的 n nn 的阶乘。
    2 g7 |5 J: f9 H% c3、记忆化搜索: X( ]6 x" T0 d7 H) ?
    对于斐波那契函数的求解,如下所示:2 {, @* A1 B0 C
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    ! \8 n4 `# i# O9 V' Y⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)8 q; a, J7 [$ O4 y$ A
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    ) m! q; }* v' x3 B: L6 tf(n)=
    ! I! Z5 {: i0 Y0 r7 U& h% |" D$ M! n
    ! e$ ?) c( j3 d6 S+ a% M5 R7 X: ~/ b' R' S# y1 L

    + G) F0 x& V2 d  ~+ V9 ]8 Z* V1 u0 ~% q- B; d1 I. R9 c

    * k8 b3 @' y4 [( b; H; D​       
    5 A+ s& j( r/ C9 e  
    ; Q* W, m" i( F' K7 V0 D14 ?) g% Q. I7 Z2 l
    1: s4 t$ B" M3 q( f6 q2 {) J
    f(n−1)+f(n−2); R3 [3 b7 _( t8 ~
    ​       
    6 ?( D: ?; \8 n( l/ @  " Q% f9 e) H  h, A3 q( T& z7 e
    (n=0)' _1 w2 B/ f8 q9 g1 x
    (n=1)* U. ?1 t. r( o2 j" \
    (n>2)
    * B/ x- b/ {4 U0 C​        % N! d* L5 _* y! V

    . x5 F0 ?( ~$ `7 z: R9 j对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    8 B& ?  V+ v& t* c2 s, F; L0 D. m
    ( a7 S2 i! l. B8 T
    ' U) X+ a8 b. x, M- j2 O: C* B! Z
    这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    / H. R* ?/ Q: o) @5 _6 I3 {7 B; K我们通过一个动图来感受一下:/ n0 l* `, L& A4 M

    0 ?. R7 q' u* I0 U) k
    4 z& a% V4 Z, E1 E3 g1 T; |+ s; G+ Y2 M
    当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。/ [7 W) k9 k# D1 W* U" U
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。2 y" q- w, X% d9 h$ s1 q
    4、广度优先搜索; }" T/ H# H% v( Z' {
    单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
    & g, b2 W# S$ q* O! U* f我们通过一个动图来对广搜有一个初步的印象。, F  d6 y( `5 j4 H  W# {; }( c
    + L4 R8 J  o( [5 n2 s

    7 C  V- ^" t8 O% s. ~+ L# a3 @5 b$ g; w2 ?" E
    1 J' e$ Q; r! ^+ b" |0 d
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。0 b& r$ z- l% o6 ^
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。) T' P5 P& T: I0 z; m. [5 D
    这时候,算法和数据结构就完美结合了。
    3 w& I; I( O1 i8 n2)动态规划% Q; N5 C) D$ p) R6 E' i
    动态规划算法三要素:" F; [% a! D- |; Z& N' M$ k) U, d1 K. G
      ①所有不同的子问题组成的表;) s  w  w7 }- q7 k
      ②解决问题的依赖关系可以看成是一个图;
    + F( j# Z8 _; ?/ Q# D9 e/ }  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);" {" E  Q5 q, \8 g- P
    " x  H: O# `& H; z0 f  u6 l

    0 m8 l& v4 {( F) p7 I2 w' l( I如果子问题的数目为 O ( n t ) O(n^t)O(n
    5 g5 J+ m+ o) B# x; \t% m3 m% k3 G+ A1 ?
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n , v" b, Q, b3 u+ {
    e
    ' p2 r! H7 J2 j6 \ ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。  |' M% q9 C2 N1 T9 v) f, I
    1、1D/1D
    * w0 |0 J7 I6 O$ ld [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    + B4 G% ?1 J; Q. K$ n. C. B$ Sd=opt(d[j]+w(j,i)∣0<=i<j). }6 L- c9 O& \) C* R; J: D; N
    状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    $ }! z2 Q; A9 g4 o3 p& i! I+ ]# X3 t) i5 Q7 I6 G5 ^
    ; L6 d/ B$ g8 Q; J6 p9 `+ U6 j  E
    这类状态转移方程一般出现在线性模型中。5 y0 S' p% b3 H
    2、2D/0D) E4 f) ?* B+ i1 ?4 _
    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} )
    0 E4 X" Y. C0 L  `. S, b6 Md[j]=opt(d[i−1][j]+x # \) l0 U8 `5 T+ a( P
    i
    * Z. n6 W; w: b8 y+ ^! |​        , e+ i' O% W; [/ p1 q
    ,d[j−1]+y
    + @; R; g% }& H' x9 Aj6 T  v/ x' o, g# h4 m" F
    ​       
    ! o% d8 I9 B7 Q) N! | ,d[i−1][j−1]+z
      X3 \9 Z* D3 Dij+ e8 N2 w) L8 B5 {' C* H( {; i
    ​        7 w  m+ o- O- |
    )
    - E. i6 y0 D* q+ `* m状态转移如图四所示:& w& J8 p; G; q) I) @

    # k, s8 @; P' ~

    ; Z% Y- Q1 l7 A- r+ H" y/ e$ w比较经典的问题是最长公共子序列、最小编辑距离。
    : H" u/ M8 w! C- B  o# P有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列3 Z( H& }3 _, W9 g1 D
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    * k. ^, S  }8 [: }9 }! l3 \/ n" l3、2D/1D
    * f0 `, y' C, C' b% c4 I$ jd [ 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] )5 b# R# t3 w! W" c1 z6 X5 Q& y- b( \
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])
    $ ^4 h% R# J+ O4 }$ G区间模型常用方程,如图所示:
    # y4 P" _: P2 I( r6 R: f! p" l5 i
    " u! k2 a* y3 o' `5 D
    8 F9 n' k6 J5 N& G
    另外一种常用的 2D/1D 的方程为:* P, `3 u6 J3 a9 E6 D; W* E7 F
    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 )
    % K, p% {+ X/ i$ m: {& E5 \  pd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)  W7 i! P( c0 \
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    % T! L9 k9 Z5 ~/ x* x: p4、2D/2D
    / V5 o8 p8 ^% q+ {4 @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)
    3 m5 ^+ T) w- O" z% pd[j]=opt(d[i 9 ?' O0 r4 k# i+ G% N
    & ]2 N* b  s; Y
    ][j
    ! M- {# E& c- ?# u9 ~3 g1 o: n; K- W, |- Y7 P, K
    ]+w(i ; F6 p3 C  Y0 W
    3 Y9 m  v3 A6 N
    ,j
    - T2 M3 N% }9 [9 u2 L) Y6 x4 S+ ^# S) B
    ,i,j)∣0<=i
    $ m3 x4 F$ m) D; |5 s
    # e8 N, q( S7 X+ Z+ t  A) d <i,0<=j , u/ Y7 i: R% S( Y: Y: s

    ) J* Q5 K/ H% z9 M! V4 O0 | <j)
    * J( z" _8 I4 G6 ~: ~3 Y如图所示:
    7 @3 J# E# v5 H1 ]8 x* z3 g. h; C$ w8 \, t& L$ {& k

    . B1 @" A( t5 U5 Q常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。8 s9 _* H/ Z8 b& @% A+ p
    对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    ) r: g  ?+ Y0 A. i4 C* H3 Bt+e
    0 d9 J7 u6 J0 }6 n) V2 ?/ ` ),空间复杂度是O ( n t ) O(n^t)O(n
    . h; q0 i5 X/ s$ d1 q% |+ o% Ut5 q4 m- A& z7 U' F, Y) N$ r
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。, A7 \! ^+ G$ N3 J2 F7 R( P
    3)计算几何
    # p1 y7 [" d, L; U+ N- T计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    ' {5 p- X) z2 K# M1 ]- o7 G3 [如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    ( T- V# N* C2 J+ c1、double 代替 float! p8 t- }4 s5 H5 c
    c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;. t$ j/ {+ r5 Z$ ^
    2、浮点数判定
    ) P# y& Z& ^% [, ?由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);. n0 R0 ~" X! a+ i
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    : z# t* Z  P% t3 a+ W4 wconst double eps = 1e-8;
    + U6 }; f  K/ ~) [: Lbool EQ(double a, double b) {
    2 g% }6 _9 _3 C' x( Q, B' R    return fabs(a - b) < eps;
    3 k8 a  T6 i4 P7 F}
    / ?- u$ u# d" S7 E1
    1 f0 ?( Y8 S- U6 H2
    8 [6 C, G0 s- O# g* E3
    9 b8 U1 n( m1 {8 _& c5 `2 w4  h9 v/ e; d' y6 I) c0 b) P7 _3 x
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:+ ]9 ]8 _) P1 v' a
    int threeValue(double d) {
    5 e1 N( j" V4 T  y  q    if (fabs(d) < eps)
    0 r* p8 S# z9 S        return 0;; S# d4 P; c6 c3 Y# h
        return d > 0 ? 1 : -1;
    $ [9 I7 g5 o' a}
    % o( O+ C! b) a5 w2 U  @3 k) {1
    * E7 |+ A# f. J2 J- B# T2
    - P, U! y( q# {3
    , c/ r9 |5 [0 ~4
    + c1 n# p) q: T' I% S  }5
    % z3 x* X+ l+ K# k( p: V. @. `' w3、负零判定
    $ h9 P+ L. d0 e1 h. @2 x8 `因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:+ D, j) u7 c0 D, u
        double v = -0.0000000001;- p+ X4 z( e2 x- j7 m/ N) j
        printf("%.2lf\n", v);
    ! F& Q4 G+ P" S. q( v12 B' f7 C: n  m0 h8 Z, T% I
    2
    * r5 s; X4 x3 `, X- I9 }避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:9 E: {- t. v. u8 K+ x" _. }# N
        double v = -0.0000000001;
    . Y+ D& W4 A) Z) [    if(threeValue(v) == 0) {+ j; \  `& s6 y6 ^; q4 v& e
            v = fabs(v);) {6 R6 F, n0 k
        }
    7 p: K  A! W7 G1 C& M! M! k    printf("%.2lf\n", v);# p' r9 m" T) p. q% B4 [: C
    1* H9 \3 }1 q+ `
    2
    - F: h! `. a3 x38 R0 U0 @+ g2 A5 ?: h8 Q
    4: ^( @% d7 R' w$ W4 r) x4 Y& Q3 N
    5% L( Q3 y9 A4 `% g% A* Q
    4、避免三角函数、对数、开方、除法等
    + D0 @/ s' g5 g% Oc++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    ! g8 ^) n- D3 ]( {; `除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
    2 B* R5 M2 p" _$ U1 D6 i5、系统性的学习3 Z2 ^, J& f: b, I1 t
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
    ) o+ t- H6 ]: f( g  ^7 J进阶知识:多边形面积、凸多边形判定、点在多边形内判定;* `6 m. ^" n  f2 [# l$ x2 [
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。% u  X) d7 E6 c; M+ O' W1 P
    ( j6 ?: G7 T3 Y( o, J$ H) w7 ^* q
    $ s9 z2 z* r! o9 Z  D- ^8 x
    学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。9 ~+ N, j& C$ Z5 j) P+ B
    4)数论5 _6 s8 F5 g- v. ?$ X4 F
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    3 G# i& W- X  W) }1 l数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。& J. J. W  I! w5 i3 ^
    当然,数论也有简单问题,一般先做一些入门题提升信心。
    6 \- g% o3 y. s' ?7 Q0 `1、数论入门2 T! B# S2 W% a# d0 u# O& x( o: C
    主要是一些基本概念,诸如:
    3 v9 a9 Y: Z" V" p0 `整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;' b5 C6 `& r3 c9 [0 g
    2、数论四大定理( J- n# j. W2 I* [5 k/ c
    这四个定理学完,可以KO很多题:
    , ]. C0 @5 H( v- ^6 D  o欧拉定理、中国剩余定理、费马小定理、威尔逊定理
    0 |; B5 T  f  b; y( _$ ^" B& _3、数论进阶; {: [/ M0 y7 g" g
    系统性的学习,基本也就这些内容了:
    # `! D4 ^5 E3 {" `扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。! }. c" o2 K! }9 M  L; v
    5)字符串匹配3 ~9 V3 H2 [( j! k% z! u
    字符串匹配学习路线比较明确。
      {8 f! j3 z+ J- T1 f. D先学习前缀匹配:字典树。  b5 n) a/ e: ?( n% d
    然后可以简单看一下回文串判定算法:Manacher。
    ; D" u3 p) [% I7 U& h以及经典的单字符串匹配算法:KMP。
    - R  V; h5 H: f+ y4 E# ]  c, y* Q5 r实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    / q; |6 {. j7 I4 W; X  {然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    & J' M% a# f( g6 ~, L. V关于 算法学习路线 的内容到这里就结束了。
    6 u6 M$ L, E' Y如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。& k* s6 o# m, \5 U
    参考资料
    " V0 R+ ?1 q) ~" M【阶段一】C语言学习资料:《光天化日学C语言》(日更)* P! p1 r: N; N+ ~
    【阶段二】C语言例题:《C语言入门100例》(日更)
    ; h- k/ n9 @  y; `【阶段三】算法入门题集:《LeetCode算法全集》(日更)
      e+ T. o5 @" p! @  M* ]# q- b【阶段四】算法进阶:《夜深人静写算法》(周更)3 h4 C+ h0 t+ `1 b" f
    ————————————————8 X( W7 F2 g  g2 Z" u3 b
    版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。+ z, E% `( h8 E( M8 x* J! V- `
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228
    2 j" [" b3 y& K, o1 r' N; k7 i% G1 q* F& c3 t) _
    & j# M! X/ v$ @+ Q
    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 17:20 , Processed in 0.526058 second(s), 56 queries .

    回顶部