42、排序与搜索算法全解析

排序与搜索算法全解析

1. 插入排序

插入排序是一种常见的排序算法,很多人在整理扑克牌时就会使用这种方法,即一次拿起一张牌并将其插入到已排好序的牌中,使手中的牌始终保持有序。

插入排序的效率分析如下:设列表的大小为 n ,需要进行 n - 1 次迭代。在第 k 次迭代中,有一个包含 k 个元素的已排序列表,需要将一个新元素插入到该序列中。每次插入时,需要遍历初始序列的元素,直到找到新元素可以插入的位置,然后移动序列中剩余的元素。因此,总共访问的列表元素数量为:
[2 + 3 + \cdots + n = \frac{n(n + 1)}{2} - 1]
由此可以得出,插入排序是一种$O(n^2)$算法,与选择排序的效率处于同一数量级。不过,插入排序有一个很好的特性:如果列表已经是有序的,其性能为$O(n)$,这在实际应用中非常有用,因为数据集通常是部分有序的。

以下是插入排序的特点总结:
- 时间复杂度 :平均和最坏情况下为$O(n^2)$,最好情况下(列表已排序)为$O(n)$。
- 适用场景 :适用于数据量较小或部分有序的数据集。

2. 归并排序

归并排序是一种比选择排序更高效的算法,其基本思想非常简单。假设我们有一个包含10个整数的列表,先假设列表的前半部分和后半部分都已经排好序,然后将这两个有序的子列表合并成一个有序列表,每次从两个子列表中选择较小的元素添加到新列表中。

归并排序的Python实现代码如下:

# ch12/mergesort.py
##
#  The mergeSort function sorts a list, using the merge sort algorithm.
#
## Sorts a list, using merge sort.
#  @param values the list to sort
#
def mergeSort(values) :
    if len(values) <= 1 : return
    mid = len(values) // 2
    first = values[ : mid]
    second = values[mid : ]
    mergeSort(first)
    mergeSort(second)
    mergeLists(first, second, values)

## Merges two sorted lists into a third list.
#  @param first the first sorted list
#  @param second the second sorted list
#  @param values the list into which to merge first and second
#
def mergeLists(first, second, values) :
    iFirst = 0    # Next element to consider in the first list.
    iSecond = 0   # Next element to consider in the second list.
    j = 0         # Next open position in values.

    # As long as neither iFirst nor iSecond is past the end, move
    # the smaller element into values
    while iFirst < len(first) and iSecond < len(second) :
        if first[iFirst] < second[iSecond] :
            values[j] = first[iFirst]
            iFirst = iFirst + 1
        else :
            values[j] = second[iSecond]
            iSecond = iSecond + 1
        j = j + 1

    # Note that only one of the two loops below copies entries.
    # Copy any remaining entries of the first list.
    while iFirst < len(first) : 
        values[j] = first[iFirst] 
        iFirst = iFirst + 1
        j = j + 1

    # Copy any remaining entries of the second list.
    while iSecond < len(second) :
        values[j] = second[iSecond] 
        iSecond = iSecond + 1
        j = j + 1

以下是一个使用归并排序对随机数列表进行排序的示例代码:

# ch12/mergedemo.py
##
#  This program demonstrates the merge sort algorithm by
#  sorting a list that is filled with random numbers.
#
from random import randint
from mergesort import mergeSort

n = 20
values = []
for i in range(n) :
    values.append(randint(1, 100))
print(values)
mergeSort(values)
print(values)

归并排序的时间复杂度分析如下:
设$T(n)$表示对长度为 n 的列表进行归并排序所需的访问次数。在归并过程中,每次添加一个元素到结果列表中,平均需要3次访问(一次访问结果列表,一次访问第一个子列表,一次访问第二个子列表),总共需要$3n$次访问。此外,在开始时需要将原列表复制到两个子列表中,需要$2n$次访问,因此总共需要$5n$次访问。可以得到递归公式:
[T(n) = 2T(\frac{n}{2}) + 5n]
假设 n 是2的幂,即$n = 2^m$,通过推导可以得出$T(n) = 5n\log_2(n) + n$。忽略低阶项 n 和常数因子5,归并排序是一种$O(n\log(n))$算法。

