📢博客主页:https://blog.youkuaiyun.com/WhereIsHeroFrom
📢欢迎各位 👍点赞 ⭐收藏 📝评论,如有错误请留言指正,非常感谢!
📢本文由 英雄哪里出来 原创,转载请注明出处,首发于 🙉 优快云 🙉
作者的专栏:
👉C语言基础专栏《光天化日学C语言》
👉算法进阶专栏《夜深人静写算法》
👉C/C++大厂面试专栏《C/C++ 面试 100 例》
👉奇奇怪怪的专栏《学姐教我写代码》
文章目录
一、前言
本文适合对算法处于朦胧期的初学者,文字浅显易懂,并且配有生动有趣的动图,也是作者呕心沥血之作,希望对刚入大学,或者职场上想要涉足算法的青年同僚有所启示。
采用风趣幽默的故事的形式,对基础C语言进行了一个复习,并且引入了一些简单的一行算法,从最简单的语法开始讲起,套入故事情节,相信你一定可以看懂。
学习算法,任何时候都不嫌晚,大不了就是大器晚成而已,所以无论你是30岁,40岁,50岁,甚至60岁,只要下了决心,就已经成功了一半!本文的故事发生在 ❤️《学姐教我写代码(二)》一行代码一个算法(上)❤️ 的几天后,剧情扑朔迷离,作者至今回忆起来还历历在目。
- 本章能够学到的东西大概如下:

二、梦醒时分
- 自从上次梦到学姐教了我四个算法以后。过了两天,三天,四天 一直没有等到学姐再次进入我的梦中……

- 当我快要绝望之时,天上突然下起了倾盆大雨。

- 然后,学姐竟然从天上下了下来!

- 我嘞个去啊! 我扶起学姐,发现她是紫色的!说明我还在梦里咧!因为根据 《学姐教我写代码(一)》十道题搞定C语言 所说,现实中的学姐是橙色的呀!
- 原来我根本就没有醒过,我问学姐剩下那四个算法到底是什么?
- 学姐愣了一下!

- 我嘞个去啊!学姐,该不会是脑子瓦特了吧!
- 看来得帮助学姐恢复记忆,不然那剩下的四个算法我就永远学不到了。
- 这时候身边突然出现了一颗胶囊,上面写着六个大字:

- 我嘞个去啊!果然是做梦啊,要什么来什么!
- 但是这颗胶囊怎么充斥着诡异的味道!

- 于是我把药丸给学姐服下。
- 只见一道绿光闪过!立刻风云大作!

- 再回头看时,学姐竟然:
- 成了绿色的!!!



- 然后学姐迅速写下了四行代码,就匆匆离我而去了!
- 这四行代码分别是:
void A(int *a, int *b) { *a = *a ^ *b, *b = *a ^ *b, *a = *a ^ *b; }
int B(struct Point a, struct Point b) { return a.x * b.y - a.y * b.x; }
int C(int n, int m) { return n == 0 ? (m == 0 ? 1 : 0) : (C(n-1, m) + C(n-1, m-1) ); }
int D(int a, int b) { return b == 0 ? 1 : ( ((b&1) ? a : 1) * D(a*a, b/2) ); }
- 这时,我突然感觉头部被硬物敲击,反应过来时,发现眼前赫然站立着一个橙色的学姐。
- 太好了,我终于回到现实世界了。


- 我把梦里发生的事情一五一十的告诉了学姐……


- 磨蹭了半个小时以后……
- 等她回来时,我发现我的手臂上竟然有那 四!行!代!码!
- 学姐你看!!!

- 学姐会心一笑,嗯!然后,她告诉我这四行代码的含义。
三、算法解析
算法一
交换两个变量的值
void A(int *a, int *b) {
*a = *a ^ *b, *b = *a ^ *b, *a = *a ^ *b;
}

