数学建模社区-数学中国

标题: ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏) [打印本页]

作者: 杨利霞    时间: 2021-7-8 15:06
标题: ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
# e) b! l* N! n4 O: @
❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
' u  |+ `' x4 g$ r$ R# X: ?+ L" Y5 Y0 o; `% L1 p, c- h( _
前言
2 M" o# l. B& Y3 \" W/ f  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。, K" ~4 C0 [& a9 |% c/ |6 t
  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
9 i) |8 g* V6 N% V( I; c5 c* y  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
2 {  W7 R0 D3 y4 W! i  ?  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。( P1 t+ x9 l4 c/ E( [8 }
) m6 p# q' s# W9 J

( q7 A9 C% H- x3 s( g' A9 l8 h% F( ]% w/ H5 g# i  }+ u
5 b1 f9 _9 Y1 j; |( |9 P
6 m3 v' I2 [, J" o- t! s. S
8 r" b% c1 G. l) }% B, }

  f0 c! P. K' I- N( j* X1 v
3 T0 R( H# Y0 d* o
图片较大,文章中有拆解,需要原图可以留言找我要哈
6 g+ @; w. ~, J, h, G1、基础语法学习9 @4 D, g  n% r, g+ D7 _
算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。2 g9 K2 C- p+ B0 |. {. V; C
因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。+ M2 u; P* }' Q2 x, @1 _& U8 p

. G+ i* B  f" L/ ?
9 o& i  B3 y, P' u( h6 ^; B

6 H1 ~& Z, H, m& C6 Q& W  T! C
! Y; H9 N9 K$ t9 Z% c' g: F
1)HelloWorld
! q8 @% R. x0 m' F6 E# r无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。; q4 g$ B9 B3 {3 C; s! M
2)让自己产生兴趣$ x2 G$ ]1 d0 J' I/ c
所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。; P8 S4 }3 y/ ~, g' T
刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
; ]: L$ ?2 G. Y) D2 ?3)目录是精髓( |6 q2 {( R& A
然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
4 j, d; t- J3 w; T) Q% ]$ R1、第一个C语言程序
$ s# o) L5 S3 f2 [9 ]1 R, M* Z% T2、搭建本地环境) |- I; y: W4 G. `5 w
3、变量
0 N- ^' l1 o) i4、标准输出  l/ `6 V2 c' T( A6 M+ l8 p. W
5、标准输入! }- y' ?0 u2 r. a3 m4 s* ~0 ~
6、进制转换入门( {- C4 @) E9 M% t( [0 U1 r
7、ASCII字符. D% {) }. y/ h1 o
8、常量4 g0 P! p6 j4 Q* r6 h! f& V. f
3 B5 F6 }: a% P' }9 \' m7 ]$ I8 `
* d, s0 M/ x' R7 V' y
如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
1 L6 S# T, g9 A! _  L) I4)习惯思考并爱上它
1 X6 _0 y# A+ A6 p+ v: E只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。. z+ r+ G* S$ |* v% s* `+ v
就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。" _% x1 I: @, k! Q
5)实践是检验真理的唯一标准& _$ Y3 M! M$ F
光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。* o) j7 j0 i1 }2 I9 I; S, f4 v
所以,记得多写代码实践哟 (^U^)ノ~YO: m; v, }1 {4 D
6)坚持其实并没有那么难
; [! v  k! I! L& {8 M3 u& [每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。, p: @/ v3 d# W
7)适当给予正反馈
1 _" q3 {& A& Y然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。# N# P9 }# _. r' u# |
当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
/ }2 X; [4 r* E, C0 W/ C& V看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
# O) z) P& {7 |8)学习需要有仪式感
% b% K/ A" X3 v, f2 S' P* R那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
- F! `, }" g7 W. D# {介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!; V9 X5 }1 @& w- I" w% m
我也很希望大家的学习速度能够超越我的更新速度。
3 g: Q- k6 q+ {# P2、语法配套练习3 A0 r! m) i' ^* T- Q/ Q7 V
学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
  J, Q3 {* |: ^6 [0 @而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:& P1 u3 n& y/ |( e8 t
0 `. J3 y) g% l

4 X: \; y% R6 {( ~+ V7 ~: n4 [- o* K# s- S

9 N( `* f# `/ J从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
- d; G% E5 y  `3 ?# V' G) d这里可以列举几个例子:
2 H' z* L9 d; A& y2 d% d! m) f1、例题1:交换变量的值! ?: ?5 z( E) F; c
一、题目描述7 I, I2 ?* j2 t# B
  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。4 G7 [) n" l( v) f1 O& `

3 t; |+ ^1 H/ H: `

# G- D* V( ]3 v. _3 {+ H* v: a
0 s: k% g) J* A" Y0 S: f
7 [6 I+ Q1 O1 _/ u
二、解题思路
: S1 L( i3 r) }1 L6 e难度:🔴⚪⚪⚪⚪5 @- W' z: P: k5 j+ Z
* ~6 v' S6 M- T. W
- w) t* h1 o  i: W
这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。" C8 P4 \3 m0 A- E
a, b = b, a" x/ l0 a0 y0 M" D+ ^, @9 ?
1
) ~$ t" Z  ~4 e" u* ~; Q在C语言里,这个语法是错误的。1 Z/ |3 B3 e5 H! l- e  d
我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?$ A; k5 \* A0 x1 Q+ k: J
当然是再找来一个临时杯子:
  {3 a$ i7 e# S: e1 z6 Q  1)先把 a aa 杯子的水倒进这个临时的杯子里;! u  A! a/ T8 `8 L- ?& T
  2)再把 b bb 杯子的水倒进 a aa 杯子里;: [2 S$ m. j# K( I% y+ X% o
  3)最后把临时杯子里的水倒进 b bb 杯子;; s: U# s9 M* F. X, Z
& P8 |+ D; i! S) r! u0 f

& h! u" @% p. ^) k: F这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
) L' o  c9 w0 y4 m. w' u
/ D: T. l$ a: @  l

9 F# k, b7 V4 M' ?: m3 {三、代码详解1 P9 z) J4 G3 P; k* o: i' F
1、正确解法1:引入临时变量( A! @+ m0 Y3 m, i$ ]  x1 J. n' Y. r
#include <stdio.h>& y. [  v4 y7 B
int main() {
6 f6 R7 `$ l5 s& k8 C  w5 o. `    int a, b, tmp;! q! s0 f+ t7 n
        while (scanf("%d %d", &a, &b) != EOF) {
' L1 H9 P; Q, z6 T. ^            tmp = a;   // (1)
- w9 ^% o4 j* v; u( M, {. |            a = b;     // (2)
: k. B+ @) ?+ J, o            b = tmp;   // (3)" \# @* H* X: _( U7 C& e& E. O
            printf("%d %d\n", a, b);
  d4 m( ~, R; p2 w! v        }
% }2 x/ }# q% U+ Y- g0 H7 I3 f# G2 A        return 0;
8 {4 `% `4 L7 n, M& S. K}* h$ f  R# Z" s7 D2 Q& [
1* k1 \# \& H( T9 X9 A) C  y
2
, R5 E9 W! r5 v' ?, \5 T3: s+ J6 Y, y# w3 O
4; ?6 b" h* ]6 i" @  k
55 w: h3 R; M2 N; D
6
- O$ l: U) a) X2 z- @; F7
+ E2 X0 `7 p4 U+ Q# q3 i8
( |$ s9 P# W/ l! l$ a, v9
6 `8 S$ |4 s  c; l+ s1 }  D10' Z3 a% a6 t, H3 s8 c
11: e8 G0 z  f+ ~- ~
( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;* b: i  Q6 _: s6 _, i! @3 G% {
( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
3 U! L8 o1 i& `9 m( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
5 B6 R' n& J( s7 t. t$ _9 c4 v这三步,就实现了变量 a aa 和 b bb 的交换。
  r0 N) @6 `2 ]' F9 ?" V2、正确解法2:引入算术运算
6 ~7 q, h2 f: k#include <stdio.h>  `4 h1 [" ]' A1 N3 t. Q9 C( j
int main() {
! E0 c2 G  f; R0 ~! [. K    int a, b;
# O; p! S! P1 R2 _8 J8 C' m/ ]        while (scanf("%d %d", &a, &b) != EOF) {# b" d3 C+ _: D3 M/ ]
            a = a + b;   // (1)7 C% O$ {: U" r8 d) ]# z3 L( q
            b = a - b;   // (2)* L6 V- S+ i/ a
            a = a - b;   // (3)
. \$ u; S, u) U            printf("%d %d\n", a, b);
$ b/ i* [# l5 K  r        }
% c& |5 G5 Y+ N0 q+ V        return 0;, f' ^& L5 r9 w; J; X
}
6 T* M: V" J2 E2 ~+ R9 t! J! v1, [1 h! b1 U3 O/ q% F: a+ m" K
2
8 f/ z/ N0 W2 y* L3
5 m, e" o5 g& P4! ~& c& g( p% t% X. L, i
5
9 n6 T& I+ o) T6
, t( {. E$ \6 ~7 B) C8 K) s% c% d* z72 m- Y3 a- c6 C
8
6 ?; {  G7 y  q6 Y6 n' R) V6 q9
, C2 B! w& \* F3 C9 B- U10
: k1 [; I' p7 J7 O9 b11
% ~& Z" P" D: _5 w# S( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;: i( y# ^; r3 @7 i6 w- _
( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;5 k* x) |) h- a) q& C3 s5 X+ ~
( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;- Z7 r1 c- B) l& b# h
从而实现了变量a和b的交换。
6 |# Q1 ~/ ?% [* \9 C! x" s6 O* d* u3、正确解法3:引入异或运算
4 ?% Q4 `7 ?; m" ?9 p' `  q9 ~首先,介绍一下C语言中的^符号,代表的是异或。
8 ~% U" W" P4 L+ G. e: ~7 T4 T. \* J二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
0 Q$ g2 `9 ~0 c: i6 k左操作数        右操作数        异或结果
7 [# w$ A& R- k& [! n# p/ N0        0        0
/ l4 b$ W, u" y3 B9 ~! S# Q9 y1        1        0# Y7 Q3 F1 R- {! P% N
0        1        1: y6 r+ p6 H% \: X. ^# E: w
1        0        12 m! t* @: T: f& F
也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
; n: S/ @% ^$ p9 k* K. |这样就有了三个比较清晰的性质:' k) u- J7 M# D# H' l2 M$ `
1)两个相同的十进制数异或的结果一定位零。
3 W, F0 z7 d. V' X2)任何一个数和 0 的异或结果一定是它本身。
6 t$ Q" @3 u9 _7 }6 C) b3)异或运算满足结合律和交换律。
/ P' j7 N7 j7 u#include <stdio.h>( N' F! p  d. o5 c0 g
int main() {
. V( X. e* m. ~# Y  f8 r    int a, b;# C( R- B: [5 P/ @" _3 y! B
        while (scanf("%d %d", &a, &b) != EOF) {
2 n% Q6 B1 r2 [8 I4 U  D; @1 x0 _            a = a ^ b;   // (1)/ I! @3 j/ ^. b3 {
            b = a ^ b;   // (2)
1 s7 `( n8 e+ i9 D3 t( l            a = a ^ b;   // (3)5 o3 F' G# G6 R4 w0 L5 m3 R$ y% Z
            printf("%d %d\n", a, b);$ P( s; x( m0 q3 v8 y" _" J
        }9 Y' a- T, q+ K; J2 S
        return 0;
4 d6 S5 Y. w% X3 [' r& y}6 h" u# T& I) t1 S% p8 _
14 ]" d+ M* D( B
2
! p' B5 u9 y8 o" A% s# q3. T; {8 s) K2 ^1 L7 b+ A
4+ B, b& h8 U$ _7 r0 J
5
% @& S0 j+ L6 G1 ~  c& B( Z2 R6
+ O* h  K, O& C" T  Z3 i5 b2 v7+ M: V" e) n8 L0 f
82 F; a8 L5 F& D
9
: \4 ~$ T, [6 e  E6 E0 `10
9 k8 J) Y% X7 r" s1 ?, x113 f7 Q9 i) f7 h3 n1 Q
我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。2 p- p7 N$ s. J* b/ F
而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。
$ ^* x5 @5 z9 b4 g从而实现了变量a和b的交换。
9 H$ y7 ?$ M$ L7 M, @$ t1 r( o* b( R6 u+ k6 X" f

  B5 Z3 B+ M5 W: R7 v& |4、正确解法4:奇淫技巧
7 ^$ K8 a' {# D当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
- E( U' A8 ~* }9 {#include <stdio.h>
' q- f! J" a" e6 T2 U0 Yint main() {
: @1 y: x  @  ?% z; S  B* m4 [/ R9 Y0 r    int a, b;
+ k/ A8 ?7 }. c$ E: Z! d        while (scanf("%d %d", &a, &b) != EOF) {1 Q4 ?, [! `& }% J: H. I
            printf("%d %d\n", b, a);
! ~/ A: m; ]$ e* V2 c) `6 u        }
. I5 r0 U0 [, }+ z1 W        return 0;! z3 X4 c7 q' }. B" R' Y3 ?% ]
}
% O/ H. E! a% X3 m8 |% q% Q1
. z& S; q* u( s" U1 @22 w+ U4 i4 \* |7 e& Q* L! L
3
* D$ x7 g0 O; n3 o4$ R5 O2 g7 J- w; L! X4 B3 t/ L  M
54 j7 y% T5 D+ O8 d2 y# c
6
" @) \( a0 z; [3 e7
7 P5 F' w* s" [1 @% N; ]# k2 n8$ I( _+ r/ C- U3 h1 I- Y' P3 j" N
你学废了吗 &#129315;?
8 ~' \' @  \  Y) l. Y$ }0 q; v2、例题2:整数溢出
: k2 d" v" S' T一、题目描述
5 L  w2 Z: b/ T9 z8 s: O  先输入一个 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 + h  }8 l7 I& s; ^/ M
62
. L8 U+ n1 o- }! v/ `( z  F& T5 f ),输出 a + b + c + d a+b+c+da+b+c+d 的值。- e/ s! b: X! W& B& i! [, Q' _
2 ^; h& g' L' }: y
3 L2 j2 g% Z  S3 B; B5 j
二、解题思路
7 P1 b# f3 M+ e2 ~难度:&#128308;&#128308;⚪⚪⚪
, G3 O& e1 f  d+ M7 ]+ p' ^. A1 ]2 j- \

( q5 k' i  Q7 R2 u- Z1 @( l2 L这个问题考察的是对补码的理解。) h" [5 R$ Y! Y
仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
9 ^% q; I3 h, x. \! |8 T& O2 P62
9 p3 q% J! _/ S. t7 P. u ],这四个数加起来的和最大值为 2 64 2^{64}2
9 ?. S0 }6 u; m6 z4 P$ U! u7 S! A64) ?3 P& E. ]# e' x% T$ D
。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
$ L6 A8 \2 G, g! l  e9 O5 u9 d+ ?63; g& D$ i- R4 ]. ?2 _2 [
−1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12
$ q% Q, H0 I' A2 y5 r64" }# V/ u; g6 m- M
−1。9 [1 M  |* u3 {' s/ b! M
但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 1 i" W& t1 S2 J) p
62& Z9 T" F; J' L9 @
  时,结果才为 2 64 2^{64}2 ; l2 A3 g0 S& ^; w3 H) Y' G) Y
64
! r* l- G3 C9 L3 l  X0 c3 X* G ,所以可以对这一种情况进行特殊判断,具体参考代码详解。* `+ ^+ @' r% r% @& ^
三、代码详解
( i/ O, F3 F3 c% L* L( d6 E% ^#include <stdio.h>
9 B( o7 }, R5 ^1 ~; b% l6 rtypedef unsigned long long ull;                           // (1)6 x1 p: w( z, B: ]/ L* y5 c# H4 w
const ull MAX = (((ull)1)<<62);                           // (2)# @- e9 e7 [/ w5 u" d/ K1 D  R+ \- T
" h  e6 O" t$ D' d1 Y
/ x5 r# Q! \. ]1 z8 [" x2 A
int main() {
6 I+ V* ]9 ?% o        int t;" O1 g! p- Q9 u9 j
        ull a, b, c, d;
2 s+ X. {5 Z, I- Q6 s- k        scanf("%d", &t);
8 V: C$ B9 o% m9 z        while (t--) {
' W. R6 R" C& {                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)% x# w0 Z1 i1 z/ R& y- D
                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)% a& A0 I0 ~/ X/ f" |
                        printf("18446744073709551616\n");             // (5)
% P% q: v+ |' v  P9 o                else
/ \: z: X$ v4 B% M! Q+ W* E                        printf("%llu\n", a + b + c + d);              // (6)
5 |; H/ f( `0 L$ X- V) s        }
! p& Y; K, m9 u. `; g+ x        return 0;& b& d+ L9 z% S$ k# G+ c
}
* P* E0 @( y7 _5 F9 |0 x1
- V+ B. X& x, V( ^# P% r" A3 k4 O3 p2
/ h# I! f% o1 s+ K; k+ l3$ {7 a5 ]- [1 H, J( ?
4* F* W8 s! M2 S
5
1 @9 z: X4 T) J1 r- H64 D) ?8 r- h' y$ K
78 G3 F0 {2 f' A
8
0 o* F& z# g, t2 [. ~) F9# n* F. T. `/ D7 o' R5 q7 B6 E
105 f- S# [  t9 x' z
115 u. }. ^$ l! ^0 ~7 E) }! f7 V
123 Y2 L  z4 j5 M/ n# o: c
13  S9 |% V0 I) ~* I4 I& B: F1 h
14) I) L/ r% w6 v1 n7 O5 t% N) W
15. ?: ~# ?' B" d+ b. N1 m
16) e; }) _5 G6 }
17; C. w3 K; H4 f7 L- {; t: V
( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;' z4 I+ y$ `5 V  d+ w" }! n5 g
( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 ( W5 p5 k5 `" m" O4 Z4 ?
62
5 M5 o2 U# ?1 O- O9 j* m1 N, s* D ,这里采用左移运算符直接实现 2 22 是幂运算;
" e; x( M4 r/ p. x8 V数学        C语言/ ?. F+ q* t$ |, V, J
2 n 2^n2
( H* q3 `/ u8 j; m) z# O- an  R2 y. k0 K. j4 z5 b2 e
        1<<n7 `' C$ X3 F* J  W: R& k
需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
- ]6 d+ I  ^' _" R/ q5 Q: c1 ]( 3 ) (3)(3) %llu是无符号64位整型的输入方式;, ?# H$ t$ F# O  X7 o6 @) [" m
( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;& M. h  Q6 T) m& A+ j
( 5 ) (5)(5) 由于 2 64 2^{64}2
' T6 R5 j+ b; H. C64
) _2 v# O1 }- p. p- |, ^  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;5 w2 M+ O$ I& e: e
( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 # d2 y! r! {! o) {: c2 t* m1 D
64
( A9 R" H) r0 o. f6 i −1] 范围内,直接相加输出即可。8 [6 G( o5 v' g$ l" Z. j
由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
7 e, T4 D0 V) g, n- k为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
4 j4 R( i- O5 e: @7 ]7 q+ j9 q# s) y. d7 {* d/ H! M* G- D( ?

; r& x' ^9 X) J5 ~  i& e3、数据结构
; K( C; ~/ W- f3 R4 o6 ?《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
2 A+ L8 Q8 x# ~' \# y1、什么是数据结构: ^( ]( y. f# K9 f& d" g1 O
你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
: h6 o. ~3 Y5 z5 c/ z如果一定要给出一个官方的解释,那么它就是:5 E9 p$ ]. V/ V# Y# T
计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
" r. J0 E7 c/ V6 d! Z8 a+ P& U  G: r: C
6 a$ G4 i% \2 A
是不是还不如说它是堆,是栈,是队列呢?' e! Q  T# \8 F: _
是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。& x( D* ^3 B/ v7 d7 e
2、数据结构和算法的关系
9 \8 Z4 }; O% c  |# [6 e0 D. J' L  A很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
" h/ Q$ l* R  V2 a数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。3 L; L5 [. E4 B  p' f4 x! D
而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
, [4 n$ U  q+ Z8 m8 e讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
4 }, E' ~, C# d3、数据结构概览8 R" X0 p. T$ d! i
周末花了一个下午整理的思维导图,数据结构:0 [0 p; \# y$ ]  W
2 m" p* F3 Y/ K$ J  ~( C' A
/ m$ C; d1 p2 O4 {% L% @5 Z
常用的一些数据结构,各自有各自的优缺点,总结如下:. {' J# T9 u  Y
a、数组
: Z4 \. ^2 E  c0 E内存结构:内存空间连续) W" f2 u& c5 Q- k  N  u
实现难度:简单7 V: Y; c9 I' U+ u( |; T1 p
下标访问:支持
/ V0 W7 W- j" ^- r7 `. n分类:静态数组、动态数组: {/ q7 K1 r' H7 f: a- A. l
插入时间复杂度:O ( n ) O(n)O(n)4 U/ T# _% |3 u# A# y  k$ J
查找时间复杂度:O ( n ) O(n)O(n)( r3 E6 n6 \% q2 R
删除时间复杂度:O ( n ) O(n)O(n)! U; R& j7 l$ R2 T0 O1 F* p1 N8 v

, h" p9 x1 z% \) T& k) r* T& O) W
2 l6 _6 d; b. E* y2 W3 _2 u
b、字符串
/ D) f' z, F  W6 I% G' [  C内存结构:内存空间连续,类似字符数组
. [2 z) M1 z& h: D' W实现难度:简单,一般系统会提供一些方便的字符串操作函数
( ?9 P$ O. r& s7 K8 ^下标访问:支持
) x, H4 U4 J* {- j7 q插入时间复杂度:O ( n ) O(n)O(n)
' ?# z. b0 v+ ?+ F* `0 r查找时间复杂度:O ( n ) O(n)O(n)
2 f3 g7 t5 ^" T+ \3 V: V删除时间复杂度:O ( n ) O(n)O(n)
4 }0 @  }  S: h/ x6 j9 l: E3 K% E  v& l

% @+ F2 Q( A9 s3 Yc、链表( }2 o6 n6 S( D' f& T0 A3 |
内存结构:内存空间连续不连续,看具体实现5 H, O" D" W3 t+ ?8 }
实现难度:一般
+ D/ I5 B& I" Y下标访问:不支持
  S$ }; W1 B; J. Z: r& g  z分类:单向链表、双向链表、循环链表、DancingLinks
; K- a7 K6 {# D插入时间复杂度:O ( 1 ) O(1)O(1)7 g7 \% m) r/ s6 T& k9 {
查找时间复杂度:O ( n ) O(n)O(n)4 x4 A, `  e  a' s1 }- g
删除时间复杂度:O ( 1 ) O(1)O(1)
& z, G( U- y9 O# J
( Q3 u$ _) r% {7 o- r

+ R1 G% v% t( b0 l/ [d、哈希表
9 M' R& h9 B5 {1 a内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续" G& {+ ^) p: N; ]9 |/ \
实现难度:一般
9 B7 y! H/ p4 e( g下标访问:不支持# B) a' P8 ~: c* A- n" ~2 l
分类:正数哈希、字符串哈希、滚动哈希
) E2 p9 c$ w( K) U9 x, K# M插入时间复杂度:O ( 1 ) O(1)O(1)
4 D. h5 C" Z, C/ S/ H. U8 w' n查找时间复杂度:O ( 1 ) O(1)O(1)
9 M4 l) ]" Q% @/ Q( l9 M3 A6 A删除时间复杂度:O ( 1 ) O(1)O(1)
0 w6 e6 g! |) R, P/ I7 l0 X
' z' `4 h) _# R% ~  \; m

  `+ T9 V; O# c4 o2 c4 g, |e、队列- Z7 k. }1 m  I
内存结构:看用数组实现,还是链表实现
* S! m: P+ Y: Z9 ^实现难度:一般6 k7 s: d. {) n0 Z+ H
下标访问:不支持4 S7 N9 N4 g- G$ u
分类:FIFO、单调队列、双端队列* ^: E: o, _2 @& D5 {. U0 Y
插入时间复杂度:O ( 1 ) O(1)O(1)2 a4 r; P% x* b/ c, A/ r, I$ ^; k
查找时间复杂度:理论上不支持
2 I' V! _6 ~; P6 G4 g6 V删除时间复杂度:O ( 1 ) O(1)O(1). v# `  G& }& s. i0 Z
0 e3 A9 s& h4 I- a- F
  ^9 [) m5 B) ?% i1 g- w
f、栈; C8 \; ^  U% d* n9 }" D
内存结构:看用数组实现,还是链表实现
  w# J  H( g) V( y, {3 w4 u实现难度:一般; d; e; F, h; v. d& x. N0 k) `5 X$ G
下标访问:不支持( ?3 Z  |" I$ I
分类:FILO、单调栈
* L" X( ~% s" U' q  ]插入时间复杂度:O ( 1 ) O(1)O(1). }3 Y4 H* I- a7 W. S
查找时间复杂度:理论上不支持7 _# b% f7 A8 b6 E
删除时间复杂度:O ( 1 ) O(1)O(1)
1 @7 W- Z/ k! L! w6 d" A6 k- ~! s7 H' T. p1 E, e4 i" a5 s  D

+ n+ F6 y* b' i3 \g、树
0 R! f3 n) }9 P6 b* w. ?3 n内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
9 U' @! {/ x: z- |实现难度:较难3 N# n) h7 \) [3 Q" H' h  n
下标访问:不支持
2 v& s5 ]8 R: I0 M( c分类:二叉树 和 多叉树; c. R% ]+ w' w! Y. @6 m
插入时间复杂度:看情况而定
( x% a3 g' i4 F5 j/ N, X查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log , A4 j- T( Z0 i7 R
2
4 X' U1 Q! b0 P​        : N. b% R( y# ?9 [: _3 a
n)
" `2 v4 o" j! Y8 K% R删除时间复杂度:看情况而定
) M9 y. }3 ?/ w) [. n
" g  h( g0 H, d- w

- \( I4 ~  g5 G" K" o3 W1、二叉树
# q. b7 B) S. F1 q( C0 @二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
+ v5 ]3 H# b0 ?- B1 a# Z6 z其中,堆也是一种二叉树,也就是我们常说的优先队列。
4 W% G; }) o. Z9 M& s2、多叉树
. U7 }0 T/ ^3 y: X2 e& `B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
, c5 \! w* U  D6 m7 eh、图
* a+ L' |2 _+ E, C内存结构:不一定4 m, s: P3 W4 D3 w9 Y, {* U  Y
实现难度:难7 {; C9 Q  n+ e
下标访问:不支持* k5 [* _6 K' Q7 o" B/ E
分类:有向图、无向图
" n8 }6 J& B( ^: E4 c插入时间复杂度:根据算法而定
. b, T, x+ Q# r! c$ {查找时间复杂度:根据算法而定  `/ r' N6 R- r5 k! U
删除时间复杂度:根据算法而定
. t0 ?0 U* ?3 R6 J0 Q: J
7 M; g  \/ g/ \+ L
8 R8 |; t6 s. c2 Q0 O: W8 S1 X$ A
1、图的概念
, K  b4 P! ]. U, j在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
8 I8 x* P2 ~, e* @$ }8 _图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。" A. N" d. o+ E; }1 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 为权值,可以是任意类型。6 t; {# A+ w: X& ^1 l- ^3 Y
图分为有向图和无向图,对于有向图, ( 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;
& z! E4 U* ?* E1 T6 ^6 d; B2、图的存储& n% M+ G4 w& E$ _) i4 t0 b
对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。6 ~; J& n8 v, Y3 }6 x5 m$ j
2 ^3 w, s% h! s' \4 y( r

