漫谈排序算法及其优化方案

简介: 在计算机科学中,排序算法(Sorting Algorithm)是一种将一组数据按照指定顺序进行排列的算法。通常情况下,这种指定的顺序是由一个*排序关键字*所决定的,例如,按照学生的考试成绩排序,或者按照工资大小排序等

一、 简介

1. 什么是排序算法

在计算机科学中,排序算法(Sorting Algorithm)是一种将一组数据按照指定顺序进行排列的算法。

通常情况下,这种指定的顺序是由一个排序关键字所决定的,例如,按照学生的考试成绩排序,或者按照工资大小排序等。

2. 排序算法的分类

排序算法可以按照算法的思想或实现方式进行分类,常见的分类方法包括以下几种:

  • 比较排序:通过比较元素的大小关系来进行排序。常见的比较排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序等。

  • 非比较排序:不依赖于元素之间的比较关系,而是根据元素的特殊性质来进行排序。常见的非比较排序算法有计数排序、基数排序、桶排序等。

  • 内排序:所有排序操作都在待排序序列中进行,适用于数据量较小的场景。常见的内排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序等。

  • 外排序:由于内存限制等原因,所有排序操作不能在待排序序列中完成,需要通过对外存进行读写,适用于数据量较大的场景。常见的外排序算法有归并排序、多路归并排序等。

  • 稳定排序:在排序操作中,如果存在两个相等的元素,排序后它们的相对位置不发生改变,称为稳定排序。常见的稳定排序算法有插入排序、归并排序等。

  • 不稳定排序:在排序操作中,如果存在两个相等的元素,排序后它们的相对位置可能发生改变,称为不稳定排序。常见的不稳定排序算法有选择排序、快速排序等。

二、 常见的排序算法

排序算法详解

1. 冒泡排序

1.1 算法思想

【算法描述】每次比较相邻的两个元素,如果顺序不对则交换,这样每一轮冒泡至少可以确定一个元素的位置。

1.2 代码实现

void bubbleSort(vector<int>& nums) {
   
    int n = nums.size();
    for (int i = 0; i < n - 1; i++) {
   
        for (int j = 0; j < n - i - 1; j++) {
   
            if (nums[j] > nums[j + 1]) {
   
                swap(nums[j], nums[j + 1]);
            }
        }
    }
}

1.3 时间复杂度及空间复杂度分析

【时间复杂度】最好情况下时间复杂度为 O(n),最坏和平均时间复杂度均为 O(n^2)。

【空间复杂度】冒泡排序为原地排序,空间复杂度为 O(1)。

1.4 稳定性分析

冒泡排序是一种稳定的排序算法,因为在交换顺序时,如果相邻元素相等也会进行交换,所以相等元素的顺序不变。

2. 快速排序

2.1 算法思想

【算法描述】以一个基准数为轴,将序列分为两部分,左边比基准数小,右边比基准数大,再对左右两部分进行递归排序。

2.2 代码实现

int partition(vector<int>& nums, int left, int right) {
   
    int pivot = nums[left];
    int i = left + 1;
    int j = right;
    while (i <= j) {
   
        if (nums[i] <= pivot) {
   
            i++;
        } else if (nums[j] > pivot) {
   
            j--;
        } else {
   
            swap(nums[i], nums[j]);
        }
    }
    swap(nums[left], nums[j]);
    return j;
}
void quickSort(vector<int>& nums, int left, int right) {
   
    if (left < right) {
   
        int pivotPos = partition(nums, left, right);
        quickSort(nums, left, pivotPos - 1);
        quickSort(nums, pivotPos + 1, right);
    }
}

2.3 时间复杂度及空间复杂度分析

【时间复杂度】最好情况下时间复杂度为 O(n log n),最坏情况下时间复杂度为 O(n^2),平均时间复杂度为 O(n log n)。

【空间复杂度】快速排序为原地排序,空间复杂度为 O(1)。

2.4 稳定性分析

快速排序是一种不稳定的排序算法,因为在交换顺序时,相同元素的顺序可能会发生改变。

3. 插入排序

3.1 算法思想

【算法描述】将一个待排序的序列分成两部分,未排序部分和已排序部分。每次将未排序的元素取出,在已排序中从后往前找到合适的位置插入。

3.2 代码实现