1)逗号表达式
- 对于C语言中的逗号表达式,你可以理解成就是为了分隔两个语句,从左往右计算即可,如下代码:
#include <stdio.h>
int main() {
int a, b, c, d;
d = ( a = 3, b = a + 3, c = b + 5, 56 );
printf("%d %d %d %d\n", a, b, c, d);
return 0;
}
- 对于这段代码输出的答案为:
3 6 11 56
- 可以看出,这个逗号表达式等价于:
a = 3;
b = a + 3;
c = b + 5;
56;
- 并且整个逗号表达式的值,就是最右边的那个表达式的值,即
56。
2)变量取地址和指针
- 可以用
&符号对一个变量取地址,取了地址后他就变成了一个指针变量,如下:
#include <stdio.h>
int main() {
int a = 5;
int *pa = &a;
return 0;
}
- 这里
a是一个普通变量,pa是一个指针变量。
3)指针取值
- 指针说白了,就是一个变量的地址;在一个指针前面加上
*符号的含义,就是取这个地址里面的值。看个例子加深理解:
#include <stdio.h>
int main() {
int a = 5;
int *pa = &a;
printf("%d\n", *pa);
return 0;
}
- 这段代码输出的值为 5。也就是
a和*pa是等价的;&a和pa是等价的; - 我们可以认为
*和&是两个互逆的操作。
4)异或的性质
- 二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
- 1)0和0异或为0;
- 2)1和1异或为0;
- 3)0和1异或为1;
- 4)1和0异或为1;
- 也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
- 这样就有了两个比较清晰的性质:
- 1)两个相同的十进制数异或的结果一定位零。
- 2)任何一个数和 0 的异或结果一定是它本身。
- 3)异或运算满足结合律和交换律。
5)算法解析

- 首先,我们可以把这段代码拆分开来,因为逗号表达式是可以拆分的,于是得到如下代码:
void A(int *a, int *b) {
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
- 然后,由于这里指针取值了,所以我们可以认为就是普通变量,也就是把
*a当成是A即可,把*b当成是B即可。函数内部,可以理解成是:
A = A ^ B;
B = A ^ B;
A = A ^ B;
- 我们直接来看前两句话,相当于
B等于A^B^B,根据异或的几个性质,我们知道,这时候的B的值已经变成原先A的值了。 - 而再来看最后一句话,相当于
A等于A^B^A,还是根据异或的几个性质,这时候,A的值已经变成了原先B的值。

算法二
叉乘
- 叉乘是用来判定一个顶点是在一条射线的左侧还是右侧。

1)点
- 对于一个二维空间中的点,只需要两个数字就能表示,即
(x, y)分别代表的是横坐标和纵坐标,如图所示:

- 可以用如下数据结构来代表一个点:
struct Point {
int x, y;
};
2)向量
- 向量则代表了两个点之间的差值,即横坐标相减,纵坐标相减。并且向量是有方向的。如图所示红色的箭头代表的就是一个向量。