6 v" e( D. B( v" P, N1)邻接矩阵! v2 v6 v- d! j" ]
邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。( w. a. h5 e3 h) }
它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。) ]0 B% g, s" R
[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[
3 S9 A* j' I4 B  F01∞9∞0∞8320∞∞∞30* T. R. W; ^, H- Z8 I9 @
0∞3∞102∞∞∞0398∞0
# x3 i, c* C0 E* v1 X8 Q$ O\right]3 l0 P+ M) d8 [) n: h
9 r! ?# M2 E7 L) v2 _
8 `9 ~- p) \4 F6 b) w5 X! ?

( n* }4 `$ d1 y$ ^9 d$ q9 I  L
6 k& Z1 \7 r% \) ~* E​       
7 H: h: X* i6 X; D, c) p# {& w  7 s3 d5 f. B* O, V# y9 U
0
) ]+ e2 o; m3 w  a& q9 K/ f# {9 o1
% u/ v, M9 J! D' K+ C& ?
; i5 M: A  G' a- }) D2 K0 X9$ [* X* y! _% N9 V5 k
​       
( |; z. f6 q" h, d4 X. E  
7 X# n* B. Y/ [; e2 V' B# ]. j1 a7 [
0
& C! v' x) v/ [
3 m5 ^: A: ~6 u! W+ C9 ?8
  v9 ?  O$ z4 f$ ]5 [, ?: P​       
& s( m; q* g3 B( [  `  
, N7 w% j  \, ]32 E# n, ?* @, u  ?0 N; u" Y  L0 j
2
& [$ [. U2 _- ~4 I& j/ Y0 _0; K- W  e( R" j& |0 v

$ R# ]# d8 G$ u" r# {" s​       
1 b% {# Q# [( t; J  
* ?" l+ J$ g3 b& \+ B# Q* V3 H3 v
$ C! k/ {0 `3 {4 Y
  M: f- a6 d3 J3 o3
4 o9 r+ |) u: V/ R4 J- D2 S. m0* G. ?. D$ N* C: v  |3 O. n
​        1 C: u9 v* Z" X
  
3 M, W; z7 L/ Q7 e9 K0 L
1 i* B- U- `7 C) o+ J0 L! Q/ }& u2 t7 I3 q
+ J% n: L# f1 r( N3 i+ a

3 l/ [* G& u# a) ~, x. |​        7 m- U; L) o6 k- a, \2 G1 |3 t
: A" Q/ U- e9 M+ U6 {- _
2)邻接表
$ W( A' ~) D& _1 X4 R' V邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
/ |' o. Y0 Y1 j它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
9 m, q* j! z) z! e1 D6 y8 s, s# B如图所示,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) 二元组。# ~) @. \+ @0 ]. M( G+ j

3 R# Y+ s' ~) k- g0 @

' `6 j5 {( r- ~) x; X9 d在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;$ f6 G  {, N; S
    vector<Edge> edges[maxn];1 O" ]7 F8 x. T5 }
1( C' H  ~" Z% C. o* b# L1 `
3)前向星% ^! O3 }  L3 A; u3 l7 z  y' I
前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
( _& E) n! k; B% O# T9 F它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
7 f% o) m4 T9 W" s, |$ j如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
$ O8 q5 c2 [$ ]8 D/ S
# U& c! k, I, t/ e* ]

0 _; O9 D, c9 p) r5 B  B2 B那么用哪种数据结构才能满足所有图的需求呢?' Z0 X4 [! i* u) o
接下来介绍一种新的数据结构 —— 链式前向星。
# X6 p0 b5 x: q) V. @  h0 v/ B$ w- }4)链式前向星; _- c) n/ g0 q
链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。9 v5 o( a! v! ?. y* d/ R
具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。9 w0 H, I  F" C9 c
边的结构体声明如下:( z' l- Z: e' B# p1 v9 H
struct Edge {, |; z6 Z+ b, E& z8 o, g* D! T. X
    int u, v, w, next;
6 i4 ~! P# n3 S; N) a    Edge() {}
) P/ v" P, w* T3 j( m    Edge(int _u, int _v, int _w, int _next) :
+ s5 I$ n1 H3 p, G; D- T4 @        u(_u), v(_v), w(_w), next(_next) 8 g# d) `- j% o0 i' m+ ]: [
    {; s) d1 [3 W6 V5 Y* c: X* i
    }
1 M/ ~$ d9 x* P" P% p+ g. t}edge[maxm];
4 X5 X* C  F* }  h" H& h, j8 M18 ~9 i& {" m! v$ F4 J( O
2  s' I7 O4 N" [
3
6 }7 L: Z/ x1 K" F" n2 O4
# u+ H& m6 ]5 ~& {# R. G* z58 Q$ l; d, g3 B% S
6
" D2 V# g+ ^- T0 }' k7
) I4 V+ w, e4 Y0 z' P( F; X8
; [% \& m9 q/ A1 i初始化所有的head = -1,当前边总数 edgeCount = 0;" k, R( h6 m# X6 Z' J
每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
. f% v6 N. S! G* k  z' `void addEdge(int u, int v, int w) {
5 R% A& P. p2 z  B  F/ s9 E7 B    edge[edgeCount] = Edge(u, v, w, head);
* n: b: b% n6 y! G- C    head = edgeCount++;
1 V: t' G6 r  i8 V% u}
) c. S4 q9 f+ p3 @4 Z1
2 ?, V, e! u6 d. Z  w* s  q6 x; P! Q2; ~9 B' n7 x* B/ d. ^' k$ n
3
* E( p% g, Q3 ~4. P; K* E1 N/ W7 [0 r8 Z+ `
这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。5 Z) r7 s6 }, M+ G8 `. E
调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
6 G/ q2 a0 [* j5 Xfor (int e = head; ~e; e = edges[e].next) {! i9 l5 T* j2 A, F$ ?9 }4 s
    int v = edges[e].v;) V$ c! J! C3 S6 n8 P, F* z0 n
    ValueType w = edges[e].w;
$ `# n4 I; I% g) r( t    ...  W0 G9 L8 u6 Q" F" D& f8 p
}6 |3 @' H9 b6 B: @0 f
1
% Q/ g  {' j" x29 J+ ~1 q2 @  l6 {. A
38 b3 n) e" n' O+ K+ [: t' x5 O" M0 s
4
/ \) J! t5 i6 v  g/ M55 X5 v8 k+ M9 D. c9 k& B9 i
文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
# f. _% j0 X% _0 i$ |9 w' L8 e: x4、算法入门6 L" C3 n% b) L' U+ _# @
算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。" c/ t$ X0 m# D' K3 `

1 s6 }/ J' O; m0 C" `

+ `" Z7 x: ]6 d- O3 s1 S& P入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
, i& N, c' {3 Z  w, s8 G+ M: f对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
, F0 Z* Z  y( K5 O! w$ c1、枚举
% H6 j- s0 y1 l$ Z6 M% E5 m: i枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
+ a( t. F/ p2 \, R对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
8 ^, J, N5 D* S4 B1 E2、排序' ~: J# A6 k, j: n
既然是入门,千万不要去看快排、希尔排序这种冷门排序。0 H6 S* L- v5 ?# d
冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
) i% }' t5 n# J2 V  [  x# @C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。4 ~" {' @/ s8 n! Y
3、模拟0 j2 }0 m6 F" g9 G2 v+ h
模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
9 v+ b& @' P+ _$ ?; X1 Q' t, w, U不管时间复杂度 和 空间复杂度,放手去做!
8 z: j$ c* z$ W- _* \但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
, x( P- X* H# U4、二分
; j; Y# |# e+ E+ h8 ^/ Y& Q二分一般指二分查找,当然有时候也指代二分枚举。! J: f" N* e2 G+ `( r$ Z
例如,在一个有序数组中查找值,我们一般这个干:
! T: X( D/ I0 p! m- e/ L1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;( V  [/ R* S6 ]0 Y8 N
2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:. y! z8 Z& X2 z/ N
  2.a)目标值 等于 数组元素,直接返回 m i d midmid;4 E) @* h8 \9 q
  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
% H; S3 T% w8 M( d0 |& f9 G  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
2 a1 L% U2 F' T3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
( i1 W9 j0 p6 y9 y; `5、双指针, |9 N; p( J; s- y  f7 l7 d4 h* e
双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。! K9 |" m7 b' V2 _

' \; _: U/ X% |: F- n
* M: M% B! q' K' P% y# N+ A
6、差分法
5 Z/ Y( K8 o2 a9 u4 B% M差分法一般配合前缀和。
  d$ Q( {& H9 P0 Y5 d1 u对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;( m! Y* y$ N% ]' h% n
假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
: ?) M! h2 D" u; m% Mx
$ y, u. t% V+ y3 E​       
( V1 v: j1 T3 s9 M& l) f' V ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g ! H6 L7 b/ |% c: F
r
! E0 c+ m# v/ ~5 \. Y% ]: [​        2 |9 I/ p: M6 J' ]% }
−g 3 s  H. c& o; S; W5 J- m" ^' L  {) m
l−1
! x) V% ^; f7 u, f& J- m  p0 N0 G​        ; n4 }6 M% R: m7 t
;分别用同样的方法求出 g r g_rg
$ X1 Y% d' x. v, Qr
: W+ A4 |; a: e2 {/ I4 J* p, r​       
" _3 L# h9 m9 |  和 g l − 1 g_{l-1}g
/ O' l5 a* Q4 b3 r$ j4 ]0 V" c9 }* Yl−1( }( F/ g4 c2 q: f; K- v
​        % Q# V1 R" n' ~. I, @$ c7 F9 g8 h
,再相减即可;
* ]- l7 u/ u. N$ C7 k2 W2 n1 X8 J5 [: V9 |" A

/ c6 x' W, J% v9 U9 Y% M3 P7、位运算
8 \9 B8 W0 ?' P$ c8 j3 G' n7 }6 o位运算可以理解成对二进制数字上的每一个位进行操作的运算。
/ H) y# ~  Z3 {- z4 ?. ^4 u5 l位运算分为 布尔位运算符 和 移位位运算符。" q: A. t! t4 b- d
布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
3 u& I8 k, _3 \$ f+ s4 c  K2 j" q如图所示:
: O/ \* H5 n6 z+ `6 ~: g( A' G
1 F7 k* g) ~; ?1 N1 d
1 i: R1 F/ T, f' w8 q
位运算的特点是语句短,但是可以干大事!9 J% a6 G* h* v4 G8 K# @9 v" _
比如,请用一句话来判断一个数是否是2的幂,代码如下:9 {  X; P: h0 A5 s8 a+ e
!(x & (x - 1))
1 \3 b" \7 k- T3 K1
1 v. W: I  Y4 U8 I! M# W& [" k8、贪心6 G6 P4 z, }* T4 ]- @& r2 L
贪心,一般就是按照当前最优解,去推算全局最优解。
: E. Q' i. r/ P. C% Y: g  e5 X* Q4 K, A所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。! T: e8 A! _) Q  o
9、迭代" f, D) ~2 B% q9 b
每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
) m5 X( M& u. Q% F6 ]* N5 p10、分治
( p& o+ `# h; y, r; Z分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。- ~& H1 o  \' j: F7 k) [1 e9 H4 Y
5、算法进阶
$ H6 W6 q$ f* q6 h* ~/ f4 \算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。
; Y/ {, q' x, O4 E$ K2 j9 `如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。6 a3 e' U2 k- \
这个系列主要分为以下几个大块内容:
5 Q7 F3 H3 P% U6 Y3 p5 w  1)图论- X2 b* }  U) a: i! V* i
  2)动态规划
9 q6 ?8 G  W2 }  }( E6 X  3)计算几何
8 R# u( t$ F0 m9 v, ?  }$ r" ^# F  4)数论3 N0 b; ?: i+ B' M, a
  5)字符串匹配( _" ?, c& Q& F
  6)高级数据结构(课本上学不到的)
3 j+ ?+ _! v' e; F6 P  7)杂项算法2 `; H+ W) v+ F1 n5 g" n
) L$ Q$ g% q& Y" R& n+ V9 [

