QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4380|回复: 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
    : Y2 {% c7 I2 n9 Y
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
    ! [( I1 [* ?/ z8 s6 A. O' x$ e0 J
    前言
    - @5 U+ i( U- J* M, u7 {3 u1 ?+ R9 f  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。# Q* H* \  m; [3 f* c# c8 J1 h  Y$ E
      希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    8 c& y% l: z" U, R! s  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。7 v8 N! T! z# Q! U1 a+ X
      每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。. w; B! c8 X  o4 W' b1 T
    6 ?) b; {( p% r$ Q
    ' X7 O$ n9 r# K/ B8 i4 t$ n; Y/ U

    ! s' \( p& q! h3 S" q

    5 H! _: G( G; G) H( x
    / o) c4 j7 G/ t4 Z% i0 L: _
    . [2 Q' v  Z% [, f6 l
    , S3 w+ |- C" _2 a2 t6 u
    3 B% z( E$ j/ H3 U6 \% ?+ N
    图片较大,文章中有拆解,需要原图可以留言找我要哈
      r' v, w0 w1 C6 \' \5 H1、基础语法学习
    % \  O5 Y# ?7 D! {算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。% o7 }5 E# `; z# Z! J' N
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    6 G3 D0 b2 q* }0 A" G& b$ @+ d" R! ~4 b; H# m  t$ w# P
    ; p- ]6 r5 U6 E* d6 `, b5 h' x2 C
    : I( A* i( [8 ?: F9 U" H3 W
    5 d1 ?) k2 I8 s/ `! a# C! }& A) I
    1)HelloWorld
    % T- T# G* t7 U3 m8 S9 Y, ^无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。0 F! o6 h  c: }
    2)让自己产生兴趣
    & S( Z1 g- b! p  \% R所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    ) T8 Q/ c5 ?2 O. x0 @! a+ S7 O刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。3 k2 @7 O, ^% u" R. _
    3)目录是精髓" D  z6 O" O; L: N- j5 M% e+ L5 c7 n
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:# b# d  ?. I; ^: E
    1、第一个C语言程序
      M5 B  u: e, N6 e* w! X4 ]2、搭建本地环境) l$ j, D5 D7 g+ _
    3、变量5 w& k) c$ j7 [1 P: ?
    4、标准输出
    . h% a. D- i# Q% l, n0 l5、标准输入' W$ {1 q, p, U0 H3 e* ]6 E# A
    6、进制转换入门+ a$ B7 H2 {) X: \& V) D% f" K
    7、ASCII字符
    , C9 U3 l5 N7 d8、常量
    9 @6 V0 ~& x7 I8 u/ G) s7 K( C/ r. j9 w5 A  o
    5 C5 q# [: H2 A& C! |8 v
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。& Q# d8 r, h8 i7 z' v
    4)习惯思考并爱上它
    8 I8 E6 s7 o3 d9 n只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
    4 v  y7 k8 ]- g& E就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。( W. ~0 i1 ]6 p( B. x4 W, ?4 u
    5)实践是检验真理的唯一标准
    0 u" g: _7 d3 k5 k/ @) b) {光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。( R7 m; f5 F% q) W2 r& p' s
    所以,记得多写代码实践哟 (^U^)ノ~YO
    / z+ C7 G. `) q3 n6)坚持其实并没有那么难9 K7 p% M3 v8 s- ~( p4 Q
    每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    8 _4 }; d7 D, i, Y  \; e7)适当给予正反馈" o- ~: x" x) [- w
    然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    # e' g5 r8 ^1 i& W0 Z当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。  t8 h# M. f# {, F$ m
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。, Y8 W6 ]) i$ M- v, ~( w
    8)学习需要有仪式感
    % C1 Z6 z' p' r. f9 ~0 y那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!& \/ ]% j) [* C8 j2 d
    介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!
    & t/ _" `! C# E  _" Z我也很希望大家的学习速度能够超越我的更新速度。: r& w7 W) ^7 Z* R3 r* z  ~% d- V. C
    2、语法配套练习5 z; W& A) z' }/ {* n6 b. L
    学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。! S; P: r! G0 K9 w4 V6 d- D/ a
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:) S7 r. [. C. q7 y1 S" e  T( V

    + c& N& |4 h& s% v6 R! V1 L9 ^! O$ m

    & N; S( e8 r: e8 f7 M' S+ S& c
    5 h8 G# R; o. q1 N+ O7 H: t
    ' {  s" Z4 M; k$ K6 K1 @5 q" ~
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。5 F( m. u, Z/ U  H' g: Q( |
    这里可以列举几个例子:. ?) H! G0 F8 a' P# _2 M" C
    1、例题1:交换变量的值
    ( q4 H+ u  x! T. h8 B+ y. b5 P一、题目描述
    4 {$ Z4 w+ x: _, W; c/ A* o1 r  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    6 y: |  B* B/ {4 K; C$ S, X8 V$ B% R, d' o, c) u

    2 ^7 D: e# a& x3 q2 d- i+ t/ p: D  ?' x
    ! t3 m' |0 `4 o) ^+ w' K) g6 z
    二、解题思路. w0 G" a3 o5 G1 G
    难度:🔴⚪⚪⚪⚪4 d, z5 v$ _6 r! F6 c) J
    5 V: P8 \: e" T+ J* v% m6 N$ F
    ! M! ?8 N+ s2 ^2 o7 S( |, b9 h
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
    " e$ W" ?$ t% s8 P5 r/ }, F1 ra, b = b, a' K2 |% E2 |& ]% ?5 p- d4 c
    1" c9 `) p4 K; s& d% ^, }% \
    在C语言里,这个语法是错误的。. r3 L  \$ Y8 a* W% _7 l
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?  j# E; K0 p$ O: ]8 ~  Y3 s4 {
    当然是再找来一个临时杯子:
    . l8 Y4 B7 H/ E, a1 M  1)先把 a aa 杯子的水倒进这个临时的杯子里;; ~9 X. e( P- H
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    : f; H7 g0 P- [  3)最后把临时杯子里的水倒进 b bb 杯子;/ p) R8 M- I1 T& Q. b

    7 t% z' k$ v  P- V  C0 m% _& ~

    * s+ T" m! M' Z这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。/ t9 m. V4 c7 E5 Y( w. k( Z
    ' V/ ~0 ^. g% Q& u

    3 q: g+ }- {  ]/ i# j6 |4 o& [三、代码详解
    * U: C+ o' ]% n) y$ g1、正确解法1:引入临时变量' k" x& l5 d' d7 R: X: {
    #include <stdio.h>& I% R& c: V' O1 ]# d
    int main() {
    " I% j) ?3 Z  C% w9 i6 B$ X    int a, b, tmp;) Q5 f" r9 ^6 R8 K+ q
            while (scanf("%d %d", &a, &b) != EOF) {+ \4 m6 G: F: F- l3 B  |% \
                tmp = a;   // (1)6 [, S2 X" W# u" `0 _% u
                a = b;     // (2)
    2 Y  ~' l7 s: z/ E" E/ u5 q7 u            b = tmp;   // (3)
    ; i- K( z! k' g; O; A& c4 D7 l$ U            printf("%d %d\n", a, b);" ~: Y6 ^/ X8 X5 w6 ~( @9 r) x# N5 o& C
            }- \6 n$ {8 }6 v2 j6 S8 z
            return 0;6 n2 X4 N2 u0 M, `7 ?
    }
    6 h/ u' I. L$ ?( o1+ q; w2 W; D) k2 f( N
    2- L* C/ Q2 D4 |+ p( h5 c- M8 \. {
    3* E" G; n; m  _$ m3 v+ }
    4- H: K- w& r+ d
    51 K; f1 Q- A4 c+ ?( W
    6( N) a7 ?! I9 ?% a+ S0 A7 V7 f) k
    7/ i8 D$ j# ~) ~+ r6 y' N
    8: |7 Z0 k1 D" ~) x7 g7 i$ ^- W) D; w
    9( i: ~8 q; Q6 a4 _& l6 p$ V
    10- j5 g# h6 ?/ a# i4 r$ W+ w
    11
    + _7 l% L6 l% G6 |8 l( q( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;' b2 y; w) s5 d
    ( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;, v" p8 Y6 h: d3 X
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;: }0 {* {# w5 S0 ^  p. ]
    这三步,就实现了变量 a aa 和 b bb 的交换。$ N* E7 b; ~- ?  z, m1 ^
    2、正确解法2:引入算术运算" w, t0 D! }5 s4 ^$ d
    #include <stdio.h>& s3 P. `0 E: T8 M$ m- ~+ T
    int main() {2 R) y5 J1 E' Q/ j, J( D
        int a, b;% a9 w+ V) o1 s
            while (scanf("%d %d", &a, &b) != EOF) {
    & W- ]8 h0 v6 g% z( I9 Y. i$ D! R            a = a + b;   // (1)
    ; u/ ^4 z  D; P6 }            b = a - b;   // (2)
    - V2 b& @9 C( I% W7 l. h: T8 T4 W- s            a = a - b;   // (3)3 k3 E9 r9 s4 ~6 N
                printf("%d %d\n", a, b);
    ! n& b+ D; G) q) ?) b# @( H        }
    $ D! L/ {3 q1 ~* H) X# V7 J% J        return 0;5 _  P8 m" p. f$ T$ m$ G; k' _
    }& M6 G, u  s4 ?3 V: b! U
    12 O. f7 M7 D: I) J/ s
    2
    8 X" T  ~3 d- N$ k: o3
    ) M2 o( q( _! j! d9 E2 B4' l1 W0 q& A9 U, a: O
    5
    8 v& J3 R# \& Q; i  H4 D5 y5 o7 P6
    # y! Z- x! W4 Y" N! X2 S1 ~2 G72 D7 @6 u" l" \0 s& g+ a
    8
    $ s4 u+ k, ^( |; ?& l9
    1 \# ?4 T' x* H; R' X7 A10
    4 l7 @9 b4 m9 p& G6 K11
    4 Y6 u: g  w# J- Z( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;! T- i8 l) H* K
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    # U+ G, X7 d( ~( E1 F( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;9 j4 }3 P3 ]9 L
    从而实现了变量a和b的交换。
    : l9 r- x1 p5 |* G. H; H3、正确解法3:引入异或运算
    7 \3 P  P- r% S& {7 c首先,介绍一下C语言中的^符号,代表的是异或。; i2 S# z+ H) L  j: e$ o7 f% b4 b) r
    二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    * E% Q& c6 Q5 `" p7 _左操作数        右操作数        异或结果
    ! C7 N7 s; B6 f% M7 I0        0        0" B+ e( k+ t, e7 p7 S
    1        1        08 `8 ^# v& n# I% x$ Y
    0        1        1  O0 I8 m  V! k9 u  l* s
    1        0        1! ~. A/ s+ v& {6 D* {  E
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。$ c+ [/ v5 y: K- x5 q+ e( Z: ~
    这样就有了三个比较清晰的性质:
    - _9 ~' c1 L+ R8 x8 j( |1)两个相同的十进制数异或的结果一定位零。. c* p; P6 c; u9 C) \, s
    2)任何一个数和 0 的异或结果一定是它本身。" E+ L" ~1 ^6 c5 L; [
    3)异或运算满足结合律和交换律。
    8 |- }- M9 |/ m( @5 e* J, D#include <stdio.h>6 C1 q$ X" h3 R- V
    int main() {$ O8 {6 ]9 u' ?7 i2 U4 ~
        int a, b;0 P+ f9 o: E2 a' e5 |+ ~$ {
            while (scanf("%d %d", &a, &b) != EOF) {
    7 N8 J+ i6 |4 T" F' _            a = a ^ b;   // (1)
    0 X( [/ |* o5 q: h+ }( E( Z            b = a ^ b;   // (2)
    ' N! E5 g& Z, D/ r, B8 F            a = a ^ b;   // (3)+ z6 `, b$ E: F: b2 q. B2 q
                printf("%d %d\n", a, b);
    ( p! {# g& Z) w0 ?  a        }4 E. F# \% c) |3 l- H3 z& G
            return 0;1 M6 ^2 r, D7 e+ Q( V
    }' H4 H% y4 X# T2 w. R
    13 ]& _+ v3 V' v' w
    2
    " ?: b: _$ S9 K; Z' L- |. ^, T  @3
    ; J6 W( @  o+ L4 R7 k4
    9 F/ h- G! _& M5
    % Q/ _5 q( x" a+ H  K$ {& R6$ C$ R3 x% j' ]' N2 H
    7
    6 Y6 |) d0 j8 Y7 S8: L; e/ \# ], L7 h- {' M
    9) W0 ^: G6 E9 S1 ]* {
    10
    6 _- J  L5 p! ^3 ~11! H- B- |$ D2 c
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
    2 B" s1 R: u# U# b* q3 x5 c而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
    6 f! e6 x/ }9 H. Z8 \从而实现了变量a和b的交换。5 n# ]+ i. [+ I
    3 p1 V* ~- t8 x( ~3 E/ I
    - a4 W% o9 y2 M5 I
    4、正确解法4:奇淫技巧2 s5 s  n/ R' k5 I
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:; S! m; _7 @  P4 m3 V
    #include <stdio.h>
    & [* G4 I) k7 I) y2 ~; Yint main() {
    # b' _( O1 v* L1 I. f7 V" j    int a, b;6 Q4 |, E. E3 P3 z
            while (scanf("%d %d", &a, &b) != EOF) {! s5 h9 |) o" w1 t, Z; ^
                printf("%d %d\n", b, a);9 ^, r! w: x- J9 V3 A
            }5 }/ g  R) g, t; ^# `
            return 0;/ g* r. A& \' Z& F  L, `
    }
    . S; w5 z3 _4 Q: {+ h9 ~1  p+ Z5 P5 @% l1 u9 {0 K6 w6 c
    2, _+ A4 L3 c( p7 U; R
    3! Q- U% U# ^& j' e
    4* }5 y- U1 @- {6 q
    50 h) \# I% `) M" m& d0 h, _! q0 ~0 I
    6$ l- h+ @* _# d+ v  G8 ^
    7" a" R' s# D  b% `! K% @$ S7 k
    8' g/ r' A+ G5 L( ?" [( b! X
    你学废了吗 &#129315;?8 Y( T0 y4 T1 z3 ?1 m( G) X8 r  A
    2、例题2:整数溢出
    # U8 x  R$ K  ?2 s9 e一、题目描述  S3 `% E+ h4 ]  j1 c# p, R
      先输入一个 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 2 c" _# g5 N& R" i* n, o. {
    62
    ; I5 n9 R* w( K! J ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    . }6 v- F  ?2 }* d+ M) P: a
    " }  f0 K$ ?0 n6 R$ e
    ) o* v% r0 v" t/ \3 ^) S
    二、解题思路, K7 x3 w/ x' r; }
    难度:&#128308;&#128308;⚪⚪⚪2 g# ~& a  n7 v" {" Y9 P  N

    . P# ?' F- s2 q* s1 d* _" X

    + p' e% F0 d$ t* {这个问题考察的是对补码的理解。/ [/ K2 z, X2 A' F4 \) Z
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 " N9 K+ A- }* h3 H" e
    62
    - m+ }8 n8 d: r- ?$ {; ?" [% F/ H ],这四个数加起来的和最大值为 2 64 2^{64}2
    . S, \, ~+ W7 s6 r8 r646 _9 D% P5 V! `" S0 ~9 g, |
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12 # Q  ?, G# e& `4 l
    63
    9 k0 Y2 W8 g* `" }; @9 R −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 / m/ a$ j; m, j+ ~2 o$ E
    64
    2 ^: L% M9 M5 O& a% v −1。
    + }5 w% R2 f& Q* [6 Q7 f1 [但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    ' Q/ s! h  L+ W62
    8 a' [& ~+ r0 D; _; f  时,结果才为 2 64 2^{64}2 & n4 E( y* s$ @: r8 o5 |& A- y$ g% x, Q
    64
    8 W; o( x3 |% l2 [2 j% P. Y ,所以可以对这一种情况进行特殊判断,具体参考代码详解。, k* |. d  f6 t+ M+ G2 {$ v9 T0 g9 j
    三、代码详解
    8 t$ c  d( C, }1 s; ]8 }#include <stdio.h>9 t3 R# e6 R4 c
    typedef unsigned long long ull;                           // (1)* r" Z: O9 ?* Y* y3 }' e
    const ull MAX = (((ull)1)<<62);                           // (2)3 P; j0 v; T7 L$ i% v
    5 k2 d+ j  M; C3 i! o( |

    ' P) x; F& z5 k% b8 k& k: vint main() {( G, T. v: H( r8 j
            int t;5 h% j5 J! d- k2 a. T# k
            ull a, b, c, d;
    1 x, q0 V4 N; r  R% S' g# D( Y        scanf("%d", &t);3 n  a( h* g8 w; O' q( g, E2 ]. P
            while (t--) {
    3 J, }+ X, n! K                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)
    8 n9 H5 \6 u; J# h3 r% z                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)5 U6 h) u8 K" Q* i
                            printf("18446744073709551616\n");             // (5)4 A6 o8 p& l4 O
                    else
    6 C+ S2 l2 }: [                        printf("%llu\n", a + b + c + d);              // (6)
    " |3 |* M2 K% E1 X/ H        }0 {' p. p. o; P$ {
            return 0;" g* u$ j7 T4 w5 s6 L( Q
    }3 s* u# g% G: F" I, u# |# o7 k
    1
    : z4 J" Z3 A- N7 s- a/ S& S& w, P2( O9 c) @! k& o& u# y. v) _1 W
    3: R7 T$ f0 z! t2 @) r
    4
    % ]% V7 I+ d: N( t# h" o3 e! _1 V5
    * w8 p! t6 b' F- ]( k, W6 X6
    $ N5 x2 ~/ t% ~) U4 x* _+ u4 R7
    : r& T5 i. W  Y: r6 Z  y8/ e% p! t  `- q$ C' }6 G! M
    9
    5 C9 Z. B0 ^/ F8 e% u" ^5 u4 s: y10
    ' x: o' u$ U+ B% U11
    ' ?" `0 ~/ V0 P4 ]& G12
    3 v5 }7 H) y/ c- c* ~" n* o13( W+ y, n# l/ Y4 Y
    14. ?( ~" L/ T0 [) r6 |
    15. f- L+ J* F1 |3 S: \  k: W
    16
    + r) ~; A  |' b* Q: {, w9 {! V17
    ( s7 `9 o% z8 x) m4 J% C( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;. @) b+ {% n% b/ ?
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 % n. a, M# ~. U. Q3 K
    62
    ! z: N+ Q2 j, B  I- [/ z, O ,这里采用左移运算符直接实现 2 22 是幂运算;
    ' @  T5 G% p# x1 A数学        C语言4 a0 C! q$ N' p. \5 x) c& o1 s
    2 n 2^n2 % {  v: \3 ?- Q; A  C  Q
    n& ?% w: Z# n& z2 g) p
            1<<n% T* B8 d1 F1 F# U7 |+ y
    需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;9 }$ O) i# o1 }4 k/ ?; X# y: E
    ( 3 ) (3)(3) %llu是无符号64位整型的输入方式;0 `* k# }& `3 {1 o5 n% {) b
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    2 y: W' ^9 m/ K' {. E6 o! @( 5 ) (5)(5) 由于 2 64 2^{64}2 . k! ?4 a6 z% E- a% O0 j0 i+ M
    64, [2 k3 g+ y  k7 q) f
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
    7 @& @1 Y+ ^( Q( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    5 b- T9 X( K2 e) U9 E( P4 X& O' M64. K3 E! K9 Q# Q3 e
    −1] 范围内,直接相加输出即可。
    ( A; l( X8 U6 e/ o3 j" U由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    + u3 ^; R+ P. Q$ }为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。0 u$ v; Z$ b, K, s
    & X5 [2 E/ }5 E; h1 J! T" F
    6 }' ^# u) p- ^4 ~/ o, ]$ i" X9 [( J
    3、数据结构
    " X9 w- ~- q9 K  z# b《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    " w$ f  |6 _" ^5 _, @. e1 w1、什么是数据结构
    8 J7 T& S8 M: t你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。9 X9 ^1 j; j0 \% M! w- k
    如果一定要给出一个官方的解释,那么它就是:
    * O/ [* @$ ]5 ~% v/ c- U计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。, D/ A. L" p, }9 S: C
    / C  T9 a+ h. _8 n2 p8 Z7 c' A/ S
    & s) O# J% L# O6 `* y; |' ^# s
    是不是还不如说它是堆,是栈,是队列呢?' Q% }+ n3 [  g5 I1 ?
    是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    " r4 D: l# C( R0 A2、数据结构和算法的关系. @" j* h: S7 r% y- \- z
    很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。# ~9 f8 j( N9 Y4 L
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    $ k8 L5 H4 ~+ n1 Y0 ^. ^5 s而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    + l$ H# n1 Y# k. X讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。3 L6 H) V7 \" a& R+ m# O' V& b
    3、数据结构概览9 C, P6 P' N0 j7 h* z7 q  D
    周末花了一个下午整理的思维导图,数据结构:
    - y' q, Y# }) ?5 v: K8 Z- z+ m- a( V" T4 L8 B

    + f- W- v0 {, o2 l: d+ N常用的一些数据结构,各自有各自的优缺点,总结如下:
    3 p8 ^9 F& g* o; l, S! aa、数组
    " Q" N3 o# E0 k5 |内存结构:内存空间连续
    - }. `. D3 a6 k( u! I实现难度:简单* d3 z1 f  z( u/ }- a
    下标访问:支持' ]5 K8 V) y1 Y  v. f
    分类:静态数组、动态数组  n; n  Y# l# J( b3 D0 K
    插入时间复杂度:O ( n ) O(n)O(n)
    0 c5 F$ s/ [" y" Z+ A1 U, P, f查找时间复杂度:O ( n ) O(n)O(n)* M% T% E. k+ l8 X
    删除时间复杂度:O ( n ) O(n)O(n)
    8 N& }& I, ~5 F7 `0 q6 _; ~4 @6 G! ^2 h: T
    3 m$ ~2 @8 G5 |( u9 ?
    b、字符串
    $ ~* g% P* F2 W8 Z' P. |8 q( K内存结构:内存空间连续,类似字符数组+ P# h8 K) S, B- Z* ^4 T0 V2 T
    实现难度:简单,一般系统会提供一些方便的字符串操作函数' z( ?4 }' m  e# P5 [$ S# i" k
    下标访问:支持: `) P) d- ?- j6 I/ F* h( w( m, r3 P" E
    插入时间复杂度:O ( n ) O(n)O(n)% J5 q2 J7 k& _2 \
    查找时间复杂度:O ( n ) O(n)O(n)
    3 v" N  n. i$ I! M; k; ]删除时间复杂度:O ( n ) O(n)O(n)" v" r- z4 v2 D: A) |6 H/ |
    5 `- a, `9 R* ~  e, S6 R4 K( a

    % ?# p9 h' g3 G+ U) |2 f3 W+ u' Pc、链表
    . O' u: K1 Q9 Q% g内存结构:内存空间连续不连续,看具体实现
    9 D+ ?+ `6 Y4 c/ n* }实现难度:一般! S9 i+ m. N; j. {4 j
    下标访问:不支持2 e5 }+ f- c+ I4 k2 U
    分类:单向链表、双向链表、循环链表、DancingLinks
    $ ]* Z: h: z' D( M! s插入时间复杂度:O ( 1 ) O(1)O(1). ^/ F8 S! N5 O4 d5 r- N
    查找时间复杂度:O ( n ) O(n)O(n): w2 w" t2 Q- r1 l: n8 X% h- D1 q
    删除时间复杂度:O ( 1 ) O(1)O(1)1 e( w3 Z' ?6 \4 L! [6 U

    * N9 t- Y7 M& p2 Y
    + ]% C& S! v! u7 D) J
    d、哈希表! q& N0 Q1 v1 j2 t7 U0 h) L3 T- q
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续& |- y& L2 ^) Y! x  Y
    实现难度:一般5 @6 ?- d+ g8 B  u5 U: l
    下标访问:不支持# o9 ]2 i1 O( z: j1 N( B
    分类:正数哈希、字符串哈希、滚动哈希
    * ~- K1 E" r# }" ?2 z插入时间复杂度:O ( 1 ) O(1)O(1)# e& Q$ E& k; e: O& y9 h
    查找时间复杂度:O ( 1 ) O(1)O(1), p; ]- k. j( i/ c  c
    删除时间复杂度:O ( 1 ) O(1)O(1)( i  e0 v7 V# Q2 R

    . o) f  Q0 k2 O

    ' E4 s6 ~, c% w( e5 g' i& i: ?+ he、队列2 o/ }: {1 g" f) k
    内存结构:看用数组实现,还是链表实现7 x2 H0 z  h  K+ K; ?3 j; ~2 i  g, g- j
    实现难度:一般# r6 q9 D* v  N$ v! i
    下标访问:不支持
    " C/ W& ^; D6 f+ a' u" k0 \: u& s* l分类:FIFO、单调队列、双端队列4 `  Y* Z: c, t7 l5 v' R
    插入时间复杂度:O ( 1 ) O(1)O(1)
    1 _9 _: O% J# g& L2 {- A查找时间复杂度:理论上不支持
    5 ^; \+ J9 b% P$ Z* b: S删除时间复杂度:O ( 1 ) O(1)O(1)
    6 V6 m7 K! F) s* X" a3 l. @+ v9 [! Q, c$ K
    1 d, o' L+ v# @- T' v2 o  b, {3 g  ^' |
    f、栈
    $ v* r6 H, N* U8 {/ B内存结构:看用数组实现,还是链表实现8 t8 M5 l: p: F" U! H7 q! t
    实现难度:一般
    8 l6 C) A- h  x7 ?下标访问:不支持
    & X5 K% U; X5 ~- e分类:FILO、单调栈  w. Q+ k( ~% l7 P
    插入时间复杂度:O ( 1 ) O(1)O(1)
    ' E& }( S+ d* t# T( w; ?  ?查找时间复杂度:理论上不支持
    ( w9 i% {; }' h: f* m/ [5 h! c  I9 M删除时间复杂度:O ( 1 ) O(1)O(1)) Q! `! z  I- J) a* K0 B8 e

    " c1 T+ ]. b: E: \! C* _# \. X2 n- U

    5 M2 f7 L9 y6 G& p4 Z2 J" [5 Fg、树
    " `) G% j1 e2 \8 Y内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
    4 @6 U- |  p: T  ^5 q实现难度:较难
    0 B; @$ h2 R( A" O1 h9 a, e& z3 ^下标访问:不支持
    8 \. M& s# ]$ N& T; M4 w分类:二叉树 和 多叉树
    2 c; B7 T* i) y) B1 ?, `插入时间复杂度:看情况而定+ ]5 }+ Z1 \/ f! W4 ?/ d1 O
    查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log ) K4 e& A# W+ }. {2 F
    2
    ; I& ?/ b' S4 d) n​        0 P6 \! b) E: V" V3 U$ q
    n)8 H* Y6 b! q. g7 Y: Q- h
    删除时间复杂度:看情况而定
    8 ?! s2 b9 w  z  P8 b2 Z+ }4 d9 I' u6 f. ^0 A/ W

    ' U6 s: Q1 d# f1、二叉树
    " K( G0 w( s1 I7 o6 o0 w二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。9 U5 d) _+ W; J9 y% _
    其中,堆也是一种二叉树,也就是我们常说的优先队列。/ I' h& M0 U4 Z* u. N
    2、多叉树2 V6 H' B$ f, _) h% E, \
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
    $ @8 t; F/ l! u$ fh、图, m& p$ D( k+ B3 M& Q3 G5 B
    内存结构:不一定
      Z' R* h1 V5 _1 y, O* {( x实现难度:难
    & E2 K) q7 {: l; }5 U下标访问:不支持
    ( p' |3 S9 ~% e) v$ R2 ~# }分类:有向图、无向图
    / l+ ~7 w8 r5 N0 j$ c, G插入时间复杂度:根据算法而定1 @5 L4 f0 P8 {! @1 e
    查找时间复杂度:根据算法而定
    # |0 I6 J' G' w7 @* H7 S删除时间复杂度:根据算法而定  w2 Z. |0 N1 k, f, s
    : a% X' W) y+ R/ E4 x: f3 w" M

    : H( G9 r3 F1 s2 E8 S! W1、图的概念* U$ ?; }& [2 f6 e: z( b
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:  T* H! v/ _, a( E
    图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。- A/ c, t% i. e# J
    对于无权图,边由二元组 ( 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) R$ s5 v* M: E7 ~4 }; p图分为有向图和无向图,对于有向图, ( 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;  k0 J' y2 H: S' Z& C
    2、图的存储
    7 l9 Y8 Q) |* u+ V. Y  d对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
    & O# L) ]7 j7 N6 G+ j3 F9 U0 E0 n  D$ `& h

    0 X$ T6 K0 ]: C! N% K1)邻接矩阵
    - K, K# \! L4 A邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。, b; [7 r, w$ L$ g3 |, F, c
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。& L( X" Q1 |0 Y: c# q! J7 Q+ E
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[/ H; k( J- O: q# d& l% q; r7 C
    01∞9∞0∞8320∞∞∞30( \. V$ x/ B/ o- z- p- v
    0∞3∞102∞∞∞0398∞0
    % @( S5 i. U6 _# ]$ ]\right]
    - d$ |6 j8 Q& V% p0 X; E  R9 H
    ; ~% @9 M9 o( o1 X, L: h2 K! A2 [) [) b, f6 @# Y9 N7 q

    + c- G; ~/ o! W" k5 r, F2 b3 A' h) S; N! ^7 r- h
    ​       
    4 t3 @+ q# r$ R( E, s  % ~# ]9 M) E/ b1 Q  I
    0; c: z) i% z4 E$ g- ^4 h" @
    1$ M/ \% d+ i0 ?: D1 U
    - n5 e" `0 B; k% u0 `1 b
    9
    ( g( D# a2 M( m) g​       
    $ _6 P$ K) m6 J( ]9 v+ Y    x- }" }6 b& c- E+ X4 ^
    2 }3 R# d, w. @; |
    0
    ) |" p& A) s/ Z' L. f% C5 q1 R. \- Q$ U0 F; y2 k
    86 |" H8 s" g& G1 d. ?
    ​        ' v# ~' T+ x& k% [/ g* Q
      
      }! P8 I; S% C6 t  D2 J3  b+ u/ @# Z. \  J1 o! F8 _2 n
    2
    : A' @. I3 g4 ]4 U9 k6 S# N0
    0 V4 Z3 @  Q7 @
    , }8 B( {0 @1 U5 N7 s5 h. I​       
    & A8 N( s  M; p3 C1 \  ; _9 E' A: s  C1 x; \0 r
    3 y. z, w* y) |7 V% d

    8 X% C: B+ {" M' t3
    4 a& y0 ^! _4 X/ n: z0* g) a: d& d: w& p! T) @2 x1 v: ^
    ​       
    4 K/ x) d6 m: f5 g  % Y6 h: U% G# c& I8 t
    3 P* ^, A$ z. Y
    " {0 j# b" `$ j2 I( c) c
    * A: [. n, Y/ R% l# [8 Y$ g: l

    " f/ O' Z. ^- v# p) b. u- }/ J​        $ c: v2 _( l: d6 H4 B8 }

    ( c' m2 S7 T4 A# P5 }) c2)邻接表$ E% m' @* {9 R( f
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。. @$ A0 J% z+ C5 A
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    # u2 L0 q; A3 Q- E8 K如图所示,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) 二元组。! w+ ^, U/ f+ Z1 i6 ^; _
    : z+ T6 X) R! u! `: `

    8 h. c$ N, Z' c8 U/ I5 N在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    7 ~( y# R. X8 N2 w    vector<Edge> edges[maxn];
    7 a( L! R( x, p1; Q2 t7 n7 T5 D
    3)前向星
    1 C" ~3 ^" K" ]4 H, k& m. p+ [前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。3 |: s5 W9 _/ U( T! t% n- l/ E
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。3 k" w  Q% B4 k( W! R. A3 l
    如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    4 `( |# i7 y  J. L, i0 @9 b
    % {1 `4 d6 ^! J! h! ?1 p7 Q
    4 P' L; z5 Q) |4 O
    那么用哪种数据结构才能满足所有图的需求呢?% A) I# i2 x0 J0 \+ S
    接下来介绍一种新的数据结构 —— 链式前向星。
    * s- j; q% z  ~5 Q# ~4)链式前向星. O+ H) }9 q3 x( J* C+ h
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。# q) w2 N9 E: F* P! s
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。! V! ~0 O0 F1 h. b
    边的结构体声明如下:) _8 z" q! I$ T7 x3 ~0 n) }
    struct Edge {
    $ @# N3 T- k$ Y- n    int u, v, w, next;7 r1 X- u, e$ x% L! [
        Edge() {}& D" @) N6 a/ U$ P1 D2 f* K; b# d6 L3 ~
        Edge(int _u, int _v, int _w, int _next) :
    2 J+ }1 q# e3 H& S- e3 X        u(_u), v(_v), w(_w), next(_next)
    2 U; I3 @4 K  \' x* s    {0 W0 d6 O0 I. @( }. X9 A1 c3 d
        }
    . r6 E1 p8 \7 n# y5 \3 H& \}edge[maxm];! p! U, q% ], @2 _/ }: v
    15 X7 G6 W7 @; n' V4 Z+ y( C2 ^
    2
    . @/ \/ A9 N$ {5 U; q( `8 C3
    ! L6 R& I& Y: }; c4
    ) I* A) w( {1 k0 J4 K% X. X5
    5 f& p3 G  f& m7 |0 N6& c. t" O( I* T% `
    73 I7 a, y. C* `5 B  g# ^
    8( S$ |3 B  F# G) C+ _
    初始化所有的head = -1,当前边总数 edgeCount = 0;7 x" L/ f6 C3 y# T: z
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    6 O% s, D0 }+ ^. qvoid addEdge(int u, int v, int w) {9 S  k* \' d# e2 W1 O4 R
        edge[edgeCount] = Edge(u, v, w, head);- S2 a4 i5 D. ^: G/ _& J8 w, e
        head = edgeCount++;2 c* }) c- h- }; q6 G
    }' j: s# E9 o3 `3 Y7 d
    1
    8 T, Z* W/ }$ w5 E2 g2
    4 ^; }) E+ R  t+ h+ J3  W0 S' A" o( g7 Y& t2 Y
    4$ ]) P" R8 ^' X0 R! a  W
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    + K1 M4 u; _! R调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    & P: c8 n+ b0 Y1 }" X4 `for (int e = head; ~e; e = edges[e].next) {
    ' L2 L5 Z  v8 o$ M+ U/ j" u  u    int v = edges[e].v;
    ( p' _& ]6 E% z- S    ValueType w = edges[e].w;4 A) a* A6 a( Q9 O# K
        ...4 w. l8 t3 U" B# Z( _
    }, Y& c6 f7 Z2 R7 r
    1
    0 p; Q( g6 U7 Q( k8 L/ z29 _$ x; R! p0 t
    3
    $ K, P* K; q3 g6 ]+ _/ ~: w+ q# n% \4* s3 Q9 ]8 M  [7 t, O  J
    5
    8 U+ }2 @! _! C& u3 p9 A: P* u9 _- G文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。# s5 u! k! S. z$ `1 M+ M( h2 u/ l
    4、算法入门
    & U; B, j' X% E! o9 a. x8 P算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。: [4 z* O$ m2 C/ n$ r

    $ W: M' [( w+ r6 |# w

    : {, F& G/ u' i入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。5 _$ s* [8 t: C$ q9 ^. `) ~
    对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。6 n9 W- ]% g% L- v+ N: F
    1、枚举
    7 X# n9 z& }( U* V* }枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    - _* W+ w. `& F$ @' y7 e; Y对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。' ]  l- t; H! \9 f4 m
    2、排序
    5 s' T0 Q/ P% f. E既然是入门,千万不要去看快排、希尔排序这种冷门排序。% b% e" M# w2 g
    冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。- _) S5 C0 h* X  M# l0 `3 j  a
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。
    & n1 k5 }4 o* v- d3、模拟
    % K4 c7 W0 F! m. e" }+ V; Y模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。) ]2 D8 ~9 Z, C5 b; W
    不管时间复杂度 和 空间复杂度,放手去做!
    " u6 Z) l5 d, \% C" ]$ m但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    3 {/ A) g5 D8 y9 j: W" t4、二分
    1 e" [. Q+ F7 P6 E二分一般指二分查找,当然有时候也指代二分枚举。
    9 v' }8 i" J- G: {4 C3 [( H& k2 q* ?例如,在一个有序数组中查找值,我们一般这个干:
    : m  @, y5 `- J6 B+ ?1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    " l8 B7 i3 H* j  b2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    8 a) W. @  Q' f% a  2.a)目标值 等于 数组元素,直接返回 m i d midmid;% L6 t+ A# t0 V# p3 g& e
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    * E7 H# ~" P7 ~- W  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    8 M+ S4 O; O- ?- u- m3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。! A$ N" S0 K9 R, F' e$ p4 e/ O
    5、双指针# R; k- o0 ~- `" p; D
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。# \; U8 f+ I- |+ t0 v
    ' k2 X/ Q+ Z$ q* B
    ) P2 N6 h+ g4 ]
    6、差分法* a1 W; Z8 n+ y7 Z- D! Z
    差分法一般配合前缀和。
    * N) h0 F: d$ k对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;
    , C( i9 o5 b7 @# x" ]& x* x, `假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg ! r- a% o/ n5 w1 P7 b# M6 d8 o* `
    x& [3 V& Y! G8 ]3 x7 f, @
    ​        9 M$ `& B- ^! |  Z
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g ' _! ]( y. N. q" E5 M
    r
    9 P2 v: @6 ^' L. }​       
    " ]3 a) V1 \6 Y$ h$ W: d7 B' W −g
    0 B4 s3 \" N, s7 G+ v0 pl−1
    $ i. P2 m! i, y0 R/ p​        3 y7 g5 V+ r7 K0 q+ {6 S
    ;分别用同样的方法求出 g r g_rg
    5 b9 p4 \; @0 {  Sr0 g+ D4 [$ N. O  _( v5 Q! y
    ​       
    : P8 c) c) a' ~: K  和 g l − 1 g_{l-1}g
    5 b$ T/ G' C7 @% P9 Bl−1
    % N0 Z/ N- j4 X​       
    " q2 v6 s$ W$ {8 l/ i ,再相减即可;$ O, v% k- w0 q. H, A  U% e: P

      f) H0 m  s; A! M- W7 v: T  r

    0 [+ |% z" {" i! \5 M  C" r5 @7、位运算7 ]. [+ R- L' z8 |1 J" W' \9 w% M
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。$ `- y% k1 I& k; |+ ^
    位运算分为 布尔位运算符 和 移位位运算符。
    0 A1 u  S3 a9 E! U6 d* L. X布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
    5 x# Q0 L2 E# B% Q如图所示:
    # s' ~+ b: H* u( K; d: M" O( i3 _" [9 F( V7 J. r+ S: h2 `, z

    3 ^5 c3 {  n' c4 p) s位运算的特点是语句短,但是可以干大事!
    6 U6 E3 E9 n8 i5 D  i比如,请用一句话来判断一个数是否是2的幂,代码如下:
    - m  ?# p  r) v0 O6 {) ?!(x & (x - 1))$ i, a+ z9 U5 l+ X- _
    17 N" u5 t4 e! n4 X0 N
    8、贪心
    # b: Z9 Z! q. A  r/ Y9 m: ~6 X贪心,一般就是按照当前最优解,去推算全局最优解。
    , y2 F" X5 C5 }( o! M9 \所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    - }& N, @# J* @1 O9、迭代$ I  B; n& ]' M7 f; r
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。+ k! s. Y, n7 X" o) h- Z2 G: _
    10、分治
    8 Q! v4 x7 {* P/ f  l& H分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
    6 }& j4 r! |9 U- U5、算法进阶2 R$ r( A# }6 B
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。, G6 M9 Q1 d7 F/ e
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。
    # v) E" j+ G* B  M6 c1 T这个系列主要分为以下几个大块内容:
    7 P7 x$ t& R# ~; k* J  1)图论
    - `7 ]3 ?$ k  ^6 E  2)动态规划
    ; r4 R. y* E9 p& D; m" w  3)计算几何
    5 E2 I" |+ ~3 b% z, E  4)数论  \" x  ^3 ~( e, C/ A# n
      5)字符串匹配( |5 W! W5 b# u5 q
      6)高级数据结构(课本上学不到的)5 a/ A  x  K$ G/ W
      7)杂项算法
    + e" Q8 Q* m/ \9 _8 A6 [  G9 G0 `
    ( A. M2 ?1 V1 g- u
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:4 H& X" ?" S9 W6 n1 E, r

    0 P: v# `2 y' H. X( a+ ^
    + ?- J5 A8 f0 E6 W4 l" Y$ |1 ]
    - v% d4 z* n6 S6 A9 p" h

    + V+ g# M9 J+ J; o& R1)图论
    ) @( a3 ^* w0 l/ U' ^& {1、搜索概览% B" i1 R% Z, s7 c* Y* _6 k2 h
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。) V* ^  w* C* ^

    9 P0 |) s2 c4 X& l& Q
    & }  N1 w# {* C+ O% h/ a
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。8 S3 V9 Y2 C9 k2 _
    2、深度优先搜索
    1 E3 R1 G8 ~6 l! I# M深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;; m8 ~* \1 E! X/ d4 C! Q6 r4 @; i
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。, w" S+ l$ N/ Q: l8 L$ v) g9 {
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
    - k8 i5 T( A! U$ Y! x; ^3 e% b如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。; ?9 C( Q9 T% |/ [' |

    & |. z& y& d; X$ a. o! e( {

    6 W8 ?$ E# L3 D7 O1 x+ _- q# `红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:. t0 H( M/ S3 g  o: K; @1 E. f
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    3 P. O7 v( {" A' o3 [9 |: J1/ b. f# k- T" d, Q2 Y
    同样,搜索的例子还有:
    . y# w1 R- o3 j! A2 g& }3 c- S/ G! T4 d! \

    % a- C% a) c" ~6 ]( G9 C1 g" V计算的是利用递归实现的 n nn 的阶乘。7 M5 z# N7 {# t5 |; r% h# T
    3、记忆化搜索* I$ p* u. S5 R2 M+ A
    对于斐波那契函数的求解,如下所示:
    2 u8 ?8 d: K) G$ m! f& Zf ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    $ q* z. w& g+ x  S6 @⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
    2 K  k, `6 O( {. v{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
    3 c2 N  B' \/ Q6 W) _, z2 lf(n)=
    " M/ i/ R! @4 K/ j' i  T& e3 o" u% b! B3 Z  s8 ~
    ' v8 q& `3 U+ \6 b# V6 U

    & y9 b1 |! U% F% E! p. x# }
    / Y( f  o( j/ l
    ! u, ~+ m6 g, ~% w' t! L1 `​        - F3 }$ R: Z+ C
      
    4 v4 X: K1 `. ^) W/ M" z1
    7 r6 x$ z/ M0 J1& V# Y3 R8 D; L+ L
    f(n−1)+f(n−2)
    5 M! ?4 u: \" Z6 J1 ]9 S​       
    , x8 F, }3 n% c* o* I9 o  9 N) |, J" H( J4 Q
    (n=0)
    $ M& k# F0 r( C1 R(n=1)6 w7 u3 i/ E/ N/ @" h/ x5 X3 b
    (n>2)7 c7 x" W6 p  N2 G$ J2 L6 n
    ​       
    8 o# F( @( k& }! @! k; Q) l% B: a2 x
    # z7 K, w* J1 \" }对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    % J$ L+ W. f' }8 E* t! B  D) m3 {5 y6 W( ]
    + Z* }" [. `& T/ M9 g
    这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。' Z) ~! R1 z3 Q5 I' l; f% n  O+ z
    我们通过一个动图来感受一下:
    ' B7 l: E* \, f- r( q; I
    5 m" w" w  e5 k# P3 F3 H, _
    + k4 Z7 S6 C% @) u  ~; t
    当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    ; u" @2 G* d, C$ B& T- L这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。/ m3 t* }. x1 s+ e+ n" J, i1 I! k4 l
    4、广度优先搜索
    - X0 y2 {* {, `* H- Z7 l! S- M6 t单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。- F6 `0 C2 q& b4 ]
    我们通过一个动图来对广搜有一个初步的印象。, v3 u8 j! r, N/ n) ^% W- ?8 u* i

    5 ~; m4 p: A! p1 h: X. p. o
    , R! i' u$ E1 ]0 G( a
    5 P. T  \5 H3 `: O- L2 F+ H% s/ g8 q
    , s5 a7 U* u3 p) g
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。3 P; l5 W" _( E0 h- m8 k
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。3 [9 h* S/ y. S: E3 V/ N) i, M. A
    这时候,算法和数据结构就完美结合了。
    $ ^5 {0 z( C. \; m' }2)动态规划
    + n+ b3 ^) [2 [+ v动态规划算法三要素:
    * [. g; f: U# r! r5 t- C" }  ①所有不同的子问题组成的表;
    . B6 e) P, w' x  w1 t, D0 r  ②解决问题的依赖关系可以看成是一个图;- N: M$ z! n7 ]8 R, l
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
    5 Q. V$ J+ H5 j, F* K: v
    ) j) J. ~6 [2 C

    ( R- t6 [4 h7 t2 F. I5 M4 ~" E$ ]如果子问题的数目为 O ( n t ) O(n^t)O(n
    " `% k: B( l& j& o) Y) Ft& G. d7 U8 E& \+ R
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n
    # z  Y& n' y. b+ i. R3 Ue, U" h$ X- D9 `6 L) d
    ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。7 y5 a5 W  }: X  I
    1、1D/1D  _7 ?2 r& o& U0 c4 n( w- ]
    d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )- s5 P0 `  s. q+ @3 i4 z
    d=opt(d[j]+w(j,i)∣0<=i<j)
    1 d$ `% W' |7 z( C4 J状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):8 i& p+ k* X  F

    " d) l4 |) ]; c9 Z8 n( V6 r4 q+ S( S
    ( V  _$ c) m+ A5 U* m+ I( B
    这类状态转移方程一般出现在线性模型中。: T9 w3 |! H" n) e
    2、2D/0D
    / E) ?8 v  T% G/ xd [ 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} )
    / H6 c  r* g1 G. z" n0 M  Qd[j]=opt(d[i−1][j]+x
    5 `2 s- P$ v8 K$ _9 ~i
    7 W+ s% P+ b0 E6 _/ v0 f9 H​        0 w0 \; ]& j6 _6 B+ Y) x/ \' g
    ,d[j−1]+y - E' N( d( J( B: G
    j
    ' _% D7 p/ F: K! T​        4 q( I  ]9 D2 H1 u: k% v
    ,d[i−1][j−1]+z , M6 P7 t: C+ B  G6 Q: b, X
    ij- ]$ K4 s; h8 ~. j1 Y
    ​       
    # a% O8 B) V% d )
    5 D! }4 i) Z" N状态转移如图四所示:# H. d( C1 i2 Y3 b1 H, ^
    + K) M4 q8 z  e& g8 S" q5 o/ X

    * y7 J  ~- j' l; l% I" [比较经典的问题是最长公共子序列、最小编辑距离。
    ' m$ f9 i$ t/ e" U1 l1 J4 `, L有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    , D& X4 I1 r2 c5 f有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离7 A8 K7 o/ m5 {0 _% q
    3、2D/1D: t) e  Z4 I; z4 v  v- r* S
    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] )
    & ]7 F$ P# z# o5 A0 Od[j]=w(i,j)+opt(d[k−1]+d[k][j]). U9 C, |0 r# t4 v
    区间模型常用方程,如图所示:
    , j' D) r1 x; V+ O7 k' u; e6 N  [2 W- H1 }% T
    $ D+ k" ]* x  e
    另外一种常用的 2D/1D 的方程为:
    7 v, }  R1 K9 c) u7 f4 [3 c! {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 )
    ; x3 U' |. y, p" o1 fd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
    8 r9 c/ W# `: |0 A) }1 I8 K0 p区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    / q& W! i* F( R% ^0 L4、2D/2D
    + V" }+ y+ ^: w3 O" S. Ed [ 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)
    $ v) s5 _, l( r; @* M$ Id[j]=opt(d[i - @: {" v5 ]% f  M% H6 q& G, s

    5 n) q1 Y2 U3 z1 C3 b& g; W# O ][j % D4 T" F! }+ R; A, p
    9 Y6 R$ x9 z& ?4 _
    ]+w(i
    4 @6 H- G% @  A- ~: y4 p0 E% u& \
    8 d( t6 y$ z1 S ,j ; G) ~) q: n6 \1 \: [$ }
    7 F& i& ]& x, j* _9 z
    ,i,j)∣0<=i
    3 r: W7 O. A' E+ ~" b1 T2 ?. E8 t& s6 t( e6 `
    <i,0<=j
    + A3 I8 m* A9 n% a
    ) R9 ?+ |4 _2 W; o <j); S  I! ^  W4 G7 ?* j4 R
    如图所示:8 i0 P' I; r6 ~; v& \
    : u: r+ j0 c4 ?. b2 O+ |
    4 Z  S8 P, K, d3 J% Q8 t
    常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    ' T8 ^8 k" f% X% n" c- [对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n ( |/ E/ P1 _- x0 o$ o1 n) a
    t+e
    3 i+ ?2 {7 N1 |$ q2 M( d) ? ),空间复杂度是O ( n t ) O(n^t)O(n 2 C, \% o3 R1 D+ z
    t
    - y& z" {6 U  {$ T/ ~! [- H' t ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。- I; h- K# k9 {7 k7 T
    3)计算几何
    & K  ^# \' t' ?# j; O6 ^5 m1 Y, K4 S计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    ; }6 [0 k+ c# O) W  R如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。3 T9 n: Q5 `" I1 u% D2 b
    1、double 代替 float
    / R" V9 }+ s4 w9 c: f' D# ]c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;
    " J# Y% ^# M6 }/ ^& K. i+ }9 W2、浮点数判定
    & r) e9 u2 `( I% ~, a由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);8 T6 l4 o( u5 h) W
    两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:6 U7 d  @% q, E2 B  o5 r+ `9 n
    const double eps = 1e-8;3 X' O# x+ J- Q: ]0 {+ t
    bool EQ(double a, double b) {+ A% v7 ?- @2 N, j5 m' J2 W  i( i
        return fabs(a - b) < eps;
    - }8 E7 p# C# L; N}  W% ~; q8 H6 O( W  \' ]  ?. @
    1
    , F, h; `. g  X& ]" L2: T5 ]: M7 W/ O* J; q4 S
    3
    9 U- |& s  X+ X$ t; |4
    * V& P& X/ H+ d* k并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    - ]" K( l% v. A  w( D) f+ wint threeValue(double d) {# [% [* |6 q& ~$ S3 F
        if (fabs(d) < eps). m6 p; W4 m0 b4 {! z
            return 0;
    5 _0 Q/ X* u  P* W+ C- x7 `    return d > 0 ? 1 : -1;# _6 `6 Y8 C4 a6 _6 M
    }% D' g3 i+ X) h' A- L* i- `/ Q
    1. ?8 B0 W* z- S  _  M! W
    2, {" \- o" I7 |& |' v
    3
    - o1 ^  _+ E5 f6 n$ }. e) u4 p4
    3 u5 o/ A  g: u9 }1 P5
    / H/ G* B0 i$ f* E1 q4 o! B3 A3、负零判定
    6 L0 t+ P9 n7 p  q因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    / W% l$ G; e% \2 E3 O$ Y    double v = -0.0000000001;
    + p* Q' B0 }2 L1 J# \    printf("%.2lf\n", v);
      H3 o8 O# `! t6 r  L1# V& \3 \% a9 P, D) N& h8 }" ~/ Q
    2, E# C/ F: s! S
    避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    ) {6 e0 K6 f1 A8 W) I+ g    double v = -0.0000000001;. G4 K$ G. S* c3 `
        if(threeValue(v) == 0) {( P/ D7 f. g6 F: |& M  D3 V' h' L
            v = fabs(v);1 A" F3 p9 m2 v+ E" U6 v
        }
    # J" D6 I, p6 Z    printf("%.2lf\n", v);, h  d7 C& I& q$ B: p
    1
    / T; d3 r' f, ]6 L7 B  G21 b8 \0 ]$ Y0 E) v1 K8 @1 n
    3
    ( ]/ G) i& H3 Z0 ?4 ^4
    ! |" ?4 }" h7 e! _# P/ i) \5& Y: q" H. o+ k* P+ D; x( G0 `
    4、避免三角函数、对数、开方、除法等. c9 z) G# H& u5 }
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    + n/ u! Y0 r, {! e' w) ^2 m1 E除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。# l$ q' s3 Y3 x' C: y
    5、系统性的学习! h+ H% T/ x, ~* E; J* U  x
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
    ) ~+ q4 O0 c' I+ h进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    3 R: I! Y: U; S8 X相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。
    / E9 P9 y! j6 _' k
    " t' g- v5 a' Q3 N0 _$ Q

    . {. i& s- f6 b: B4 N3 X4 C% Y8 q$ `学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。. w2 A" G- ]( O1 @) x
    4)数论6 Q; y0 T4 U4 q1 t9 ~2 L; N
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!5 J$ w0 q8 s* o1 |6 N1 ]2 v) C  ]
    数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    & x' {$ e; d; p1 p2 e当然,数论也有简单问题,一般先做一些入门题提升信心。
    + j! q$ A6 T* c. a1、数论入门
    # l. C! s, p* M2 F6 v主要是一些基本概念,诸如:  |5 d0 @4 }+ S0 ?& g
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;
    0 C' ?$ L% N" a/ Q  ^5 v2、数论四大定理
    ! c+ R1 L2 t, s, ~; W: m这四个定理学完,可以KO很多题:  x) B6 z, y- @) g
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理
    9 P( [+ p( U; q1 ?$ g: s# @9 s3、数论进阶
    4 d8 S' e9 Z2 H5 Z5 q. j系统性的学习,基本也就这些内容了:, [( I$ I8 r: j* X0 P
    扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。! u' e3 j. d! V  R6 V
    5)字符串匹配- n3 G; L( e) X
    字符串匹配学习路线比较明确。
    0 W1 Z) v6 A. D) }/ t先学习前缀匹配:字典树。0 ~& }3 w4 c% i# v( S
    然后可以简单看一下回文串判定算法:Manacher。4 w- K3 A1 u' V0 [8 u: o! m
    以及经典的单字符串匹配算法:KMP。
    + }) E3 b5 y- M; \实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
      ]+ `+ T; U  x. c6 Z然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。0 O8 Y/ k; d/ |  r- \8 ?7 n0 d$ {7 Q
    关于 算法学习路线 的内容到这里就结束了。5 x3 ?( y  `+ j% W4 B
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。; ]' \7 M! }+ d' a# V
    参考资料  `5 ]; j$ ^7 t$ b
    【阶段一】C语言学习资料:《光天化日学C语言》(日更)5 K% R2 Q$ p) }# ~. F. x
    【阶段二】C语言例题:《C语言入门100例》(日更)* p# O! i: f% |! H! g
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)
      E" B, V, c& V' n【阶段四】算法进阶:《夜深人静写算法》(周更)1 H& l& l1 o( S2 ]5 I0 o
    ————————————————1 G: }% @: P- Z! M; G
    版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    4 y: j: @7 X) R0 B8 m8 Z原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/1183822288 F. ]* B3 ], }
    - S9 c# P# R) A! V) d" O: P$ v% E

    ; ~2 ]$ B- Z  [/ b6 A3 ]
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-15 04:01 , Processed in 0.465916 second(s), 55 queries .

    回顶部