- 我们可以表示成点
(4,7)和原点(0,0)的差。
3)叉乘的运算
- 叉乘(又叫叉积、向量积,外积),表示的是两个向量经过运算后,和这两个向量垂直的向量(和点乘不同,它的结果还是一个向量),如下:
a ⃗ = ( x a , y a , z a ) \vec a = (x_a,y_a,z_a) a=(xa,ya,za) b ⃗ = ( x b , y b , z b ) \vec b = (x_b,y_b,z_b) b=(xb,yb,zb) a ⃗ × b ⃗ = c ⃗ = ( y a z b − z a y b ) i ⃗ + ( z a x b − x a z b ) j ⃗ + ( x a y b − y a x b ) k ⃗ \vec a \times \vec b = \vec c = (y_az_b-z_ay_b) \vec i + (z_ax_b-x_az_b)\vec j + (x_ay_b-y_ax_b)\vec k a×b=c=(yazb−zayb)i+(zaxb−xazb)j+(xayb−yaxb)k - 其中 i ⃗ = ( 1 , 0 , 0 ) , j ⃗ = ( 0 , 1 , 0 ) , k ⃗ = ( 0 , 0 , 1 ) \vec i = (1, 0, 0), \vec j = (0, 1, 0), \vec k = (0, 0, 1) i=(1,0,0),j=(0,1,0),k=(0,0,1)
- 当两个向量都在 z = 0 z = 0 z=0 的平面上时, z a = z b = 0 z_a=z_b=0 za=zb=0,退化为二维的情况,则有 a ⃗ × b ⃗ = ( x a y b − y a x b ) k ⃗ \vec a \times \vec b = (x_ay_b-y_ax_b)\vec k a×b=(xayb−yaxb)k
- 代码实现如下(因为 k ⃗ \vec k k 方向已经确定,为垂直原向量,所以返回值可以定义为一个数值类型):
Type Point2D::X(const Point2D& other) const {
return x*other.y - y*other.x;
}
- 在二维计算几何中,叉乘可以用来判断两个点是否在一个向量的同一侧,如图二-5-1所示:

- 对于向量
s
⃗
=
O
S
\vec s = OS
s=OS,假设有任意一个点
T
(
x
,
y
)
T(x,y)
T(x,y),对原点到这个点做一个向量
t
⃗
\vec t
t。那么有叉乘:
s ⃗ × t ⃗ = ( 10 y − x 10 ) k ⃗ \vec s \times \vec t = (10y-x10) \vec k s×t=(10y−x10)k - 1) s ⃗ × t ⃗ > 0 \vec s \times \vec t > 0 s×t>0,则 ( x , y ) (x,y) (x,y) 在 s ⃗ \vec s s 左侧(参考点A);
- 2) s ⃗ × t ⃗ = 0 \vec s \times \vec t = 0 s×t=0,则 ( x , y ) (x,y) (x,y) 和 s ⃗ \vec s s 共线(参考点B);
- 3) s ⃗ × t ⃗ < 0 \vec s \times \vec t < 0 s×t<0,则 ( x , y ) (x,y) (x,y) 在 s ⃗ \vec s s 右侧(参考点C);
- 所以,只要两个点分别和对应向量做叉乘,再将结果相乘,如果值大于0,则代表同侧;小于零代表异侧;
- 叉乘还代表了两个向量组成的平行四边形的面积,有公式:
a ⃗ × b ⃗ = ∣ a ⃗ ∣ ∣ b ⃗ ∣ s i n θ \vec a \times \vec b = |\vec a||\vec b|sin\theta a×b=∣a∣∣b∣sinθ - 同样也代表了两个向量组成的三角形的面积的两倍。
4)算法解析
int B(struct Point a, struct Point b) {
return a.x * b.y - a.y * b.x;
}
- 所以这个代码就是叉乘的运算。
算法三
杨辉三角
int C(int n, int m) {
return n == 0 ? (m == 0 ? 1 : 0) : (C(n-1, m) + C(n-1, m-1) );
}
- 杨辉三角就是组合数啦。

