diff --git a/Contents/01.Array/01.Array-Basic/01.Array-Basic.md b/Contents/01.Array/01.Array-Basic/01.Array-Basic.md index 33b72011..ff535448 100644 --- a/Contents/01.Array/01.Array-Basic/01.Array-Basic.md +++ b/Contents/01.Array/01.Array-Basic/01.Array-Basic.md @@ -10,19 +10,14 @@ ![数组](https://qcdn.itcharge.cn/images/20210913163542.png) -如上图所示,数组中的每一个数据元素都有自己的下标索引,下标索引从 `0` 开始,到 `数组元素个数 - 1` 结束。数组中的每一个「下标索引」,都有一个与之相对应的「数据元素」。 +如上图所示,假设数据元素的个数为 $n$,则数组中的每一个数据元素都有自己的下标索引,下标索引从 $0$ 开始,到 $n - 1$ 结束。数组中的每一个「下标索引」,都有一个与之相对应的「数据元素」。 从上图还可以看出,数组在计算机中的表示,就是一片连续的存储单元。数组中的每一个数据元素都占有一定的存储单元,每个存储单元都有自己的内存地址,并且元素之间是紧密排列的。 我们还可以从两个方面来解释一下数组的定义。 -- 第一个方面是 **「线性表」**。 - -线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前、后两个方向。数组就是一种线性表结构,此外,栈、队列、链表都是线性表结构。 - -- 第二个方面是 **「连续的内存空间」**。 - -线性表有两种存储结构:「顺序存储结构」和「链式存储结构」。其中,「顺序存储结构」是指占用的内存空间是连续的,相邻数据元素之间,物理内存上的存储位置也相邻。数组也是采用了顺序存储结构,并且存储的数据都是相同类型的。 +> 1. **线性表**:线性表就是所有数据元素排成像一条线一样的结构,线性表上的数据元素都是相同类型,且每个数据元素最多只有前、后两个方向。数组就是一种线性表结构,此外,栈、队列、链表都是线性表结构。 +> 2. **连续的内存空间**:线性表有两种存储结构:「顺序存储结构」和「链式存储结构」。其中,「顺序存储结构」是指占用的内存空间是连续的,相邻数据元素之间,物理内存上的存储位置也相邻。数组也是采用了顺序存储结构,并且存储的数据都是相同类型的。 综合这两个角度,数组就可以看做是:使用了「顺序存储结构」的「线性表」的一种实现方式。 @@ -34,7 +29,7 @@ 计算机给一个数组分配了一组连续的存储空间,其中第一个元素开始的地址被称为 **「首地址」**。每个数据元素都有对应的下标索引和内存地址,计算机通过地址来访问数据元素。当计算机需要访问数组的某个元素时,会通过 **「寻址公式」** 计算出对应元素的内存地址,然后访问地址对应的数据元素。 -寻址公式如下:`下标 i 对应的数据元素地址 = 数据首地址 + i * 单个数据元素所占内存大小` +寻址公式如下:**下标 $i$ 对应的数据元素地址 = 数据首地址 + $i$ × 单个数据元素所占内存大小**。 ### 1.3 多维数组 @@ -44,7 +39,7 @@ ![二维数组](https://qcdn.itcharge.cn/images/20210916222435.png) -二维数组是一个由 `m` 行 `n` 列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即 **「数组的数组」**。二维数组的第一维度表示行,第二维度表示列。 +二维数组是一个由 $m$ 行 $n$ 列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即 **「数组的数组」**。二维数组的第一维度表示行,第二维度表示列。 我们可以将二维数组看做是一个矩阵,并处理矩阵的相关问题,比如转置矩阵、矩阵相加、矩阵相乘等等。 @@ -52,19 +47,19 @@ 在具体的编程语言中,数组这个数据结构的实现方式具有一定差别。 -`C / C++` 中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如: +C / C++ 语言中的数组最接近数组结构定义中的数组,使用的是一块存储相同类型数据的、连续的内存空间。不管是基本类型数据,还是结构体、对象,在数组中都是连续存储的。例如: ```C++ int arr[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; ``` -`Java` 中的数组跟数据结构定义中的数组不太一样。 `Java` 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如: +Java 中的数组跟数据结构定义中的数组不太一样。Java 中的数组也是存储相同类型数据的,但所使用的内存空间却不一定是连续(多维数组中)。且如果是多维数组,其嵌套数组的长度也可以不同。例如: ```Java int[][] arr = new int[3][]{ {1,2,3}, {4,5}, {6,7,8,9}}; ``` -原生 `Python` 中其实没有数组的概念,而是使用了类似 `Java` 中的 `ArrayList` 容器类数据结构,叫做列表。通常我们把列表来作为 `Python` 中的数组使用。`Python` 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如: +原生 Python 中其实没有数组的概念,而是使用了类似 Java 中的 ArrayList 容器类数据结构,叫做列表。通常我们把列表来作为 Python 中的数组使用。Python 中列表存储的数据类型可以不一致,数组长度也可以不一致。例如: ```python arr = ['python', 'java', ['asp', 'php'], 'c'] @@ -72,11 +67,16 @@ arr = ['python', 'java', ['asp', 'php'], 'c'] ## 2. 数组的基本操作 -数据结构的操作一般涉及到增、删、改、查 4 种情况,下面我们一起来看一下数组的基本操作。 +数据结构的操作一般涉及到增、删、改、查共 $4$ 种情况,下面我们一起来看一下数组的这 $4$ 种基本操作。 ### 2.1 访问元素 -**访问数组中第 `i` 个元素**:只需要检查 `i` 的范围是否在合法的范围区间,即 `0 <= i <= len(nums) - 1`。超出范围的访问为非法访问。当位置合法时,由给定下标得到元素的值。访问操作不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 +> **访问数组中第 $i$ 个元素**: +> +> 1. 只需要检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。超出范围的访问为非法访问。 +> 2. 当位置合法时,由给定下标得到元素的值。 + +访问操作不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 示例代码如下: @@ -92,7 +92,13 @@ value(arr, 3) ### 2.2 查找元素 -**查找数组中元素值为 `val` 的位置**:在数组无序的情况下,只能通过将 `val` 与数组中的数据元素逐一对比的方式进行检索,也称为线性查找。建立一个基于下标的循环,每次将`val` 与当前数据元素 `nums[i]` 进行比较。在找到元素的时候返回元素下标,找不到时可以返回一个特殊值(例如 `-1`)。线性查找操作依赖于数组中元素个数,因此时间复杂度为 $O(n)$。 +> **查找数组中元素值为 $val$ 的位置**: +> +> 1. 建立一个基于下标的循环,每次将 $val$ 与当前数据元素 $nums[i]$ 进行比较。 +> 2. 在找到元素的时候返回元素下标。 +> 3. 遍历完找不到时可以返回一个特殊值(例如 $-1$)。 + +在数组无序的情况下,只能通过将 $val$ 与数组中的数据元素逐一对比的方式进行检索,也称为线性查找。线性查找操作依赖于数组中元素个数,因此时间复杂度为 $O(n)$。 示例代码如下: @@ -110,11 +116,16 @@ print(find(arr, 5)) ### 2.3 插入元素 -插入元素操作分为两种:「在数组尾部插入值为 `val` 的元素」和「在数组第 `i` 个位置上插入值为 `val` 的元素」。 +插入元素操作分为两种:「在数组尾部插入值为 $val$ 的元素」和「在数组第 $i$ 个位置上插入值为 $val$ 的元素」。 + +> **在数组尾部插入值为 $val$ 的元素**: +> +> 1. 如果数组尾部容量不满,则直接把 $val$ 放在数组尾部的空闲位置,并更新数组的元素计数值。 +> 2. 如果数组容量满了,则插入失败。不过,Python 中的 list 列表做了其他处理,当数组容量满了,则会开辟新的空间进行插入。 -**在数组尾部插入值为 `val` 的元素**:如果数组尾部容量不满,则直接把 `val` 放在数组尾部的空闲位置,并更新数组的元素计数值。如果数组容量满了,则插入失败。不过,`Python` 中的 `list` 做了其他处理,当数组容量满了,则会开辟新的空间进行插入。在尾部插入元素的操作不依赖数组个数,其时间复杂度为 $O(1)$。 +在尾部插入元素的操作不依赖数组个数,其时间复杂度为 $O(1)$。 -`Python` 中的 `list` 直接封装了尾部插入操作,直接调用 `append` 方法即可。 +Python 中的 list 列表直接封装了尾部插入操作,直接调用 `append` 方法即可。 ![插入元素](https://qcdn.itcharge.cn/images/20210916222517.png) @@ -127,9 +138,15 @@ arr.append(val) print(arr) ``` -**在数组第 `i` 个位置上插入值为 `val` 的元素**:先检查插入下标 `i` 是否合法,即 `0 <= i <= len(nums)`。确定合法位置后,通常情况下第 `i` 个位置上已经有数据了(除非 `i == len(nums)` ),要把第 `i` 个位置到第 `len(nums) - 1` 位置上的元素依次向后移动,然后再在第 `i` 个元素位置插入 `val` 值,并更新数组的元素计数值。因为移动元素的操作次数跟元素个数有关,最坏和平均时间复杂度都是 $O(n)$。 +> **在数组第 $i$ 个位置上插入值为 $val$ 的元素**: +> +> 1. 先检查插入下标 $i$ 是否合法,即 $0 \le i \le len(nums)$。 +> 2. 确定合法位置后,通常情况下第 $i$ 个位置上已经有数据了(除非 $i == len(nums)$),要把第 $i \sim len(nums) - 1$ 位置上的元素依次向后移动。 +> 3. 然后再在第 $i$ 个元素位置赋值为 $val$,并更新数组的元素计数值。 -`Python` 中的 `list` 直接封装了中间插入操作,直接调用 `insert` 方法即可。 +因为移动元素的操作次数跟元素个数有关,最坏和平均时间复杂度都是 $O(n)$。 + +Python 中的 list 列表直接封装了中间插入操作,直接调用 `insert` 方法即可。 ![插入中间元素](https://qcdn.itcharge.cn/images/20210916224032.png) @@ -144,7 +161,12 @@ print(arr) ### 2.4 改变元素 -**将数组中第 `i` 个元素值改为 `val`**:改变元素操作跟访问元素操作类似。需要先检查 `i` 的范围是否在合法的范围区间,即 `0 <= i <= len(nums) - 1`。然后将第 `i` 个元素值赋值为 `val`。访问操作不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 +> **将数组中第 $i$ 个元素值改为 $val$**: +> +> 1. 需要先检查 $i$ 的范围是否在合法的范围区间,即 $0 \le i \le len(nums) - 1$。 +> 2. 然后将第 $i$ 个元素值赋值为 $val$。 + +改变元素操作跟访问元素操作类似,访问操作不依赖于数组中元素个数,因此时间复杂度为 $O(1)$。 ![改变元素](https://qcdn.itcharge.cn/images/20210916224722.png) @@ -163,11 +185,15 @@ print(arr) ### 2.5 删除元素 -删除元素分为三种情况:「删除数组尾部元素」、「删除数组第 `i` 个位置上的元素」、「基于条件删除元素」。 +删除元素分为三种情况:「删除数组尾部元素」、「删除数组第 $i$ 个位置上的元素」、「基于条件删除元素」。 + +> **删除数组尾部元素**: +> +> 1. 只需将元素计数值减一即可。 -**删除数组尾部元素**:只需将元素计数值减一即可。这样原来的数组尾部元素不再位于合法的数组下标范围,就相当于删除了。时间复杂度为 $O(1)$。 +这样原来的数组尾部元素不再位于合法的数组下标范围,就相当于删除了。时间复杂度为 $O(1)$。 -`Python` 中的 `list` 直接封装了删除数组尾部元素的操作,只需要调用 `pop` 方法即可。 +Python 中的 list 列表直接封装了删除数组尾部元素的操作,只需要调用 `pop` 方法即可。 ![删除尾部元素](https://qcdn.itcharge.cn/images/20210916233914.png) @@ -179,9 +205,15 @@ arr.pop() print(arr) ``` -**删除数组第 `i` 个位置上的元素**:先检查下标 `i` 是否合法,即 `o <= i <= len(nums) - 1`。如果下标合法,则将第 `i + 1` 个位置到第 `len(nums) - 1` 位置上的元素依次向左移动。删除后修改数组的元素计数值。删除中间位置元素的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此删除中间元素的最坏和平均时间复杂度都是 $O(n)$。 +> **删除数组第 $i$ 个位置上的元素**: +> +> 1. 先检查下标 $i$ 是否合法,即 $0 \le i \le len(nums) - 1$。 +> 2. 如果下标合法,则将第 $i + 1$ 个位置到第 $len(nums) - 1$ 位置上的元素依次向左移动。 +> 3. 删除后修改数组的元素计数值。 -`Python` 中的 `list` 直接封装了删除数组中间元素的操作,只需要以下标作为参数调用 `pop` 方法即可。 +删除中间位置元素的操作同样涉及移动元素,而移动元素的操作次数跟元素个数有关,因此删除中间元素的最坏和平均时间复杂度都是 $O(n)$。 + +Python 中的 list 列表直接封装了删除数组中间元素的操作,只需要以下标作为参数调用 `pop` 方法即可。 ![删除中间元素](https://qcdn.itcharge.cn/images/20210916234013.png) @@ -194,7 +226,9 @@ arr.pop(i) print(arr) ``` -**基于条件删除元素**:这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。删除多个元素操作中涉及到的多次移动元素操作,可以通过算法改进,将多趟移动元素操作转变为一趟移动元素,从而将时间复杂度降低为 $O(n)$。一般而言,这类删除操作都是线性时间操作,时间复杂度为 $O(n)$。 +> **基于条件删除元素**:这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。 + +删除多个元素操作中涉及到的多次移动元素操作,可以通过算法改进,将多趟移动元素操作转变为一趟移动元素,从而将时间复杂度降低为 $O(n)$。一般而言,这类删除操作都是线性时间操作,时间复杂度为 $O(n)$。 示例代码如下: @@ -209,7 +243,7 @@ print(arr) 到这里,有关数组的基础知识就介绍完了。下面进行一下总结。 -## 3. 数组总结 +## 3. 数组的基础知识总结 数组是最基础、最简单的数据结构。数组是实现线性表的顺序结构存储的基础。它使用一组连续的内存空间,来存储一组具有相同类型的数据。 diff --git a/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md b/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md index a310b530..4ae112a8 100644 --- a/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md +++ b/Contents/01.Array/02.Array-Sort/01.Array-Bubble-Sort.md @@ -2,52 +2,71 @@ > **冒泡排序(Bubble Sort)基本思想**: > -> 第 `i (i = 1, 2, …)` 趟排序时从序列中前 `n - i + 1` 个元素的第 `1` 个元素开始,相邻两个元素进行比较,若前者大于后者,两者交换位置,否则不交换。 +> 经过多次迭代,通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 -简单来说,「冒泡排序法」通过相邻元素之间的比较与交换,使值较小的元素逐步从后面移到前面,值较大的元素从前面移到后面。 +这个过程就像水底的气泡一样从底部向上「冒泡」到水面,这也是冒泡排序法名字的由来。 -这个过程就像水底的气泡一样向上冒,这也是冒泡排序法名字的由来。 +接下来,我们使用「冒泡」的方式来模拟一下这个过程。 + +1. 首先将数组想象是一排「泡泡」,元素值的大小与泡泡的大小成正比。 +2. 然后从左到右依次比较相邻的两个「泡泡」: + 1. 如果左侧泡泡大于右侧泡泡,则交换两个泡泡的位置。 + 2. 如果左侧泡泡小于等于右侧泡泡,则两个泡泡保持不变。 +3. 这 $1$ 趟遍历完成之后,最大的泡泡就会放置到所有泡泡的最右侧,就像是「泡泡」从水底向上浮到了水面。 + +::: tabs#bubble + +@tab <1> + +![冒泡排序 1](https://qcdn.itcharge.cn/images/202308152226863.png) + +@tab <2> + +![冒泡排序 2](https://qcdn.itcharge.cn/images/202308152227763.png) + +@tab <3> + +![冒泡排序 3](https://qcdn.itcharge.cn/images/202308152227002.png) + +@tab <4> + +![冒泡排序 4](https://qcdn.itcharge.cn/images/202308152227621.png) + +@tab <5> + +![冒泡排序 5](https://qcdn.itcharge.cn/images/202308152227175.png) + +@tab <6> + +![冒泡排序 6](https://qcdn.itcharge.cn/images/202308152227578.png) + +@tab <7> + +![冒泡排序 7](https://qcdn.itcharge.cn/images/202308152228488.png) + +::: ## 2. 冒泡排序算法步骤 -1. 第 `1` 趟排序,从序列中前 `n` 个元素的第 `1` 个元素开始,相邻两个元素依次进行比较和交换: - 1. 先将序列中第 `1` 个元素与第 `2` 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换; - 2. 然后将第 `2` 个元素与第 `3` 个元素比较,如果前者大于后者,则两者交换位置,否则不交换; - 3. 依次类推,直到第 `n - 1` 个元素与第 `n` 个元素比较(或交换)为止。 - 4. 经过第 `1` 趟排序,使得 `n` 个元素中第 `i` 个值最大元素被安置在第 `n` 个位置上。 -2. 第 `2` 趟排序,从序列中前 `n - 1` 个元素的第 `1` 个元素开始,相邻两个元素依次进行比较和交换: - 1. 先将序列中第 `1` 个元素与第 `2` 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换; - 2. 然后将第 `2` 个元素与第 `3` 个元素比较,若前者大于后者,则两者交换位置,否则不交换; - 3. 依次类推,直到对 `n - 2` 个元素与第 `n - 1` 个元素比较(或交换)为止。 - 4. 经过第 `2` 趟排序,使得数组中第 `2` 个值最大元素被安置在第 `n - 1` 个位置上。 -3. 依次类推,对前 `n - 2` 个元素重复上述排序过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 - -## 3. 冒泡排序动画演示 - -![](https://qcdn.itcharge.cn/images/20220812131649.gif) - -1. 初始序列为:`[6, 2, 3, 5, 1, 4]`。 -2. 第 `1` 趟排序,从序列中前 `6` 个元素的第 `1` 个元素开始,相邻两个元素进行比较和交换:: - 1. 先将序列中第 `1` 个元素与第 `2` 个元素进行比较,也就是将 `6` 和 `2` 进行比较。因为 `6 > 2`,所以两者交换位置,交换位置后,`2` 在第 `1` 位,`6` 在第 `2` 位。 - 2. 然后将第 `2` 个元素与第 `3` 个元素比较,也就是将 `2` 和 `3` 进行比较。因为 `2 < 3`,所以不用交换; - 3. 依次类推,直到第 `5` 个元素与第 `6` 个元素比较(或交换)为止。 - 4. 经过第 `1` 趟排序,使得 `6` 个元素中第 `6` 个值最大元素被安置在第 `6` 个位置上。此时序列变为: `[2, 3, 5, 1, 4, 6]`。 -3. 第 `2` 趟排序,从序列中前 `5` 个元素的第 `1` 个元素开始,相邻两个元素进行比较和交换:: - 1. 先将序列中第 `1` 个元素与第 `2` 个元素进行比较,也就是将 `2` 和 `3` 进行比较。因为 `2 < 3`,所以不用交换; - 2. 然后将第 `2` 个元素与第 `3` 个元素比较,也就是将 `3` 和 `4` 进行比较。因为 `3 < 5`,所以不用交换; - 3. 然后将第 `3` 个元素与第 `4` 个元素比较,也就是将 `5` 和 `1` 进行比较。因为 `5 > 1`,所以两者交换位置,交换位置后,`1` 在第 `3` 位,`5` 在第 `4` 位。 - 4. 依次类推,直到第 `4` 个元素与第 `5` 个元素比较(或交换)为止。 - 5. 经过第 `2` 趟排序,使得 `5` 个元素中第 `5` 个值最大元素被安置在第 `5` 个位置上。此时序列变为: `[2, 3, 1, 4, 5, 6]`。 -4. 依次类推,对前 `4` 个元素重复上述排序过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。此时序列变为:`[1, 2, 3, 4, 5, 6]`。 +假设数组的元素个数为 $n$ 个,则冒泡排序的算法步骤如下: -## 4. 冒泡排序算法分析 +1. 第 $1$ 趟遍历:对前 $n$ 个元素执行「冒泡」,从而使第 $1$ 个值最大的元素放置在正确位置上。 + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,如果前者大于后者,则两者交换位置,否则不交换; + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,如果前者大于后者,则两者交换位置,否则不交换; + 3. 依次类推,直到第 $n - 1$ 个元素与第 $n$ 个元素比较(或交换)为止。 + 4. 经过第 $1$ 趟排序,使得 $n$ 个元素中第 $i$ 个值最大元素被安置在第 $n$ 个位置上。 +2. 第 $2$ 趟遍历:对前 $n - 1$ 个元素执行「冒泡」,从而使第 $2$ 个值最大的元素放置在正确位置上: + 1. 先将序列中第 $1$ 个元素与第 $2$ 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换; + 2. 然后将第 $2$ 个元素与第 $3$ 个元素比较,若前者大于后者,则两者交换位置,否则不交换; + 3. 依次类推,直到对 $n - 2$ 个元素与第 $n - 1$ 个元素比较(或交换)为止。 + 4. 经过第 $2$ 趟排序,使得数组中第 $2$ 个值最大元素被安置在第 $n$ 个位置上。 +3. 依次类推,重复上述「冒泡」过程,直到某一趟排序过程中不出现元素交换位置的动作,则排序结束。 -- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),则只需经过 `1` 趟排序,总共经过 `n - 1` 次元素之间的比较,并且不移动元素,算法就可结束排序。因此,冒泡排序算法的最佳时间复杂度为 $O(n)$。 -- **最坏时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列,或者最小值元素处在序列的最后),则需要进行 `n - 1` 趟排序,总共进行 $∑^n_{i=2}(i−1) = \frac{n(n−1)}{2}$ 次元素之间的比较,因此,冒泡排序算法的最坏时间复杂度为 $O(n^2)$。 -- **冒泡排序适用情况**:冒泡排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,冒泡排序方法比较适合于参加排序序列的数据量较小的情况,尤其是当序列的初始状态为基本有序的情况。 -- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变值相同元素的相对位置,因此,冒泡排序法是一种 **稳定排序算法**。 +我们以 $[5, 2, 3, 6, 1, 4]$ 为例,演示一下排序过程。 + +![冒泡排序](https://qcdn.itcharge.cn/images/202308152309725.png) -## 5. 冒泡排序代码实现 +## 3. 冒泡排序代码实现 ```python class Solution: @@ -64,4 +83,15 @@ class Solution: def sortArray(self, nums: List[int]) -> List[int]: return self.bubbleSort(nums) -``` \ No newline at end of file +``` + +## 4. 冒泡排序算法分析 + +- **最佳时间复杂度**:$O(n)$。最好的情况下(初始时序列已经是升序排列),只需经过 $1$ 趟排序,总共经过 $n$ 次元素之间的比较,并且不移动元素,算法就可以结束排序。因此,冒泡排序算法的最佳时间复杂度为 $O(n)$。 +- **最坏时间复杂度**:$O(n^2)$。最差的情况下(初始时序列已经是降序排列,或者最小值元素处在序列的最后),则需要进行 $n$ 趟排序,总共进行 $∑^n_{i=2}(i−1) = \frac{n(n−1)}{2}$ 次元素之间的比较,因此,冒泡排序算法的最坏时间复杂度为 $O(n^2)$。 +- **冒泡排序适用情况**:冒泡排序方法在排序过程中需要移动较多次数的元素,并且排序时间效率比较低。因此,冒泡排序方法比较适合于参加排序序列的数据量较小的情况,尤其是当序列的初始状态为基本有序的情况。 +- **排序稳定性**:由于元素交换是在相邻元素之间进行的,不会改变值相同元素的相对位置,因此,冒泡排序法是一种 **稳定排序算法**。 + +## 参考资料 + +- 【文章】[11.3. 冒泡排序 - Hello 算法](https://www.hello-algo.com/chapter_sorting/bubble_sort/) \ No newline at end of file diff --git a/Templates/01.Array/Array-BubbleSort.py b/Templates/01.Array/Array-BubbleSort.py index fe8131f2..45a104bb 100644 --- a/Templates/01.Array/Array-BubbleSort.py +++ b/Templates/01.Array/Array-BubbleSort.py @@ -1,14 +1,21 @@ class Solution: def bubbleSort(self, arr): - # 第 i 趟排序 + # 第 i 趟遍历 for i in range(len(arr) - 1): + flag = False # 是否发生交换的标志位 # 从序列中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较 for j in range(len(arr) - i - 1): # 相邻两个元素进行比较,如果前者大于后者,则交换位置 if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] - + flag = True + if not flag: # 此趟遍历未交换任何元素,直接跳出 + break + return arr - - def sortArray(self, nums: List[int]) -> List[int]: - return self.bubbleSort(nums) \ No newline at end of file + + def sortArray(self, nums): + return self.bubbleSort(nums) + + +print(Solution().sortArray([5, 2, 3, 6, 1, 4])) \ No newline at end of file