- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 565611 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174906
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
9 H0 l3 d# R3 c6 H% C0 z$ m: V❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)/ U3 J$ N1 c, q, H9 E8 v0 R
5 f; `0 I- T7 Q% |前言
7 S/ `- `6 ]; g! U: s" S6 d( s/ ]- B 所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
+ e8 T. h1 R( Y$ d* D. I1 g$ \+ X 希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。0 F) @4 E t- ^% ~5 L5 H
刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
- P U e" r- T 每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
# R# ?/ t8 r3 H5 A* o2 g+ U% ?$ D6 y) `) m# F, m6 t' Q8 c
: b5 s- K5 t/ X; O( C* n
W. Y( L$ E4 C# }' I
" q9 V0 S2 \ `7 l: _! _ t% [8 u# ~' V- W* p
6 E5 Z( g7 w7 H: U3 q, ]
0 a) {1 }3 O2 ~+ V1 T- X5 Q! V4 Q: m( C H1 S" e) R
图片较大,文章中有拆解,需要原图可以留言找我要哈
- k* i4 [- Z& N1、基础语法学习
8 \( K+ N2 I) H, L- g算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。4 |( t+ U$ z/ Z% U( b
因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。- ^3 E7 S( g% @0 v, f0 T; d
+ Q, I$ A$ a5 z+ o
" G- q5 P! s7 B5 q: g! N$ s9 h6 K1 L
, [; m$ [! l3 U: v$ M1)HelloWorld+ i) x/ V2 U" y
无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。' U0 \" T- k" O% i+ P
2)让自己产生兴趣
1 u# m3 O; {5 z所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。4 K% n! E/ T- s
刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
6 w C, }& R9 E8 O; }/ s3)目录是精髓; h* u6 }( |/ i6 ^5 J2 Y" D) j
然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
, Q4 S, d: a2 t8 N1、第一个C语言程序
1 ]3 `, T) U$ X, ^2、搭建本地环境
4 b5 R o! v) A* r# j5 l3、变量
$ m2 q! X( k% z4、标准输出
/ R3 T1 K4 c) B9 @! D3 `, v5、标准输入& v( f9 p7 o% t
6、进制转换入门
) B* c5 D3 c: K- ?7、ASCII字符
0 K( u5 ^0 j& I8、常量
* T7 X1 P" p' x9 I, U
1 I! a/ `) ]9 i; k; q/ J0 |- l( g3 {, |0 c, N& q" Y1 j
如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
, ?% d: F* i2 q& k, V% a" `4)习惯思考并爱上它
- G1 G, c! [2 _ ~( G只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
" y2 Z1 O/ Y/ q2 t: Q9 m就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
9 L+ r" K: s) y5)实践是检验真理的唯一标准0 D$ s$ ]+ Y1 ~: z2 @
光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。; C$ W7 D' g. t
所以,记得多写代码实践哟 (^U^)ノ~YO8 d7 a, W% C3 [5 f; \* i+ z
6)坚持其实并没有那么难
9 ]9 T- D* Z# k. j每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
: Z6 `8 h! q& ^9 K9 h: y: y7)适当给予正反馈
. k Q/ }8 w( N% X* m+ ?然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。5 z6 g \ S# G" d8 ^2 g9 ~* Y Y" ^
当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。
/ V( X/ D. o! H$ e- x$ m看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
0 H. ?& N" W+ a. K2 z4 \$ O8)学习需要有仪式感2 M% f) E! s V x A
那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
3 \" M+ L2 N4 ~4 v. H6 F q0 \0 r介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!/ j7 e0 E0 T: [$ L9 A
我也很希望大家的学习速度能够超越我的更新速度。
3 k% N5 K4 U# M% P. \2、语法配套练习
9 n7 d0 r( G" F8 S! B$ A' S. \学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。' p$ n: R8 n' _' j1 l
而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:. u1 W$ c7 {* C+ o/ m
7 T5 u4 w* M( l/ B# d$ R& L- _ o$ z( a& i
4 A( H( O$ N, r" X( J
/ m, q1 M6 K/ m% A2 \$ J6 g ^" t a2 [从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。+ ~! A: d7 T- ^
这里可以列举几个例子:6 s! C" A! _5 C @& B) B$ q
1、例题1:交换变量的值" a+ F7 {' Y" J( [" D, P
一、题目描述9 u0 [7 [/ B( ]/ R
循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
6 m0 y1 \% ~- }
9 _ `# s! E: c
$ v2 ~" U8 M/ P: m
2 n U* N) \5 P5 i$ D+ B; b& x+ {
二、解题思路
' F# h+ {4 S5 |! R! X, q! _难度:🔴⚪⚪⚪⚪1 }8 w$ O5 M9 e" ~; b1 j u
5 ?! O3 x2 L6 H% J
' w q6 ]6 [/ W2 ?# j& D0 m
这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
7 F( ~) H( J. v2 Ja, b = b, a4 I* Z* _: Z; t5 ~( T
1; `, J0 F& a! o8 p/ b
在C语言里,这个语法是错误的。: K1 M% L% C& T/ [( Y
我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?6 w; e3 ^ z4 k1 G5 G
当然是再找来一个临时杯子:
3 j8 Q. z, F* X1 C% G- O 1)先把 a aa 杯子的水倒进这个临时的杯子里;
& x0 _7 M; j2 y+ }6 @) g( P1 \ 2)再把 b bb 杯子的水倒进 a aa 杯子里;
( E) ?; D7 j% B, w$ @+ n; h5 G# } 3)最后把临时杯子里的水倒进 b bb 杯子;
v) w `! `4 c* s: U5 ~7 n
9 @3 M: A) v: e# b
3 C7 L7 ]- Y4 X! Y这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
; t" D- K' e5 n. M8 @9 f$ }- q* P J# O# v3 S$ y- H# n- H
$ K$ l6 a1 K4 n
三、代码详解' w4 Z$ ?& _% z6 G, I
1、正确解法1:引入临时变量
; R7 [$ N. U; R/ w0 [. x" Z5 X#include <stdio.h>
6 Q8 V: q2 w; z' F! p! aint main() {0 M# j1 B8 O3 u( H; C6 E
int a, b, tmp;
, u! y$ F u, S while (scanf("%d %d", &a, &b) != EOF) {. m8 u; U* t9 u8 j. M' x0 t9 s
tmp = a; // (1)
3 {( d9 T9 o4 J! W! G7 ~ a = b; // (2)" E4 K9 X; ^0 j. G7 p- ~
b = tmp; // (3)7 d. u M! |& N) g5 g
printf("%d %d\n", a, b);
! h+ E7 m& W$ _0 e! Y% H }
, b* b# H+ _: w return 0;% y7 r- z. Q2 S
}2 m8 u. ], n* r8 u# ]) v6 |
1) Z4 G: ]! }% @
20 u) k! k/ o( }5 V
3
- g5 o }0 V; u4 ~4
) z! I3 ^- `5 k* L3 ?. m5& ]2 F5 i9 H4 L% J. C u
6# ]6 q7 K. a/ z' w) q6 v, j- \
7
- e" v& O6 y; }/ }; i* s82 ~0 g9 p. {# N$ Z( ?. h+ H
9
5 |. X, a1 L8 p1 }- P! j# C10& y g, Y( t) J) v6 L" Y: _
11
. U3 |" k" a$ C% b6 e% z- c( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;) J5 A$ q" v0 v* x( i# M$ z
( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;
1 V6 e2 R! q$ @) F/ W( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
7 Q9 S9 C0 A z( x. c* I这三步,就实现了变量 a aa 和 b bb 的交换。8 |# C( D; x& e. g# ^" D
2、正确解法2:引入算术运算( p4 Y) c- h5 c+ R- ~
#include <stdio.h>$ s, ~ g' d4 o" a' M% E: ]
int main() {
# L/ ^. H0 U* D- o. A int a, b;
: n/ Z9 d' D9 c while (scanf("%d %d", &a, &b) != EOF) {
0 }$ `5 P; {8 i$ J& h' q2 }$ w0 k a = a + b; // (1)
3 B: \9 m: q0 c& ?8 E+ \& L0 j' }; i b = a - b; // (2)
+ M* T! t, [! S* Y% ^; C a = a - b; // (3)2 V: `% k( V6 b( I
printf("%d %d\n", a, b);% I7 O$ t) M/ `# V
}/ _/ z# R: H/ f: D" [* N" |
return 0;+ q% i9 ^" ~8 `- Q
}
7 ^, `- S6 X' \8 o1& \' x7 Q1 c- a, z M" N
2
7 ^ g8 m6 u7 w3
7 s. P" T9 ]% m& o" q4
; e2 I( h; M# L9 n O2 Z5 f5 s2 g3 U# M' N% U
6
, D6 G, E. ]6 z0 c x2 w) a' [7- K: e+ z( d8 l- U8 C* b( }$ Z9 V/ R
8
- j- G5 A& Q3 j% U1 {9) a+ }) I( _& l4 S9 e6 m
10
, ?/ ]7 C Q1 @/ h# k11
9 u7 s6 `* |) B4 W% y M" H( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
$ K# a4 x& N9 M- S; [, @$ x( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
0 m: k9 J( w6 K; P J( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;( ~" s9 A- |9 g" }& {) F! r9 ^
从而实现了变量a和b的交换。
' {7 g$ _; O7 P9 c3、正确解法3:引入异或运算. T( F6 ?' P7 U2 X M. e
首先,介绍一下C语言中的^符号,代表的是异或。) O' |) h) C8 ?4 h
二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
5 p0 n& _0 [9 [! y8 A左操作数 右操作数 异或结果
- [6 n% D, O- x8 O( u. X6 E6 u: P- U0 0 0$ k! [ f+ }3 m/ y1 x0 r
1 1 06 I4 f0 h8 ^( S6 c% P
0 1 17 S4 n# [6 c n& R, v' x
1 0 1
( b. Z& P* d% J0 l% h+ b: Z也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。* l; j3 v: p8 x+ i0 l
这样就有了三个比较清晰的性质:2 A" V( A6 v" r \' u: j6 ], f
1)两个相同的十进制数异或的结果一定位零。5 h* q5 v: n6 M
2)任何一个数和 0 的异或结果一定是它本身。! I: |' ?4 }1 P& f) t
3)异或运算满足结合律和交换律。
4 l' S L) O$ @7 Y7 a$ d0 y#include <stdio.h>$ S, K% s1 D$ g; }
int main() {7 j" [1 O9 `9 U' { b( K
int a, b;7 M t% G3 o$ f/ B
while (scanf("%d %d", &a, &b) != EOF) {
+ p$ `! H0 p: K( a. i a = a ^ b; // (1)
9 i. z+ M* q2 x% q b = a ^ b; // (2)- @ z, L! p- K1 h8 \+ I- q6 f
a = a ^ b; // (3)
, h- y/ M* m, ]0 p- S, V; _ printf("%d %d\n", a, b);
2 l& w5 a" j1 w5 F9 d& A }1 G `* b3 ]% j) {3 w: L4 k" C" F0 O& _0 \
return 0;
4 o K6 k* T7 w+ B}4 [4 I6 `2 U( t2 w6 y
1
7 ?0 Y. X h9 s* }3 B& }2. F3 P7 [- Q. C& r; X, r( O: H9 z, A
3
* F4 ?5 E# ^* H0 p7 W" _40 |7 t. A( V q% N' j
5
- o0 c" u2 h2 Q6 Q) n& e9 u, O68 o7 E8 ^, g! I" a5 e ], d
7
5 O# }7 ~* \6 j _5 B/ e& t! Z, U8( f/ U5 f8 n7 N
9
; b$ B$ u/ x5 X3 R10- J% K7 h @" ^$ s) ]
11
3 ]$ e1 G, o- v* f: l8 @9 c2 H3 W8 L我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。
% W- W }' w) U! q! C! Z# D* [( l而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。4 q2 R$ G* W! j0 Q! S
从而实现了变量a和b的交换。
: z, V# b) p' O' Q, |8 w+ H/ z# M, E7 Q/ U' L( D& V
- t! f7 g6 R5 m" a; N
4、正确解法4:奇淫技巧. v$ |. G0 W6 m1 e3 n6 C
当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:. n$ S- B' P* y" h! S
#include <stdio.h>* P% U8 O' K2 x8 a: J
int main() {
1 z% h1 ?! ]4 i4 s int a, b;0 L- J6 e3 r3 a8 y, l
while (scanf("%d %d", &a, &b) != EOF) {
. b0 }" W# w/ c% G printf("%d %d\n", b, a);
2 T. R" Z0 G. J5 g+ Y9 ?7 U( ` }0 a4 [6 A x/ }* q2 ~, ]) @' M
return 0;
& ~% _/ V3 m1 K6 r6 B+ Q2 n}" r8 l2 e; e- P# h+ w' I0 r
17 f! J. Y$ ?1 h8 Z5 P# i2 W
29 z6 w) C8 w, ~
3
7 Q: Q" E' Z9 b% U" H47 y( i* S( P0 E
5- m( X% k% S% Z3 [
6
' w" Q2 u% e# M1 d% D- v7
" _+ i( h5 `3 ]7 H9 n6 ]8
7 ~* A+ v8 d. _ y" w; Q% M6 N你学废了吗 🤣?- `. l7 z; b$ T! Z5 F; Q( F
2、例题2:整数溢出5 z$ d% b% C0 [* H9 ]: W
一、题目描述$ d9 M- Y, v d' J7 U
先输入一个 t ( t ≤ 100 ) t (t \le 100)t(t≤100),然后输入 t tt 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62})a,b,c,d(0≤a,b,c,d≤2
; D: h' h* I* z& A62
! i" _7 C* P, V% Q ),输出 a + b + c + d a+b+c+da+b+c+d 的值。1 Q0 _8 T0 |; O& L2 l
# E, e: J' {! Y
' g' |' M% \ y0 A) S& ]
二、解题思路
: v, h4 T2 g7 H% c% F" U难度:🔴🔴⚪⚪⚪
* L L. o; [# L1 d% L% |2 O6 d0 ^ k4 j6 ?( Q7 U1 y
) c5 s: i, W4 x: B& C% p( Q
这个问题考察的是对补码的理解。
+ V+ D6 ~$ @* W: a, @仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
5 E7 _: f) w/ F- V1 f- g62
7 \; Z# r/ O( c2 H ],这四个数加起来的和最大值为 2 64 2^{64}2
( M ]* e6 z/ ~* Z64. E6 z" d$ G% H3 D' J
。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
4 K( R; E+ t* |0 q+ l/ c9 t63" O4 }9 W/ e& H- s; t
−1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 4 {4 f* B& p I; U6 H2 z
64
: i! ~, n' B6 L* r/ p −1。. y' n% w* Z' {2 @4 Y% B. U" ^- b
但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
# I5 c9 [1 w2 O6 c/ i8 }8 W0 A62- l7 D& t1 L+ D p8 n! t5 p
时,结果才为 2 64 2^{64}2
0 S7 n7 ^& K5 [2 Z8 c+ O64
6 |# B6 q6 I% D' S" F) |" M ,所以可以对这一种情况进行特殊判断,具体参考代码详解。
- V% u& z! Z% h) D# ]0 f三、代码详解# y2 F3 e G- M* C. R7 D
#include <stdio.h>' p+ D' G6 u- H0 U8 G) o# q$ j6 Q
typedef unsigned long long ull; // (1). q$ A2 u* G- V# m3 H
const ull MAX = (((ull)1)<<62); // (2)
% a9 b0 g. D) W2 U4 Q' V! @ e; x6 q, N/ W& \! T& U; r% ]* J1 ]9 c
% S" \5 l8 D) u9 w- ]- p: z( M( t
int main() {
) |( A. s; K$ S1 Q5 K int t;7 o: x8 A# i! E3 g0 P" ~
ull a, b, c, d;. ]! }9 i% E$ d4 z$ r. ]. c
scanf("%d", &t);
) i! c% j3 r, Z% ~: c: P+ o while (t--) {
. J# g$ j. ~( g3 `' o scanf("%llu %llu %llu %llu", &a, &b, &c, &d); // (3); t% a2 i4 W6 f- E# U
if (a == MAX && b == MAX && c == MAX && d == MAX) // (4): `! R/ d( T# F; [3 |5 h: p* a
printf("18446744073709551616\n"); // (5)( o% r% u( U% G. U% `
else
! o& M( P0 G; D" {; h printf("%llu\n", a + b + c + d); // (6)
* y% e$ J- j& o( b+ \5 _! V# ]/ m6 V! B }% m; l+ N+ @- U' ~
return 0;( F0 S y G5 C1 Y: }( y
}! p3 \1 V3 f0 h0 H
1
$ W$ ^/ O# p A2
/ k& c- s* K6 X7 A38 K0 h n; o2 f$ R
4
& Q" [, _# [) W5 @8 t. q# P- Z" r! ?; b1 ]* H
67 q( @7 P& N5 N: Q" V! m0 |+ P g: k
77 c8 G' V; M* r1 M8 @% H1 C4 N6 ^
8
* Y+ `2 M# O( B9 M) q9/ k! X8 A% g# B. D- g
10# x; @8 M: ]' N! V
11
; D' F+ G: K; N+ x12
' {9 I" H8 m# s4 y4 V& R5 N13) d' F) z6 v0 @1 {4 v ~: p
14
8 c: d+ s) [8 _# j8 T! Q15, o2 o$ v3 Z. e7 V; u
160 C: T) n X: h2 j3 K$ S" m6 R
17
& |/ v1 @- p$ ]' I; l- m( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;+ s$ G" v/ s x2 r0 j& F
( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 + o6 |" |. k- a% M, d
62- Q5 O- e) E; a2 }; ?$ e8 }
,这里采用左移运算符直接实现 2 22 是幂运算;
' v6 o z' \+ R8 [数学 C语言( o! l9 u0 H) a. L2 I D
2 n 2^n2
6 Q- ]/ ]9 ^* A( ]! R4 V" m5 w% pn
# ~0 x. T7 y* Z+ ]' P: F 1<<n
; I8 b1 N$ E' Y& j1 _需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
9 G4 {( J! i: n) V. ]3 A) D( 3 ) (3)(3) %llu是无符号64位整型的输入方式;' I' N, ~8 [1 p5 E8 S
( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;$ T Q$ s" T3 U: n7 b: ]% g
( 5 ) (5)(5) 由于 2 64 2^{64}2 * [8 c; k6 h( N4 f0 _) v* G
64
; R" o. F% P/ x1 w& I! F, O4 `2 o 是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
# w5 R6 k6 b2 h3 t7 Z ^! a7 r' Z" R7 V* K( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
3 V9 c% v2 E; s; B) l" Q647 x% _9 s: T1 R# I) O+ X1 p" Q
−1] 范围内,直接相加输出即可。
) ]6 |# R# ` h/ Z3 T% K由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。4 ~3 F3 E0 v5 ^& v/ L b1 Y
为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 🤣。
2 t2 ~6 ]% J* Y: E3 A
. S9 G* Z, k; R
0 u% |% M7 Q$ S& W% m3、数据结构
5 R7 P9 c) V6 B( t% u《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。 g1 x# c% q0 h4 p# {* H
1、什么是数据结构
7 O; C: i+ C/ ]% k你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。4 o2 P1 S5 E! S. X4 b+ [
如果一定要给出一个官方的解释,那么它就是:; h/ v* Z% g# A: T6 ]
计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
* ]: H2 l/ I. F- Y- `7 J! u% t
% q1 y' a W/ N ?6 n5 b' A5 h# D0 ?+ A, w' [
是不是还不如说它是堆,是栈,是队列呢?. v" z! c5 w2 k$ ~: a
是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。0 X5 V8 x" {5 r/ T! R
2、数据结构和算法的关系1 O# l8 F7 L' O+ d$ Y3 v1 q( ]( \- U
很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。/ ~6 J4 \' T; [7 |' {
数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。+ y8 A; g+ H- l; {, r) _) _
而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?7 G3 L: O: D9 I* a1 I @
讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。- Q) J- H. d* }" m6 r
3、数据结构概览' ~9 z1 w" r6 [' p- F* }) J$ f: d9 M
周末花了一个下午整理的思维导图,数据结构:
+ p+ A& O' E r( n% x$ c" m; ]/ n
2 n. H' t6 @- E' }; c. Z/ n# h" L1 ]- u8 h; B* m# W ?; t
常用的一些数据结构,各自有各自的优缺点,总结如下:
- \( v& A2 b# M$ ] c+ Za、数组8 F R# W' G, F
内存结构:内存空间连续. B: g: R2 k+ N& M
实现难度:简单
, J# Q% ]( B# X" O0 i2 U3 w下标访问:支持
" p1 [0 b$ f; ]3 T0 K& P$ Z7 Z分类:静态数组、动态数组; n7 P. U y0 ^6 D$ z% b
插入时间复杂度:O ( n ) O(n)O(n)
6 b# u$ E _% ?4 I7 X4 n查找时间复杂度:O ( n ) O(n)O(n)
8 [% G$ a) q# }# ~删除时间复杂度:O ( n ) O(n)O(n)6 x+ E. R7 R. q& m
+ G3 I& ]$ c2 C/ g# D9 Q5 g! b% A8 R! h q6 Z$ B) Y
b、字符串& z& b6 N( q2 ~2 y5 V& J$ c; C: C
内存结构:内存空间连续,类似字符数组
! H4 s6 ]: J% j1 e, ~实现难度:简单,一般系统会提供一些方便的字符串操作函数
h8 U" \% e0 O5 V下标访问:支持
% }! }( @) a4 G8 S8 i( Q' s插入时间复杂度:O ( n ) O(n)O(n)
; n4 M0 ]% N0 W1 U8 g) y查找时间复杂度:O ( n ) O(n)O(n)3 \4 x+ H) V* p4 n5 D6 s
删除时间复杂度:O ( n ) O(n)O(n)$ @. P$ M& a9 i9 P4 m+ F
* d* [* N' C& f+ O8 W/ m
2 |5 Z& h& R+ Mc、链表
' X3 @1 S4 j6 t+ K内存结构:内存空间连续不连续,看具体实现9 r# q1 h, ^" u! I0 {. v
实现难度:一般5 L* z9 T/ p* w2 d4 p
下标访问:不支持& t2 p& [* P! a4 P# }. |
分类:单向链表、双向链表、循环链表、DancingLinks) s6 Q9 x& m! N# |: ?! P/ Q7 n
插入时间复杂度:O ( 1 ) O(1)O(1), l1 b* U& {( g* \" U2 f$ @
查找时间复杂度:O ( n ) O(n)O(n)- ]( `( ?0 B |3 L: k, }0 V
删除时间复杂度:O ( 1 ) O(1)O(1)8 L4 @+ _3 K$ P. U
' v( w) F R1 v0 O7 b7 Q0 ~
1 G9 Q5 h" u! J( Yd、哈希表
3 r4 o1 C8 d2 k) G8 o6 Z" [& \8 a内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
" ` f6 `+ r0 N实现难度:一般
) B. o$ s2 Q1 R4 W下标访问:不支持. g6 T& T- u' _# o7 X
分类:正数哈希、字符串哈希、滚动哈希
* U# G( ^' ^! `. G1 S4 f3 h插入时间复杂度:O ( 1 ) O(1)O(1)
5 y% t- R+ ], v% t& X" q& T: G+ W( W查找时间复杂度:O ( 1 ) O(1)O(1)
) ?' @, p4 P/ [' F$ E5 J删除时间复杂度:O ( 1 ) O(1)O(1)
6 H* X3 n, y5 G; [5 j4 i
+ t; r$ K9 R" {: q0 [0 z! Q
( n2 Y- D- \( l! h/ L8 Q2 _& Re、队列+ A! a' W6 O7 H# g0 ~
内存结构:看用数组实现,还是链表实现
) h* ` e% s2 g$ v" _实现难度:一般 F1 r/ p |$ z& ]
下标访问:不支持* M5 J3 y* ^# s0 V- o5 f2 S
分类:FIFO、单调队列、双端队列, |2 o3 [+ q9 W" b6 c( _* z
插入时间复杂度:O ( 1 ) O(1)O(1)8 T5 t! u- I/ j9 V N! H M& r
查找时间复杂度:理论上不支持
) n+ z" {/ W2 W$ k/ t) s K% e删除时间复杂度:O ( 1 ) O(1)O(1); ?4 \- w. M! n+ h' @% J
+ W: W0 A: g, ], y( b( Q
. ]) h/ R" ]! Af、栈
b3 @9 @2 P6 w# y \$ [% k内存结构:看用数组实现,还是链表实现
$ {; I7 V$ J$ D6 L实现难度:一般* }9 R+ o3 C/ A1 e
下标访问:不支持8 \- q3 r6 S9 x8 E5 e" o: j2 M) ?
分类:FILO、单调栈
0 A! q8 N; A( z0 h) ?6 p P# L* S插入时间复杂度:O ( 1 ) O(1)O(1)
% A# }# d, f# d6 s: L9 e/ Z& S- l查找时间复杂度:理论上不支持
4 B5 h0 V% Z2 v) W0 a$ s" c: v删除时间复杂度:O ( 1 ) O(1)O(1)
$ @' h) x1 b. J! D B5 u2 [5 E8 |! j+ c/ a7 S
4 J% N3 s6 h( _6 B6 G0 A _. }
g、树5 g( t% D! F4 b9 d4 c
内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续; R) F/ |! ?3 ^3 q* Q' y
实现难度:较难
8 W9 S# L% o# V- E& ~下标访问:不支持+ @5 |6 j. M3 W% I6 K! P
分类:二叉树 和 多叉树$ y& z2 H& A, H. r8 K
插入时间复杂度:看情况而定
" q6 P3 x5 n$ q7 G+ I查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
; R4 F# \* M4 o2
5 P1 b# I1 O! D % ]; b0 Y7 L* l% L
n)/ v% s; p" M8 U6 i& E8 I
删除时间复杂度:看情况而定
) ^8 w5 k% b# u
; F! O5 [8 v+ s, _ E. a& ^' L! F% c2 O. `
1、二叉树
* q+ o0 `* L8 x4 K; d( m二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。
) P( v& t& b+ m+ P/ ^其中,堆也是一种二叉树,也就是我们常说的优先队列。
7 ?6 p3 H7 B; G2、多叉树
7 q; ?. u( Y4 f6 J$ N$ DB树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。
0 ~- Q8 L% a4 Ah、图7 R' N# Q- @# X: O& u; C
内存结构:不一定
+ Z$ w. z& z: B2 ^. s: c实现难度:难
; f, H p6 b6 X+ _; H# t- ~( [% v下标访问:不支持- O8 U# @9 _+ p. E9 q9 @' Q! N
分类:有向图、无向图
' Z( V6 g6 p! W8 x插入时间复杂度:根据算法而定
/ p. A7 f' U" V查找时间复杂度:根据算法而定
. r- ?3 q, C- s( ~. @" M+ T. u删除时间复杂度:根据算法而定
5 I8 ?- Z: _! C" x5 e$ b' |" A+ h8 o( [- c
$ q$ m0 V1 F* t. n$ v2 R
1、图的概念
8 k8 _% P% b2 T8 _5 o在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
, k! Y' t6 l" B( k' ]) c3 l! r图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。
$ U2 b- H( G! w, i: w8 i对于无权图,边由二元组 ( 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 为权值,可以是任意类型。
" f. |+ M0 _) D8 \5 T8 j图分为有向图和无向图,对于有向图, ( u , v ) (u, v)(u,v) 表示的是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v;对于无向图,( u , v ) (u, v)(u,v) 可以理解成两条边,一条是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v,另一条是从顶点 v vv 到 顶点 u uu 的边,即 v → u v \to uv→u;
1 q% l) q2 g& G3 j4 @- K' `2、图的存储 D9 L$ f2 E |/ n* [( }1 R7 p/ C
对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
- C3 [$ K, \, g. g$ d' O' l! D
( C7 |( `5 k5 V: V, B/ i+ K) I" ^; x# b2 d# P% w1 s
1)邻接矩阵
; e+ w, n# u! h+ O& E邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。
6 W4 n. g: {; [) h它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。
+ `) f/ o$ h1 ?9 \- W, |[ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[6 x ~3 _- @/ A& V3 ], l' L
01∞9∞0∞8320∞∞∞303 ^% Z5 T/ }; R" s) i/ u* x6 G% D4 ^
0∞3∞102∞∞∞0398∞00 g8 W1 f/ a& h7 R9 o
\right]6 \8 B1 x) y3 ~% B4 e
⎣) q9 l1 X! ~. p* B0 j* u
⎢9 |6 ^, X7 O! w3 Y. ?
⎢# [! R- c# d' X1 r1 B, a: f: u! Z
⎡
/ q8 ^! H* E N- O, b
+ B. h% { _! Z2 F* J: c
6 m! R0 A- t! H3 j% ~0
% }- x' X9 C% P& U1
% t* m2 x3 j7 E4 n) f5 A∞4 X. _4 _3 A* d( e
9
. E! p$ \5 D% Q' z8 i7 S- H5 U9 r% ]; F % c" U2 W. \# M" a" L$ e( `
/ K% ?' l( r% @+ k∞
2 s o' T( ~& L/ t: q& }9 d6 F0: o6 x. h1 ^8 f6 b$ ~$ [: l/ S
∞1 }% z: h+ o0 U, G7 Y& o6 W: M
8
" Z9 O/ ^0 v( j3 Z4 p9 a4 [8 x
) G' ?( y" q9 @' v+ F
. B- n! k8 u- q4 o: ]3$ e/ }! J4 F$ C# J
26 y, {! j+ u, n @
0
6 R5 K- n1 q6 Q" ^+ |∞/ T$ T6 l6 ~) i5 X6 b
9 B8 R0 Z1 M# w% z( M' R p+ Y
' t3 B$ z, H ]/ m# l2 ^
∞% f% f! v: j! t- _: o2 u, [$ T9 k4 J; Y
∞5 f! r& q8 c. ~- D H
3# b" ]% v' r( e$ {6 M% B0 X
0
# X& R9 ]; k# w D4 h
6 C1 E1 C/ ?) y: f : Z# j* ^8 h+ M- M% N& o
⎦' q. b* w- K+ R' S/ D( y& j2 q
⎥9 a# {- `' u* T8 ?
⎥
4 N7 y! O% ~4 w6 U+ y⎤' [8 t& w- o8 a" W8 V
( E2 @* d6 l- Z% x
) T2 _2 Q2 Q. J% @4 T% X$ ]2)邻接表
2 s/ X' [; a% P2 P* P邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
" ~* s8 L& `" V' g0 j; g8 u它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。2 O6 J7 ]# z, `3 T
如图所示,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) 二元组。
* I5 i6 `5 ~3 p B+ B7 |/ L D9 b& h) V' N6 X, M" N6 m7 k
+ U0 x `% K" W2 D
在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;( o; R3 P' U/ E/ s; Y
vector<Edge> edges[maxn];
5 }. I+ `6 ~2 p; Y0 h* \1
& q# g& Y$ O) A+ J- U9 I: H& z3)前向星
0 `9 I5 \) @" L8 i' N前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。 c$ A' o. v; O- k/ N5 h" r
它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
. v. ]. t% \4 \0 z, {: I+ c如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
3 H$ W/ K' K* z1 p* {
/ z6 z' B# \# |. [5 e& N
6 c- B# `) ]5 m4 V那么用哪种数据结构才能满足所有图的需求呢?
0 R* a0 c6 d3 t! n$ p+ u O接下来介绍一种新的数据结构 —— 链式前向星。9 K! i: F. Z& Y1 b4 Y7 e
4)链式前向星
4 l s2 \( K2 [+ _) 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 指向下一条边。
4 C) t' y5 D3 X* U* q具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
% H1 x* x) W5 ]+ J8 F边的结构体声明如下:, h. N1 G7 p9 `5 d
struct Edge {
1 m. s4 v7 |4 @, v int u, v, w, next;
6 _% a/ h+ ~& P. |* ^) \ Edge() {}1 @2 t5 n- X3 R) O k% a9 I
Edge(int _u, int _v, int _w, int _next) :' O% L& |( Y; x$ N/ L0 n9 V
u(_u), v(_v), w(_w), next(_next)
$ u4 B* |$ w4 u# p: O$ t& u {6 J0 l! d9 f0 \+ N8 \8 ~" Q' G
}
/ Q$ ~; _. @- P+ F, n}edge[maxm];
! w9 i P- o4 x; e2 h11 e; F# _0 U/ u% z; G8 b# Z
2
* N3 k5 W' u+ f4 M6 _3
5 F" x# y/ ]1 c, A41 j. A& ~* t! ?) W
58 t4 Z, \7 y! E6 r7 K% e/ o# Z
6
]% K& C9 n3 M5 u5 v; H( v) B7
, |3 O( E: K" s83 K$ Z; B- I+ ^4 D2 E; R
初始化所有的head = -1,当前边总数 edgeCount = 0;
# `2 W: V0 P+ B6 z! q每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:3 e/ X& f8 @# c6 T2 r2 `9 Y" A. I
void addEdge(int u, int v, int w) {) G) M1 b4 K' S5 O8 G. l" @* y! F
edge[edgeCount] = Edge(u, v, w, head);7 D" x( G# g9 n
head = edgeCount++;! z8 m9 t+ j1 l) t! J# h: P+ \
}/ @% ^+ q2 e9 Q/ S, s- @: V
1
" e, f% Y6 W2 V/ @. V# O% f2
) ?, e2 g; |3 w5 o# n3
& L1 Q& j: K4 Q' X( B& o& p7 K4& D) B3 n5 J; A7 P6 D G0 m
这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。 i4 p6 s7 c5 D! W5 }, K
调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。) t' u- d" H6 u: Q
for (int e = head; ~e; e = edges[e].next) {" d! Q& B/ `# S/ a& L2 B% V3 y& B/ l
int v = edges[e].v;* ~( g8 s7 b0 [5 ]. G! v
ValueType w = edges[e].w;" ~- x( V3 G# Q- R" e+ \
...
3 v2 ]" T0 i/ R# X5 ^4 E}
8 _1 r) f! d* t0 e! u6 S, g1# Z1 z. N4 |+ J( B0 ^1 R3 b3 K3 o- C3 S
2( u, o$ L6 l; S* \& k+ R
3! S" S; r C! g: g" f, W% h7 P
4
0 F/ Q4 e* L7 H* h1 D& e58 c1 d" P5 @; M5 _4 h+ M, P: }
文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
^9 m( A4 h0 @+ u1 G* X- o4、算法入门
5 j7 D0 r' _: T7 i/ y, ]算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
7 }0 J ~3 j& a
( u& W6 ^: g: X! a6 S! I& k! e: _5 J. P+ p6 g1 n, X! r
入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。. C/ H% G8 t6 I/ _" }
对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。/ g! o0 X; i' s2 Q
1、枚举* ^/ ^8 q3 x" s
枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。, c" g. ]' ?7 Z# u3 S' u) A
对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。( m4 j1 Z% O5 @" G% h7 o9 S
2、排序
0 i$ j, F: g" V8 N) _5 y% l# |# v既然是入门,千万不要去看快排、希尔排序这种冷门排序。% O& U- [2 z x% U
冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
5 ?! C. w# @$ o5 f! ~C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。& p P: v% @" j$ [, ?, [3 i# q. j
3、模拟9 X1 Q- l1 F( A
模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
9 Y& g n5 X+ @6 [! ^8 A1 ]3 ~6 W不管时间复杂度 和 空间复杂度,放手去做!( K1 t9 y4 q* q
但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。; S' i2 z8 d' e- } i9 _0 V
4、二分9 v+ r+ ?9 s8 f1 c
二分一般指二分查找,当然有时候也指代二分枚举。
% ~* o" X& t$ S( h& a例如,在一个有序数组中查找值,我们一般这个干:
k5 t' o$ G2 d( @0 {% x1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
( l# N1 P6 ^" L. U5 M2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:+ M- v2 g, n4 u3 i' T$ F$ [( v
2.a)目标值 等于 数组元素,直接返回 m i d midmid;
/ C; A: F+ i* i) r$ y# \ 2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;5 {% s% T% ~2 G9 q+ I
2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
$ A: C4 \1 I r6 i' S( M$ B3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
' L7 m, K# }% o! K8 ?5、双指针
1 n/ H/ s4 M- e% ]9 J* F双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。
; m- l- [! V' z
4 O$ Z, @2 c- ` x' b6 L
5 D2 v2 Q& `( B+ R) E/ J6、差分法
1 b+ ]; H5 d0 W6 ?% b" M$ h差分法一般配合前缀和。- {; l% N$ S/ U, w# x- z4 _4 f; g5 S
对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;+ [2 N# I4 M" \# h/ F: g$ u
假设 [ 0 , x ] [0, x][0,x] 内的 g o o d n u m b e r good \ numbergood number 数量为 g x g_xg " e1 e; Z: x9 E, A' R2 ^
x
) Z# x+ S4 ~1 w- s8 X * @9 ^# V/ j0 t) n5 A; u& W
,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g
6 {* _9 T; l* n2 qr
" Y7 M6 @: @/ W, F; S7 G" N 0 _, p- L% E6 S+ s
−g
* j& o! U# f% t) s9 m0 Z& `l−1 O1 @4 D2 B& l7 V: f2 N
8 K- x6 }. N5 l8 B# V6 i: D
;分别用同样的方法求出 g r g_rg
1 q. v" Z% w1 R$ Cr, g5 h+ z7 Y7 o( A5 X o
, f) t; M4 o0 n1 N# o& ], {
和 g l − 1 g_{l-1}g % X& D/ S( Z9 D
l−1
! q3 @3 j/ I8 _" O1 X) y+ ?- u
4 I; v1 P7 O; t$ y- g# n ,再相减即可;0 i- {5 X& |: d
! ~3 J& p# f% R! m3 p& @; Y/ B a o& R2 C
7、位运算
1 X3 b8 T; J3 d位运算可以理解成对二进制数字上的每一个位进行操作的运算。& @. }- S& R7 k& t% ~
位运算分为 布尔位运算符 和 移位位运算符。
/ j* ?2 M8 O7 g布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。# u) @3 ]5 U1 A
如图所示:8 B1 z, a% l" D* `) X
+ i* }/ ~7 o/ l& g! v J2 c4 [, ~5 e7 Q3 m6 k6 _ N: j9 R9 P
位运算的特点是语句短,但是可以干大事!, S+ n* P+ G9 M
比如,请用一句话来判断一个数是否是2的幂,代码如下:: p% |1 Y- @2 K8 w2 }" V' _! `
!(x & (x - 1))
" D/ i4 G7 h) l2 @/ f& l15 m7 a, M! E0 l5 ?& Z
8、贪心3 M' R9 V# ^9 z+ h
贪心,一般就是按照当前最优解,去推算全局最优解。) i+ e7 L4 z! F" G0 T
所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
3 c& f0 @ X/ j% }! Z g9、迭代2 u7 M2 ?9 H# M* c
每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
' }0 e+ N @5 y! c* G, F6 G10、分治
$ A' g! P( T4 Q" s5 v分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。& h7 ~: e4 P* u& T7 O, W, q
5、算法进阶( p1 T" p! n) w
算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。1 U1 P, X$ V" M* I0 F
如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。
9 A* J# x8 L/ q$ o0 t这个系列主要分为以下几个大块内容:
) d$ {, ~% Z; {9 B 1)图论
9 r/ }& y8 R3 F( z K* D. w 2)动态规划
0 g8 ~# ^7 t2 E0 C8 Q# Z 3)计算几何4 _2 d& [. y7 U$ I3 T# `5 Y' I
4)数论; S. ], y# f4 R% r) Q1 {4 I
5)字符串匹配
2 w* R$ `3 ~& _2 r0 k" q/ W* Q- C 6)高级数据结构(课本上学不到的)
+ Y" D `# S# b( r( U* T3 W 7)杂项算法4 u- K! Y2 k4 b- M& ~/ ^. B4 x
& K. B. W0 K* ?( j7 o; Z% z+ e$ ^3 K; v% u- h- d- E
先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:
$ o6 H1 f- S+ ?) V
3 P1 i) n$ [+ i7 x! _1 Q# s3 X7 X# a2 q
8 y7 ?. J( J$ }' b& l9 D: v( o
6 j: D" w( M% z9 Z1)图论" O# G7 P1 d% o1 }' i7 d
1、搜索概览
* P9 g0 S; v8 k: i: i5 c3 x图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。
$ U: E/ \9 r o7 @" ~/ w- Y8 [- T3 d
+ w- L4 w0 }9 j! H8 f! }3 w$ G
比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
* X d; Z8 d- z# A- |1 f. {- F2、深度优先搜索2 G' F# X# ]: B
深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
7 Y# S; @ X2 N3 F原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。4 F' o4 t/ l" n) q+ j9 V6 P! p) Y
但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
5 ^4 x- r, @2 c; m8 j9 d如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。, d4 m) h) x/ c3 M' v! T
* V' }0 b. Z5 ~6 y1 w, k# c
( u: l8 f. i6 L红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:
' \3 h D9 s" @/ i0 m5 W! l' O! ? 0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 69 c4 R! m/ b4 X: u
18 l6 m1 G6 Q E& o% q
同样,搜索的例子还有:/ h* u" U$ T' W9 ~% a; I
" |9 M9 F$ r4 q7 l. A! E# H* w+ \6 g" B) n2 e2 P8 Y; {
计算的是利用递归实现的 n nn 的阶乘。1 |. G3 E7 i; G9 u2 b
3、记忆化搜索
8 d% A/ o$ D& R: u3 P5 g2 a. X对于斐波那契函数的求解,如下所示:7 G$ ^1 P6 ?7 k$ B: i
f ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =2 ~8 y; z0 ]6 B% g2 x3 r, v
⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)( [+ L7 U. B6 b, n H7 p
{1(n=0)1(n=1)f(n−1)+f(n−2)(n>2), |" ^+ b9 M4 z3 O8 G
f(n)=
0 M! u% Z. x3 ?0 W& \⎩
9 n1 }9 _4 C" p8 b" {⎪7 m: y- G8 h$ O9 j! y
⎨
% F: _! @ X- [% c⎪+ Y3 Y6 D2 W% G3 y- A# _. m! V
⎧* ]7 {6 d8 Q) i0 H* u0 }
$ X% i: l, U" g3 A) p4 G9 S
/ |' }+ \! D% |' L1
; t8 `, ~5 ? G1( g3 r) q# C( O: G- R- p
f(n−1)+f(n−2)
) S0 t! Q/ @5 Q) o/ [
: k' I$ ]7 p: l+ A
7 s5 \- D; W6 M G+ f/ T" d0 v(n=0)
$ `1 Y1 v, O6 W3 g(n=1)
/ Y% {- ^" l! p7 A6 W6 [(n>2)
% F+ C' }- m; a1 }
4 R0 h. m6 A! O $ V& Q o* c. C3 k+ J& W1 a, H! d
对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:5 a2 f, o* F: n2 x7 w" b
4 o+ {+ v( b- ]1 q$ ^, c) _
7 g! |, D6 H9 c- ~! v1 ?
这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。- W& k3 n, r2 Z, M/ Z
我们通过一个动图来感受一下:) i3 k& Z$ g r# o" {* }/ d
: t# \% g* b `- I+ w" O; W. ]. j- n' ^' h: D1 A6 f+ ~% {
当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
2 ^: p. r" `4 J! g0 z5 \这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
) O6 X! L! V6 u! ^5 i6 J; e/ D$ Z4、广度优先搜索4 ]8 V8 ?& \/ X, t' g! T% [
单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
! J7 v- d) a0 \# h5 c我们通过一个动图来对广搜有一个初步的印象。
6 S, h' w0 P2 }# _7 i- s, u* G; A- K1 U
2 k7 ?6 t6 J; f" K! Z: p
2 Z Z: B5 G# w" o/ t7 d5 j! c+ e( A+ N; m/ x% u# |6 L# j
从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。' Z( f$ m V5 z4 o( e8 w
那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。% j& t, m# S g8 l3 T5 P% S
这时候,算法和数据结构就完美结合了。
( q7 W8 G- r, P* F0 i8 O% h6 i6 i2)动态规划
`9 R' r' ]( |% G- U动态规划算法三要素:; ?: c6 p. X0 b% i
①所有不同的子问题组成的表;
! y& B( f, Y) P) @ ②解决问题的依赖关系可以看成是一个图;+ R( ~& H1 d6 K8 ^
③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);, y9 \; `3 T( Q$ ^
6 G! U W6 N0 W+ i1 ]. u: i0 P
/ Z2 c. t7 ]- E如果子问题的数目为 O ( n t ) O(n^t)O(n
0 A; O- S; D* St
2 g4 [8 [5 R. t; X ),每个子问题需要用到 O ( n e ) O(n^e)O(n : z" T8 a( s5 S, U$ `
e
* e2 }# Y$ k7 b: U2 Q; _ ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。8 _9 i9 r, M, [9 O1 q; u1 \6 M
1、1D/1D
( ^% J9 @1 T9 v6 C- k5 Dd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j ), ^0 A6 G4 U% ^1 `
d=opt(d[j]+w(j,i)∣0<=i<j)/ ~8 t7 r# i" c1 d4 m
状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
# p2 F5 L) T3 Q) d% w% }
+ m [% S0 h: o" e' n1 t6 \% h, `+ V, G0 D g
这类状态转移方程一般出现在线性模型中。
' K$ N0 N8 d2 G3 S2、2D/0D* y% T4 J* T# ^- H& A
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} )% n% ^. h7 L [5 M+ n/ e( f
d[j]=opt(d[i−1][j]+x 9 U, O0 I2 ~/ d. f6 ?% Y7 r; o$ E
i4 D: L- J% b% ]# I% k6 r
5 m2 x; i, X6 h m ,d[j−1]+y
2 I6 U4 X! c1 Fj
6 T7 _. e' q; S+ \3 c! [* z
+ t6 R0 `. S! Z1 U, X% s' w3 \0 _ A ,d[i−1][j−1]+z
+ Q* Y/ t9 I% q8 z( jij o! I6 s2 ]! O% h, h
" E7 s1 f; C+ Q- ~9 n# B, {7 _ )
$ ]' V& M: s( Z8 a) q2 G状态转移如图四所示:8 f' O/ K; A5 l
+ N2 I) F/ A: h! Z5 }" K+ {8 D7 A9 Y% b6 _ Z4 m0 g5 z- Z. ^9 O
比较经典的问题是最长公共子序列、最小编辑距离。6 w! i: w: d/ H' b# k# p
有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
2 ]- H8 b9 N" ?2 g* D" t: V: o有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
6 M$ L. E0 u! J( y3、2D/1D
9 R$ J, u& X5 _# e* h$ A" r5 Wd [ 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] )& c! i( U1 T; U& g: Z6 m7 ]
d[j]=w(i,j)+opt(d[k−1]+d[k][j])
& u6 U2 B1 K! |7 V7 z区间模型常用方程,如图所示:
0 m- ~6 w5 z2 N0 c. l( ~3 I4 E. k C. ^7 h7 T( }" a) o
$ j. F1 q2 E# j( a7 W$ y) E4 N& R另外一种常用的 2D/1D 的方程为:% r( l, H' L. u7 R
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 )- G9 j) N! G s; u& j
d[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
2 g4 z# e7 X8 t9 K5 A区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP K$ p8 A% ~% S/ }) G- G; g
4、2D/2D
, |8 L% [, m2 _6 ?- u, nd [ 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)
% `+ N2 r- s6 J) i. V# e V1 b7 Sd[j]=opt(d[i
+ b9 Q+ Y7 [! \6 O+ ~$ y) v′9 v+ C$ M# H) j W0 P- h+ y3 y. ?/ @
][j ( _: k1 ~1 [' l/ a( V
′
- Y/ G- w( ^4 n& }7 L ]+w(i 8 q, a3 I$ ?) x$ G
′
& }; Z) G9 K$ H. ^* A: t ,j 2 C) }. r$ ^1 h
′6 s, x! P7 |7 k3 R% x* v3 j; O2 V
,i,j)∣0<=i ) M, m, J; o2 b) K
′2 Q# N, |9 \# o* C7 @( L7 ^
<i,0<=j
8 a8 m# p8 A0 Q" \+ M8 Q5 x+ p% q′
4 I8 U3 V8 `& r8 c* d) a+ d! b <j)
! V; R8 w0 d! ]* {! l2 c如图所示:
7 f' v# Y7 @# G% Q( \3 E4 \7 ^+ \4 t
M+ D9 m+ s. w常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。8 r' M+ v# ?" l. {8 v. s+ k% z
对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
+ p* z) s% a) o* D0 D* r$ nt+e) S# @8 S$ t2 x9 h
),空间复杂度是O ( n t ) O(n^t)O(n . E& R5 K h, d4 q; B# \/ v
t
/ B }/ p3 m u$ i. Q ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。9 i. m) w) w5 Y* ]* P% a3 P
3)计算几何8 }. y0 } o! S- e; p$ M
计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
9 U2 H" r1 [* O% x/ s+ f如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
; l, p! h9 j9 g0 x% i* }1、double 代替 float
! | u9 K' X5 ~, x7 n: D' b! h9 _5 Pc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;; f. f W }+ m1 N6 N
2、浮点数判定) p A4 h& r' b
由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
/ q9 L- ]9 m4 D3 M' \5 a两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
; V% M3 @0 f9 ?5 r! y+ d- rconst double eps = 1e-8;# u) i2 q9 P$ p5 R
bool EQ(double a, double b) {' f* D3 }7 g4 P
return fabs(a - b) < eps;
+ ]1 N4 C' D; X2 m7 ?}
( x3 \/ f6 Z6 r; G) j1 V1. a4 f' C& W+ t' h1 Q
2
. [8 F E2 D. k# [% G4 D3/ @6 [3 G" E* w
4( f4 X- \: F& y [" x% t. g
并且可以用一个三值函数来确定某个数是零、大于零还是小于零:" Z2 K& Z( a8 e$ q3 ^" M; H" K7 A0 x
int threeValue(double d) {* a4 U) n0 j( c& M& I$ @3 r
if (fabs(d) < eps)! m" U) @7 O1 E, e/ [/ k6 V/ N
return 0;
* T) s' F2 E$ g% p ` return d > 0 ? 1 : -1;: k7 _% r3 @0 ]5 ]) V. i+ K
}
$ o+ g" e+ R8 i1
- p" o' _$ g, e8 c+ k) s2
8 K% T) b: \* n1 ^( }2 ^3
9 \2 @. z) a) r, P4
8 s: T2 w- w7 ?6 [* F: o) r9 q5$ x: h9 _6 j K' @( u r! x
3、负零判定
7 V) k4 n) x1 r0 r因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
: P7 y+ R' y0 V0 e5 h5 `/ _ double v = -0.0000000001;: S4 Y; n) J* h7 E- r
printf("%.2lf\n", v);
: i U4 c: O! s16 u) L K; G6 a: p2 |% r5 @
2
; ]4 n2 Q( W6 S" P6 u5 F3 J避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
+ J" M) f9 ^( }. m* A double v = -0.0000000001;6 P' ]2 f% A' D; I% ~9 C
if(threeValue(v) == 0) {7 A0 }& z% i9 w3 P, g
v = fabs(v);
7 {( r2 j: u6 |0 N; V }6 d3 `9 x, C- R, O
printf("%.2lf\n", v);% z6 V1 T' b( j/ u0 u. d, F
1
0 v8 B7 N' _; p) I) u29 @; e5 q( i& b$ Y% n1 N0 N
3
% o% z2 S/ H% U0 k" }, G7 S8 E4
% \) y) A& D: D- w% I* Q5
; `8 M" H" w2 L! H, [' K: X4、避免三角函数、对数、开方、除法等
5 m: X; P; N$ a3 x; \c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
6 i* O6 T9 K1 [' _9 `除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
0 d% N5 F) D; Z- r, K1 @; x5、系统性的学习
+ ~6 {9 P( v& e. z# \7 F基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
4 @4 R7 v7 M. g: S: O进阶知识:多边形面积、凸多边形判定、点在多边形内判定;3 c8 m4 h- T4 k; P, w' w
相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。 z6 U" w% l' ~3 U0 w
) Y4 e1 D8 M: r9 {6 f* V
' Y% D3 Q8 l* u学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
( w$ }0 I2 x, Z4)数论
5 `& |9 E& { u9 h6 G9 ^+ u1 I刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!5 \) a3 H* Z3 M9 i/ ?
数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。! ^, U: }+ B5 q& q# ]2 l5 X
当然,数论也有简单问题,一般先做一些入门题提升信心。+ t& r: U/ l6 u- i0 R* L( S8 J5 j N5 ^
1、数论入门
1 \! X$ C! h( [3 Q9 }6 u* s主要是一些基本概念,诸如:
9 C% m1 o3 S' u整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;! v3 S/ X. i- Z1 f
2、数论四大定理
# ~: ^7 T9 E# K! Z/ [7 Y. l |3 r这四个定理学完,可以KO很多题:
7 Z4 T5 M2 R) l |; G0 c3 v欧拉定理、中国剩余定理、费马小定理、威尔逊定理9 ~4 J# R2 Z/ G3 D. h
3、数论进阶
' v% M5 v. |, a5 C1 a: _+ |! O+ e系统性的学习,基本也就这些内容了:2 S8 T% B+ X1 N& E- J& k
扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。
6 h4 B% O( M" B2 }7 H5)字符串匹配
7 r" u5 F4 Z- Z- S/ f \$ k1 w% W字符串匹配学习路线比较明确。
6 H' s4 [6 `' m8 a' i+ A1 O9 b先学习前缀匹配:字典树。# u7 Q8 M+ ?+ |( K O2 T
然后可以简单看一下回文串判定算法:Manacher。 p. e0 s' n0 K: k
以及经典的单字符串匹配算法:KMP。
9 Y, p9 o6 ?+ h7 t' u实际上平时最常用的还是 BM 算法,而ACM中基本不考察。, P0 N3 ]% x+ T8 a6 W+ [, L
然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
5 a' t, U# ?- ~9 E8 f) ~关于 算法学习路线 的内容到这里就结束了。: q( N3 D" a( Y
如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
3 U( l$ g" O1 r参考资料
. P8 R) {/ j% [6 m3 D+ K【阶段一】C语言学习资料:《光天化日学C语言》(日更)
7 r7 p l- m# K6 h, s【阶段二】C语言例题:《C语言入门100例》(日更)/ a. }! e& a0 s" r Z
【阶段三】算法入门题集:《LeetCode算法全集》(日更)! ^- \" C2 `7 B/ k0 B9 ?/ U
【阶段四】算法进阶:《夜深人静写算法》(周更), c0 |+ L! I, P, Y- |6 J
————————————————
3 F# m; b! l. K+ v2 X6 M0 `版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。1 b+ `; z) `2 N" B& k; B$ w
原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/1183822286 k& ~* d( l0 y3 d0 A; I' o
+ w3 d% B' N" O1 K G0 ~
. @# v( {; s6 V+ R1 A |
zan
|