本系列为作者学习记录
感谢Jack-Cui视频的启发
视频地址:【计算机科学速成课】[40集全/精校] - Crash Course Computer Science
系列文章目录
从零开始的自我提升计划:计算机科学(一)
从零开始的自我提升计划:计算机科学(二)
从零开始的自我提升计划:计算机科学(三)
前言
本文内容包括:
- 算法入门-Intro to Algorithms
- 数据结构-Data Structures
一、算法入门
通常,解决一个问题有许多不同的方法,这些不同的方法称为“算法”,即解决问题的具体步骤。一般来说,所用步骤越少越好,但是有时也会关心其他因素,例如占了多少内存。
记载最多的算法是“排序”,排序一般针对的是数组(Array)。最简单的是选择排序,即从数组中选出最小(最大)的元素,将其放在第一个位置,然后再选出次小(次大)放在第二个位置,不断重复这个过程,直到排序完整个数据。
算法的输入大小和运行步骤之间的关系称为复杂度。使用大O表示法,例如上述的选择排序的复杂度即为
O
(
n
2
)
O(n^2)
O(n2),效率很低。另一种排序算法称为归并排序,例如数组内元素为:
[239, 307, 214, 250, 384, 299, 223, 312]
首先将其不断二分,直到每个数组大小为1,二分过程如下:
# 第一次二分
[239, 307, 214, 250] [384, 299, 223, 312]
# 第二次二分
[239, 307] [214, 250] [384, 299] [223, 312]
# 第三次二分
[239] [307] [214] [250] [384] [299] [223] [312]
接下来就可以进行归并,归并过程选择归并前的数组内第一个元素进行比较,将更小的放在归并后的数组内。例如在第二次归并时,比较[239, 307]和[214, 250]两个数组内第一个元素的大小,即把214放入归并后数组的第一个位置内,然后再进行比较,此时比较的是[239, 307]和[250]两个数组内第一个元素的大小,不断重复这个过程,即可完成排序,过程如下:
# 第一次归并
[239, 307] [214, 250] [299, 384] [223, 312]
# 第二次归并
[214, 239, 250, 307] [223, 299, 312, 384]
# 第三次归并
[214, 223, 239, 250, 299, 307, 312, 384]
归并算法的复杂度更低,为 O ( n log n ) O(n\log n) O(nlogn), n n n为比较+合并的次数, log n \log n logn为合并步骤的次数。
另一个经典算法是图搜索,图是用线连接起来的一堆节点,可把图想象为地图,节点为城市,线是公路,每个城市之间的路程不同,用成本或者权重来代称。要计算某两个节点之间的最小成本,最简单的方法即尝试每一种路,计算总成本后排序,这就是蛮力方法,复杂度为
O
(
n
!
)
O(n!)
O(n!)。
另一种更简单的算法为Dijkstra算法,这个算法每轮计算总是从成本最低的节点开始。从初始位置开始(成本为0),计算到达相邻节点所需的成本,并记录在相应的节点位置处。然后计算下一个成本最低的节点到达其相邻节点所需成本(此时需要加上当前节点已经耗费的成本),如果相邻节点已经记录了成本,则比较当前所需成本和已记录的成本,将更小的那个成本记录在该节点处。
例如图中,第二轮计算时成本最小的点位KING’S LANDING,从KING’S LANDING到RIVERRUN的成本为25,加上当前节点的成本8,总成本为33,比RIVERRUN记录的成本10要高,因此RIVERRUN存储的成本仍为10。经过一轮一轮的计算,即可得到最小成本,该算法的复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
后面Dijkstra算法有了改进,复杂度降低为
O
(
n
log
n
+
l
)
O(n\log n + l)
O(nlogn+l),
n
n
n为节点数,
l
l
l是有多少条线,对图中的例子来说,有6个节点9条线,改进前的复杂度为
O
(
6
2
)
=
36
O(6^2) = 36
O(62)=36,改进后的复杂度为
O
(
6
log
6
+
9
)
=
13.7
O(6\log 6 + 9) = 13.7
O(6log6+9)=13.7。
二、数据结构
上节中提到一种基本的结构,数组,也叫列表或者向量。数组内的值是在内存中是连续存储的,访问数组内值,需要使用下标(index)。
字符串与数组比较类似,但是它是由字母、数字、标点符号等组成的。在内存中存储字符串时,结尾通常有个表示字符串结尾的符号,NULL或者**\0**。计算机有很多处理字符串的函数,例如strcat将两个字符串连接起来。
在表示矩阵时,通常使用二维数组,即数组内的元素为数组。表示多维矩阵时,增加数组维度即可。
结构体可以将多个变量打包在一起,这些变量类型可以不同,因此结构体可以创造更加复杂的数据结构。例如使用结构体创建一个节点,存放一个变量和一个指针,指针指向内存的地址。将节点连接起来就可生成链表,链表可以是循环的,成为循环链表,也可以不循环,最后一个节点的指针为“NULL”。
队列也是一种使用链表的数据结构,通常有入队和出队操作,入队只能将新的节点放置在队尾,而出队只能从队首操作,即队列具有先进先出(FIFO)的特点。只需要将队列稍作修改,就可形成栈,栈的特点是后进先出,即出栈和入栈都只能从栈顶开始操作。
将节点的指针改为多个,分别指向不同的节点,就形成了树,最顶端的节点称为“根节点”,根节点下属的节点称为“子节点”,子节点的上层称为“母节点”,无子节点的节点称为“叶节点”。若每个节点至多可以有2个子节点,则称为“二叉树”,树的特点是单向连接,只能从根到叶。
倘若想数据随意连接,可以使用“图”来表示,上节中连接城市的图就是这种结构,节点之间可以随意指向。
上述为几种最主要的数据结构,在此之上,还有各种变体,例如“红黑树”和“堆”,不同的数据结构适用于不同的使用场景。
点击文中的“红黑树”和“堆”即可访问响应的资料。
另外这里推荐一个数据结构测试器,使用动画可视化数据结构,便于理解。
链接:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
总结
本文介绍了算法入门和数据结构的基本内容,参考资料中详细介绍了红黑树和堆的原理。