void insertionSort(vector<int>& nums) {
   
    int n = nums.size();
    for (int i = 1; i < n; i++) {
   
        int j = i - 1;
        int temp = nums[i];
        while (j >= 0 && nums[j] > temp) {
   
            nums[j + 1] = nums[j];
            j--;
        }
        nums[j + 1] = temp;
    }
}

3.3 时间复杂度及空间复杂度分析

【时间复杂度】最好情况下时间复杂度为 O(n),最坏和平均时间复杂度均为 O(n^2)。

【空间复杂度】插入排序为原地排序,空间复杂度为 O(1)。

3.4 稳定性分析

插入排序是一种稳定的排序算法,因为在交换顺序时,如果相邻元素相等也不会进行交换,所以相等元素的顺序不变。

4. 选择排序

4.1 算法思想

【算法描述】每次从待排序序列中选择最小(或最大)的元素放在已排序序列的末尾。

4.2 代码实现

void selectionSort(vector<int>& nums) {
   
    int n = nums.size();
    for (int i = 0; i < n - 1; i++) {
   
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
   
            if (nums[j] < nums[minIndex]) {
   
                minIndex = j;
            }
        }
        swap(nums[i], nums[minIndex]);
    }
}

4.3 时间复杂度及空间复杂度分析

【时间复杂度】最好、最坏和平均时间复杂度均为 O(n^2)。

【空间复杂度】选择排序为原地排序,空间复杂度为 O(1)。

4.4 稳定性分析

选择排序是一种不稳定的排序算法,因为在选取最小元素时,相同元素的顺序可能会发生改变。

5. 归并排序

5.1 算法思想

【算法描述】归并排序是一种分治思想的排序算法。将待排序数组不断地二分,直到每个子数组中只剩下一个元素。然后,将这些子数组两两合并,直到最后只剩下一个有序的数组。

5.2 代码实现

void merge(vector<int>& nums, int left, int mid, int right) {
   
    int n1 = mid - left + 1;
    int n2 = right - mid;

    vector<int> L(n1);
    vector<int> R(n2);

    for (int i = 0; i < n1; i++) {
   
        L[i] = nums[left + i];
    }
    for (int j = 0; j < n2; j++) {
   
        R[j] = nums[mid + 1 + j];
    }

    int i = 0;
    int j = 0;
    int k = left;

    while (i < n1 && j < n2) {
   
        if (L[i] <= R[j]) {
   
            nums[k] = L[i];
            i++;
        } else {
   
            nums[k] = R[j];
            j++;
        }
        k++;
    }

    while (i < n1) {
   
        nums[k] = L[i];
        i++;
        k++;
    }

    while (j < n2) {
   
        nums[k] = R[j];
        j++;
        k++;
    }
}

void mergeSort(vector<int>& nums, int left, int right) {
   
    if (left < right) {
   
        int mid = left + (right - left) / 2;
        mergeSort(nums, left, mid);
        mergeSort(nums, mid + 1, right);
        merge(nums, left, mid, right);
    }
}

5.3 时间复杂度及空间复杂度分析

【时间复杂度】最好、最坏和平均时间复杂度均为 O(n log n)。

【空间复杂度】归并排序空间复杂度主要由递归过程中需要开辟的系统栈空间决定,空间复杂度为 O(n)。

5.4 稳定性分析

归并排序是一种稳定的排序算法,因为在归并的过程中,当两个元素相等时,我们先将左边的元素插入到结果数组中,从而保证相等元素的顺序不变。

6. 堆排序

6.1 算法思想

【算法描述】利用堆的性质进行排序的方法。将待排序的序列构造成一个大根堆。然后,依次取出堆顶元素(即当前最大元素)将其置于对应位置,再调整堆结构,使其满足堆的性质。

6.2 代码实现

void heapify(vector<int>& nums, int n, int i) {
   
    int largest = i;

    int left = 2 * i + 1;
    int right = 2 * i + 2;

    if (left < n && nums[left] > nums[largest]) {
   
        largest = left;
    }

    if (right < n && nums[right] > nums[largest]) {
   
        largest = right;
    }

    if (largest != i) {
   
        swap(nums[i], nums[largest]);
        heapify(nums, n, largest);
    }
}

void heapSort(vector<int>& nums) {
   
    int n = nums.size();

    for (int i = n / 2 - 1; i >= 0; i--) {
   
        heapify(nums, n, i);
    }

    for (int i = n - 1; i > 0; i--) {
   
        swap(nums[0], nums[i]);
        heapify(nums, i, 0);
    }
}

