QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 3741|回复: 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
    # y+ B- }$ o9 n1 X; @$ v+ I. J
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏); M! ?2 v2 @0 N  J/ R
    % c# ^( }+ F% V& x8 Z4 ^
    前言% ^$ |7 b7 y1 o2 C; K
      所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    3 H" D. J! h( }- Q9 B% j) k  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    ( x8 D- R" b7 H; g4 o  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    : O, g+ k2 ~3 t0 Z2 U" P  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。$ @! O8 R! W( b% n! s# J

    / s  a; f+ V9 s; t
    # i9 o' Z1 n: x/ d5 f: Y% Z" F
    0 g, X. Y7 d/ Z# Z
    / E0 k# o% w( o, \: n+ F/ f% L

    1 w+ l; i- I8 D
    3 C' t% [9 m1 z

    - ]; ]0 J) Y$ ^0 P
    % y- t+ t( x) G+ ?2 ?6 Y
    图片较大,文章中有拆解,需要原图可以留言找我要哈! n+ W4 p% b+ m! n
    1、基础语法学习, g  o  s7 H: q8 M6 e1 `2 {9 t
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    # A" {6 Y0 f, g+ D" n因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    - I2 W- {( q4 M: s% W* Z3 f  Q& a. _- @& X# J4 X

    ) L; P( z; {$ y0 x2 ~9 @+ G  O5 Q' }5 f0 v7 C8 ?
    2 B' M& @: B8 d% S
    1)HelloWorld% Q/ u+ k  ^- l0 j7 j
    无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。) w7 C7 U4 n3 f8 m# Y. f) i% X, s
    2)让自己产生兴趣
    % B: v: c8 @% S所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    2 f% i4 f* e+ r" S! \1 p# c刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。: Q) K# I$ ?; _  J* F. v
    3)目录是精髓
    8 ~1 `) Q! {$ K+ n2 K$ L, _/ ]然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:5 ?$ v8 l  n6 G
    1、第一个C语言程序
    5 y7 T8 D* |: ^7 f2、搭建本地环境6 V# U# w6 i8 D/ z$ B7 H5 k; }
    3、变量  W$ t% H: _* p- g$ X' y
    4、标准输出
    6 e% P3 K) p8 |- g: t- z5、标准输入
    " ~9 j: c# b4 Z  A: p6、进制转换入门0 ?9 t- T! a) v+ R
    7、ASCII字符- r, G( F3 m/ b7 @3 d- e0 l8 Z# A
    8、常量
    2 K0 r2 _* P( v8 r+ g0 S& g. a. W+ T2 r0 M% @2 @( x

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

    ! L7 H$ E( J$ Q: P( W  [
    , f& y, w0 F6 P6 [5 X- _% E
    8 W# ?4 |" m. L

    9 F- F" E# t, i" Y9 D从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
    1 t: r3 @) V, W+ U. a这里可以列举几个例子:
    1 S- H/ S# V1 T2 \: b2 u  g1、例题1:交换变量的值0 Y. V/ }1 u- Q' I9 f
    一、题目描述8 Z* `& D8 n# |' l$ }
      循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。' G1 |8 e3 f2 y0 U
    5 f; V1 S1 i- P$ h

    7 @, M$ ^$ i& p+ g  a. F* ]; j( z$ D; v, ~' T8 W& v  h

    * f. M( W; l/ C" Y) h, e二、解题思路
    ; r. k) w  R8 M# `& p难度:🔴⚪⚪⚪⚪
    1 c/ m, w% N' L# ]/ _' H+ ^( i$ {; y9 _% m' `( h0 \! t! N0 o
    : x) Q1 {: @( y7 [- {5 l) ^3 v
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。3 y, n' l. K7 ^1 @2 T6 E9 |6 d9 Y
    a, b = b, a+ e+ W3 E) M3 l  ?$ |3 @0 [8 Q
    1- L, l! {8 B( O5 A. l# I+ v
    在C语言里,这个语法是错误的。
    ( t3 U# ^2 X  J( a$ O) C我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?( W2 g2 ~9 M$ g
    当然是再找来一个临时杯子:
      i1 n& A6 d# r) k( N  1)先把 a aa 杯子的水倒进这个临时的杯子里;) ]" X" _, i6 K: D( g: O' m
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    ; s, ~$ m8 g. o1 v0 b, |! ~  3)最后把临时杯子里的水倒进 b bb 杯子;# k% c4 a( @* A, s$ k; x6 y
    ! c) _- Z3 A; v2 f5 S( [

    * ~8 R5 A, Y5 C, R/ o6 i这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。2 N, {! x/ ^" W* P

    ! \1 F- ^) n# L0 i5 H% R! S

    6 E5 m( R; r& _1 V  g. Y0 p: Q三、代码详解& B2 r6 F) Z: y! j0 w! S% S- D
    1、正确解法1:引入临时变量
    2 A6 k4 X' _6 s) `#include <stdio.h>. G. P. K/ k, F" H5 L# K0 }
    int main() {
    4 b1 C7 `3 a) I- D$ n" {2 O/ c/ M    int a, b, tmp;, L) ]& x9 t- @# w
            while (scanf("%d %d", &a, &b) != EOF) {  n" R/ g% ]; w. p& u
                tmp = a;   // (1)
    " j" O) G. C8 _! `            a = b;     // (2)
    ; @9 M" u4 _4 J( B9 T$ k' [            b = tmp;   // (3), f& A2 k% O; T6 P- ~/ i: S
                printf("%d %d\n", a, b);7 P9 M# g9 m1 L
            }% I8 t0 m, k# r6 o
            return 0;2 D1 F% X1 k8 W) r
    }7 Z# j! \; ]) G
    1
    6 a5 _  a% K- y' N27 q( d$ T. O! H* r
    3
    8 S8 x0 ?4 c# X" U1 F4 {41 o. P/ Y& z* U: I0 |
    5* w3 H7 I/ ?/ H8 o. f( }9 a
    66 D/ @  k/ j4 ]) t) A4 ?5 l2 J
    7  h+ b2 O) M3 ]) b
    8
    5 P+ t& P: Y# r- @" x" f9  w$ e* H' z. C# ^! y
    10- R: X; Y% S4 E+ o
    11
    1 X: H3 r9 I! \) s; Y6 {( o( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;
    7 R/ g+ g" ^6 w( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
    4 }  _! g$ Z4 R  ^: d( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;) |3 [7 W' c, l/ E: z0 G3 J
    这三步,就实现了变量 a aa 和 b bb 的交换。
    & v& h6 T: `; t2、正确解法2:引入算术运算
    , u$ i9 j! z- R. Q" c2 F0 l2 C#include <stdio.h>
    . D% a4 J5 b6 e' Sint main() {0 g: w. r; @! H7 A
        int a, b;
    2 h' k1 {4 e* a( i4 ~2 Q, B/ T        while (scanf("%d %d", &a, &b) != EOF) {! g/ q5 n" q- _6 @8 q' Z
                a = a + b;   // (1)& Y) l7 g5 |0 U, Z0 @6 r
                b = a - b;   // (2)
    # @+ k- s( }0 v. v+ S- L            a = a - b;   // (3)# k7 C$ S: W2 f4 q
                printf("%d %d\n", a, b);
    ( q* t" e  }* W, K) {        }
    7 D, x1 o; ]7 }$ l( g. Z7 i        return 0;
    7 Q/ r1 o9 Y' {( I* C6 t}
    8 L+ K1 H: F& v) k. X1: U% |" ]! A) f
    2, _* j' {) j1 V: f9 o! R
    3
    ( x# Z- h1 J2 o  I4
      y5 K9 k4 I4 M: ^' [. s5
    - z7 D) \* P$ y- v# l& C6' X7 k& a/ K: `5 F
    7
    - ]* M9 x$ Z! T; {! \/ V8: \( r% [  Z! }9 u# S: S) e1 f
    9; C& U0 t0 M7 G/ E: h+ z2 K
    10
    2 g/ O- Y7 o' n" j6 W2 L: }3 r# W115 r! W( i+ `+ d" r' u- I; U
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;4 [+ }- z& h0 M+ r" A; A6 M1 U6 i( {
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;+ J2 V! `( E% _+ ^0 z
    ( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    4 o9 z6 N* r) O" z5 e从而实现了变量a和b的交换。4 |- J  H, P& O, S$ l
    3、正确解法3:引入异或运算8 G$ c- _' m- {( p3 U
    首先,介绍一下C语言中的^符号,代表的是异或。
    # Y1 b& `+ k! i# m3 w) i/ X  P. k! R二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:9 c" h# N# r( m) z: v% {
    左操作数        右操作数        异或结果
    ; d8 m" L, k' d& }' H! ^0        0        0
    4 o( h, n& T8 X7 Q8 A3 c% \1        1        00 J. G8 `5 N( s. ]' N
    0        1        1
    . h2 e; s0 l/ m$ H8 F1        0        1
    / m3 D8 @, N8 `# b  W: P: I- V6 x也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。  m' K: r# u6 U; C
    这样就有了三个比较清晰的性质:% n, [; N0 P2 S+ _, T, w
    1)两个相同的十进制数异或的结果一定位零。
    8 @% e( [( m8 ?2)任何一个数和 0 的异或结果一定是它本身。
    5 n* S% ^. q" t" Q/ H+ I) v3)异或运算满足结合律和交换律。
    " ?& W) o3 A; a7 e# ^8 h9 j; }#include <stdio.h># q  ?4 P# i$ y) R8 l% y9 L7 d5 W
    int main() {$ q& j0 n. a0 _* n# j
        int a, b;
    , H/ b/ M6 v! n  \: ?  K        while (scanf("%d %d", &a, &b) != EOF) {
    - p9 x- ?4 Z3 x) p7 Y            a = a ^ b;   // (1)5 v9 z( }5 I% E: c0 E+ p0 S8 z8 Y: L* M
                b = a ^ b;   // (2)0 m* E' {# P0 }/ @. \7 P  G( w
                a = a ^ b;   // (3)
    0 ?) |8 ^9 L5 ^( B7 t; \  Z0 s' Y            printf("%d %d\n", a, b);) i9 N/ C; q! f9 I+ E4 U$ @8 S
            }* v  x0 g( M) z
            return 0;
    # n+ s, U4 o& u}
    . j$ N$ C- M  b& {1" q! ~0 n/ T; z% M1 k8 h
    2( b* Q/ T# \+ [8 D" ~: i+ a$ V
    3, S5 }  ]% }) x1 z' [; ~
    41 X) X9 O$ c8 q5 ^: \. i  F
    5
    % |" }5 ~9 w7 f7 r" x6& y# N% t) q( r7 b: l# }, a$ V% K0 j" q
    7
    5 b3 u. A$ O3 i9 I8
    4 |* e! K" ^( F7 p. M9- d. h$ ^9 }) o: Q
    10! p/ a, C) s' ~- ~' p1 ^: [3 v, f
    11
    ' r( C' Q. U8 o' g我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。& O) `# U4 E+ A# f2 p# }8 ?9 ?
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。/ R. T+ y/ e9 ~4 ]4 k2 x
    从而实现了变量a和b的交换。) s9 l+ z* c8 \
    ) }  `/ [- k2 n/ }4 o" h

    + N7 y# ~# g  r9 g% j  Y4、正确解法4:奇淫技巧( s& |2 N2 G" s' u6 w
    当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
      N: p7 W2 |0 B( \#include <stdio.h>
    7 l! R; {2 }) J. o% X; j5 Y6 V. vint main() {  y( r; i  l1 h+ o, f8 ?
        int a, b;% Q" W6 T  o6 z- j- a
            while (scanf("%d %d", &a, &b) != EOF) {
    % y, B& E" j3 d* W7 [/ G, m            printf("%d %d\n", b, a);4 [+ n2 Z- ]# d7 ^) e$ z  l) w& P
            }* v; l- l! y2 `, v! R% p9 a
            return 0;
    5 e( v7 R: O" s6 ?1 U! B}
    2 [' D& c& I1 `& m11 M; b5 }  k. A* B* C8 C
    2/ @, Q( B8 `$ h9 O# N9 K# o0 h1 |
    3, s: a7 O" _" K6 \% ?
    4
    + s. D" s3 Y4 x" K- ]5# G; B- C$ v- m0 l0 b' Q" l  F
    6
    * x, ]# c- x5 U5 N% |76 y4 S! p+ {7 w+ N+ c
    8  |+ _8 X( R4 l
    你学废了吗 &#129315;?* }8 J3 ?7 R9 {/ i0 L/ E
    2、例题2:整数溢出
    3 o, d* k7 R& D  G! n一、题目描述
    9 \, }' q) u* R9 [5 y9 _  先输入一个 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 , d* A2 r$ k. M! R
    62: L8 I. F! e/ |
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。3 Z" L3 A% [" n, r. Z
    7 r4 N* k, ~3 W- o: P% n/ J
    * v  _/ C5 l4 M, v
    二、解题思路
      Q' b) j4 l! f; O+ \3 q难度:&#128308;&#128308;⚪⚪⚪5 d* O. p/ W# z1 H

    ' k6 i3 N; L! s5 [
    $ @2 U5 L0 Q0 w5 k* A, C0 ~1 K. k( g
    这个问题考察的是对补码的理解。
    1 x  H5 i) a  i9 @7 h1 w4 Z  T仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
    / p+ H: |1 ]+ B2 [" I" f- }, f# X8 i6 Y62
    2 o4 @7 \* o( k ],这四个数加起来的和最大值为 2 64 2^{64}2
    ! ]' G  f/ h  b0 e1 P, g64+ l+ }4 k; r- y+ F/ D* O
    。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
      Y1 O0 Q% j$ w4 O6 N63
    ; f/ X: Y3 r' n2 F −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 6 B, Y) J) ~- A7 [4 e( n/ w: N
    64
    9 I  b; J* J" t9 h −1。9 I0 v$ W" [. A! R5 K
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 2 `/ c7 J7 b8 Y7 w' \* v
    62
    9 K% ]/ y+ x7 q) S& h  时,结果才为 2 64 2^{64}2
    " n: m0 F+ `6 o7 Y# @64. r: b& h; \. J. V
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
      [  O8 j) I/ j) _$ {三、代码详解
    / v( s8 `0 I& f! C5 c#include <stdio.h>
    + |! B5 Q# A) \typedef unsigned long long ull;                           // (1)5 H; {% T4 c& O) W5 X  I
    const ull MAX = (((ull)1)<<62);                           // (2)
    * `6 n' w2 I; w# C# t8 V- ]; U( L0 u

    + a7 t+ l( `# `+ z4 I3 N# oint main() {
    0 k' a9 I# Q! k# _        int t;
    9 k& ?) M% [$ j  w0 P5 ?( _        ull a, b, c, d;
    - u+ J. Q9 e) m8 [+ B        scanf("%d", &t);
    7 A- I8 m) C4 u/ Y$ O1 l        while (t--) {
    ! r" l4 P& H8 G1 ^" D- ~6 j                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3). N6 i- l% X/ t: ^: V# Q; ~
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)3 }# ?; K8 m! s! W
                            printf("18446744073709551616\n");             // (5)
    ) v+ H$ ]8 s. L# w# x                else
    7 K$ B4 s+ o9 s1 k                        printf("%llu\n", a + b + c + d);              // (6). i; l/ w" R1 f7 M9 I
            }
    ) H" w9 a: K  ^( D, f3 S, G        return 0;
    * a5 b+ O. A# n+ A% \$ V; _}) b. c, I6 E. y
    16 D6 [0 `7 e: b
    22 q% P% p5 M2 u
    3  d5 u# {, h  ~' E% `" Y  k
    4
    3 d! K6 s+ G& A' H" j! q( S; Q5
    $ Y0 ^+ Y6 @+ i& T1 ?9 K60 S; i% G. j4 n
    79 V) I- @5 E" l1 }. w1 H, C
    8  P: P1 T5 r, _6 x
    94 m6 x3 x# U& j8 i( l/ x* q, @5 A! \
    10, R" K& `) w0 l4 F
    11
    3 Z) ?/ H6 Y* O! e5 H12( \6 X1 {( n2 s6 K3 c6 ~1 E
    13& J* K$ B0 \% `( A/ A7 D
    14) D2 J7 W+ z2 l7 t6 x6 O; l! Y
    156 v  G8 c6 f: r5 \
    16
    4 r" {9 a6 w( ]) u; B( f6 U17
    1 z4 q: P& t  ?# h. ~6 z( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;1 Q6 a2 C* c7 B$ S$ b5 P+ _
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    % g! i' r8 U* k  w& q62
    5 p8 |% w+ R2 d7 e2 O* } ,这里采用左移运算符直接实现 2 22 是幂运算;
    $ r0 q$ m1 A) U+ J! V" v9 @; v数学        C语言
    * ^, V  w- J. I6 v' U/ R9 X2 n 2^n2
    + \3 x! D5 |& Gn
    9 v0 {0 n  b: K% O         1<<n
    # q% R( d3 o3 r9 Z需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;" z$ m) k) C8 d& C: X; h& {9 A
    ( 3 ) (3)(3) %llu是无符号64位整型的输入方式;: q# f* K9 h1 N8 B
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;! Y! \9 P& R. M0 Q
    ( 5 ) (5)(5) 由于 2 64 2^{64}2 2 v0 m; o5 G" u
    641 }, c3 P; z1 k; N
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
    3 t' A; e- `9 h! I3 `& ^3 f; \( O( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
    3 j* a% [2 q  m; w! B64+ F  o. O* V2 I$ s8 I( C6 ]
    −1] 范围内,直接相加输出即可。
    ( P: ^' S/ n! \6 o4 d由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    / g$ v( b  u1 b6 |  J为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    7 o  E7 p5 W* e4 R$ ~+ M
    0 ]( O6 n1 r- c6 W

    3 @. K* N* `1 Y# r( `" j" J/ |3、数据结构, i0 ~7 C# w1 h! A
    《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。" |- X/ ?' h) W  H& v, a7 T
    1、什么是数据结构
    3 S, O6 O6 `+ g3 @3 ~* _你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    - @; m1 b, [' h如果一定要给出一个官方的解释,那么它就是:
    # o$ ~6 a% e9 J% Y: @3 t+ ?计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。! G7 ^5 }, ?8 n( C6 a+ E

    ; E7 I" c5 ~7 W
    $ p  |& Q( [$ K5 a3 j3 _
    是不是还不如说它是堆,是栈,是队列呢?
    % R5 L0 |2 d: q- C( b" y. @是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。1 b7 X, L7 d/ J* V! W2 m% r, O
    2、数据结构和算法的关系
    1 l! V0 F; b$ X9 y很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。" H& d& P9 O/ w
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    ! P/ J6 [: K. c' I+ J' R而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    % L- L% @! J1 E3 V; P讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    - e/ H1 x, z$ |; n, d9 k! K3、数据结构概览
    4 L) s. Z& Y6 `6 N+ a+ n% U周末花了一个下午整理的思维导图,数据结构:% ]$ X5 E0 D* }! M+ l8 J8 v+ \
    ! v& I6 i% U  ]. g2 W4 q
    % p8 t6 w# j' g3 F/ ^) |! \! W
    常用的一些数据结构,各自有各自的优缺点,总结如下:
    : `" L$ s! a. Z* s4 M9 U' a" ]a、数组
    ; d" J9 j# K7 z内存结构:内存空间连续7 A1 a% r$ n2 T, C/ _% G
    实现难度:简单, ]# ?  J0 u; H
    下标访问:支持
    6 {0 |7 T# P% s分类:静态数组、动态数组
    # X+ U% o$ j2 s6 T9 {! J2 w插入时间复杂度:O ( n ) O(n)O(n)* G, h  B/ W1 p$ v# N
    查找时间复杂度:O ( n ) O(n)O(n)/ |0 q, r# W( c( ^
    删除时间复杂度:O ( n ) O(n)O(n)+ C& O- l7 h3 d% Y- J
    6 `( P0 H! a# T% d! J% J
    - J. i7 a8 o, k* }5 v0 Y0 w
    b、字符串. O5 w: w/ y: E( H
    内存结构:内存空间连续,类似字符数组! z+ T5 l0 b6 B$ Q* g6 e+ S( I
    实现难度:简单,一般系统会提供一些方便的字符串操作函数% p. N6 ?& n' Y9 E, C8 |' m1 \- T
    下标访问:支持& M/ \- [) H" E7 D. ^) D
    插入时间复杂度:O ( n ) O(n)O(n)
    8 Z0 k" F" K! G, R; l查找时间复杂度:O ( n ) O(n)O(n)
    5 P1 U, Z+ y5 g1 h删除时间复杂度:O ( n ) O(n)O(n)# r# y, X1 @& S+ @& m; E
    0 P0 |# m2 L( s; D7 Q
    3 B5 P* x  [- Q+ `  ^
    c、链表
    : _% H! R7 F5 O3 G内存结构:内存空间连续不连续,看具体实现7 H7 h6 w' }$ t5 H& G* M
    实现难度:一般1 X  j! Y9 ^$ S5 \! I4 t1 l
    下标访问:不支持
    . F9 V1 e/ z" h4 H8 r8 q分类:单向链表、双向链表、循环链表、DancingLinks, ~  Y8 B' y0 |" r6 Z/ f
    插入时间复杂度:O ( 1 ) O(1)O(1)
    & A/ T6 C# `* ]查找时间复杂度:O ( n ) O(n)O(n)  X: l* b# d# y( T
    删除时间复杂度:O ( 1 ) O(1)O(1)/ Y1 Q. V1 x& H1 Y. A4 _6 k
    , ~' A3 n, R2 V$ ~- H7 F
    4 P& f( ~  v& D5 z8 G
    d、哈希表
    8 Z( L7 M) T. ]7 E" l6 \内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
      X% C* ^  G: `% p2 O" p1 T5 G实现难度:一般' l/ K: W8 _' k- f$ h( j" ~
    下标访问:不支持* l3 H  j3 j4 Z. `3 s5 x( V2 |
    分类:正数哈希、字符串哈希、滚动哈希
    2 X" O4 e8 R& O6 Q7 P. B插入时间复杂度:O ( 1 ) O(1)O(1)
    * z' o3 A9 m; D  A+ Z5 \查找时间复杂度:O ( 1 ) O(1)O(1), Q% j6 Z% M6 x4 c1 x' N7 E( f
    删除时间复杂度:O ( 1 ) O(1)O(1)
    ) [( A8 x. {! ?' m$ u5 f8 p  u& R' F) y, W2 }2 c
    ( Y0 d: W! y6 G1 x' y7 Z8 M0 }- L
    e、队列. ~8 r8 L$ x: C; {4 X- ^
    内存结构:看用数组实现,还是链表实现
    % `1 v, n6 c8 M1 D2 [# N实现难度:一般
    4 z4 i' I8 ]- n下标访问:不支持9 E# q9 a* Q2 r5 u5 j- x9 H
    分类:FIFO、单调队列、双端队列  N1 e& T) ]; h# E$ ?! T. I
    插入时间复杂度:O ( 1 ) O(1)O(1)
    + I9 H1 C0 z2 M5 [/ W: ]4 m查找时间复杂度:理论上不支持
    8 \, I. ~+ W; l- Z# v删除时间复杂度:O ( 1 ) O(1)O(1)
    ) d' p( {8 n' P: o
    % S2 _4 |. X! U4 o  ~

    3 T: E5 d) a9 \9 c7 ef、栈
    % L, W% A5 x. ]( X9 q0 X内存结构:看用数组实现,还是链表实现2 E9 Q" X, F1 S% p4 [# _
    实现难度:一般
    4 U0 P" M7 M' y* L. i下标访问:不支持
    9 h2 Y) b* p; r6 X8 N) u& B分类:FILO、单调栈0 F" P7 L% ^- J$ j5 k
    插入时间复杂度:O ( 1 ) O(1)O(1)
    % s- t7 r/ ^/ w, F2 c$ P9 c7 J查找时间复杂度:理论上不支持1 j. c( A! n0 D. A9 F! B
    删除时间复杂度:O ( 1 ) O(1)O(1)
    ) ?4 P- @7 w# @9 \9 q4 }$ V0 G  r: N# ~
    8 W5 B2 K/ `, n* q& c% ?
    g、树# A5 v- f% x, u* b5 ?( x4 `
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续, w+ R& `1 l$ n6 `2 s3 j
    实现难度:较难
    9 E0 b8 P' _5 T' M. F下标访问:不支持* K' u% ?# d% n
    分类:二叉树 和 多叉树$ Q' n& Q# f  w
    插入时间复杂度:看情况而定
    + q: Z9 U2 a& y& ]8 }! a0 P; ^9 b查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
    % s% \8 E$ r7 r2; x% x0 l+ {! i! r  A- d( D
    ​        # \4 K. B9 M0 i7 _) y7 t
    n)
    6 f& O- X& n- H  m6 A删除时间复杂度:看情况而定$ ^5 D9 S. T5 S1 I1 o
    # H! R! r0 u3 [7 W7 K4 H" W% U3 c

    * L/ C3 T& J0 f6 s% v7 u" J1、二叉树: D) ?3 {9 r/ b$ E
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
    # W9 g% c6 i: h& W: ~. y2 O3 ~; _其中,堆也是一种二叉树,也就是我们常说的优先队列。( n: s& P5 ?6 a% q( V% \- @
    2、多叉树
    ; t3 P; y- h, s- c! V5 p7 [B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。) H: \9 s, \$ a4 g: H( P
    h、图) Y2 O0 t3 N. d) X( s) P, A
    内存结构:不一定
    . A0 b% h* K4 Z% J) R实现难度:难
    ) M+ H9 M5 o8 c+ ~1 u4 j下标访问:不支持  X; [" B9 P; ]( Q- ^
    分类:有向图、无向图
    ) ]8 O6 g" O/ y, V  U: F0 P插入时间复杂度:根据算法而定
    5 V- R3 U) j. _* c/ {% y7 D查找时间复杂度:根据算法而定
    - N) N) J% o  G/ ~删除时间复杂度:根据算法而定4 i7 b, ~; ]6 g3 V
    5 Y; j9 M% q0 }, c4 @  J  `  a  t
    , v4 W% V8 A) n  @- f
    1、图的概念
    , S! v; d  H) U: N5 x; m+ W在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    $ I& C8 {5 C4 w4 P" G0 K图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。3 G* r7 U, X, K
    对于无权图,边由二元组 ( u , v ) (u,v)(u,v) 表示,其中 u , v ∈ V u, v \in Vu,v∈V。对于带权图,边由三元组 ( u , v , w ) (u,v, w)(u,v,w) 表示,其中 u , v ∈ V u, v \in Vu,v∈V,w ww 为权值,可以是任意类型。
    ! `2 d8 \) i" b# M' p' e% ]1 v, v图分为有向图和无向图,对于有向图, ( 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;
    / e) D  J# k2 u" \+ o2、图的存储8 V7 w  [5 e. ]1 l9 C! Q% r6 i
    对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。0 r) \$ k( N1 \6 s( ~0 U
    4 \% w$ r  G4 p8 u! b; p, t
    & n3 M& d/ P7 d6 D7 y  R
    1)邻接矩阵
    # B8 w: M. Y6 @/ l" R邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。; \& U) c/ l2 M2 F+ A7 {  _" |, R
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。
    + Q9 T' Z  y: }! d) I. E[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[: g+ d3 }' P! h6 F- t
    01∞9∞0∞8320∞∞∞30
    - t$ N6 `! E7 W7 F" {0∞3∞102∞∞∞0398∞0
    ( D  G: s$ w' ], @$ H$ ?\right]9 ?7 o2 f8 c( ?# D" _! D" T; U

    3 Y' a- X. t* w1 f
    2 E5 m8 G/ s. G) k5 M  _1 o5 y" [
    # \' B+ O% C! B" @" c$ A5 U! G+ q) ~8 `" Z% y+ Y
    ​       
    9 t! C+ w' U# [' Q! `' j8 Z! Q  ; A! @0 r- ~7 a7 n" x% h
    0
    5 ^7 X8 L7 G$ H: r1
    - a1 i2 ~8 G* C% c5 Z
    ) H! z9 E6 r) T8 a. G9- ~) @0 ?& C3 x
    ​       
    ( I! i1 a. I2 w* y  
    ! S4 y/ r, U) S9 h, k: @- B" n
    * ]  W6 |& X; n) M: x* k+ W; h. Q0
    2 V  `3 I* b- x5 t1 f# v
    " C, m% v4 K4 V! Q& i; |- Y+ D8 U84 J9 U  z* m8 j" a+ q) K
    ​       
    ( }5 \/ G, J2 X1 ?2 l  6 t" Y5 P$ F( z
    3
      ?9 O8 T6 S' C2% N, s" d4 D7 Z/ \8 P5 N
    0# S+ l( P! `+ Y% O& o% q# D, a

    6 V3 t, K1 X8 O% p2 ^​        1 }% \8 B0 `  b6 j6 ~
      
    5 z! t" U' o! k8 ^3 c+ c" i3 v+ Z* [7 f+ w' [  }5 H" `
    5 i% g! W4 i6 S
    34 G: F# j6 W' s" n; G/ I
    0& W; R$ @' H2 Q$ N" W0 a+ x- X, d
    ​        5 a6 d, y) h9 e
      
    # Y+ l) p5 e; ]) O: ~' v- k' y. {  z# Q

    * g0 Q/ b% D1 {6 @6 w  p* O0 `% ~# r9 K+ Y7 C- G# o6 I
    ! t6 e1 j; c7 w
    ​       
    " I9 o) L& }5 r/ c) q! M& h) T 0 e8 P' @& H! X; o
    2)邻接表5 X6 Q( t6 P) R; M
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。6 A) d2 U1 Z+ P; H8 s; l
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    9 u1 i2 S- G; s% k0 U# D如图所示,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) 二元组。% q, Z' k! w( [; m) B* R5 i

    6 K6 y/ G& J! [" V1 r- E* H
    ; v  q! {9 e6 e4 p5 ~( s! s( e
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    * a8 Y0 u( Q1 R2 ?' c    vector<Edge> edges[maxn];4 I9 A$ k& W, E8 A9 w5 j- [2 y' q
    1
    : u4 C5 B1 s/ [  m6 j' L! T) d3)前向星
    ( |: h0 P" H( X& \# h7 n' ]前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。$ u$ _" z6 L9 i# J2 R+ k
    它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    - Y6 f" E' j+ h3 c如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。# d. _9 R7 z! U" x4 J

      N8 _' z  {& j" Z# L# u
    - N" E) E* t2 }. e3 [- R+ ^0 L2 T
    那么用哪种数据结构才能满足所有图的需求呢?+ T/ r2 P. R1 @4 G. f4 ~0 f) u4 N
    接下来介绍一种新的数据结构 —— 链式前向星。9 c; f7 H. a3 p! Q
    4)链式前向星; h& Q9 u* k8 ], Y5 w# h8 U1 u, W5 R' S
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。1 n, p0 p2 g- }5 l6 C2 `; @2 k
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。# Q  w8 ~9 F6 P$ A0 }0 c2 ]
    边的结构体声明如下:  l7 p( k% g5 \( q/ G& T8 m! c6 a
    struct Edge {' s; j/ J2 j1 @0 `2 D* [- h
        int u, v, w, next;
    9 l% B% d( q" W- I. _( _    Edge() {}, J, z" u: P( r! j
        Edge(int _u, int _v, int _w, int _next) :( [" D  v6 L' @5 }0 O4 ?+ `' ?' j0 N
            u(_u), v(_v), w(_w), next(_next)
    * C- J4 o% z+ }- L% y8 h0 f    {2 k$ w& ]# a9 t. r
        }% ~, P6 ~( Z- {& c% X3 q
    }edge[maxm];
    3 m$ x; w+ ~! C, s0 Z6 {2 u1
    5 a% M) A& {! b; G" q) o# S2
    9 Q7 y! W1 L% L) M! B: ?$ Y3  }- Q7 @0 l: T) I& X
    4# q6 M" J' P* t$ y6 b
    5
    2 S2 p1 }6 g- q0 b$ E4 N6
    / h& S9 b0 Y- L) t/ T) H73 m4 q  M: [4 D9 F2 D
    8
    - |" x' o1 X+ I- q7 s; R) _8 f初始化所有的head = -1,当前边总数 edgeCount = 0;0 {9 U+ W% ^0 Z7 i0 Q1 Q
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    ) }- E0 ?& e% a# Z/ d' X8 d4 [- K1 ~void addEdge(int u, int v, int w) {
    / Q$ M$ ]/ C# z( H3 _! e0 t" K) \    edge[edgeCount] = Edge(u, v, w, head);! v% g8 L0 }. @. i0 y8 w. r* E, T; `! V
        head = edgeCount++;; ~% h0 H! Z" k7 B* R; R- r
    }7 Z9 |: w. c, C0 ^
    1" ~  I; h) O3 K8 N3 a
    2
    / q' h7 N0 B& m$ o3 b' p33 A6 I; V& k+ q/ k# q* H' v
    4
    ! n4 h  J- I7 N% w; t: I, T' M3 [这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    7 }5 {6 p4 [5 Y: |. g& u6 D调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。; j8 n. Y. R) t0 b
    for (int e = head; ~e; e = edges[e].next) {/ W- w- E" u; u6 z. i3 ]# R
        int v = edges[e].v;: s: O& y8 f; Y7 K
        ValueType w = edges[e].w;
    2 k, _! S& m" s: r    .../ X4 g( k6 }/ H4 f$ W
    }
    + q( H  B& J1 \1
    2 C& P* ?% n9 F( L2
    5 m) E" ?& s2 q" h  ?3; O* r' `, X" @6 }
    4
    & V# E8 K) n& n; J5 `5# I( m9 o0 x2 E* d) x
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。$ G* S6 o3 Z. Q0 o1 M. `
    4、算法入门% }" c% J* R! R
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    , R+ W* c$ y8 O% p4 i% g8 ^8 i. C; S4 ^4 N  p( ?

    6 M  L8 y' U( A: n5 [; |, `% o! c入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    8 H6 z* \, V  o对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。) s$ f4 I) B2 a4 |# i4 w$ V
    1、枚举
    5 H3 w0 ?; S7 {  y8 {) L枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。3 {% I( c$ ~6 T3 C3 H7 j9 t7 `
    对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。4 c$ T) K& C: T) [
    2、排序
    ! z' ?1 g' q; r3 M9 `$ Q7 N既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    0 @; H4 Y6 C( H  p冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
    . h; O& X# H$ i- \* C, c( WC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。' T4 M, w& c- D" D
    3、模拟$ @1 h* E# G2 y+ U
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。6 z* n" I8 Z6 S, u2 d% W. G
    不管时间复杂度 和 空间复杂度,放手去做!
    $ i' V$ A' e! e/ s! W# }) y/ t但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。, C7 E! f0 Z" h7 V# N1 ~
    4、二分5 C' n; t& d  G* k) ~" Z$ C
    二分一般指二分查找,当然有时候也指代二分枚举。
    $ J' D6 d9 a* e3 U# @* J8 G- s例如,在一个有序数组中查找值,我们一般这个干:
    : r  b1 R  s6 `6 ^) _1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;) r- c$ r5 ?" V$ `
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    $ {7 Q# x& j; a' }9 h: X( p  2.a)目标值 等于 数组元素,直接返回 m i d midmid;1 Z$ `* Y8 {5 `1 Z
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;1 I+ {2 D: Y% T" W( {8 y
      2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;% r2 R0 X: F2 f- j. i5 I
    3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
      M( l* s2 `) A: [) G, X. g5、双指针
    9 w7 C$ C- r* C3 Q9 d$ Y, I+ j. E* b( k双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。) j6 @2 U$ a+ J4 a3 @% Q" L

    7 L# J' a9 i1 m% s
    ) V: n; d' @. b
    6、差分法2 g! F, N0 ]0 i6 F
    差分法一般配合前缀和。
    ' r1 Q' \% {  E0 |- k对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;
    ( W4 g1 D' L+ `( R$ T假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    1 c  f0 w+ c) P! {3 ^: Yx$ F: K3 }7 t4 n5 }, ~. y
    ​        : B) N7 i6 G# J! a' @6 d( p$ \: W7 g9 B
    ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
    - V3 y* K; n( A/ Y# p3 or
    9 g% y( _# _9 f3 C+ H, z3 @​        ; q# r1 X- w: F9 V- ]
    −g % }- ]) ^/ B2 `- o6 r+ p
    l−1/ {7 a5 R2 `; v! ^+ b* s
    ​       
    2 P- n$ b  z- _' q3 }" g ;分别用同样的方法求出 g r g_rg
    & i5 J- t7 s7 C, P: ^" J; i, Kr
      B7 y$ ^; k( d. v​        # g& X4 P- w  @
      和 g l − 1 g_{l-1}g 5 u+ T, X4 j- G, L+ G
    l−1  }# \, W* F# C+ J; \: N0 n- S
    ​        ! z5 r+ p; K( S  G4 F
    ,再相减即可;
    & L; _2 G- r6 _3 K! x7 n7 e2 Q: T4 t' ?1 F$ E
    ! i1 Q! o; I2 w# {  n
    7、位运算
    , ?: R+ ?6 x# j. N6 O- F, u位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    / g' U6 o0 e  C7 b) r, k3 t) a+ j位运算分为 布尔位运算符 和 移位位运算符。. m7 Q7 p! g9 p1 B4 R
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
    % y! c. f6 v7 H* L0 E; N+ S' X0 r, Q如图所示:
    5 z5 r: V. p" ~. b4 d# H
    6 G6 u+ \3 @9 l) W

    7 ^  b# T8 m- W; g位运算的特点是语句短,但是可以干大事!
    % r  h. j8 x( J$ g- U* A  H7 f比如,请用一句话来判断一个数是否是2的幂,代码如下:
    1 K8 o' ~* Q, D3 S8 K!(x & (x - 1))' S2 X4 D! Z& Y8 s
    1
    6 J' R1 I/ V& R) b' b4 A) y3 L, J8、贪心
    / H, b7 T. [8 S$ Y8 r" m, x* {贪心,一般就是按照当前最优解,去推算全局最优解。3 e. ^& Z# B8 c+ ?3 ?6 O' ^
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。) A7 E6 Y! E7 A
    9、迭代# I3 i* W& e! k5 K0 S2 _
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
    * d+ O2 h4 N* {10、分治
    ' q. S1 j* b+ n* J$ ]# b分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。
    7 s3 w# O' ]6 k* k/ A' M5、算法进阶3 E+ d& Y/ c8 D7 `2 E
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
    - \: G5 e+ j' f6 |4 B如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。
    1 _  \5 p6 n( ^( {6 `& q这个系列主要分为以下几个大块内容:' U7 I3 I2 b5 P
      1)图论
    - N0 q3 O& Y' T1 ^3 ~% v' q) {4 O: {  2)动态规划6 S& a7 X7 x- ~4 C" k9 o
      3)计算几何/ X- ?; B* U& I, C+ ]# w
      4)数论; |4 P1 U6 P4 u/ V( y( h% t
      5)字符串匹配3 _" l6 D0 n; k& ]' Y
      6)高级数据结构(课本上学不到的). A4 C& N) b" J# E  r0 \
      7)杂项算法% f' z: e2 H3 J0 J+ F0 B
    $ G& n+ T/ t! Y

    5 d/ u" i4 f7 Z5 j2 D3 \# o8 s" V5 N先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:' E+ F: V' F8 z  X3 P3 [& C

    , b- |+ [) Z0 c- I* k, t

    * j+ k+ A2 z& k0 x0 F- `' z# f  y6 ^  `+ \+ {4 v) g7 @) Y; @

    3 B: L) B; J% J1)图论& u; l2 m5 W- [* k2 ^
    1、搜索概览" g9 z8 m4 q$ H8 i$ n
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。5 J; m5 W  s! S: |
    9 `: i9 d( K. B
    & A% h! ?, X* @% `+ J3 ~
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    - l# ~6 f  Z; y8 c" _2 ^9 i2、深度优先搜索3 h1 a3 X, t# o! L' W
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;( M" Y7 [) {+ Q$ Q- v
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。
    5 f/ q: k6 o* A3 ~5 d$ O1 v但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。# m% Q4 h! d5 P: [
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。/ L; I3 E! x, C6 l

    $ b' @% T8 y' P) }
    & Z' P, @, [& t9 Z# Q' l
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:. g/ T% d6 f) ?1 P* }
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    ! g/ ^* F7 H2 v% S! ~% t9 Z& ~19 s: G) ]' U- N9 t7 r
    同样,搜索的例子还有:
    ) R) I1 R5 o0 O- U
    6 K/ D; ~: u1 d$ ~2 P

    1 l  @. Y6 N: b4 f6 D/ c" q) ~! u计算的是利用递归实现的 n nn 的阶乘。  S1 H; f3 R" J, ~
    3、记忆化搜索1 M* n6 ^3 c& A1 A
    对于斐波那契函数的求解,如下所示:% A- Z# E2 Z, I! o; Q
    f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =7 N; j( ^' T4 \7 b. B- w
    ⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)
    4 A% _- n$ M; F6 u{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)" |! `2 j+ @% f& j2 F2 L0 j
    f(n)=
    * E3 @7 K/ ~6 E% [+ l! v+ J% h1 A
    * ~) }: F1 U1 Y- i# W8 `2 Z' A- T3 K

    / k6 `4 Z, M6 P# f  U& m6 e
    8 N9 t# F# R$ {4 U3 U5 k+ o/ A
    : C3 Z7 U5 p( H5 {: W​        : M% Q, X  u  P! O* T
      : D+ M7 e6 S8 F( _
    1
    2 h* N0 T+ l" o' J8 ~' d! ~& w1; ~1 u( s3 X0 f3 J) B+ _- L5 Z
    f(n−1)+f(n−2)
    ) J% j0 s/ ?2 e  J2 O​       
    5 W) W5 U+ K3 Y( x  
    5 G- w  S+ C* S! i(n=0)' g" P3 S: q2 ^" N: z, i% S" L% h
    (n=1)
    + t' \1 W  Y) ^8 T$ j# t(n>2)
    ! a+ ?' B; q. b/ p/ j​          l: p! S! P$ B8 \9 ]

    ! l4 Z3 p$ U% N/ K3 N" \! _  ?对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:# {- G) t, [% p0 K9 S4 @

    ' v$ W5 ~# H3 Z$ Z6 A! N, d" ?

    " S. R# [& S5 i6 ^) Q- G8 Y! c, A这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    0 K% z. u# o2 `7 K) o, \* X我们通过一个动图来感受一下:& O, n" _. {6 p6 q1 c% U. i+ e

    * \8 v1 k: }5 K

    $ J: y6 P. x. H. [/ G5 I当第二次需要计算 f ( 2 ) f(2)f(2) 和 f ( 3 ) f(3)f(3) 时,由于结果已经计算出来并且存储在 h [ 2 ] h[2]h[2] 和 h [ 3 ] h[3]h[3] 中,所以上面这段代码的fib != inf表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。8 z2 z0 T* \3 {% m3 M
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。- l1 P  p; T& P8 X9 J
    4、广度优先搜索
    ( S' L1 q" J6 `单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。( G. \" P5 ~6 X! w+ o( z
    我们通过一个动图来对广搜有一个初步的印象。
    8 E: p  I7 s% E8 j& z8 U( W; |
    * M3 f. S$ r) p" L. {

    8 u  U' Q" K" @2 E" e1 |3 ^
    $ W* I. E+ I1 W) a" `8 F$ f# g. e! M
    : W% t1 e, J0 \1 y- Y
    从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
      Y$ a( g4 Q1 M# C* f那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。7 z6 |* T! {7 `, C3 ~0 r4 }/ j- q
    这时候,算法和数据结构就完美结合了。6 B$ T5 U( Q' R" C, @/ w0 K9 n  y
    2)动态规划% k$ u  ?* y! g  {& [4 j" ?+ g
    动态规划算法三要素:- P  j/ b# j+ o  B# s) b: U
      ①所有不同的子问题组成的表;
    / ?2 G4 t$ X: q* ?0 u7 l  ②解决问题的依赖关系可以看成是一个图;
      n% h" y0 P  m% Z5 ^  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);6 s3 v) d2 ?+ R8 @4 R; n

    2 ]% j. {* j/ ?7 ~" y! l
    ' ]- _& I- S& ^7 A- E& U4 n- b; a6 r
    如果子问题的数目为 O ( n t ) O(n^t)O(n : P* }0 ?# t. Y
    t
    & y) Y% I$ W" d$ g5 g) X$ z5 m' ]: P ),每个子问题需要用到 O ( n e ) O(n^e)O(n
    # Q( }9 [7 A, T4 Oe
    7 G. ?& f. H1 O/ g* ` ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
    9 |$ V/ ^& U. L% ^( l. A9 Q3 L1、1D/1D7 `5 X: |1 z" |8 S
    d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )! `( c8 ^6 d! E9 [
    d=opt(d[j]+w(j,i)∣0<=i<j)
    + i( d: v; Z! v' b状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    ) h* y2 \$ U# B# M) t: I7 }9 H# F* U. ?  M
    - e/ d  x0 h6 t' u5 @3 @/ y
    这类状态转移方程一般出现在线性模型中。, \" z6 J* l( f+ p, C' R3 f9 S2 u
    2、2D/0D
    8 ?' P: W1 p8 F% w) v6 u1 q  t! bd [ 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} )
    6 I) @" }' l+ U) @5 x; ]d[j]=opt(d[i−1][j]+x
    $ p4 V- J$ \/ Y) [i+ A7 T, U$ G5 n4 ~$ W: l' \5 X
    ​       
      l/ N, x( C5 a# n; e1 g ,d[j−1]+y 7 K: y( p! @" K% E8 ^2 h3 {
    j5 N0 R9 l& X, y3 E
    ​       
    4 [! i& ]6 }- \/ G% j8 c5 i3 ? ,d[i−1][j−1]+z ' w& h1 ^2 w1 E1 H0 I
    ij- T) J$ G# C# h, J: \; X) ]
    ​       
    ' z7 y0 g1 v) z9 l# d% {8 W ), Q' J9 k9 t, v; M: g3 y
    状态转移如图四所示:
    * {- A/ z4 P6 \: q4 D8 B
    7 R( D) \: m5 u7 S* l
    ( N& f# c8 R% Y2 B" I( |
    比较经典的问题是最长公共子序列、最小编辑距离。
    " x; _1 A) L# y有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    ) b9 G5 Z' Q1 k( k  O% e& B有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离3 J! q+ g1 ]* H
    3、2D/1D
    $ b7 B8 ^# s! w$ ^3 n* u: Fd [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[j] = w(i, j) + opt( d[k-1] + d[k][j] )0 P+ N# A2 A+ }+ P
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])) F4 I# e5 z8 G8 g9 O2 y
    区间模型常用方程,如图所示:/ _4 ^& A% U0 e7 H! G

    8 ?, _. u7 d' H0 @9 R/ X# b$ {
    1 h) B. \6 @% `. {2 \# n2 e; [
    另外一种常用的 2D/1D 的方程为:
    % X9 W8 G3 U6 G$ g4 Ed [ 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 )
    ) G/ c# O$ q5 M; L% p3 Kd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j); g! X" S/ Z$ O
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
    4 E$ {2 J7 E+ S9 n( \5 t6 V0 o4、2D/2D
    % U5 c% Q* B" ?- `& ^& @, k3 s% C2 Jd [ 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)
    6 n' J( h  N+ j5 J3 }2 V/ Kd[j]=opt(d[i
    1 x0 C# @5 K6 o) V. d" O( u8 h
    5 n: f( {/ X6 f; L& R' F ][j
      Z9 H! i1 d) J$ L3 K* g, P8 b0 I. Q* J$ F; n2 B' z. [; m7 M
    ]+w(i
      C: v; U$ O! M1 c0 w8 n
    + y/ }0 T, f% N6 T" ~6 r. d  g$ b ,j 3 j1 l( f# B& e; p8 P7 v' A

    # o( O, B4 [  T: q* Q* m2 ]) D: b ,i,j)∣0<=i
    1 R: {( R! h7 y( q( m
    7 B) M. _4 f* b8 Q* x <i,0<=j
    ! L8 }; |- u2 N9 ]; y) S, x' ?# Q
    - B$ a  Y+ n' Y <j)5 @9 r$ K2 b* ]
    如图所示:* Q% l3 I2 e8 E# o3 S5 w' d" A8 P
    $ `8 v, ~' R2 c$ T! y

      R% b, I; K7 E+ E9 x! r常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。% `" A' |6 M& U
    对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    # o. E3 U; M$ A9 g) T2 w( b& Wt+e
    ' a5 V. z9 ?9 p" g' l1 S ),空间复杂度是O ( n t ) O(n^t)O(n 8 h9 P3 R  O+ J" P2 t2 d8 x
    t
    ! h6 {4 T. H+ ~ ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。5 a  n" l: R  l- g( A0 g
    3)计算几何
    8 T, @7 X; t  X" j& z$ |, p( _7 h计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。) w* L' O/ w" P3 J( W+ @( |
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    8 j1 L: l( l0 g6 v1、double 代替 float+ G! {5 c  O  `! h: X
    c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;
    3 }! @6 g* m. g7 p* R3 z2、浮点数判定1 G# w7 n4 A& x
    由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    7 H8 G8 ~2 C- t& j* e两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    6 a) f8 x* z8 t) z. pconst double eps = 1e-8;3 p4 J/ |6 {$ K- C& v9 E1 o5 |
    bool EQ(double a, double b) {6 ]) \. A! t, o+ c- F
        return fabs(a - b) < eps;
      g/ c; e; x% H& h! o. W7 Z% n}/ i9 C  [/ h; l! W9 x# O
    11 G. z( A" ]1 d) R& c) e9 _. u
    2
      L# X2 [# E6 F2 h. W! r3
    ' ~, y1 z; b6 j9 S) W4
    7 Q6 R# Z. H$ j5 k' t5 x并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    5 J$ ]7 N8 n  i4 Y2 ]  Xint threeValue(double d) {
    0 k  T$ Y( c* }9 e) T7 h: R% P  e    if (fabs(d) < eps)3 i- ^7 O: S" ~0 T( y7 w
            return 0;
    ) v$ N6 ~" Y. N; [: J    return d > 0 ? 1 : -1;/ I& N4 p8 p2 e1 I! q: T
    }
    1 z1 b% v# C. [8 [: I8 A4 q5 V16 @4 E; d, t* b0 |7 o
    22 m! o- ]! ~2 O/ t  v/ o" @
    3
      ^1 M# ~% ~3 }# R* l$ h/ O& u. w; B4. P8 I. ^* q6 R1 ]
    5
    ! s8 Q1 ?+ m: s9 I$ U% F3、负零判定
    " |2 ~+ F4 z5 c  {" U! U因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:; Q1 r3 H+ X" }7 @8 N% m  g' \) @
        double v = -0.0000000001;
    1 Z1 I: @: q* _7 Z. g0 n/ V    printf("%.2lf\n", v);' x& ]; S# K* l) I! A4 I, f0 \
    18 r" Z* ^/ I% Q6 Y) X2 Z. U( C! F
    2' N( [9 }0 E! e+ U* D3 }; X
    避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:. c# x0 [. k, ]3 z
        double v = -0.0000000001;3 J% n& z! i2 a5 M  L( R( x5 _& k2 k* M
        if(threeValue(v) == 0) {
    7 H8 ]* e1 y$ G$ M" N        v = fabs(v);5 D' e! v1 \7 m9 }
        }+ c/ w' p$ E1 S7 V0 M+ v9 h
        printf("%.2lf\n", v);- z. Y9 J; {& Q
    1, L4 t, E# K+ t. f
    2
    7 e1 C9 }/ Z  f3
    6 t) |! M2 h! n2 p$ @$ [# K4
    / |, ?" e9 e, Y$ b' f( m3 ]7 A+ C' {5
    7 Y9 q9 S9 s& F! U" {4、避免三角函数、对数、开方、除法等; r. ^' _+ e, B8 C. ?  j. L
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。$ W8 h# b" g2 a5 s8 N
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
    - I9 k( x4 y% d9 }5、系统性的学习) j0 M- U: P) ?4 v( @6 l/ q5 c
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;* P' X$ w/ G9 m/ f
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    9 G2 n# D) ]# g* k7 e* M" B! v相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。
    * x/ {0 \/ V. u8 j6 U$ r( y
    & I) @4 ~7 C- d3 \

    % h) y! t) Z+ a$ s2 Q; l学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。+ j8 c- N9 Z( t7 z6 Q+ J
    4)数论
    6 m, t: f5 \0 Q刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    7 r0 _* m1 b9 T! n4 p" v数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。
    ; j8 K. X3 |8 n, a当然,数论也有简单问题,一般先做一些入门题提升信心。
    ( Z. H. `4 b. n+ f8 Y1、数论入门
    0 R7 I2 C% G: @, p, {/ [0 h( R: g主要是一些基本概念,诸如:
    ( Y% x$ T$ j7 T1 x2 }9 O5 o* ?整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;" E0 R4 T" x- H: s8 L
    2、数论四大定理
    9 L! C  ]$ }# E( n这四个定理学完,可以KO很多题:
    / I2 |7 y8 d+ h/ U0 Q5 V' F/ K6 O欧拉定理、中国剩余定理、费马小定理、威尔逊定理9 y- J# t$ {9 p5 k/ p
    3、数论进阶
    4 M+ i3 [$ v- w% {4 @3 x系统性的学习,基本也就这些内容了:
    2 h: Y% f4 S8 @& S0 G' r扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。, M8 }. [& @0 H) z( C8 h1 \
    5)字符串匹配
    / k: Y4 ^$ K* H3 A9 H字符串匹配学习路线比较明确。6 H$ h, z5 ^4 I6 Q* [" x
    先学习前缀匹配:字典树。9 ~/ o: V4 a: r* q
    然后可以简单看一下回文串判定算法:Manacher。
    & I) _. I+ l. Y4 _$ i% X. u9 x0 U. r以及经典的单字符串匹配算法:KMP。' G2 ?% N/ a* r4 j% N6 u' P+ V
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
    # m) M1 }7 O  m: P! I$ [, j2 G3 e然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。& p* B% R& P$ k" s+ {% c) O
    关于 算法学习路线 的内容到这里就结束了。7 N3 V+ _) t- p3 v: n
    如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。2 l. u8 m4 {; j* o  g1 l
    参考资料$ X/ }8 m/ `, `  x. Y4 w- o
    【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    ) r/ K* [3 N9 N5 h& F  u3 N【阶段二】C语言例题:《C语言入门100例》(日更); J7 ]8 a9 I  L' e% z4 L6 F$ w& b
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)
    ( b) U$ R0 R7 S6 u) P5 ?【阶段四】算法进阶:《夜深人静写算法》(周更)6 P8 p2 v5 k3 e7 V# R, U& @2 ^
    ————————————————
    3 d. U* M% c/ ~; `8 b版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ M* g, C! K) |( H9 T- N
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228- g9 ?1 l  C% |- d5 [) W

    5 l. d% c3 |4 s: W. Q- O# l0 t. Y3 p/ m5 K1 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, 2025-7-24 03:09 , Processed in 0.391496 second(s), 55 queries .

    回顶部