归并排序的特点总结:
- 时间复杂度 :平均、最坏和最好情况下均为$O(n\log(n))$。
- 适用场景 :适用于数据量较大的数据集,性能稳定。

以下是归并排序和选择排序的性能对比表格:
| n | 归并排序 (秒) | 选择排序 (秒) |
| — | — | — |
| 10,000 | 0.105 | 9 |
| 20,000 | 0.223 | 38 |
| 30,000 | 0.344 | 85 |
| 40,000 | 0.470 | 147 |
| 50,000 | 0.599 | 228 |
| 60,000 | 0.729 | 332 |

归并排序的流程可以用以下mermaid流程图表示:

graph TD;
    A[开始] --> B[列表长度 <= 1?];
    B -- 是 --> C[返回列表];
    B -- 否 --> D[将列表分成两半];
    D --> E[对左半部分递归排序];
    D --> F[对右半部分递归排序];
    E --> G[合并左右两部分];
    F --> G;
    G --> H[返回排序后的列表];
3. 快速排序

快速排序是一种常用的排序算法,与归并排序相比,它不需要额外的临时列表来存储和合并部分结果。快速排序基于分治策略,首先对列表中的元素进行分区,使得分区 values[start] ... values[p] 中的元素都不大于分区 values[p + 1] ... values[to] 中的元素,然后递归地对这两个分区进行排序。

快速排序的Python实现代码如下:

def quickSort(values, start, to) :
    if start >= to : return
    p = partition(values, start, to)
    quickSort(values, start, p)
    quickSort(values, p + 1, to)

def partition(values, start, to) :
    pivot = values[start]
    i = start - 1
    j = to + 1
    while i < j :
        i = i + 1
        while values[i] < pivot :
            i = i + 1
        j = j - 1
        while values[j] > pivot :
            j = j - 1
        if i < j :
            temp = values[i]   # Swap the two elements.
            values[i] = values[j]
            values[j] = temp
    return j

快速排序的平均时间复杂度为$O(n\log(n))$,但最坏情况下的时间复杂度为$O(n^2)$。如果选择分区的第一个元素作为基准元素,当输入列表已经有序时,就会出现最坏情况。通过更巧妙地选择基准元素,可以大大降低最坏情况发生的概率。在实际应用中,当列表较短时,通常会切换到插入排序,因为插入排序在处理短列表时的总操作数更少。

快速排序的特点总结:
- 时间复杂度 :平均情况下为$O(n\log(n))$,最坏情况下为$O(n^2)$。
- 适用场景 :适用于大多数数据集,但需要注意避免最坏情况的发生。

4. 线性搜索

线性搜索是一种在无序数据集中查找元素的基本算法。如果要在一个任意顺序的数值序列中查找一个数,只能逐个检查所有元素,直到找到匹配项或到达序列末尾,这就是线性搜索,也称为顺序搜索。Python的 in 运算符在判断一个元素是否包含在列表中时使用的就是这种算法。

线性搜索的Python实现代码如下:

# ch12/linearsearch.py
##
#  This module implements a function for executing linear searches in a list.
#
## Finds a value in a list, using the linear search algorithm.
#  @param values the list to search
#  @param target the value to find
#  @return the index at which the target occurs, or -1 if it does not occur in the list
#  
def linearSearch(values, target) :
    for i in range(len(values)) :
        if values[i] == target :
            return i
    return -1

线性搜索的时间复杂度分析如下:如果假设目标元素存在于列表中,平均需要访问$n/2$个元素;如果目标元素不存在,则需要检查所有 n 个元素。因此,线性搜索是一种$O(n)$算法。

线性搜索的特点总结:
- 时间复杂度 :$O(n)$。
- 适用场景 :适用于无序数据集的查找。

以下是线性搜索的流程mermaid流程图:

