当前位置: 首页 > news >正文

排序算法详解

排序算法全面解析

排序算法是计算机科学中最基础也最重要的算法之一。它将一组数据(例如数字列表、字符串集合)按照特定的顺序(升序或降序)重新排列。高效的排序算法对于优化其他算法(如搜索和合并算法)的效率至关重要。

一、排序算法的基本思想与分类

1. 什么是排序?

排序是将一个记录的任意序列重新排列成一个按键值有序的序列的过程。这里的“键”是记录中用于比较的部分。

2. 为什么需要排序?

  • 快速查找:在有序数据中查找特定元素远快于在无序数据中查找(例如二分查找)。
  • 数据分析:排序后的数据更容易进行统计分析、识别模式或异常值。
  • 算法基础:许多其他算法依赖于输入数据的有序性。
  • 用户友好:向用户展示有序数据通常更直观、更易于理解。

3. 排序算法的分类

排序算法可以根据多个标准进行分类:

  • 比较排序 vs. 非比较排序
    • 比较排序:通过比较元素之间的键值来确定它们的相对顺序(如冒泡排序、快速排序、归并排序)。其时间复杂度下限为 O ( N log ⁡ N ) O(N \log N) O(NlogN)(基于决策树模型)。
    • 非比较排序:不通过比较键值,而是利用元素的特定属性(如数字的大小、范围)进行排序(如计数排序、基数排序、桶排序)。它们可以在特定条件下达到线性时间复杂度 O ( N ) O(N) O(N)
  • 稳定性
    • 稳定排序:如果两个具有相同键值的元素在排序前后的相对位置保持不变,则该排序算法是稳定的。
    • 不稳定排序:可能改变相同键值元素的相对位置。
  • 空间复杂度
    • 原地排序 (In-place):排序过程中仅需要常数级别的额外空间( O ( 1 ) O(1) O(1)),或者最多 O ( log ⁡ N ) O(\log N) O(logN) 的额外空间(如快速排序的递归栈)。
    • 非原地排序 (Out-of-place / Not-in-place):需要额外的存储空间来辅助排序,通常是 O ( N ) O(N) O(N)(如归并排序)。
  • 内部排序 vs. 外部排序
    • 内部排序:所有待排序数据都存储在内存中进行处理。
    • 外部排序:数据量过大,无法一次性加载到内存中,需要借助外部存储(如磁盘)进行排序。

本文将重点介绍几种经典的比较排序算法。

