选择排序&冒泡排序

在看排序的时候,我们可以先简单了解一下时间复杂度,空间复杂度和稳定性的含义。

时间复杂度

时间复杂度是计算机科学中用于评估算法性能的一种度量,主要用于描述算法在运行过程中所需的时间与输入规模之间的关系。它帮助我们理解算法的效率,特别是在处理大规模数据时。

概念

1.输入规模:通常用 n 表示,代表输入数据的大小或数量。例如,在排序算法中,n 可能表示待排序元素的数量。

2.运行时间:算法执行所需的时间。随着输入规模的增加,运行时间可能会增加。

3.渐进表示法:时间复杂度通常用大O符号(如 O(n)、O(n²))表示,描述算法在最坏情况、平均情况或最好情况的运行时间增长趋势。

分类

1.常数时间复杂度 O(1)

  • 算法的运行时间不随输入规模的变化而变化。例如,访问数组中的某个元素。
  • 示例:arr[0]。

2.对数时间复杂度 O(log n)

  • 算法的运行时间随着输入规模的增加而以对数形式增长。常见于二分查找等算法。
  • 示例:二分查找在已排序数组中查找元素。

3.线性时间复杂度 O(n)

  • 算法的运行时间与输入规模成正比。常见于遍历数组或链表。
  • 示例:遍历一个包含 n 个元素的数组。

4.线性对数时间复杂度 O(n log n)

  • 运行时间是输入规模与对数的乘积。许多高效的排序算法(如归并排序和快速排序)具有这种复杂度。
  • 示例:快速排序、归并排序。

5.平方时间复杂度 O(n²)

  • 算法的运行时间与输入规模的平方成正比。常见于嵌套循环的算法。
  • 示例:冒泡排序、选择排序。

6.立方时间复杂度 O(n³)

  • 算法的运行时间与输入规模的立方成正比。常见于三重嵌套循环的算法。
  • 示例:某些矩阵相乘算法。

7.指数时间复杂度 O(2^n)

  • 算法的运行时间随着输入规模的增加呈指数增长,通常出现在解决组合问题的算法中。
  • 示例:解决旅行商问题的暴力法。

8.阶乘时间复杂度 O(n!)

  • 算法的运行时间与输入规模的阶乘成正比,通常出现在排列组合问题中。
  • 示例:生成所有可能的排列。

空间复杂度

空间复杂度是计算机科学中用于描述算法在运行过程中所需的内存空间与输入规模之间的关系的度量。它帮助我们理解某个算法在执行时对内存的需求,通常以大O符号表示。

组成部分

1.固定部分(固定空间)

  • 这些是程序在运行时无论输入规模如何都需要占用的内存空间。例如:
    • 常量变量、简单数据类型(如整数、字符等)的存储。
    • 固定大小的数组或结构体。

2.动态部分(可变空间)

  • 这些是与输入规模直接相关的内存空间。随着输入规模的增加而增加。例如:

    • 动态分配的内存(例如使用数组、链表、树等数据结构所需的内存)。
    • 递归调用时每次调用所占用的栈空间。

    分类

1.O(1) - 常数空间复杂度

  • 算法所需的内存量是固定的,与输入规模无关。
  • 例子:在数组中交换两个元素,不需要额外的空间。

2.O(n) - 线性空间复杂度

  • 算法所需的内存量与输入规模成正比。
  • 例子:存储n个元素的数组,或者在递归过程中使用的栈空间。

3.O(n^2) - 平方空间复杂度

  • 算法所需的内存量与输入规模的平方成正比。
  • 例子:使用二维数组(如图像处理中的像素数据)。

4.O(log n) - 对数空间复杂度

  • 这些空间通常与对数递归算法(如二分查找)的调用栈深度相关。

计算示例

示例:递归斐波那契数列

cpp 复制代码
def fibonacci(n):  
    if n <= 1:  
        return n  
    return fibonacci(n-1) + fibonacci(n-2)

在上面的代码中,分析空间复杂度如下:

  • 1.固定部分:不管输入 n 是多少,使用的基本数据类型(如整型)是固定的,所以这部分的空间复杂度是 O(1)。

  • 2.动态部分:由于这个算法使用递归,每次递归调用都会占用栈空间。最大深度为 n,所以栈空间的需求是 O(n)。

结合这两部分,斐波那契数列的总空间复杂度为 O(n)。

稳定性

排序的稳定性是指:在排序过程中,如果两个元素的值相等,它们在排序后相对顺序保持不变。这一特性在某些应用场景下是非常重要的,下面是排序稳定性的一些主要作用与应用场景:

1. 保留相对顺序

稳定排序确保相同元素的相对顺序不会改变,这在处理相同关键字的数据时尤为重要。例如,考虑包含学生姓名和成绩的列表。在对成绩进行排序时,如果使用稳定排序算法,原本相邻的学生姓名顺序将保持不变。

2. 复合排序

在复合排序中,稳定性使我们能够进行多层次的排序。例如,首先按姓氏排序,然后按名字排序。如果姓氏排序是稳定的,名字相同的人的顺序不会因姓氏的排序而改变,这样可以保持最终的排序结果符合预期。

3. 数据完整性

在许多实际应用中,数据的完整性非常重要。例如,在电子商务网站上,产品的排序可能首先依据价格,然后依据销售量。稳定的排序算法保证了价格相同的产品按销售量的顺序呈现,从而避免了对消费者造成混淆。

4. 用户体验

