不算复杂的简介
冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经过交换慢慢“浮”到数列的顶端,就像水泡一样。
冒泡排序算法的步骤如下:
--比较相邻的元素。如果第一个比第二个大,就交换它们两个。
--对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
--针对所有的元素重复以上的步骤,除了最后一个。
--持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
简单的例子
以下是冒泡排序的一个简单例子:
假设现在有7个数:[5, 3, 8, 4, 2, 9, 1]
那么
- 首先,我们从第一个数开始,比较相邻的两个数,如果前一个数比后一个数大,则交换它们的位置。
- 对数组进行这样的遍历,直到最后一个数。在这一轮遍历中,最大的数会被交换到数组的最后一个位置。
- 接着,我们忽略最后一个已经排好序的数,对剩下的数重复上述过程。
- 这个过程一直持续到整个数组都被排好序。
按照这个过程,我们可以得到以下的排序步骤:
第一轮冒泡排序:
- 比较5和3,交换位置 -> [3, 5, 8, 4, 2, 9, 1]
- 比较5和8,不交换 -> [3, 5, 8, 4, 2, 9, 1]
- 比较8和4,交换位置 -> [3, 5, 4, 8, 2, 9, 1]
- 比较8和2,交换位置 -> [3, 5, 4, 2, 8, 9, 1]
- 比较8和9,不交换 -> [3, 5, 4, 2, 8, 9, 1]
- 此时,最大的数9已经在最后一个位置,我们将其记下:[3, 5, 4, 2, 8, 9, X]
第二轮冒泡排序:
- 忽略最后一个数,比较3和5,不交换 -> [3, 5, 4, 2, 8, X]
- 比较5和4,交换位置 -> [3, 4, 5, 2, 8, X]
- 比较5和2,交换位置 -> [3, 4, 2, 5, 8, X]
- 比较5和8,不交换 -> [3, 4, 2, 5, 8, X]
- 此时,第二大的数8已经在倒数第二个位置,我们将其记下:[3, 4, 2, 5, 8, X]
第三轮冒泡排序:
- 忽略最后两个数,比较3和4,不交换 -> [3, 4, 2, X]
- 比较4和2,交换位置 -> [3, 2, 4, X]
- 此时,第三大的数5已经在它应该在的位置,我们将其记下:[3, 2, 4, 5, X]
第四轮冒泡排序:
- 忽略最后三个数,比较3和2,交换位置 -> [2, 3, 4, X]
- 此时,第四大的数4已经在它应该在的位置,我们将其记下:[2, 3, X, 5]
第五轮冒泡排序:
- 忽略最后四个数,2已经在它应该在的位置,我们将其记下:[2, 3, X, 5]
至此,整个数组已经排好序,结果就是 [2, 3, 4, 5, 8, 9, 1]。
注意:在实际的冒泡排序中,我们通常不会用“X”来表示已经排好序的数,而是在每次遍历时减少需要比较的范围。
冒泡排序 python 代码
以下是一个使用 Python 编写的简单冒泡排序算法实现:
def bubble_sort(arr):
n = len(arr)
# 遍历所有数组元素
for i in range(n):
# 由于最后的 i 个元素已经在正确的位置上,所以每次遍历时可以减少范围
for j in range(0, n-i-1):
# 交换当前元素和下一个元素,如果当前元素大于下一个元素
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
# 待排序的数组
data = [5, 3, 8, 4, 2, 9, 1]
# 调用冒泡排序函数
sorted_data = bubble_sort(data)
# 打印排序后的数组
print("Sorted array is:", sorted_data)
当你运行这段代码时,它会输出排序后的数组 [1, 2, 3, 4, 5, 8, 9]
。这个冒泡排序算法的基本思想是通过重复遍历要排序的列表,比较每对相邻元素,如果它们的顺序错误就把它们交换过来。遍历列表的工作重复进行直到没有再需要交换,也就是说该列表已经排序完成。
冒泡排序 C语言 代码
以下是使用C语言实现冒泡排序算法进行排序的代码:
#include <stdio.h>
void bubbleSort(int arr[], int n) {
int i, j, temp;
for (i = 0; i < n - 1; i++) {
// 内层循环负责进行相邻元素的比较和交换
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换两个元素的值
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int data[] = {5, 3, 8, 4, 2, 9, 1};
int n = sizeof(data) / sizeof(data[0]);
bubbleSort(data, n);
printf("Sorted array:\n");
for (int i = 0; i < n; i++) {
printf("%d ", data[i]);
}
printf("\n");
return 0;
}
当你编译并运行这段C语言代码时,它会输出排序后的数组 [1, 2, 3, 4, 5, 8, 9]
。这个冒泡排序算法的核心是通过双层循环来实现,外层循环控制遍历的次数,内层循环负责在每次遍历中进行相邻元素的比较和条件交换。随着遍历的进行,较大的元素会逐渐移动到数组的末尾,就像气泡从水底升到水面一样,因此得名“冒泡排序”。
冒泡排序 Java 代码
以下是使用Java实现冒泡排序算法进行排序的代码:
public class BubbleSortExample {
public static void main(String[] args) {
int[] data = {5, 3, 8, 4, 2, 9, 1};
bubbleSort(data);
System.out.print("Sorted array: ");
for (int i : data) {
System.out.print(i + " ");
}
}
public static void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// swap arr[j] and arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果在这一轮遍历中没有发生交换,说明数组已经排序完成,可以提前结束
if (!swapped) {
break;
}
}
}
}
当你运行这段Java代码时,它会输出排序后的数组 [1, 2, 3, 4, 5, 8, 9]。Java中的冒泡排序算法与C语言类似,也是通过双层循环来实现排序。外层循环控制遍历的次数,内层循环负责进行相邻元素的比较和交换。如果在一次完整的内层循环中没有发生任何交换,说明数组已经是有序的,这时就可以提前结束排序过程。这种优化可以提高冒泡排序在最好情况下(数组已经是有序的)的时间效率。
时间复杂度O(n)
冒泡排序的时间复杂度根据输入数据的情况而有所不同。
-
最好情况时间复杂度:O(n)。当输入数组已经是完全有序时,冒泡排序只需进行一次遍历就可以确定数组无需任何交换,因此时间复杂度为线性。这种情况下,冒泡排序的效率是最高的。
-
最坏情况时间复杂度:O(n^2)。当输入数组是完全逆序时,冒泡排序需要进行最大数量的比较和交换。在这种情况下,算法需要进行 n-1 轮比较和交换,每轮需要比较的次数分别是 n-1、n-2、...、1,因此总的比较次数是 (n-1) + (n-2) + ... + 1 = n(n-1)/2,所以时间复杂度为二次方。
-
平均情况时间复杂度:O(n^2)。对于随机输入数组,冒泡排序的平均时间复杂度也是二次方。虽然在平均情况下,冒泡排序可能不需要进行最大数量的交换,但是其性能通常不如其他更高效的排序算法,如快速排序、归并排序和堆排序等。
由于冒泡排序的实现简单,它经常被用于教学和理解排序算法的基础概念。然而,在实际应用中,由于其较高的时间复杂度,冒泡排序通常不是最佳选择,特别是在处理大量数据时。更高效的算法,如快速排序和归并排序,通常会被优先考虑。
空间复杂度O(1)
冒泡排序的空间复杂度通常是 O(1)。
这是因为冒泡排序是一种原地排序算法,它不需要额外的存储空间来执行排序操作。所有的数据交换都是在原始数组内部进行的,不需要创建新的数组或使用额外的数据结构。在冒泡排序的每次迭代中,只需要一个临时变量来帮助交换元素的位置,因此额外的存储需求非常小。
由于冒泡排序在空间上的这种高效性,它在处理那些无法分配额外内存或者内存受限的情况时是一个不错的选择。例如,在嵌入式系统或移动设备上,内存资源可能非常有限,冒泡排序的原地排序特性就变得非常有用。
然而,需要注意的是,尽管冒泡排序的空间复杂度很低,但由于其时间复杂度较高,它在处理大数据集时可能不是最佳选择。在实际应用中,通常会选择那些时间复杂度和空间复杂度都相对较低的排序算法,如快速排序、归并排序等,以实现更高效的数据处理。
缺点
冒泡排序作为一种简单直观的排序算法,尽管易于理解和实现,但它也存在一些明显的缺点,限制了其在处理大型数据集或性能要求较高的场景中的应用:
-
效率较低:冒泡排序的平均时间复杂度和最坏时间复杂度都是 O(n^2),这意味着对于包含大量元素的数组或列表,排序所需的时间会随着元素数量的增加而显著增加。在处理大数据集时,冒泡排序的性能可能会变得不可接受。
-
需要多次遍历:冒泡排序需要多次遍历整个数据集,每次遍历都可能需要进行元素交换。这导致算法在最坏情况下需要进行 n^2 次比较和交换,其中 n 是数据元素的数量。
-
不适合动态数据:冒泡排序在每次遍历过程中都需要访问数组的每个元素,这使得它不适合处理动态变化的数据集,因为频繁的数据插入和删除可能会导致已经排序的部分需要重新排序。
-
额外的交换操作:冒泡排序需要通过交换操作来移动元素到正确的位置,这可能会导致额外的开销,尤其是在交换成本较高或者数据元素较大的情况下。
-
没有适应性:冒泡排序不具有适应性,即它不能从已经部分排序的数据中获益。即使输入数据已经部分有序,冒泡排序仍然会执行完整的遍历和交换过程。
由于这些缺点,冒泡排序通常不适用于生产环境中的大型或复杂数据集。在实际应用中,更高效的排序算法,如快速排序、归并排序和堆排序等,通常是更好的选择。然而,对于小型数据集或者作为教学示例,冒泡排序仍然是一个有用的工具。
优点
尽管冒泡排序存在一些缺点,但它仍然具有一些优点,使得它在某些情况下是一个可行甚至是首选的排序算法:
-
简单易懂:冒泡排序的算法逻辑非常简单,易于理解和记忆。它是许多初学者学习排序算法时的第一个实例,因为它直观地展示了如何通过重复比较和交换来实现排序。
-
实现容易:冒泡排序的实现非常直接,只需要两层循环和基本的比较逻辑。这使得它成为编程入门教学中常用的算法之一。
-
原地排序:冒泡排序不需要额外的存储空间来执行排序操作,它在原始数组上进行修改,因此空间复杂度为 O(1)。这对于内存受限的环境或系统非常有用。
-
无需额外数据结构:与一些需要额外数据结构(如栈或队列)的排序算法不同,冒泡排序不需要创建或使用任何额外的数据结构,这简化了实现过程。
-
稳定性:冒泡排序是一种稳定的排序算法,这意味着相等的元素在排序后保持它们原始的相对顺序。这在需要保持元素稳定性的场景中是一个重要的特点。
-
渐进性:冒泡排序是一种渐进式算法,即在最好的情况下(输入数据已经完全有序),它能够在第一次遍历后立即完成排序。这种特性使得冒泡排序在处理接近有序的数据集时可以非常高效。
-
适用性:对于小型或基本有序的数据集,冒泡排序可以提供足够的性能。在这些情况下,其简单性和稳定性可能使其成为合适的选择。
总的来说,冒泡排序的优点在于其简单性、易实现性和稳定性,这使得它在教学和某些特定应用中仍然具有一定的价值。然而,在需要处理大量数据或对性能有较高要求的场景中,通常会选择其他更高效的排序算法。
排序--关联数据
在001中,我们了解了桶排序,并知道了它的缺点,桶排序通常用于处理数值范围分布明确的数据,而关联数据则涉及到多个属性的组合。在学生列表的情况下,我们不仅关注学生的成绩,还关注他们的姓名、学号和班级等其他信息。桶排序无法直接处理这种多属性的关联数据,而关联数据的排序则需要考虑所有这些属性。
以下是一个简单的Python代码示例,展示了如何根据学生的成绩进行排序,同时保持其他关联信息的一致性:
class Student:
def __init__(self, id, name, score, class_name):
self.id = id
self.name = name
self.score = score
self.class_name = class_name
def __repr__(self):
return f"学号: {self.id}, 姓名: {self.name}, 成绩: {self.score}, 班级: {self.class_name}"
def bubble_sort_students(students):
n = len(students)
for i in range(n):
for j in range(0, n-i-1):
if students[j].score > students[j+1].score:
students[j], students[j+1] = students[j+1], students[j]
# 创建学生列表
students = [
Student(1, "Alice", 85, "A"),
Student(2, "Bob", 90, "B"),
Student(3, "Carol", 80, "A"),
Student(4, "Dave", 95, "B")
]
# 按成绩进行冒泡排序
bubble_sort_students(students)
# 打印排序后的学生列表
for student in students:
print(student)
在这个例子中,我们定义了一个 Student
类来存储每个学生的关联信息。然后我们使用冒泡排序算法对 Student
对象列表按照成绩进行排序。冒泡排序允许我们在保持其他关联信息不变的情况下,仅根据成绩进行排序。
相对桶排序的优点包括:
-
多属性排序:冒泡排序可以处理具有多个属性的复杂数据结构,而桶排序通常只适用于单一属性的数值数据。
-
保持关联性:在排序过程中,每个学生对象的所有属性(学号、姓名、成绩和班级)都会保持一致,而不会像桶排序那样可能导致数据的关联性丢失。
-
灵活性:冒泡排序可以轻松地修改以根据其他属性进行排序,例如按姓名或班级排序,而不需要对算法本身进行大的改动。
-
适应性:冒泡排序适用于任何可比较的数据类型,包括字符串、自定义对象等,而不仅仅是数值类型,这使得它在处理关联数据时更加适应性强。
需要注意的是,尽管冒泡排序在处理关联数据方面具有优势,但对于大型数据集,它的效率可能不高。在实际应用中,可能会选择其他更高效的排序算法,如归并排序或快速排序,并通过适当的数据结构来保持数据的关联性。//这是一个冒泡排序值得反复强调的事情
背景扩展:
冒泡排序(Bubble Sort)是一种简单直观的排序算法,它的背景和拓展可以从以下几个方面进行理解:
1. 历史背景
冒泡排序的概念可以追溯到计算机科学早期的算法研究。由于其简单性,它经常被用作算法教学的入门例子。尽管没有确切的证据表明是谁首次提出了冒泡排序,但这种算法已经被使用了几十年,并且在许多入门级编程课程中仍然是一个重要的教学内容。
2. 算法原理
冒泡排序的基本思想是通过重复遍历待排序的列表,比较相邻的元素,并在必要时交换它们的位置。每一轮遍历后,最大的元素会“冒泡”到列表的最后位置。随着遍历的进行,列表中未排序的部分逐渐减少,直到整个列表有序。
3. 算法优化
冒泡排序的一个简单优化是引入一个标志,用于记录在一轮遍历中是否发生了元素交换。如果在某一轮遍历中没有发生任何交换,这意味着列表已经是有序的,因此可以提前结束排序过程。这种优化可以减少不必要的遍历,尤其是在最好的情况下(列表已经有序)。
4. 算法变体
尽管冒泡排序通常被认为是一种效率不高的排序算法,但它的一些变体,如鸡尾酒排序(Cocktail Sort)或双向冒泡排序(Bidirectional Bubble Sort),在某些情况下可能会提供更好的性能。这些变体通过在列表的两端同时进行冒泡或减少比较次数来尝试提高效率。
5. 应用场景
由于冒泡排序的实现简单,它适用于小型数据集或教学演示。在实际应用中,冒泡排序可能不会是首选,但在某些特定情况下,如数据规模小、内存使用受限或对算法的实时性要求不高时,冒泡排序仍然是一个可行的选择。
6. 与其他算法的比较
冒泡排序通常与其他排序算法进行比较,如快速排序、归并排序和堆排序等。这些算法在大多数情况下都提供了更好的性能,尤其是在处理大型数据集时。然而,冒泡排序的简单性和稳定性使其在某些特定场景下仍然具有优势。
7. 入门学习意义
冒泡排序作为算法学习的基础,对于初学者来说具有重要的教育意义。它不仅帮助学生理解排序算法的基本概念,还培养了他们分析问题和解决问题的能力。此外,通过研究冒泡排序的效率和优化,学生可以学习到算法分析和算法设计的重要原则。
总的来说,冒泡排序虽然在实际应用中的使用有限,但它在算法教育和基础概念理解方面发挥着重要作用。通过学习冒泡排序,学生可以建立起对算法性能、复杂度分析和优化的基本认识,为学习更高级的算法打下坚实的基础。