6.3 时间复杂度及空间复杂度分析

【时间复杂度】最好、最坏和平均时间复杂度均为 O(n log n)。

【空间复杂度】堆排序为原地排序,空间复杂度为 O(1)。

6.4 稳定性分析

堆排序是一种不稳定的排序算法,因为在进行交换时,相同元素的顺序可能会发生改变。

7. 希尔排序

7.1 算法思想

【算法描述】首先将待排序序列分组,对每个子序列进行插入排序,然后逐渐减少子序列的间距,进行插入排序,直到完成排序。

7.2 代码实现

void shellSort(vector<int>& nums) {
   
    int n = nums.size();
    for (int gap = n / 2; gap > 0; gap /= 2) {
   
        for (int i = gap; i < n; i++) {
   
            int temp = nums[i];
            int j = i - gap;
            while (j >= 0 && nums[j] > temp) {
   
                nums[j + gap] = nums[j];
                j -= gap;
            }
            nums[j + gap] = temp;
        }
    }
}

7.3 时间复杂度及空间复杂度分析

【时间复杂度】最坏时间复杂度为 O(n^2),平均时间复杂度为 O(n log n)。

【空间复杂度】希尔排序为原地排序,空间复杂度为 O(1)。

7.4 稳定性分析

希尔排序是一种不稳定的排序算法,因为在进行插入排序时,相同元素的顺序可能会发生改变。

8. 计数排序

8.1 算法思想

【算法描述】将要排序的数据分成几个桶来存储,每个桶内的数据所代表的值相同,桶内数据不做排序处理。最终结果就是所有桶的数据依次排列组合。

8.2 代码实现

void countingSort(vector<int>& nums) {
   
    int n = nums.size();
    if (n == 0) {
   
        return;
    }

    int maxVal = *max_element(nums.begin(), nums.end());
    vector<int> count(maxVal + 1);

    for (int i = 0; i < n; i++) {
   
        count[nums[i]]++;
    }

    for (int i = 1; i < maxVal + 1; i++) {
   
        count[i] += count[i - 1];
    }

    vector<int> res(n);
    for (int i= n - 1; i >= 0; i--) {
   
        int index = count[nums[i]] - 1;
        res[index] = nums[i];
        count[nums[i]]--;
    }

    nums = res;
}

8.3 时间复杂度及空间复杂度分析

【时间复杂度】最好、最坏和平均时间复杂度均为 O(n + k),其中 k 为数据范围。

【空间复杂度】计数排序需要记录每个元素出现的次数,所以空间复杂度为 O(k)。

8.4 稳定性分析

计数排序是一种稳定的排序算法,因为在进行排序时,相同元素的顺序不会发生改变。

如果你有其他关于排序算法的问题,欢迎在评论区留言,我会尽快回复并为您解答。

三、 应用场景

1. 不同场景下的排序算法选择

在实际开发中,我们需要选择最符合业务场景需求的排序算法。以下是各种排序算法的应用场景及其复杂度。

1.1 冒泡排序

冒泡排序适用于数据规模较小的情况。

【时间复杂度】

  • 最优时间复杂度:O(n)
  • 最坏时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

【适用场景】

  • 数据规模较小。

1.2 选择排序

选择排序适用于数据规模较小的情况。

【时间复杂度】

  • 最优时间复杂度:O(n^2)
  • 最坏时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

【适用场景】

  • 数据规模较小。

1.3 插入排序

插入排序适用于数据基本有序的情况。

【时间复杂度】

  • 最优时间复杂度:O(n)
  • 最坏时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

【适用场景】

  • 数据基本有序。

1.4 快速排序

快速排序适用于数据规模较大的情况。

【时间复杂度】

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n^2)
  • 平均时间复杂度:O(nlogn)

【适用场景】

  • 数据规模较大。

1.5 归并排序

归并排序适用于数据规模较大的情况。

【时间复杂度】

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)

【适用场景】

  • 数据规模较大。

1.6 堆排序

堆排序适用于数据规模较大的情况。

【时间复杂度】

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)

【适用场景】

  • 数据规模较大。

1.7 计数排序

计数排序适用于数据范围不大的情况。