graph TD;
    A[开始] --> B[遍历列表元素];
    B --> C[当前元素 == 目标元素?];
    C -- 是 --> D[返回当前元素索引];
    C -- 否 --> E[是否到达列表末尾?];
    E -- 否 --> B;
    E -- 是 --> F[返回 -1];

综上所述,不同的排序和搜索算法适用于不同的场景。在实际应用中,需要根据数据集的特点和需求选择合适的算法,以提高程序的性能。

排序与搜索算法全解析

5. 算法性能对比与选择建议

为了更直观地对比插入排序、归并排序、快速排序和线性搜索的性能,我们将它们的时间复杂度和适用场景整理成以下表格:
| 算法名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 适用场景 |
| — | — | — | — | — |
| 插入排序 | $O(n^2)$ | $O(n^2)$ | $O(n)$ | 数据量较小或部分有序的数据集 |
| 归并排序 | $O(n\log(n))$ | $O(n\log(n))$ | $O(n\log(n))$ | 数据量较大的数据集,性能稳定 |
| 快速排序 | $O(n\log(n))$ | $O(n^2)$ | $O(n\log(n))$ | 大多数数据集,但需避免最坏情况 |
| 线性搜索 | $O(n)$ | $O(n)$ | $O(1)$ | 无序数据集的查找 |

根据上述表格,我们可以得到以下选择算法的建议:
- 数据量较小 :插入排序在数据量较小时表现较好,尤其是当数据已经部分有序时,其时间复杂度可以接近$O(n)$。
- 数据量较大且要求稳定性能 :归并排序是一个不错的选择,它在各种情况下的时间复杂度都是$O(n\log(n))$,不会出现最坏情况。
- 大多数情况 :快速排序的平均时间复杂度为$O(n\log(n))$,在实际应用中通常表现出色,但需要注意选择合适的基准元素以避免最坏情况。
- 无序数据查找 :线性搜索是在无序数据集中查找元素的基本方法,虽然时间复杂度为$O(n)$,但在数据量较小时仍然是可行的。

6. 算法优化思路

除了选择合适的算法,我们还可以对算法进行优化以提高性能。以下是一些常见的优化思路:
- 快速排序优化
- 随机选择基准元素 :为了避免最坏情况的发生,可以随机选择基准元素,这样可以使最坏情况发生的概率大大降低。
- 切换到插入排序 :当列表较短时,插入排序的总操作数更少。因此,在快速排序中,当子列表的长度小于某个阈值时,可以切换到插入排序。
- 归并排序优化
- 减少复制操作 :在归并过程中,可以通过交替使用原列表和临时列表来减少复制操作,从而提高性能。

7. 代码示例与测试

为了验证上述算法的性能,我们可以编写一些测试代码。以下是一个简单的测试代码示例,用于比较不同排序算法对不同规模数据集的排序时间:

import time
from random import randint

# 插入排序
def insertionSort(values):
    for i in range(1, len(values)):
        key = values[i]
        j = i - 1
        while j >= 0 and key < values[j]:
            values[j + 1] = values[j]
            j = j - 1
        values[j + 1] = key
    return values

# 归并排序
def mergeSort(values):
    if len(values) <= 1:
        return values
    mid = len(values) // 2
    first = values[:mid]
    second = values[mid:]
    first = mergeSort(first)
    second = mergeSort(second)
    return mergeLists(first, second)

def mergeLists(first, second):
    result = []
    iFirst = 0
    iSecond = 0
    while iFirst < len(first) and iSecond < len(second):
        if first[iFirst] < second[iSecond]:
            result.append(first[iFirst])
            iFirst = iFirst + 1
        else:
            result.append(second[iSecond])
            iSecond = iSecond + 1
    while iFirst < len(first):
        result.append(first[iFirst])
        iFirst = iFirst + 1
    while iSecond < len(second):
        result.append(second[iSecond])
        iSecond = iSecond + 1
    return result

# 快速排序
def quickSort(values, start, to):
    if start >= to:
        return
    p = partition(values, start, to)
    quickSort(values, start, p)
    quickSort(values, p + 1, to)