二、冒泡排序 (Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地遍历待排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

1. 思想

通过相邻元素之间的比较和交换,使得每一轮遍历都能将当前未排序部分的最大(或最小)元素“冒泡”到数列的末尾(或开头)。

2. 使用方式

  • 主要用于教学目的,理解排序的基本概念。
  • 在数据量非常小(例如 N < 10)的情况下,其简单性可能弥补其效率的不足。
  • 可以用于检测数据是否已经基本有序(通过优化,如果一轮没有发生交换,则说明已排序)。

3. 使用流程

  1. 从数列的第一个元素开始,比较它和下一个元素。
  2. 如果当前元素大于(或小于,取决于排序顺序)下一个元素,则交换它们。
  3. 继续向后移动,对下一对相邻元素进行同样的比较和交换。
  4. 重复步骤1-3,直到到达数列的末尾。此时,最大的(或最小的)元素已经被放置在数列的正确位置。
  5. 缩小未排序部分的范围(排除已正确放置的元素),重复上述过程。
  6. 直到整个数列排序完成(例如,经过 N-1 轮,或者在一轮遍历中没有发生任何交换)。

4. 流程图 (文本表示)


START
|
V
Input array A of size N
|
V
Outer loop: i from 0 to N-2  (Represents passes)
|
V
Set swapped = false
|
V
Inner loop: j from 0 to N-2-i (Compare adjacent elements)
|
V
IF A[j] \> A[j+1] THEN  (For ascending order)
| Yes
V
SWAP(A[j], A[j+1])
|
V
Set swapped = true
|
No --+
|
V
Inner loop continues (next j)
|
V
Inner loop END
|
V
IF swapped == false THEN (Optimization: array is sorted)
| Yes
V
BREAK Outer loop
| No
V
Outer loop continues (next i)
|
V
Outer loop END
|
V
Output sorted array A
|
V
END

5. C/C++ 测试用例

#include <iostream>
#include <vector>
#include <algorithm> // For std::swapvoid printArray(const std::vector<int>& arr, const std::string& msg = "") {if (!msg.empty()) {std::cout << msg;}for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;
}void bubbleSort(std::vector<int>& arr) {int n = arr.size();bool swapped;for (int i = 0; i < n - 1; ++i) {swapped = false;// Last i elements are already in placefor (int j = 0; j < n - 1 - i; ++j) {if (arr[j] > arr[j+1]) {std::swap(arr[j], arr[j+1]);swapped = true;}}// If no two elements were swapped by inner loop, then breakif (!swapped) {break;}// Optional: Print array after each pass// printArray(arr, "After pass " + std::to_string(i + 1) + ": ");}
}int main() {std::vector<int> arr1 = {64, 34, 25, 12, 22, 11, 90};std::cout << "Original array: ";printArray(arr1);bubbleSort(arr1);std::cout << "Sorted array: ";printArray(arr1); // Expected: 11 12 22 25 34 64 90std::vector<int> arr2 = {5, 1, 4, 2, 8};std::cout << "Original array: ";printArray(arr2);bubbleSort(arr2);std::cout << "Sorted array: ";printArray(arr2); // Expected: 1 2 4 5 8std::vector<int> arr3 = {1, 2, 3, 4, 5}; // Already sortedstd::cout << "Original array: ";printArray(arr3);bubbleSort(arr3);std::cout << "Sorted array: ";printArray(arr3); // Expected: 1 2 3 4 5std::vector<int> arr4 = {5, 4, 3, 2, 1}; // Reverse sortedstd::cout << "Original array: ";printArray(arr4);bubbleSort(arr4);std::cout << "Sorted array: ";printArray(arr4); // Expected: 1 2 3 4 5return 0;
}

6. 复杂度与特性

  • 时间复杂度:
    • 最坏情况 (Worst Case): O ( N 2 ) O(N^2) O(N2) (当数组逆序时)
    • 平均情况 (Average Case): O ( N 2 ) O(N^2) O(N2)
    • 最好情况 (Best Case): O ( N ) O(N) O(N) (当数组已经有序,且使用了swapped优化时)
  • 空间复杂度: O ( 1 ) O(1) O(1) (原地排序)
  • 稳定性: 稳定 (相等的元素不会改变相对顺序)

三、归并排序 (Merge Sort)

归并排序是一种高效的、基于分治策略的排序算法。

1. 思想

分治法 (Divide and Conquer)

  1. 分解 (Divide):将待排序的 N N N 个元素的序列分解成两个各含 N / 2 N/2 N/2 个元素的子序列。
  2. 解决 (Conquer):递归地排序两个子序列。如果子序列的长度为1,则自然有序,递归结束。
  3. 合并 (Combine):将两个已排序的子序列合并成一个最终的排序序列。

2. 使用方式

  • 需要稳定排序的场景。
  • 对于需要处理大规模数据,尤其是数据无法完全载入内存(外部排序)的情况,归并排序是很好的选择。
  • O ( N l o g N ) O(N \\log N) O(NlogN) 的时间复杂度在最坏情况下依然保持,因此性能稳定。

3. 使用流程

mergeSort(array A, start, end):

  1. 如果 start >= end (基本情况:子数组只有0或1个元素),则返回,因为已经有序。
  2. 计算中间点 mid = start + (end - start) / 2
  3. 递归调用 mergeSort(A, start, mid) 对左半部分进行排序。
  4. 递归调用 mergeSort(A, mid + 1, end) 对右半部分进行排序。
  5. 调用 merge(A, start, mid, end) 将两个已排序的子数组 A[start...mid]A[mid+1...end] 合并成一个有序数组。

merge(array A, start, mid, end):

  1. 创建两个临时数组 LR,分别存储 A[start...mid]A[mid+1...end]
  2. 初始化三个指针:i 指向 L 的开头,j 指向 R 的开头,k 指向 A[start] (合并后数组的起始位置)。
  3. 比较 L[i]R[j]
    • 如果 L[i] <= R[j],则将 L[i] 复制到 A[k]ik 各自加1。
    • 否则 (即 L[i] > R[j]),将 R[j] 复制到 A[k]jk 各自加1。
  4. 重复步骤3,直到 LR 中的一个数组遍历完毕。
  5. LR 中剩余的元素(如果有的话)复制到 A 的末尾。

4. 流程图 (文本表示)

mergeSort(A, start, end):

START mergeSort(A, start, end)|V
IF start < end THEN| YesVmid = start + (end - start) / 2|VCALL mergeSort(A, start, mid)  // Sort left half|VCALL mergeSort(A, mid + 1, end) // Sort right half|VCALL merge(A, start, mid, end) // Merge sorted halves|No --+ (Base case: array is of size 0 or 1, already sorted)|V
RETURN

merge(A, start, mid, end):

START merge(A, start, mid, end)|Vn1 = mid - start + 1  // Size of left subarrayn2 = end - mid        // Size of right subarray|VCreate temp arrays L[n1], R[n2]|VCopy A[start...mid] to LCopy A[mid+1...end] to R|Vi = 0 (pointer for L), j = 0 (pointer for R), k = start (pointer for A)|VWHILE i < n1 AND j < n2 DO|VIF L[i] <= R[j] THEN| YesVA[k] = L[i]i = i + 1ELSE| NoVA[k] = R[j]j = j + 1END IF|Vk = k + 1LOOP|VWHILE i < n1 DO // Copy remaining elements of L, if anyA[k] = L[i]i = i + 1k = k + 1LOOP|VWHILE j < n2 DO // Copy remaining elements of R, if anyA[k] = R[j]j = j + 1k = k + 1LOOP|V
RETURN

5. C/C++ 测试用例

#include <iostream>
#include <vector>
#include <algorithm> // For std::vector operations if needed// printArray function from Bubble Sort example can be reused
// void printArray(const std::vector<int>& arr, const std::string& msg = "");// Merges two subarrays of arr[].
// First subarray is arr[left..mid]
// Second subarray is arr[mid+1..right]
void merge(std::vector<int>& arr, int left, int mid, int right) {int n1 = mid - left + 1;int n2 = right - mid;// Create temp arraysstd::vector<int> L(n1), R(n2);// Copy data to temp arrays L[] and R[]for (int i = 0; i < n1; i++)L[i] = arr[left + i];for (int j = 0; j < n2; j++)R[j] = arr[mid + 1 + j];// Merge the temp arrays back into arr[left..right]int i = 0; // Initial index of first subarrayint j = 0; // Initial index of second subarrayint k = left; // Initial index of merged subarraywhile (i < n1 && j < n2) {if (L[i] <= R[j]) {arr[k] = L[i];i++;} else {arr[k] = R[j];j++;}k++;}// Copy the remaining elements of L[], if there are anywhile (i < n1) {arr[k] = L[i];i++;k++;}// Copy the remaining elements of R[], if there are anywhile (j < n2) {arr[k] = R[j];j++;k++;}
}// Main function that sorts arr[left..right] using merge()
void mergeSort(std::vector<int>& arr, int left, int right) {if (left < right) {// Same as (left+right)/2, but avoids overflow for large left and rightint mid = left + (right - left) / 2;// Sort first and second halvesmergeSort(arr, left, mid);mergeSort(arr, mid + 1, right);merge(arr, left, mid, right);}
}// Helper printArray for this standalone section if needed
void printArrayMerge(const std::vector<int>& arr, const std::string& msg = "") {if (!msg.empty()) {std::cout << msg;}for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;
}int main() {std::vector<int> arr1 = {64, 34, 25, 12, 22, 11, 90};std::cout << "Original array: ";printArrayMerge(arr1);mergeSort(arr1, 0, arr1.size() - 1);std::cout << "Sorted array (Merge Sort): ";printArrayMerge(arr1); // Expected: 11 12 22 25 34 64 90std::vector<int> arr2 = {38, 27, 43, 3, 9, 82, 10};std::cout << "Original array: ";printArrayMerge(arr2);mergeSort(arr2, 0, arr2.size() - 1);std::cout << "Sorted array (Merge Sort): ";printArrayMerge(arr2); // Expected: 3 9 10 27 38 43 82return 0;
}

6. 复杂度与特性

  • 时间复杂度:
    • 最坏情况: O ( N l o g N ) O(N \\log N) O(NlogN)
    • 平均情况: O ( N l o g N ) O(N \\log N) O(NlogN)
    • 最好情况: O ( N l o g N ) O(N \\log N) O(NlogN) (严格来说,某些实现对已排序数据可以优化到 O ( N ) O(N) O(N),但经典分治是 N l o g N N \\log N NlogN)
  • 空间复杂度: O ( N ) O(N) O(N) (需要额外的数组进行合并,非原地排序)
  • 稳定性: 稳定

四、快速排序 (Quick Sort)

快速排序是另一种采用分治策略的高效排序算法。它通常比其他 O ( N l o g N ) O(N \\log N) O(NlogN) 算法在实践中更快,但这依赖于良好的基准(pivot)选择。

1. 思想

  1. 选择基准 (Pick a Pivot):从数组中选择一个元素作为基准。
  2. 分区 (Partition):重新排列数组,所有小于基准的元素都移动到基准的左边,所有大于基准的元素都移动到基准的右边。相等的元素可以到任何一边。在此过程之后,基准元素处于其最终排序位置。这个操作称为分区操作。
  3. 递归 (Recurse):递归地对基准左边和右边的子数组进行快速排序。

2. 使用方式

  • 当对平均性能有较高要求,且不需要稳定排序时,快速排序是首选。
  • 广泛应用于各种系统级排序任务中。
  • 由于其原地性(或近似原地,仅需 O ( l o g N ) O(\\log N) O(logN) 栈空间),对于内存限制较严格的场景优于归并排序。

3. 使用流程

quickSort(array A, low, high):

  1. 如果 low < high
    a. 调用 partition(A, low, high) 来选择一个基准,并将数组分区。partition 函数返回基准元素的最终索引 pi
    b. 递归调用 quickSort(A, low, pi - 1) 对基准左边的子数组进行排序。
    c. 递归调用 quickSort(A, pi + 1, high) 对基准右边的子数组进行排序。

partition(array A, low, high) (Lomuto partition scheme example):

  1. 选择 A[high] 作为基准 pivot
  2. 初始化小于基准的元素区域的末尾索引 i = low - 1
  3. 遍历从 lowhigh - 1 的元素 (用 j 表示):
    a. 如果 A[j] <= pivot,则 i 增加1,并交换 A[i]A[j]
  4. 交换 A[i + 1]A[high] (基准元素)。
  5. 返回 i + 1 (基准的新索引)。

(注:有多种分区方案,如 Hoare partition scheme,其实现细节和性能略有不同。)

4. 流程图 (文本表示)

quickSort(A, low, high):

START quickSort(A, low, high)|V
IF low < high THEN| YesVpi = CALL partition(A, low, high)  // Get pivot index|VCALL quickSort(A, low, pi - 1)    // Sort left of pivot|VCALL quickSort(A, pi + 1, high)   // Sort right of pivot|No --+ (Base case: subarray has 0 or 1 element)|V
RETURN

partition(A, low, high) (Lomuto scheme):

START partition(A, low, high)|Vpivot = A[high] // Choose the last element as pivoti = low - 1     // Index of smaller element|VFOR j FROM low TO high - 1 DO|VIF A[j] <= pivot THEN| YesVi = i + 1SWAP(A[i], A[j])| NoVLOOP (next j)|VSWAP(A[i + 1], A[high]) // Place pivot in correct position|VRETURN i + 1           // Return pivot's index

5. C/C++ 测试用例

#include <iostream>
#include <vector>
#include <algorithm> // For std::swap// printArray function from Bubble Sort example can be reused
// void printArray(const std::vector<int>& arr, const std::string& msg = "");// This function takes last element as pivot, places
// the pivot element at its correct position in sorted
// array, and places all smaller (smaller than pivot)
// to left of pivot and all greater elements to right of pivot
int partition(std::vector<int>& arr, int low, int high) {int pivot = arr[high]; // Pivotint i = (low - 1);   // Index of smaller elementfor (int j = low; j <= high - 1; j++) {// If current element is smaller than or equal to pivotif (arr[j] <= pivot) {i++; // Increment index of smaller elementstd::swap(arr[i], arr[j]);}}std::swap(arr[i + 1], arr[high]);return (i + 1);
}// The main function that implements QuickSort
// arr[] --> Array to be sorted,
// low --> Starting index,
// high --> Ending index
void quickSort(std::vector<int>& arr, int low, int high) {if (low < high) {// pi is partitioning index, arr[p] is now at right placeint pi = partition(arr, low, high);// Separately sort elements before partition and after partitionquickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}
}// Helper printArray for this standalone section if needed
void printArrayQuick(const std::vector<int>& arr, const std::string& msg = "") {if (!msg.empty()) {std::cout << msg;}for (int x : arr) {std::cout << x << " ";}std::cout << std::endl;
}int main() {std::vector<int> arr1 = {64, 34, 25, 12, 22, 11, 90};std::cout << "Original array: ";printArrayQuick(arr1);quickSort(arr1, 0, arr1.size() - 1);std::cout << "Sorted array (Quick Sort): ";printArrayQuick(arr1); // Expected: 11 12 22 25 34 64 90std::vector<int> arr2 = {10, 7, 8, 9, 1, 5};std::cout << "Original array: ";printArrayQuick(arr2);quickSort(arr2, 0, arr2.size() - 1);std::cout << "Sorted array (Quick Sort): ";printArrayQuick(arr2); // Expected: 1 5 7 8 9 10return 0;
}

6. 复杂度与特性

  • 时间复杂度:
    • 最坏情况: O ( N 2 ) O(N^2) O(N2) (当基准选择最差,例如数组已排序或逆序,且每次都选到最小/最大元素作为基准)
    • 平均情况: O ( N l o g N ) O(N \\log N) O(NlogN)
    • 最好情况: O ( N l o g N ) O(N \\log N) O(NlogN) (当基准每次都能均分数组)
  • 空间复杂度: O ( l o g N ) O(\\log N) O(logN) (递归栈空间,平均情况)。最坏情况下(退化为链表)可能达到 O ( N ) O(N) O(N)
  • 稳定性: 不稳定 (在分区过程中,相等元素的相对顺序可能改变)

优化

  • 基准选择:随机选择基准、三数取中法(median-of-three)可以大大降低遇到最坏情况的概率。
  • 小数组优化:当子数组规模小于某个阈值(如10-20个元素)时,改用插入排序,因为插入排序在小数据集上效率更高。
  • 尾递归优化:可以减少递归深度。

五、排序算法比较总结

算法平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性备注
冒泡排序 O ( N 2 ) O(N^2) O(N2) O ( N 2 ) O(N^2) O(N2) O ( N ) O(N) O(N) O ( 1 ) O(1) O(1)稳定简单,教学用,小数据可用
归并排序 O ( N l o g N ) O(N \\log N) O(NlogN) O ( N l o g N ) O(N \\log N) O(NlogN) O ( N l o g N ) O(N \\log N) O(NlogN) O ( N ) O(N) O(N)稳定性能稳定,适用于外部排序
快速排序 O ( N l o g N ) O(N \\log N) O(NlogN) O ( N 2 ) O(N^2) O(N2) O ( N l o g N ) O(N \\log N) O(NlogN) O ( l o g N ) O(\\log N) O(logN)不稳定平均性能好,常用
(插入排序) O ( N 2 ) O(N^2) O(N2) O ( N 2 ) O(N^2) O(N2) O ( N ) O(N) O(N) O ( 1 ) O(1) O(1)稳定适用于近乎有序或小数据

六、开源项目中的使用案例

排序算法是基础构件,在众多开源项目中都有应用。

  1. C++ Standard Library (std::sort):

    • 使用:GNU libstdc++ 中的 std::sort 通常实现为 Introsort。Introsort 是一种混合排序算法,它以快速排序为主,当递归深度过深(可能导致 O ( N 2 ) O(N^2) O(N2) 复杂度)时切换到堆排序 (Heapsort),而对于小规模的子数组(分区大小小于某个阈值,通常16-30)则使用插入排序。这种混合策略结合了快速排序的平均高性能、堆排序的最坏情况 O ( N l o g N ) O(N \\log N) O(NlogN) 保证以及插入排序在小数组上的高效性。
    • std::stable_sort 则通常使用归并排序或其变体,以保证稳定性。
  2. Linux Kernel:

    • 使用:内核代码中(例如 lib/sort.c)提供了一个通用的 sort() 函数。这个实现通常是基于堆排序或者归并排序的变体,因为它需要保证最坏情况下的性能,并且有时需要稳定性。例如,旧版本可能使用堆排序,新版本可能会使用一种称为 “bottom-up heapsort” 或类似归并排序的策略。内核对内存和性能的要求非常严格。
  3. Python (list.sort()sorted()):

    • 使用:Python 使用 Timsort 算法。Timsort 是一种混合稳定排序算法,派生自归并排序和插入排序,设计用于在多种真实数据上表现良好。它利用了真实世界数据中常见的“有序子序列”(runs)。Timsort 在查找这些 runs 后,使用归并策略来合并它们。
  4. Java (Arrays.sort()Collections.sort()):

    • 使用:对于基本类型数组 (int[], double[] 等),Java 使用双轴快速排序 (Dual-Pivot Quicksort),这是一种快速排序的变体,通常比传统快速排序性能更好。
    • 对于对象数组,如果需要稳定排序,或者元素数量较少时,会使用 Timsort(类似于 Python 的实现,保证 O ( N l o g N ) O(N \\log N) O(NlogN) 复杂度且稳定)。
  5. 数据库系统 (如 PostgreSQL, MySQL):

    • 使用:当对查询结果进行 ORDER BY 操作时,如果数据量小且能装入内存,可能会使用快速排序或堆排序等。如果数据量巨大,无法一次性载入内存,数据库会采用外部归并排序 (External Merge Sort)。外部归并排序将数据分块读入内存排序,将排序后的块写回磁盘,然后多路归并这些已排序的块。

七、推荐书籍或网站如何学排序

学习排序算法需要理论与实践相结合。

推荐书籍

  1. 《算法导论》(Introduction to Algorithms, CLRS):

    • 经典教材,第2章、第6-9章详细讲解了多种排序算法(插入排序、归并排序、堆排序、快速排序、计数排序、基数排序、桶排序),分析透彻,包含伪代码和复杂度分析。
  2. 《算法 (第4版)》(Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne):

    • 用 Java 实现,图文并茂,讲解清晰。第2章集中讨论排序算法,代码实用性强。网站 algs4.cs.princeton.edu 配套资源丰富。
  3. 《编程珠玑》(Programming Pearls by Jon Bentley):

    • 虽然不是专门讲排序的书,但其中很多章节和栏目都涉及排序思想和应用,强调工程实践和问题解决。
  4. 《数据结构与算法分析》(Data Structures and Algorithm Analysis by Mark Allen Weiss):

    • 有 C++、Java 等不同语言版本,对各种排序算法也有清晰的讲解和实现。

推荐网站与在线资源

  1. VisuAlgo:

    • 网址: https://visualgo.net/en/sorting
    • 特点: 提供排序算法过程的可视化动画,非常直观,有助于理解算法每一步是如何工作的。
  2. GeeksforGeeks - Sorting Algorithms:

    • 网址: https://www.geeksforgeeks.org/sorting-algorithms/
    • 特点: 包含各种排序算法的详细解释、代码实现(多种语言)、复杂度分析以及优缺点比较。
  3. LeetCode (力扣) / HackerRank / Codeforces:

    • 网址: https://leetcode.com/, https://www.hackerrank.com/, https://codeforces.com/
    • 特点: 大量编程题目,很多问题需要或可以应用排序思想。通过实践来巩固学习。
  4. Khan Academy (可汗学院):

    • 网址: https://www.khanacademy.org/computing/computer-science/algorithms
    • 特点: 提供免费的算法入门课程,包括排序算法的讲解,适合初学者。
  5. Coursera / edX / Udacity:

    • 特点: 这些 MOOC 平台上有许多顶尖大学开设的算法课程(如普林斯顿大学、斯坦福大学的算法课),系统性强,质量高。

学习建议

  1. 理解原理:先搞懂每种排序算法的基本思想和步骤。
  2. 手动模拟:拿一个小的无序数组,手动模拟算法的每一步,画图辅助理解。
  3. 代码实现:亲手用自己熟悉的语言实现一遍算法,这会暴露很多理解上的盲点。
  4. 分析复杂度:学习分析算法的时间复杂度和空间复杂度,理解其最好、最坏、平均情况。
  5. 比较优劣:对比不同算法的特性(稳定性、空间需求、时间性能),了解它们各自的适用场景。
  6. 刷题练习:通过解决实际问题来应用和巩固排序知识。
  7. 查看源码:有一定基础后,可以尝试阅读开源库中排序算法的实现,学习工业级代码的优化技巧。

掌握排序算法是程序员的基本功,也是深入学习更复杂算法和数据结构的基础。


相关文章:

排序算法详解

排序算法全面解析 排序算法是计算机科学中最基础也最重要的算法之一。它将一组数据&#xff08;例如数字列表、字符串集合&#xff09;按照特定的顺序&#xff08;升序或降序&#xff09;重新排列。高效的排序算法对于优化其他算法&#xff08;如搜索和合并算法&#xff09;的…...

[特殊字符] GSG 插件 + 渲染 101:C4D 渲染效率革命!

一、GSG 插件&#xff1a;C4D 创作的「超级加速器」 灰猩猩&#xff08;GSG&#xff09;插件是 C4D 设计师的刚需工具&#xff1a; Light Kit Pro&#xff1a;1 分钟生成专业灯光预设&#xff0c;告别手动布光烦恼GorillaCam&#xff1a;自动添加电影级相机运动&#xff0c;镜…...

centos中postfix的作用

/usr/libexec/postfix/master 是 Postfix 邮件服务器的主进程&#xff0c;qmgr 和 pickup 是 Postfix 的子进程。这些进程本身是正常的&#xff0c;但如果你怀疑服务器被用于钓鱼活动&#xff0c;需要进一步检查 Postfix 的配置和日志&#xff0c;确保它没有被滥用。 1. 检查 P…...

tocmat 启动怎么设置 jvm和gc

在生产环境中部署 Java Web 应用时&#xff0c;我们经常需要给 Tomcat 设置 JVM 参数和 GC 策略&#xff0c;以提高性能、稳定性和可观察性。以下是完整教程&#xff1a; 一、Tomcat 设置 JVM 启动参数的方式 1. 修改 startup 脚本&#xff08;推荐&#xff09; 以 Linux 系统…...

【工奥阀门科技有限公司】签约智橙PLM

近日&#xff0c;工奥阀门科技有限公司正式签约了智橙泵阀行业版PLM。 忠于质量&#xff0c;臻于服务&#xff0c;精于研发 工奥阀门科技有限公司&#xff08;以下简称工奥阀门&#xff09;坐落于浙江永嘉&#xff0c;是一家集设计、开发、生产、销售、安装、服务为一体的阀门…...

Axure设计之轮播图——案例“一图一轮播”

轮播图是一种常见且实用的组件&#xff0c;用于展示多张图片或内容&#xff0c;同时节省页面空间。在Axure中&#xff0c;通过动态面板和交互设置&#xff0c;我们可以轻松实现一个“一图一轮播”的效果&#xff0c;即每次只展示一张图片&#xff0c;并通过按钮或自动切换来浏览…...

可视化图解算法39: 输出二叉树的右视图

1. 题目 描述 请根据二叉树的前序遍历&#xff0c;中序遍历恢复二叉树&#xff0c;并打印出二叉树的右视图 数据范围&#xff1a; 0≤n≤10000 要求&#xff1a; 空间复杂度 O(n)&#xff0c;时间复杂度 O(n) 如输入[1,2,4,5,3],[4,2,5,1,3]时&#xff0c;通过前序遍历的结…...

数据库基础复习笔记

数据库 相关概念 名称全称检查数据库存储数据的仓库&#xff0c;数据是有组织的进行存储DataBase&#xff08;DB&#xff09;数据库管理系统操作和管理数据库的大型软件DataBase Management System(DBMS)SQL操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库…...

鸿蒙OSUniApp 实现一个精致的日历组件#三方框架 #Uniapp

使用 UniApp 实现一个精致的日历组件 前言 最近在开发一个约会小程序时&#xff0c;需要实现一个既美观又实用的日历组件。市面上虽然有不少现成的组件库&#xff0c;但都不太符合我们的设计需求。于是&#xff0c;我决定从零开始&#xff0c;基于 UniApp 自己实现一个功能完…...

Kotlin 协程实战:实现异步值加载委托,对值进行异步懒初始化

在实际开发中&#xff0c;我们经常遇到这样的场景。 需要立即初始化但计算成本高昂的值 val expensiveValue calculateExpensiveValue() 可能引发阻塞的初始化操作 val resource loadResourceFromNetwork()这些场景通常需要满足以下需求&#xff1a; 异步加载&#xff1a…...

MySQL增删查改进阶

文章目录 一、数据库备份二、数据库约束2.1 NOT NULL2.2 UNIQUE&#xff1a;唯一约束2.3 DEFAULT&#xff1a;默认值约束 一、数据库备份 在MySQL当中&#xff0c;数据库是如何备份的呢&#xff1f;主要通过三种方式进行备份&#xff1a; 数据库最终都是存储在硬盘上&#xf…...

Tensorflow2保存和加载模型

1、model.save() and model.load() 此种方法可保存模型的结构、参数等内容。加载模型后无需设置即可使用&#xff01; 保存模型&#xff1a; model.save(my_model.h5) 加载模型&#xff1a; # 加载整个模型 loaded_model tf.keras.models.load_model(my_model.h5) 注意&…...

通过泛域名解析把二级域名批量绑定到wordpress的指定页面

通过泛域名解析将二级域名批量绑定到WordPress的指定页面&#xff0c;需要完成两个主要步骤&#xff1a;一是设置泛域名解析&#xff0c;二是配置服务器和WordPress以实现二级域名到指定页面的映射。以下是详细的操作方法&#xff1a; 1. 设置泛域名解析 在域名注册商的管理后…...

BGP联邦+反射器实验

一、实验拓扑 二、IP规划 骨干&#xff1a; 172.16.0.0/30 ---- R2-R3 172.16.0.4/30 ---- R3-R4 172.16.0.8/30 ---- R4-R7 172.16.0.12/30 ---- R6-R7 172.16.0.16/30 ---- R5-R6 172.16.0.20/30 ---- R2-R5 非骨干&#xff1a; 172.16.2.0/24 ---- R2的环回 2.2.2.2/32 17…...

3天云南旅游规划

云南的主要旅游城市和景点。昆明作为云南的省会&#xff0c;拥有丰富的自然景观和适合短途游玩的地方。 第一天可以安排在昆明市内和周边&#xff0c;方便游玩。 第二天&#xff0c;可以考虑去大理&#xff0c;这是云南的著名旅游城市&#xff0c;距离昆明大约2小时的车程。大理…...

SCDN能够运用在物联网加速当中吗?

在当今的科技化时代当中&#xff0c;物联网已经广泛渗透在各个领域行业当中&#xff0c;随着物联网规模的不断扩大&#xff0c;数据信息的传输速度和网络稳定性成为企业需要重视的两点因素&#xff0c;而SCDN也成为安全内容分发网络作为一种融合了内容加速和安全防护的技术&…...

Go语言八股之Mysql基础详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…...

计算机视觉----基础概念、卷积

一、概述 1.计算机视觉的定义 计算机视觉(Computer Vision)是一个跨学科的研究领域,主要涉及如何使计算机能够通过处理和理解数字图像或视频来自动进行有意义的分析和决策。其目标是使计算机能够从视觉数据中获取高层次的理解,类似于人类的视觉处理能力。 具体来说,计算机…...

Problem E: List练习

1.题目描述 运用List完成下面的要求: 1) 创建一个List&#xff0c;在List中增加三个工人&#xff0c;基本信息如下&#xff1a; 姓名 年龄 工资 Tom 18 3000 Peter 25 3500 Mark 22 3200 2) 插入一个工人&#xff0c;信息为&#xff1a;姓名&#xff1a;Robert&#xff0…...

12-串口外设

一、串口外设的基本概述 1、基本定义 串口通信&#xff0c;通过在通信双方之间以比特位&#xff08;bit&#xff09;的形式逐一发送或接收数据&#xff0c;实现了信息的有效传递。其通信方式不仅简单可靠&#xff0c;而且成本很低。 2、stm32的串口 下面是两个MCU的数据交互&…...

C++(2)

二、面向对象基础 1. 类与对象 1.1 核心概念 类&#xff08;Class&#xff09; ​​定义​​&#xff1a;抽象描述具有共同特征和行为的对象模板​​本质​​&#xff1a;代码复用的蓝图&#xff0c;定义数据&#xff08;属性&#xff09;与操作&#xff08;行为&#xff0…...

GMT之Bash语言使用

GMT的操作有自己的逻辑和“命令”&#xff0c;但GMT是可以用Bash语言控制的&#xff0c;所以常常以.sh为后缀写GMT程序。 GMT程序运行步骤如下&#xff1a; 采用cd &#xff0c;定位到指定文件夹&#xff1b;以sh ***.sh运行GMT&#xff0c;得到结果。 另外&#xff0c;遇到…...

半成品的开源双系统VLA模型,OpenHelix-发表于2025.5.6

半成品的开源双系统VLA模型&#xff0c;OpenHelix https://openhelix-robot.github.io/ 0. 摘要 随着OpenVLA的开源&#xff0c;VLA如何部署到真实的机器人上获得了越来越多的关注&#xff0c;各界人士也都开始尝试解决OpenVLA的效率问题&#xff0c;双系统方案是其中一个非…...

fiftyone-dataset使用基础

1.创建dataset 将dataset写入数据库时&#xff0c;对于已有的dataset name会报错&#xff1a; 解决方法&#xff1a;指定覆盖写 添加参数overwriteTrue, 默认为False # 在写入数据集时&#xff0c;指定overwriteTrue&#xff0c;表示当dataset_name在数据库中已存在时&#…...

(1-4)Java Object类、Final、注解、设计模式、抽象类、接口、内部类

目录 1. Object类 1.1 equals 1.2 toString&#xff08;&#xff09; 2.final关键字 3.注解 4. 设计模式 4.1 单例模式 4.1.1 饿汉式 4.1.3 饿汉式 VS 懒汉式 5. 抽象类&抽象方法 6. 接口 1. Object类 1.1 equals 重写Object 的equals方法&#xff0c;两种实现方式…...

强力巨彩谷亚推出专业智慧显示屏,满足多元场景需求

LED显示屏作为信息传播与视觉展示的关键载体&#xff0c;其性能和品质的提升备受关注。为响应市场对高品质显示的迫切需求&#xff0c;强力巨彩旗下专业智慧显示高端品牌谷亚G-ART&#xff0c;携多款行业领先水平的LED显示屏产品亮相&#xff0c;为用户带来专业、高效且节能的显…...

基于Spring AI与Hugging Face TGI构建高效聊天应用:从配置到实践全解析

基于Spring AI与Hugging Face TGI构建高效聊天应用&#xff1a;从配置到实践全解析 前言 在人工智能技术蓬勃发展的当下&#xff0c;大语言模型&#xff08;LLM&#xff09;的应用场景日益丰富。如何快速将 Hugging Face 生态中的强大模型部署为可通过 API 访问的服务&#x…...

MySQL视图:虚拟表的强大功能与应用实践

在数据库管理系统中&#xff0c;视图(View)是一种极其重要却常被忽视的功能。作为MySQL数据库的核心特性之一&#xff0c;视图为开发者和数据库管理员提供了数据抽象、安全控制和查询简化的强大工具。本文将全面探讨MySQL视图的概念、工作原理、创建与管理方法&#xff0c;以及…...

matlab插值方法(简短)

在MATLAB中&#xff0c;可以使用interp1函数快速实现插值。以下代码展示了如何使用spline插值方法对给定数据进行插值&#xff1a; x1 [23,56]; y1 [23,56]; X 23:1:56*4; Y interp1(x1,y1,X,spline);% linear、 spline其中&#xff0c;x1和y1是已知数据点&#xff0c;X是…...

【拥抱AI】Deer-Flow字节跳动开源的多智能体深度研究框架

最近发现一款可以对标甚至可能超越GPT-Researcher的AI深度研究应用&#xff0c;Deer-Flow&#xff08;Deep Exploration and Efficient Research Flow&#xff09;作为字节跳动近期开源的重量级项目&#xff0c;正以其模块化、灵活性和人机协同能力引发广泛关注。该项目基于 La…...

基于开源AI大模型与S2B2C生态的个人品牌优势挖掘与标签重构研究

摘要&#xff1a;在数字文明时代&#xff0c;个人品牌塑造已从传统经验驱动转向数据智能驱动。本文以开源AI大模型、AI智能名片与S2B2C商城小程序源码为技术载体&#xff0c;提出"社会评价-数据验证-标签重构"的三维分析框架。通过实证研究发现&#xff0c;结合第三方…...

2025年PMP 学习十二 第9章 项目资源管理

2025年PMP 学习十二 第9章 项目资源管理 序号过程过程组1规划资源管理规划2估算活动资源规划3获取资源执行4建设团队执行5管理团队执行6控制资源监控 项目资源管理&#xff0c;包括实物资源和团队资源。 文章目录 2025年PMP 学习十二 第9章 项目资源管理项目团队和 项目管理团…...

AI生成功能测试文档|测试文档

AI生成功能测试文档&#xff1a;链接直达 计算机功能测试文档撰写教程 链接直达&#xff1a;生成功能测试文档工具 一、文档概述 &#xff08;一&#xff09;文档目的 明确计算机功能测试的流程、方法和标准&#xff0c;确保测试的有效性和可靠性&#xff0c;为软件的质量评…...

Python 常用模块(八):logging模块

目录 一、引言&#xff1a;日志模块在项目开发中的重要性二、从 Django 日志配置看 Logging 模块的核心组成三、logging模块核心组件详解3.1 记录器Logger3.2 级别Level3.3 根记录器使用3.4 处理器Handler3.5 格式化器Formatter3.6 日志流3.7 日志示例 四、日志模块总结 一、引…...

入门OpenTelemetry——可观测性与链路追踪介绍

可观测性 什么是可观测性 可观察性&#xff08;Observability&#xff09;是从外部输出知识中推断所获得&#xff0c;可理解为衡量一个系统内部状态的方法。可观测性是一种能力&#xff0c;它能帮助你回答系统内部发生了什么——无需事先定义每种可能的故障或状态。系统的可观…...

c#队列及其操作

可以用数组、链表实现队列&#xff0c;大致与栈相似&#xff0c;简要介绍下队列实现吧。值得注意的是循环队列判空判满操作&#xff0c;在用链表实现时需要额外思考下出入队列条件。 设计头文件 #ifndef ARRAY_QUEUE_H #define ARRAY_QUEUE_H#include <stdbool.h> #incl…...

【Linux C/C++开发】轻量级关系型数据库SQLite开发(包含性能测试代码)

前言 之前的文件分享过基于内存的STL缓存、环形缓冲区&#xff0c;以及基于文件的队列缓存mqueue、hash存储、向量库annoy存储&#xff0c;这两种属于比较原始且高效的方式。 那么&#xff0c;有没有高级且高效的方式呢。有的&#xff0c;从数据角度上看&#xff0c;&#xff0…...

77. 组合【 力扣(LeetCode) 】

文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 77. 组合 一、题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 二、测试用例 示例 1&#xff1a; 输入&…...

GpuGeek全栈AI开发实战:从零构建企业级大模型生产管线(附完整案例)

目录 背景一、算力困境&#xff1a;AI开发者的「三重诅咒」1.1 硬件成本黑洞‌1.2 资源调度失衡‌1.3 环境部署陷阱‌ 二、三大核心技术突破GpuGeek的破局方案2.1 ‌分时切片调度引擎&#xff08;Time-Slicing Scheduler&#xff09;‌2.2 ‌异构计算融合架构2.3 ‌AI资产自动化…...

LeetCode 热题 100_颜色分类(98_75_中等_C++)(技巧)(计数;双指针)

LeetCode 热题 100_颜色分类&#xff08;98_75_中等_C&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;计数&#xff09;&#xff1a;思路二&#xff08;双指针&#xff09;&#xff1a; 代码实现代码实现&a…...

【前端】:单 HTML 去除 Word 批注

在现代办公中&#xff0c;.docx 文件常用于文档编辑&#xff0c;但其中的批注&#xff08;注释&#xff09;有时需要在分享或归档前被去除。本文将从原理出发&#xff0c;深入剖析如何在纯前端环境下实现对 .docx 文件注释的移除&#xff0c;并提供完整的实现源码。最后&#x…...

TTS-Web-Vue系列:Vue3实现内嵌iframe文档显示功能

&#x1f5bc;️ 本文是TTS-Web-Vue系列的新篇章&#xff0c;重点介绍如何在Vue3项目中优雅地实现内嵌iframe功能&#xff0c;用于加载外部文档内容。通过Vue3的响应式系统和组件化设计&#xff0c;我们实现了一个功能完善、用户体验友好的文档嵌入方案&#xff0c;包括加载状态…...

AWS CloudTrail日志跟踪启用

问题 启用日志管理。 步骤 审计界面&#xff0c;如下图&#xff1a; 点击创建跟踪&#xff0c;AWS云就会记录AWS账号在云中的操作。...

PHP 编程:现代 Web 开发的基石与演进

引言 PHP&#xff08;Hypertext Preprocessor&#xff09;自1995年诞生以来&#xff0c;已成为全球最流行的服务器端脚本语言之一。尽管近年来Node.js、Python等语言在特定领域崭露头角&#xff0c;但PHP仍占据着超过78%的网站市场份额&#xff08;W3Techs数据&#xff09;。本…...

NAT/代理服务器/内网穿透

目录 一 NAT技术 二 内网穿透/内网打洞 三 代理服务器 一 NAT技术 跨网络传输的时候&#xff0c;私网不能直接访问公网&#xff0c;就引入了NAT能讲私网转换为公网进行访问&#xff0c;主要解决IPv4(2^32)地址不足的问题。 1. NAT原理 当某个内网想访问公网&#xff0c;就必…...

[已解决] VS Code / Cursor / Trae 的 PowerShell 终端 conda activate 进不去环境的常见问题

背景 PS C:\Users\Lenovo\WPSDrive\669715199_3\WPS云盘\课程\研一\ROAS5700 Robot Motion Planning and Control\Final\LaTex报告\final-v1> conda activate mpPS C:\Users\Lenovo\WPSDrive\669715199_3\WPS云盘\课程\研一\ROAS5700 Robot Motion Planning and Control\Fin…...

Kuka AI音乐AI音乐开发「人声伴奏分离」 —— 「Kuka Api系列|中文咬字清晰|AI音乐API」第6篇

导读 今天我们来了解一下 Kuka API 的人声与伴奏分离功能。 所谓“人声伴奏分离”&#xff0c;顾名思义&#xff0c;就是将一段完整的音频拆分为两个独立的轨道&#xff1a;一个是人声部分&#xff0c;另一个是伴奏&#xff08;乐器&#xff09;部分。 这个功能在音乐创作和…...

深度伪造对知识产权保护的新挑战与应对之策

首席数据官高鹏律师团队 在科技的飞速发展带来了诸多便利的同时&#xff0c;也引发了一系列复杂的法律问题&#xff0c;其中深度伪造技术对知识产权保护的冲击尤为显著&#xff0c;亟待引起广泛关注与深入探讨。 深度伪造&#xff0c;简单来说&#xff0c;是借助先进的人工智…...

【嵌入式开发-软件定时器】

嵌入式开发-软件定时器 ■ 1.■ 2.■ 3.■ 4. ■ 1. ■ 2. ■ 3. ■ 4....

3天重庆和成都旅游规划

重庆和成都都是大城市&#xff0c;各自都有丰富的旅游资源。如果要在三天内两头都游览&#xff0c;可能需要合理安排时间&#xff0c;确保既能体验到重庆的特色&#xff0c;又能在成都游览主要景点。然而&#xff0c;考虑到交通时间&#xff0c;如果从重庆到成都需要一定的时间…...