【时间复杂度】

  • 最优时间复杂度:O(n + k)
  • 最坏时间复杂度:O(n + k)
  • 平均时间复杂度:O(n + k)

【适用场景】

  • 数据范围不大。

1.8 桶排序

桶排序适用于数据范围不大但数据分布不均匀的情况。

【时间复杂度】

  • 最优时间复杂度:O(n)
  • 最坏时间复杂度:O(n^2)
  • 平均时间复杂度:O(n)

【适用场景】

  • 数据范围不大但数据分布不均匀。

1.9 基数排序

基数排序适用于数据范围不大且位数不多的整数排序。

【时间复杂度】

  • 最优时间复杂度:O(n * k)
  • 最坏时间复杂度:O(n * k)
  • 平均时间复杂度:O(n * k)

【适用场景】

  • 数据范围不大且位数不多的整数排序。

2. 实际应用案例

在实际开发中,排序算法经常用于以下场景:

2.1 数据库查询排序

在数据库查询时,我们通过排序算法对查询结果进行排序,使得查询结果更加有序、易读。

2.2 购物网站商品排序

在购物网站中,我们经常需要对商品进行排序,以便让用户更快找到自己需要的商品。

2.3 游戏中的排行榜

在游戏中,我们可以通过排行榜对用户进行排名,以增加游戏可玩性。

总之,排序算法是一项非常重要的算法,在实际开发中会经常使用到。根据不同的场景需求,选择最适合的排序算法将会大大提高程序的效率和用户的体验。

四、排序常见问题及优化方案

在程序开发中,排序算法是一个非常重要的部分。然而,当数据量过大时,常规排序算法可能会因为时间复杂度而导致程序崩溃或运行效率极低。本篇文章将为你讲解针对大数据排序问题的优化方案以及排序算法优化的方法。

1. 大数据排序问题

当数据规模过大时,普通的排序算法存在时间、空间上的挑战。因此,针对于大数据数量的排序问题,我们需要进行优化。以下是几种优化方案:

1.1 外排序

  • 概念:外部排序即数据量太大无法一次性全部载入内存中进行排序,必须借助外部存储器磁盘等设备进行存储与读取,再使用内部排序方法进行排序的方法。
  • 思路:将大量的数据分割成若干份,每份数据先进行内部排序,再进行归并排序。

1.2 分布式排序

  • 概念:将大规模数据集合分解为多个子数据集,这些子数据集可以在高速网络上交换数据、并行处理,最终将子数据集归并成有序的数据集的一种排序方式。
  • 思路:将大量的数据分割成若干份,使用分布式集群计算框架将每份数据运算并排序,最后合并成单份数据。

2. 排序算法优化

在实际开发中,除了针对大量数据排序问题的优化,我们还需要对排序算法进行性能优化。以下是优化方法:

2.1 算法优化

  • 优化循环次数
  • 减少内存操作
  • 减少函数调用
  • 避免结构体赋值

2.2 多线程优化

当排序的数据量较大时,可以通过多线程并发执行,提高程序的效率。

2.3 CPU指令优化

利用CPU的SIMD指令并行执行多个操作可极大提高程序执行效率。

3. 选择合适的排序算法

除了对排序算法进行优化,选择合适的排序算法也是提高程序执行效率的一种方法。以下是各种排序算法的时间复杂度,不同的场景需要选择不同的排序算法:

  • 冒泡排序 时间复杂度 O(n^2)
  • 选择排序 时间复杂度 O(n^2)
  • 插入排序 时间复杂度 O(n^2)
  • 希尔排序 时间复杂度 O(nlogn)
  • 归并排序 时间复杂度 O(nlogn)
  • 快速排序 时间复杂度 O(nlogn)
  • 堆排序 时间复杂度 O(nlogn)
  • 计数排序 时间复杂度 O(n + k)
  • 桶排序 时间复杂度 O(n)
  • 基数排序 时间复杂度 O(n * k)

示例代码

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

// 比较函数
bool cmp(int a, int b) {
   
    return a < b;
}

int main() {
   
    // 定义数据
    vector<int> vec = {
   5, 4, 3, 2, 1};

    // 使用sort排序,使用cmp作为比较函数
    sort(vec.begin(), vec.end(), cmp);

    // 输出排序后的结果
    for (auto v : vec) {
   
        cout << v << " ";
    }
    cout << endl;

    return 0;
}

