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