在用户界面上的表格或列表中,如果数据通过多种属性进行排序,稳定的排序可以确保用户更容易地理解数据,避免不必要的混淆。例如,在联系人列表中,按姓氏排序后,用户希望相同姓氏的联系人按照添加的顺序出现,而稳定排序可以实现这一点。

5. 算法灵活性

一些排序算法(如归并排序和插入排序)是稳定的,这种稳定特性使得它们在处理特定类型的任务时更具灵活性和适用性。例如,在某些情况下,可能希望近似相同的值保持在某种顺序,这时稳定排序便显得很重要。

好了,大致了解以上内容后,我们就开始学习选择排序和冒泡排序了。

选择排序

思想

  • 1.遍历数组,选择找到最大值,记录最大值下标maxindax,然后将最大值与最后一个值交换,即swap(vec[maxindax],vec[n-1]);
  • 2.在剩下的待排序数组中,重新找到最大值,重复第一步swap(vec[maxindex]vec[n-2]),循环操作,直至数组排序完成。

代码

cpp 复制代码
#include<iostream>  
using namespace std;  

const int N = 1e4; // 定义常量N为10000  
int num[N], n; // 声明一个整数数组num和一个整数n  

void Select_sort() // 定义选择排序函数  
{  
    for (int i = 1; i < n; i++) // 从1到n-1遍历  
    {  
        int index = 0; // 初始化当前最大值的索引为0  
        for (int j = 1; j <= n - i; j++) // 遍历剩余未排序的部分  
        {  
            if (num[index] < num[j]) { // 如果当前最大值小于j位置的值  
                index = j; // 更新最大值的索引  
            }  
        }  
        swap(num[index], num[n - i]); // 将找到的最大值与未排序部分的最后一个元素交换  
    }  
}  

int main() {  
    cin >> n; // 输入数组的大小n  
    for (int i = 0; i < n; i++) { // 循环输入n个整数  
        cin >> num[i];  
    }  
    Select_sort(); // 调用选择排序函数  
    for (int i = 0; i < n; i++) // 输出排序后的数组  
        cout << num[i] << " ";  
    return 0; // 程序正常结束  
}

时间复杂度

  • O(n²)

空间复杂度

  • 在原数组上操作,即使用了常数级空间O(1)

稳定性

  • 不稳定

实例:3 2 3 1从小到大排序(选择最小的放前面),排序之后红色3在黑色3前面,所以不稳定

冒泡排序

思想

  • 1.从左到右,相邻两数两两比较,若下标小的数大于下标大的数则交换,将最大的数放在数组的最后一位(即下标n-1的位置)
  • 2.采用相同的方法,再次遍历数组,将第二大数,放在倒数第二的位置(即n-2),以此类推,直到数组有序
  • 3.优化:当数组在遍历整个数组中没有发生交换,说明待排序数组已经有序,此时可以直接结束排序过程(用bool类型变量作标记)

代码

cpp 复制代码
#include<iostream>  
#include<string>  
using namespace std;  

// 定义常量 N,表示最大可以处理的数组大小  
const int N = 1e4;  
int n, num[N]; // 定义整型变量 n 和一个整型数组 num,用于存储输入的数字  

// 冒泡排序函数  
void Bubble_Sort() {  
    for (int i = 1; i < n; i++) { // 外层循环,表示需要进行的排序轮数  
        bool flag = false; // 标记是否进行了交换  
        for (int j = 0; j < n - i; j++) { // 内层循环,进行相邻元素的比较  
            if (num[j] > num[j + 1]) { // 如果前一个元素大于后一个元素  
                swap(num[j], num[j + 1]); // 交换这两个元素  
                flag = true; // 设置标记为 true,表示有交换发生  
            }  
        }  
        if (!flag) return; // 如果没有交换,说明已经排序完成,提前退出  
    }  
}  

int main() {  
    cin >> n; // 输入数组的大小  
    for (int i = 0; i < n; i++) { // 输入数组的元素  
        cin >> num[i];  
    }  
    Bubble_Sort(); // 调用冒泡排序函数  
    for (int i = 0; i < n; i++) cout << num[i] << " "; // 输出已排序的数组  
    return 0; // 程序结束  
}

时间复杂度

  • O(n²)

空间复杂度

  • 在原数组上操作,即使用了常数级空间O(1)

稳定性

  • 稳定
相关推荐
NiNg_1_2345 分钟前
B树及其Java实现详解
java·数据结构·b树
594h21 小时前
蓝桥杯 第十五届 研究生组 B题 召唤数学精灵
c++·算法·蓝桥杯
轻口味1 小时前
【HarmonyOS Next NAPI 深度探索1】Node.js 和 CC++ 原生扩展简介
c++·harmonyos·harmonyos next·napi·harmonyos-next
高一学习c++会秃头吗1 小时前
leetcode_2816. 翻倍以链表形式表示的数字
算法·leetcode·链表
冠位观测者2 小时前
【Leetcode 热题 100】739. 每日温度
数据结构·算法·leetcode
yanglee03 小时前
L4-Prompt-Delta
人工智能·算法·语言模型·prompt
廖显东-ShirDon 讲编程4 小时前
《零基础Go语言算法实战》【题目 2-1】使用一个函数比较两个整数
算法·程序员·go语言·web编程·go web
计算机小混子4 小时前
C++实现设计模式---访问者模式 (Visitor)
c++·设计模式·访问者模式
刘争Stanley4 小时前
训练一只AI:深度学习在自然语言处理中的应用
人工智能·深度学习·算法·链表·自然语言处理·贪心算法·排序算法
竹下为生4 小时前
LeetCode---147周赛
算法·leetcode·职场和发展