以上是C++排序常见问题及优化方案的介绍,希望对你有所帮助。

五、 排序总结

在计算机科学中,排序算法是非常重要的基础算法。不同的排序算法在不同的场景下起到不同的作用。本篇文章将总结c++排序算法的优缺点及未来发展趋势。

排序算法的优缺点

1. 冒泡排序

冒泡排序是一种简单的排序算法,它的时间复杂度最坏情况下可以达到 O(n²), 不适合排序大规模数据。但是其优点是实现简单、容易理解。

2. 快速排序

快速排序是一种基于分治思想的排序算法,不稳定的排序算法,其平均时间复杂度为 O(nlogn)。但是在最坏情况下会退化为O(n²),需要优化。它在实际使用中效率很高,是最常用的排序算法之一。

3. 插入排序

插入排序适用于大部分已排序或接近已排序的数据,其平均时间复杂度为 O(n²)。但它往往比基于比较的排序算法更为高效,因为其内部操作更为简单。

4. 希尔排序

希尔排序是一种跨度序列的插入排序,时间复杂度为 O(nlogn) ~ O(n²)。相对于简单的插入排序,希尔排序的比较次数和移动次数都有减少的趋势,但仍然比不上快速排序。

5. 归并排序

归并排序是一种稳定的排序算法,其时间复杂度为 O(nlogn)。但因为需要开辟额外的空间存储中间结果,因此空间复杂度较高。

未来发展趋势

目前,对于大规模数据的排序,已经有了并行化的思路。并行排序算法具有良好的可扩展性和灵活性,能够在分布式计算系统上进行高性能的排序操作。例如,MapReduce平台提供了了一种高效的并行排序算法,在对大规模数据进行排序时,可以通过将数据划分为多个部分,在各个节点进行局部排序,最终通过全排序合并各节点的排序结果,达到高效地排序大规模数据的目的。

此外,随着人工智能的发展,对于排序算法的实时性和精确性也提出了更高的要求。现代的机器学习算法和深度学习算法,往往需要对海量的数据进行快速排序和过滤。基于此,一些新型排序算法被提出,例如,k-means算法、局部敏感哈希算法、分布式快速排序算法等等。这些新型算法将能够更好地满足人工智能领域的需求。

目录
相关文章
|
27天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的优化算法及其应用
【10月更文挑战第8天】 本文将探讨深度学习中常用的优化算法,包括梯度下降法、Adam和RMSProp等,介绍这些算法的基本原理与应用场景。通过实例分析,帮助读者更好地理解和应用这些优化算法,提高深度学习模型的训练效率与性能。
125 63
|
2天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
3天前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
13天前
|
人工智能 算法 数据安全/隐私保护
基于遗传优化的SVD水印嵌入提取算法matlab仿真
该算法基于遗传优化的SVD水印嵌入与提取技术,通过遗传算法优化水印嵌入参数,提高水印的鲁棒性和隐蔽性。在MATLAB2022a环境下测试,展示了优化前后的性能对比及不同干扰下的水印提取效果。核心程序实现了SVD分解、遗传算法流程及其参数优化,有效提升了水印技术的应用价值。
|
12天前
|
存储 缓存 算法
优化轮询算法以提高资源分配的效率
【10月更文挑战第13天】通过以上这些优化措施,可以在一定程度上提高轮询算法的资源分配效率,使其更好地适应不同的应用场景和需求。但需要注意的是,优化策略的选择和实施需要根据具体情况进行详细的分析和评估,以确保优化效果的最大化。
|
13天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
14天前
|
存储 缓存 算法
前端算法:优化与实战技巧的深度探索
【10月更文挑战第21天】前端算法:优化与实战技巧的深度探索
13 1
|
14天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真
本项目展示了基于贝叶斯优化(BO)的CNN-LSTM网络在数据分类中的应用。通过MATLAB 2022a实现,优化前后效果对比明显。核心代码附带中文注释和操作视频,涵盖BO、CNN、LSTM理论,特别是BO优化CNN-LSTM网络的batchsize和学习率,显著提升模型性能。
|
22天前
|
存储 缓存 算法
如何通过优化算法和代码结构来提升易语言程序的执行效率?
如何通过优化算法和代码结构来提升易语言程序的执行效率?
|
21天前
|
机器学习/深度学习 人工智能 算法
[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解
[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解
56 0
[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解