1)组合含义
- 组合数表示为 C n m C_n^m Cnm,它的含义是:从 n n n 个不一样的物品中,选择 m m m 个物品的方案数。
2)组合递推式
- 递推公式 C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n−1}^m+C_{n−1}^{m-1} Cnm=Cn−1m+Cn−1m−1,可理解为:含特定元素的组合有 C n − 1 m − 1 C_{n−1}^{m-1} Cn−1m−1,不含特定元素的排列为 C n − 1 m C_{n−1}^m Cn−1m。
- 举例:
- 从 1,2,3,4,5 ( n = 5 ) (n = 5) (n=5) 中取出 2 ( m = 2 ) (m = 2) (m=2) 个元素的组合 C n m C_n^m Cnm:
- 12 13 14 15 23 24 25 34 35 45 12 \ \ 13 \ \ 14 \ \ 15 \ \ 23 \ \ 24 \ \ 25 \ \ 34 \ \ 35 \ \ 45 12 13 14 15 23 24 25 34 35 45
- 这些组合中要么含有元素 “1”,要么不含。
- 其中含有“1”的是:
- 12 13 14 15 12 \ \ 13 \ \ 14 \ \ 15 12 13 14 15
- 把里面的 “1” 都去掉,得到:
- 2 3 4 5 2 \ \ 3 \ \ 4 \ \ 5 2 3 4 5
- 等价于从 2,3,4,5 ( n − 1 ) (n−1) (n−1) 中取出1 ( m − 1 ) (m−1) (m−1) 个元素的组合。
- 其中不含“1”的是:
- 23 24 25 34 35 45 23 \ \ 24 \ \ 25 \ \ 34 \ \ 35 \ \ 45 23 24 25 34 35 45
- 等价于从 2,3,4,5 ( n − 1 ) (n−1) (n−1) 中取出 2 ( m ) (m) (m) 个元素的组合。
而总方案数等于上面两种情况方案数之和,即 C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n−1}^m+C_{n−1}^{m-1} Cnm=Cn−1m+Cn−1m−1。
3)算法解析
- 然后我们把这个三目运算符,改成 if 语句,得到:
int C(int n, int m) {
if(n == 0) {
if(m == 0) {
return 1;
}else {
return 0;
}
}else {
return C(n-1, m) + C(n-1, m-1);
}
}
- 也就是当
n==0的情况进行特殊处理,否则就是上面证明的组合数的递推公式。
算法四
二分快速幂
int D(int a, int b) {
return b == 0 ? 1 : ( ((b&1) ? a : 1) * D(a*a, b/2) );
}

1)幂指数的递推
- 对于幂指数 a b a^b ab,最简单的递推方式就是 a b = a × a b − 1 a^b = a \times a^{b-1} ab=a×ab−1。
- 令 f ( a , b ) = a b f(a,b) = a^b f(a,b)=ab, 容易得到:
- f ( a , b ) = a f ( a , b − 1 ) f(a, b) = a f(a, b-1) f(a,b)=af(a,b−1)
2)幂指数的二分方程
- f ( a , b ) = { 1 b 为 0 a f ( a , b − 1 2 ) 2 b 为 奇 数 f ( a , b 2 ) 2 b 为 正 偶 数 f(a,b) = \begin {cases} 1 & b 为 0 \\ af(a, \frac {b-1}{2})^2 & b为奇数\\ f(a, \frac {b}{2})^2 & b为正偶数\\ \end{cases} f(a,b)=⎩⎪⎨⎪⎧1af(a,2b−1)2f(a,2b)2b为0b为奇数b为正偶数
3)算法解析
- 这样,我们继续把刚才的代码的三目运算符拆开,利用上面的幂指数的二分方程,得到:
int D(int a, int b) {
if(b == 0) {
return 1;
}
if(b & 1) {
return a * D(a*a, b/2);
}else {
return D(a*a, b/2);
}
}
四、没想到可以学到这么多知识点

- 那天以后,我就对学姐刮目相看,但是一直没有理解,学姐这个神奇的存在,为什么会时常进入我的梦中,难道这就是传说中的 “日有所思,夜有所梦” ?
- 现如今,据说学姐已经嫁人,不知道她过得如何………………

📢博客主页:https://blog.youkuaiyun.com/WhereIsHeroFrom
📢欢迎各位 👍点赞 ⭐收藏 📝评论,如有错误请留言指正,非常感谢!
📢本文由 英雄哪里出来 原创,转载请注明出处,首发于 🙉 优快云 🙉
作者的专栏:
👉C语言基础专栏《光天化日学C语言》
👉算法进阶专栏《夜深人静写算法》
本文通过有趣的故事形式介绍了四个实用的算法实例,包括变量交换、叉乘判断、杨辉三角组合数计算及快速幂运算。

4004