9 T) s$ @2 Y4 L$ K先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
$ }! c! K0 o# \9 Y& Q5 }5 F2 |: @5 i1 Q

2 L6 m  b- T& m5 t* n0 [2 m& {, s0 V2 [, Z5 g& o
5 c% `3 |4 `4 c' g) D$ M" a
1)图论
+ g% T8 P: Q9 e* }. I0 v1、搜索概览0 c8 n+ m. m% b! j
图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。
4 k  C9 z- e4 d( D3 Q6 Y; I6 w1 V: e. T# n1 z# w
  W# }' l1 ^% f1 r5 R' @
比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
2 g; o5 U: r! s8 E! N2、深度优先搜索7 J; I2 U% V9 D' q1 x, d) N% L
深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;! g( ?9 f3 m% y% ]
原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。
1 ]0 z4 _( H, ?但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
1 |4 m( P8 m  k如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。: U2 J9 o, O# j$ f7 d

# ~$ t$ v; B. }2 E  r
6 [) M. s7 |+ n
红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:
8 T+ D# a4 P+ a7 v9 g+ b% n        0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6' B( b: H  M2 t* S+ D9 T/ H/ C- W
1
* ?- p3 g5 m& l; V. c同样,搜索的例子还有:7 Q; P5 _$ v" _) r% e
. E5 c5 b0 Z  O
- o! G" |% U9 v" i( K
计算的是利用递归实现的 n nn 的阶乘。
' h  ]% ?$ M9 ]* @* i8 g3、记忆化搜索# a" ^" _% r; k* l2 y
对于斐波那契函数的求解,如下所示:
7 t9 p- G# m) @- ]# mf ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
/ B+ s& |) c/ H. j⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)0 o5 s+ L; ?$ b7 e+ R- a
{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)
" W/ D; m( L. @4 M0 k$ Zf(n)=
3 i$ v5 o5 i" G, P
6 c" o* w4 o5 o! ]+ h# ?1 ~/ E. W7 V3 F" W- F3 p
$ U% L# B4 r% {, _0 h: M
+ R- O$ a. `8 m7 b

( z5 C( a' t5 q6 l. A​        ; a% X  y/ K2 |6 C& u" K4 a! o$ a
  : c/ B) v& l" j( g
1
/ e* i( v1 _  S1 `1
0 F# M: u. A1 g0 vf(n−1)+f(n−2)3 |' ^+ f, \$ T- M0 j# \$ C
​       
$ ^0 L9 V. W- D  7 C7 [& `- j# C6 N7 ]$ f
(n=0)! W/ i0 j$ L3 r0 K
(n=1)
+ {6 Q' ?& ], J& R(n>2)
% `) B7 K1 u; W3 W& |​        ' S. V% j! b5 G" y- t3 }1 P+ f7 j
' w& Z& K, _5 D0 B' m+ \! _# K
对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:+ l' f* f+ d6 d* {3 n8 q) t
+ v' j3 |) W) I/ g

7 g9 V1 ^  _. g, j这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。" B3 T* q% m4 Q2 ]* o& ~* S
我们通过一个动图来感受一下:
& l" m' L. L9 g0 u8 p7 z9 @* a1 [$ |% s- `/ O

9 k: N4 S5 ~3 P( W6 g( l& [当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
5 t( B3 T# Z4 t: n) G这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
6 ~! {& u: Y" w$ r2 {4、广度优先搜索( d# a. S# p# w# C) ]
单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。/ p& N) L- d5 Q" l. w2 }  u
我们通过一个动图来对广搜有一个初步的印象。
) q- O5 |+ j( j" c0 d( r
9 q6 T% O2 a  k! C1 n  w

% `  r( ^3 {, a* K& ?
0 Q9 q. \2 b7 o! a5 Y

0 E0 ^/ \& |. ]5 ]: q# j从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
6 a& h! r8 e, P4 {# X那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。8 V& D$ u4 Z! I/ H3 a; |8 _
这时候,算法和数据结构就完美结合了。
$ K/ i% f0 g/ Q0 E& Q) ^5 ^4 z2)动态规划; D, D% n& S, e" V1 X" K
动态规划算法三要素:
! A% C$ [+ r1 e' W- k8 R1 J1 @# t! Z  ①所有不同的子问题组成的表;: ^$ f3 e# _: e" |1 O
  ②解决问题的依赖关系可以看成是一个图;
2 j4 U$ d& k, h8 j# H  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
! V; F  i" D3 i4 L: q7 [6 j
0 q7 c4 V( C4 @) X
! [: v3 y2 c2 e9 R0 f# d2 V& i
如果子问题的数目为 O ( n t ) O(n^t)O(n 3 |2 l9 K  e0 c8 a" T
t
6 T# S/ T) y0 K& a: c6 l! h1 e ),每个子问题需要用到 O ( n e ) O(n^e)O(n
- a0 x7 l% ]6 ?) a) h; Z" ze0 }2 ]: B+ W5 I5 p
) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。2 x" R1 o/ C. @" k
1、1D/1D
: J- F; L. f4 t  R2 T5 A8 |+ Sd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )4 [( S/ x, W3 Z
d=opt(d[j]+w(j,i)∣0<=i<j)- K" [$ b; q; z: H. A
状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):( {  Q! F8 Q, g' [
* s% p$ |9 u8 G9 E  q4 Y

! ]( @6 O- z) N3 \这类状态转移方程一般出现在线性模型中。
1 M) i: e/ F. {4 p$ z2 Z2、2D/0D, K7 F% l0 y2 |5 C1 f* Y
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} )( D1 Z& @. D% I! {* o
d[j]=opt(d[i−1][j]+x 1 I4 B/ N- ?; |. D- Q
i3 S+ D. I7 ~) n2 I( ]+ q, a7 t
​       
" k7 j# ^" ]: o" w0 d2 U ,d[j−1]+y 9 x9 a  K3 U" f; ~3 R/ ?# X$ z
j
  \" P2 d: [( W7 |8 {! w: E​       
5 p# i( V) X! O6 d ,d[i−1][j−1]+z
. A: ?& {' U' o: ~5 Y  G/ Cij% _: Q6 J( q9 e5 q
​       
2 c& Q0 t# G+ k0 |( p )
6 z. }$ L. b, C: L1 X2 N状态转移如图四所示:  ?) G" M; v& i3 h6 h) e. a9 N

: L/ D: f" m% `% m% t/ F% x, I

: r" \% u( w' F% D6 M6 Z% |+ e比较经典的问题是最长公共子序列、最小编辑距离。$ g1 @/ {3 s0 [- ~
有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
9 R& Y0 k4 g7 n4 Y: _2 \5 x* F: ~有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离$ D5 a2 s# P! B
3、2D/1D
( l. d4 t% {" B3 |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] )( H  L8 b$ V6 v
d[j]=w(i,j)+opt(d[k−1]+d[k][j])
6 q% L4 w$ |8 a" X6 }区间模型常用方程,如图所示:
- c2 f) n8 H# _, n
. T8 b) K2 z8 f$ Z
# B: N, v( Y: _+ T7 U$ o6 H
另外一种常用的 2D/1D 的方程为:4 P+ u1 J' V7 L& \
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 )! F# ~: a) |0 G; T1 \' N
d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)$ K9 L( e8 Y$ x/ s$ m
区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
9 W& d7 }6 K2 p! w4、2D/2D1 r6 X' [# d( [0 J) P4 H$ e
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)7 D, j. C3 H) t6 t
d[j]=opt(d[i
; b3 t2 p, v) N: y6 w' |- l. g% O& P& ~) M' t3 G
][j 1 j" U; R$ n# ]
5 j: D' G! n! H) r) u
]+w(i
- _4 \! P* V! E
* V5 }) }+ X' K; y4 c ,j ( ~( ^* V* B+ d# a/ c8 X

1 S/ T6 c5 `- R8 m) c2 q. W ,i,j)∣0<=i 4 b) p* \- B6 m0 B5 {$ U: u

  i2 F! ]3 d7 L+ d <i,0<=j
. D% Z, j2 h" _3 x9 a7 P1 R
$ V; R/ D% \7 ^0 E( d <j)& M  |9 P3 q# E3 `9 U8 O
如图所示:
* W3 \/ t8 d! E. i4 t% y# s% k0 Z! Y5 E4 S3 l& r  B9 ?

6 d% o! R: e, C) E+ {常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
8 V7 U. Q7 n2 O+ D/ h* X对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
+ F1 ?- }; ^) _9 G& Q. l+ vt+e; }/ U3 {* N! I2 q
),空间复杂度是O ( n t ) O(n^t)O(n
+ {) `- ?/ J2 lt! e4 b3 k1 \+ B" ^
) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。, q3 A9 O0 _. U
3)计算几何
: o  f) \; a+ C" L$ q# `计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。2 I: X" z2 Y# h$ b
如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
9 ~/ ]( {; G5 x7 @. q; L" w/ L" A6 V$ P1、double 代替 float
/ ?! ^6 _# G% F) N! Lc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;. k) A! @' m- ~! o
2、浮点数判定
, ]) t" k# J) a" d由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
% {( Z& T' e( O( B1 V. Y0 c* g两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
, P: V- {! w" [  N6 s/ I  [! Gconst double eps = 1e-8;9 \# V- Y* ?- Z- Z1 }6 |" u8 X) C8 E
bool EQ(double a, double b) {
1 F  v0 f7 o4 m6 I; o1 F    return fabs(a - b) < eps;$ F/ D3 I# \" f1 A) X
}
$ D  u' t* y' m  F$ B1
  n/ Q( }4 f  k  Y5 d; q7 \, P+ c2
( k/ w+ T% W" s6 b" j* b* l: f* ]3
, w; o- s7 V7 h, g3 C49 Q7 @$ ~- h& {: Z* H3 ?; J( `
并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
# x: I# X! A! X8 e% m2 B3 ?: Fint threeValue(double d) {
* J1 k8 |* @$ L9 t) J9 w    if (fabs(d) < eps)
0 C. f3 t: w4 f9 T5 o! [        return 0;) `$ ^, i3 G* D' a/ T/ [
    return d > 0 ? 1 : -1;
2 V$ ^) d: m0 G* C}& D4 o8 Y7 o5 |5 w8 ]
1
# ~" L1 r% H6 q& G! k4 z3 Y2
7 r/ c- B; Z* a( n3
# O* m0 v1 P1 g* F5 x- k& u4
: e( {# H2 N+ Y4 I5) S! ]/ r7 D+ x3 N* S( L% A
3、负零判定# q% j& M0 L; k* M
因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
5 t5 u# ?: {" ^, H% r" l- K    double v = -0.0000000001;' I# @) {: K5 g  ?  p
    printf("%.2lf\n", v);
/ C9 T( ~4 N9 u& i9 K5 x1" `" f# n* O* t: u3 O; P. C+ C
2
8 @# c' a: s2 D! W3 U. @0 ]8 D, J避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
' s1 X! N2 a% O" Q    double v = -0.0000000001;1 y0 x, w$ H0 b# _0 ]) A
    if(threeValue(v) == 0) {
6 q3 J( S; E. u$ _        v = fabs(v);1 v- N% ]' l# l& u3 p  m; |6 x/ `
    }: T/ N4 i1 X, ]  t6 h- H5 _$ }7 W
    printf("%.2lf\n", v);' u( J9 g" @) p. L1 {! b
1
3 l6 M" l( q+ i! O( h- L. W  p2
. \4 y; O7 o1 p7 y- N# V3
; d5 g% q1 q+ u7 _, @- p& c# ?4
+ P( [, z, e1 ]: X5
- u7 x. e3 P* }! B8 B: K: [2 y4、避免三角函数、对数、开方、除法等
+ w7 q: ~( Y0 [$ U$ {: Pc++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。/ [5 r) `1 c) {2 T
除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
) A: ^0 z& V& r8 a& W5 s7 M  W- R5、系统性的学习
, n6 o$ t1 f  ?+ t9 J( _基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
7 N# I0 m) E* Y# V- r进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
# T3 c  R; O9 ^% _( ~, L, P8 C相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。8 ^: r6 I. E3 w" s; V

* Y: E: @" n2 }5 n- j: J
0 T+ o5 g% r. j1 n0 _
学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
- G/ @% l- K  P7 Q1 s4)数论9 y5 |8 ^. t2 s% I) M
刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
) ], q4 Z- g) X8 @4 n数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。$ Y$ i! l1 j( {5 {/ O
当然,数论也有简单问题,一般先做一些入门题提升信心。- @: w! b/ m: `, h+ E) E8 j
1、数论入门
. L7 R, e9 I. t主要是一些基本概念,诸如:, E7 i3 N9 E* T
整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;
8 P% v$ {8 Z' H& H2、数论四大定理
! c2 n, {$ A0 R8 i这四个定理学完,可以KO很多题:
  j7 q+ S/ Y9 k1 N2 K. E* X欧拉定理、中国剩余定理、费马小定理、威尔逊定理/ h6 t, r9 y6 G, {. D
3、数论进阶2 Z" b' R' a) O2 |! N( o; B
系统性的学习,基本也就这些内容了:
) q3 b3 G& K: o) Y) p# S; j' p" l扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。' {0 ]$ E) f) [# t: I
5)字符串匹配
8 e3 K3 R+ ]$ I! F) j& [字符串匹配学习路线比较明确。
5 j* n% p3 S9 f* `/ w( H, Q先学习前缀匹配:字典树。
1 @, s- ?2 }  z* W8 \/ {然后可以简单看一下回文串判定算法:Manacher。/ q0 |: m6 B0 {! X; L% }8 [
以及经典的单字符串匹配算法:KMP。3 c8 R+ P* C5 A8 Z4 X
实际上平时最常用的还是 BM 算法,而ACM中基本不考察。
/ C& R6 l0 a: j然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。5 h7 T- [7 ^' C2 Q4 O% L2 g
关于 算法学习路线 的内容到这里就结束了。( ?& q4 Z; X3 {/ f, \6 W5 k4 r" e9 b% {
如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
  p) A5 R; m2 Q; d7 t0 E# R2 f参考资料+ N# W, N" G) {, @0 ]/ p
【阶段一】C语言学习资料:《光天化日学C语言》(日更)
! }0 i6 l5 d' M' [  H% e【阶段二】C语言例题:《C语言入门100例》(日更)1 l! E& @& u7 i) q$ f0 u5 e
【阶段三】算法入门题集:《LeetCode算法全集》(日更)4 I9 b+ f& V! h
【阶段四】算法进阶:《夜深人静写算法》(周更)" T7 w& Y  a7 b4 p; c( s
————————————————
$ W) ?/ J6 X- t$ }版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
  Z8 A# c  o4 e6 G  N: w原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228- B; I0 m3 e5 n: `6 Q" w& K- ^) Q

0 D  x' ^4 v% @2 n# B+ ^) E# a- I$ O+ k# k+ a& ]

作者: 1051373629    时间: 2021-8-17 17:14
太难得了
+ R" E1 z; i! j4 H2 {( ]) h




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5