|
6 | 6 |
|
7 | 7 | 在学习之前,首先我们要弄清楚什么是算法?什么是数据结构?为什么要学习算法和数据结构?
|
8 | 8 |
|
9 | | -简单来说,**算法(Algorithm) 就是解决问题的方法或者过程**。如果我们把问题看成是函数,那么算法就是将输入转换为输出的过程。**数据结构(Data Structure) 是数据的计算机表示和相应的一组操作**。**程序(Program) 则是算法和数据结构的具体实现**。 |
| 9 | +简单来说,**「算法」就是解决问题的方法或者过程**。如果我们把问题看成是函数,那么算法就是将输入转换为输出的过程。**「数据结构」是数据的计算机表示和相应的一组操作**。**「程序」则是算法和数据结构的具体实现**。 |
10 | 10 |
|
11 | 11 | 如果我们把「程序设计」比作是做菜的话,那么「数据结构」就是食材和调料,「算法」则是不同的烹饪方式,或者可以看作是菜谱。不同的食材和调料,不同的烹饪方式,有着不同的排列组合。同样的东西,由不同的人做出来,味道自然也是千差万别。
|
12 | 12 |
|
13 | 13 | 至于为什么要学习算法和数据结构?
|
14 | 14 |
|
15 | | -还是拿做菜举例子。我们做菜,讲究的是「色香味」俱全。**程序设计也是如此,对于待解决的问题,我们追求的是:选择更加合适的「数据结构」,使用花费时间更少、占用空间更小的「算法」。** |
| 15 | +还是拿做菜举例子。我们做菜,讲究的是「色香味俱全」。**程序设计也是如此,对于待解决的问题,我们追求的是:选择更加合适的「数据结构」,使用花费时间更少、占用空间更小的「算法」。** |
16 | 16 |
|
17 | 17 | 我们学习算法和数据结构,是为了学会在编程中从时间复杂度、空间复杂度方面考虑解决方案,训练自己的逻辑思维,从而写出高质量的代码,以此提升自己的编程技能,获取更高的工作回报。
|
18 | 18 |
|
19 | 19 | 当然,这就像是做菜,掌握了食材和调料,学会了烹饪方式,并不意味着你就会做出一盘很好吃的炒菜。同样,掌握了算法和数据结构并不意味着你就会写程序。这需要不断的琢磨和思考,并持续学习,才能成为一名优秀的 ~~厨师~~(程序员)。
|
20 | 20 |
|
21 | 21 | ## 1. 数据结构
|
22 | 22 |
|
23 | | -> 数据结构(Data Structure):带有结构特性的数据元素的集合。 |
| 23 | +> **数据结构(Data Structure)**:带有结构特性的数据元素的集合。 |
24 | 24 |
|
25 | | -简单而言:**「数据结构」**指的是**数据的组织结构,用来组织、存储数据**。 |
| 25 | +简单而言,**「数据结构」**指的是:**数据的组织结构,用来组织、存储数据**。 |
26 | 26 |
|
27 | | -展开来讲:数据结构研究的是数据的逻辑结构、物理结构以及它们之间的相互关系,并对这种结构定义相应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。 |
| 27 | +展开来讲,数据结构研究的是数据的逻辑结构、物理结构以及它们之间的相互关系,并对这种结构定义相应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。 |
28 | 28 |
|
29 | 29 | 数据结构的作用,就是为了提高计算机硬件的利用率。比如说:操作系统想要查找应用程序 「Microsoft Word」 在硬盘中的哪一个位置存储。如果对硬盘全部扫描一遍的话肯定效率很低,但如果使用「B+ 树」作为索引,就能很容易的搜索到 `Microsoft Word` 这个单词,然后很快的定位到 「Microsoft Word」这个应用程序的文件信息,从而从文件信息中找到对应的磁盘位置。
|
30 | 30 |
|
|
36 | 36 |
|
37 | 37 | ### 1.1 数据的逻辑结构
|
38 | 38 |
|
39 | | -> 逻辑结构(Logical Structure):数据元素之间的相互关系。 |
| 39 | +> **逻辑结构(Logical Structure)**:数据元素之间的相互关系。 |
40 | 40 |
|
41 | 41 | 根据元素之间具有的不同关系,通常我们可以将数据的逻辑结构分为以下四种:
|
42 | 42 |
|
43 | 43 | #### 1. 集合结构
|
44 | 44 |
|
45 | | -- **集合结构**:数据元素同属于一个集合,除此之外无其他关系。 |
| 45 | +> **集合结构**:数据元素同属于一个集合,除此之外无其他关系。 |
46 | 46 |
|
47 | 47 | 集合结构中的数据元素是无序的,并且每个数据元素都是唯一的,集合中没有相同的数据元素。集合结构很像数学意义上的「集合」。
|
48 | 48 |
|
49 | 49 | 
|
50 | 50 |
|
51 | | - |
52 | | - |
53 | 51 | #### 2. 线性结构
|
54 | 52 |
|
55 | | -- **线性结构**:数据元素之间是「一对一」关系。 |
| 53 | +> **线性结构**:数据元素之间是「一对一」关系。 |
56 | 54 |
|
57 | 55 | 线性结构中的数据元素(除了第一个和最后一个元素),左侧和右侧分别只有一个数据与其相邻。线性结构类型包括:数组、链表,以及由它们衍生出来的栈、队列、哈希表。
|
58 | 56 |
|
59 | 57 | 
|
60 | 58 |
|
61 | 59 | #### 3. 树形结构
|
62 | 60 |
|
63 | | -- **树形结构**:数据元素之间是「一对多」的层次关系。 |
| 61 | +> **树形结构**:数据元素之间是「一对多」的层次关系。 |
64 | 62 |
|
65 | 63 | 最简单的树形结构是二叉树。这种结构可以简单的表示为:根, 左子树, 右子树。 左子树和右子树又有自己的子树。当然除了二叉树,树形结构类型还包括:多叉树、字典树等。
|
66 | 64 |
|
67 | 65 | 
|
68 | 66 |
|
69 | 67 | #### 4. 图形结构
|
70 | 68 |
|
71 | | --图形结构:数据元素之间是「多对多」的关系。 |
| 69 | +> **图形结构**:数据元素之间是「多对多」的关系。 |
72 | 70 |
|
73 | | -图形结构是一种比树形结构更复杂的非线性结构,用于表示物件与物件之间的关系。一张图由一些小圆点(称为 **顶点** 或 **结点**)和连结这些圆点的直线或曲线(称为 **边**)组成。 |
| 71 | +图形结构是一种比树形结构更复杂的非线性结构,用于表示物件与物件之间的关系。一张图由一些小圆点(称为 **「顶点」** 或 **「结点」**)和连结这些圆点的直线或曲线(称为 **「边」**)组成。 |
74 | 72 |
|
75 | 73 | 在图形结构中,任意两个结点之间都可能相关,即结点之间的邻接关系可以是任意的。图形结构类型包括:无向图、有向图、连通图等。
|
76 | 74 |
|
77 | 75 | 
|
78 | 76 |
|
79 | 77 | ### 1.2 数据的物理结构
|
80 | 78 |
|
81 | | -> 物理结构(Physical Structure):数据的逻辑结构在计算机中的存储方式。 |
| 79 | +> **物理结构(Physical Structure)**:数据的逻辑结构在计算机中的存储方式。 |
82 | 80 |
|
83 | | -计算机内有多种存储结构,采用最多的是这两种结构:**顺序存储结构**、**链式存储结构**。 |
| 81 | +计算机内有多种存储结构,采用最多的是这两种结构:**「顺序存储结构」**、**「链式存储结构」**。 |
84 | 82 |
|
85 | 83 | #### 1. 顺序存储结构
|
86 | 84 |
|
87 | | -> 顺序存储结构(Sequential Storage Structure):将数据元素存放在一片地址连续的存储单元里,数据元素之间的逻辑关系通过数据元素的存储地址来直接反映。 |
| 85 | +> **顺序存储结构(Sequential Storage Structure)**:将数据元素存放在一片地址连续的存储单元里,数据元素之间的逻辑关系通过数据元素的存储地址来直接反映。 |
88 | 86 |
|
89 | 87 | 
|
90 | 88 |
|
|
94 | 92 |
|
95 | 93 | #### 2. 链式存储结构
|
96 | 94 |
|
97 | | -> 链式存储结构(Linked Storage Structure):将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。 |
| 95 | +> **链式存储结构(Linked Storage Structure)**:将数据元素存放在任意的存储单元里,存储单元可以连续,也可以不连续。 |
98 | 96 |
|
99 | 97 | 
|
100 | 98 |
|
|
104 | 102 |
|
105 | 103 | ## 2. 算法
|
106 | 104 |
|
107 | | -> 算法(Algorithm):解决特定问题求解步骤的准确而完整的描述,在计算机中表现为一系列指令的集合,算法代表着用系统的方法描述解决问题的策略机制。 |
| 105 | +> **算法(Algorithm)**:解决特定问题求解步骤的准确而完整的描述,在计算机中表现为一系列指令的集合,算法代表着用系统的方法描述解决问题的策略机制。 |
108 | 106 |
|
109 | | -简单而言:**「算法」** 指的就是解决问题的方法。 |
| 107 | +简单而言,**「算法」** 指的就是解决问题的方法。 |
110 | 108 |
|
111 | | -展开来讲:算法是某一系列运算步骤,它表达解决某一类计算问题的一般方法,对这类方法的任何一个输入,它可以按步骤一步一步计算,最终产生一个输出。它不依赖于任何一种语言,可以用 **自然语言**、**编程语言(Python、C、C++、Java 等)描述**,也可以用 **伪代码**、**流程图** 来表示。 |
| 109 | +展开来讲,算法是某一系列运算步骤,它表达解决某一类计算问题的一般方法,对这类方法的任何一个输入,它可以按步骤一步一步计算,最终产生一个输出。它不依赖于任何一种语言,可以用 **自然语言、编程语言(Python、C、C++、Java 等)描述**,也可以用 **伪代码、流程图** 来表示。 |
112 | 110 |
|
113 | | -举几个例子来说明什么是算法。 |
| 111 | +下面我们举几个例子来说明什么是算法。 |
114 | 112 |
|
115 | | -- 例一: |
| 113 | +- 示例 1: |
116 | 114 |
|
117 | | -> 问题:从上海到北京,应该怎么去? |
| 115 | +> **问题描述**: |
| 116 | +> |
| 117 | +> - 从上海到北京,应该怎么去? |
118 | 118 | >
|
119 | | -> 解决方法:我们可以坐飞机,可以坐高铁,也可以坐长途汽车。不同的解决方案所带来的时间成本、金钱成本是不一样的。比如坐飞机用的时间最少,但费用最高,坐长途汽车费用低,但花费时间长。如果我们选择一个折中的方案,花费时间不算太长,价格也不算太贵,那我们可以选择坐高铁或火车。 |
| 119 | +> **解决方法**: |
| 120 | +> |
| 121 | +> 1. 选择坐飞机,坐飞机用的时间最少,但费用最高。 |
| 122 | +> 2. 选择坐长途汽车,坐长途汽车费用低,但花费时间长。 |
| 123 | +> 3. 选择坐高铁或火车,花费时间不算太长,价格也不算太贵。 |
120 | 124 |
|
121 | | -- 例二: |
| 125 | +- 示例 2: |
122 | 126 |
|
123 | | -> 问题:如何计算 1 + 2 + 3 + ... + 100 的值? |
| 127 | +> **问题描述**: |
| 128 | +> |
| 129 | +> - 如何计算 1ドル + 2 + 3 + ... + 100$ 的值? |
124 | 130 | >
|
125 | | -> 解决方法:我们可以选择用计算器从 1 开始,不断向右依次加上 2,再加上 3,再加上 ... 直到加到 100,得出结果为 5050。也可以选择直接根据高斯求和公式 `和 = (首项 + 末项) * 项数 / 2`,直接算出结果为 $ \frac{(1+100) * 100}{2} = 5050$。 |
| 131 | +> **解决方法**: |
| 132 | +> |
| 133 | +> 1. 用计算器从 1ドル$ 开始,不断向右依次加上 2ドル,ドル再加上 3ドル,ドル...,依次加到 100ドル,ドル得出结果为 5050ドル$。 |
| 134 | +> 2. 根据高斯求和公式:**和 = (首项 + 末项) * 项数 / 2**,直接算出结果为:$ \frac{(1+100) * 100}{2} = 5050$。 |
126 | 135 |
|
127 | | -- 例三: |
| 136 | +- 示例 3: |
128 | 137 |
|
129 | | -> 问题:如何对一个 n 个整数构成的数组进行升序排序? |
| 138 | +> **问题描述**: |
| 139 | +> |
| 140 | +> - 如何对一个 $n$ 个整数构成的数组进行升序排序? |
130 | 141 | >
|
131 | | -> 解决方法:我们可以选择冒泡排序对这 n 个整数进行排序,也可以选择插入排序、归并排序、快速排序等等。 |
| 142 | +> **解决方法**: |
| 143 | +> |
| 144 | +> 1. 使用冒泡排序对 $n$ 个整数构成的数组进行升序排序。 |
| 145 | +> 2. 选择插入排序、归并排序、快速排序等等其他排序算法对 $n$ 个整数构成的数组进行升序排序。 |
132 | 146 |
|
133 | | -以上三例都可以看做是算法。从上海去北京的方法可以看做是算法,对 1~100 的数进行求和的计算方法也可以看做是算法。对数组进行排序的方法也可以看做是算法。并且从这三个例子可以看出对于一个特定的问题,往往有着不同的算法。 |
| 147 | +以上 3ドル$ 个示例中的解决方法都可以看做是算法。从上海去北京的解决方法可以看做是算法,对 1ドル \sim 100$ 的数进行求和的计算方法也可以看做是算法。对数组进行排序的方法也可以看做是算法。并且从这 3ドル$ 个示例中可以看出对于一个特定的问题,往往有着不同的算法。 |
134 | 148 |
|
135 | 149 | ### 2.1 算法的基本特性
|
136 | 150 |
|
137 | | -算法其实就是一系列的运算步骤,这些运算步骤可以解决特定的问题。除此之外,**算法** 应必须具备以下特性。 |
138 | | - |
139 | | -##### 1. 输入 |
140 | | - |
141 | | -对于待解决的问题,都要以某种方式交给对应的算法。在算法开始之前最初赋给算法的参数称为输入。比如例一中的输入就是出发地和目的地的参数(北京,上海),例三中的输入就是 n 个整数构成的数组。 |
142 | | - |
143 | | -一个算法可以有多个输入,也可以没有输入。比如例二是对固定问题的求解,就可以看做没有输入。 |
144 | | - |
145 | | -##### 2. 输出 |
146 | | - |
147 | | -算法是为了解决问题存在的,最终总需要返回一个结果。所以至少需要一个或多个参数作为算法的输出。比如例一中的输出就是最终选择的交通方式,例二中的输出就是和的结果。例三中的输出就是排好序的数组。 |
148 | | - |
149 | | -##### 3. 有穷性 |
150 | | - |
151 | | -算法必须在有限的步骤内结束,并且应该在一个可接受的时间内完成。比如例一,如果我们选择五一从上海到北京去旅游,结果五一纠结了三天也没决定好怎么去北京,那么这个旅游计划也就泡汤了,这个算法自然也是不合理的。 |
152 | | - |
153 | | -##### 4. 确定性 |
154 | | - |
155 | | -组成算法的每一条指令必须有着清晰明确的含义,不能令读者在理解时产生二义性或者多义性。就是说,算法的每一个步骤都必须准确定义而无歧义。 |
156 | | - |
157 | | -##### 5. 可行性 |
| 151 | +算法其实就是一系列的运算步骤,这些运算步骤可以解决特定的问题。除此之外,**算法** 应必须具备以下特性: |
158 | 152 |
|
159 | | -算法的每一步操作必须具有可执行性,在当前环境条件下可以通过有限次运算实现。也就是说,每一步都能通过执行有限次数完成,并且可以转换为程序在计算机上运行并得到正确的结果。 |
| 153 | +1. **输入**:对于待解决的问题,都要以某种方式交给对应的算法。在算法开始之前最初赋给算法的参数称为输入。比如示例 1ドル$ 中的输入就是出发地和目的地的参数(北京,上海),示例 3ドル$ 中的输入就是 $n$ 个整数构成的数组。一个算法可以有多个输入,也可以没有输入。比如示例 2ドル$ 是对固定问题的求解,就可以看做没有输入。 |
| 154 | +2. **输出**:算法是为了解决问题存在的,最终总需要返回一个结果。所以至少需要一个或多个参数作为算法的输出。比如示例 1ドル$ 中的输出就是最终选择的交通方式,示例 2ドル$ 中的输出就是和的结果。示例 3ドル$ 中的输出就是排好序的数组。 |
| 155 | +3. **有穷性**:算法必须在有限的步骤内结束,并且应该在一个可接受的时间内完成。比如示例 1ドル,ドル如果我们选择五一从上海到北京去旅游,结果五一纠结了三天也没决定好怎么去北京,那么这个旅游计划也就泡汤了,这个算法自然也是不合理的。 |
| 156 | +4. **确定性**:组成算法的每一条指令必须有着清晰明确的含义,不能令读者在理解时产生二义性或者多义性。就是说,算法的每一个步骤都必须准确定义而无歧义。 |
| 157 | +5. **可行性**:算法的每一步操作必须具有可执行性,在当前环境条件下可以通过有限次运算实现。也就是说,每一步都能通过执行有限次数完成,并且可以转换为程序在计算机上运行并得到正确的结果。 |
160 | 158 |
|
161 | 159 | ### 2.2 算法追求的目标
|
162 | 160 |
|
163 | 161 | 研究算法的作用,就是为了使解决问题的方法变得更加高效。对于给定的问题,我们往往会有多种算法来解决。而不同算法的 **成本** 也是不同的。总体而言,一个优秀的算法至少应该追求以下两个目标:
|
164 | 162 |
|
165 | | -1. 所需运行时间更少(时间复杂度更低); |
166 | | -2. 占用内存空间更小(空间复杂度更低)。 |
| 163 | +1. **所需运行时间更少(时间复杂度更低)**; |
| 164 | +2. **占用内存空间更小(空间复杂度更低)**。 |
167 | 165 |
|
168 | | -假设计算机执行一条命令的时间为 1 纳秒(并不科学),第一种算法需要执行 100 纳秒,第二种算法则需要执行 3 纳秒。如果不考虑占用内存空间的话,很明显第二种算法比第一种算法要好很多。 |
| 166 | +假设计算机执行一条命令的时间为 1ドル$ 纳秒(并不科学),第一种算法需要执行 $100$ 纳秒,第二种算法则需要执行 3ドル$ 纳秒。如果不考虑占用内存空间的话,很明显第二种算法比第一种算法要好很多。 |
169 | 167 |
|
170 | | -假设计算机一个内存单元的大小为一个字节,第一种算法需要占用 3 个字节大小的内存空间,第二种算法则需要占用 100 个字节大小的内存空间,如果不考虑运行时间的话,很明显第一种算法比第二种算法要好很多。 |
| 168 | +假设计算机一个内存单元的大小为一个字节,第一种算法需要占用 3ドル$ 个字节大小的内存空间,第二种算法则需要占用 $100$ 个字节大小的内存空间,如果不考虑运行时间的话,很明显第一种算法比第二种算法要好很多。 |
171 | 169 |
|
172 | 170 | 现实中算法,往往是需要同时从运行时间、占用空间两个方面考虑问题。当然,运行时间越少,占用空间越小的算法肯定是越好的,但总是会有各种各样的因素导致了运行时间和占用空间不可兼顾。比如,在程序运行时间过高时,我们可以考虑在空间上做文章,牺牲一定量的空间,来换取更短的运行时间。或者在程序对运行时间要求不是很高,而设备内存又有限的情况下,选择占用空间更小,但需要牺牲一定量的时间的算法。
|
173 | 171 |
|
174 | 172 | 当然,除了对运行时间和占用内存空间的追求外,一个好的算法还应该追求以下目标:
|
175 | 173 |
|
176 | | -1. 正确性:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。 |
177 | | -2. 可读性:可读性指的是算法遵循标识符命名规则,简洁易懂,注释语句恰当,方便自己和他人阅读,便于后期修改和调试。 |
178 | | -3. 健壮性:健壮性指的是算法对非法数据以及操作有较好的反应和处理。 |
| 174 | +1. **正确性**:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。 |
| 175 | +2. **可读性**:可读性指的是算法遵循标识符命名规则,简洁易懂,注释语句恰当,方便自己和他人阅读,便于后期修改和调试。 |
| 176 | +3. **健壮性**:健壮性指的是算法对非法数据以及操作有较好的反应和处理。 |
179 | 177 |
|
180 | | -这 3 个目标是算法的基本标准,是所有算法所必须满足的。一般我们对好的算法的评判标准就是上边提到的 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 |
| 178 | +这 3ドル$ 个目标是算法的基本标准,是所有算法所必须满足的。一般我们对好的算法的评判标准就是上边提到的 **所需运行时间更少(时间复杂度更低)**、**占用内存空间更小(空间复杂度更低)**。 |
181 | 179 |
|
182 | 180 |
|
183 | 181 | ## 3. 总结
|
|
0 commit comments