def partition(values, start, to):
    pivot = values[start]
    i = start - 1
    j = to + 1
    while i < j:
        i = i + 1
        while values[i] < pivot:
            i = i + 1
        j = j - 1
        while values[j] > pivot:
            j = j - 1
        if i < j:
            temp = values[i]
            values[i] = values[j]
            values[j] = temp
    return j

# 测试不同规模数据集的排序时间
sizes = [100, 1000, 10000]
for size in sizes:
    values = [randint(1, 10000) for _ in range(size)]

    # 插入排序
    start_time = time.time()
    insertionSort(values.copy())
    insertion_time = time.time() - start_time

    # 归并排序
    start_time = time.time()
    mergeSort(values.copy())
    merge_time = time.time() - start_time

    # 快速排序
    start_time = time.time()
    quickSort(values.copy(), 0, len(values) - 1)
    quick_time = time.time() - start_time

    print(f"数据集规模: {size}")
    print(f"插入排序时间: {insertion_time} 秒")
    print(f"归并排序时间: {merge_time} 秒")
    print(f"快速排序时间: {quick_time} 秒")
    print()

通过运行上述代码,我们可以观察到不同排序算法在不同规模数据集上的性能差异。

8. 总结

排序和搜索算法是计算机科学中的基础内容,不同的算法具有不同的时间复杂度和适用场景。在实际应用中,我们需要根据数据集的特点和需求选择合适的算法,并可以通过优化算法来提高性能。以下是本文的主要内容总结:
- 插入排序 :适用于数据量较小或部分有序的数据集,平均和最坏时间复杂度为$O(n^2)$,最好时间复杂度为$O(n)$。
- 归并排序 :适用于数据量较大的数据集,性能稳定,时间复杂度始终为$O(n\log(n))$。
- 快速排序 :平均时间复杂度为$O(n\log(n))$,但最坏情况下为$O(n^2)$,需要注意选择合适的基准元素。
- 线性搜索 :适用于无序数据集的查找,时间复杂度为$O(n)$。

希望通过本文的介绍,你对排序和搜索算法有了更深入的理解,并能够在实际应用中选择合适的算法。

以下是不同算法性能对比的mermaid流程图:

graph LR;
    A[数据集规模小] --> B[插入排序];
    A[数据集规模大] --> C{数据是否有序};
    C -- 是 --> D[插入排序];
    C -- 否 --> E{对性能稳定性要求高?};
    E -- 是 --> F[归并排序];
    E -- 否 --> G[快速排序];
    H[无序数据查找] --> I[线性搜索];

通过这个流程图,我们可以更清晰地看到在不同情况下应该选择哪种算法。

【电能质量扰动】基于ML和DWT的电能质量扰动分类方法研究(Matlab实现)内容概要:本文研究了一种基于机器学习(ML)和离散小波变换(DWT)的电能质量扰动分类方法,并提供了Matlab实现方案。首先利用DWT对电能质量信号进行多尺度分解,提取信号的时频域特征,有效捕捉电压暂降、暂升、中断、谐波、闪变等常见扰动的关键信息;随后结合机器学习分类器(如SVM、BP神经网络等)对提取的特征进行训练分类,实现对不同类型扰动的自动识别准确区分。该方法充分发挥DWT在信号去噪特征提取方面的优势,结合ML强大的模式识别能力,提升了分类精度鲁棒性,具有较强的实用价值。; 适合人群:电气工程、自动化、电力系统及其自动化等相关专业的研究生、科研人员及从事电能质量监测分析的工程技术人员;具备一定的信号处理基础和Matlab编程能力者更佳。; 使用场景及目标:①应用于智能电网中的电能质量在线监测系统,实现扰动类型的自动识别;②作为高校或科研机构在信号处理、模式识别、电力系统分析等课程的教学案例或科研实验平台;③目标是提高电能质量扰动分类的准确性效率,为后续的电能治理设备保护提供决策依据。; 阅读建议:建议读者结合Matlab代码深入理解DWT的实现过程特征提取步骤,重点关注小波基选择、分解层数设定及特征向量构造对分类性能的影响,并尝试对比不同机器学习模型的分类效果,以面掌握该方法的核心技术要点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值