sort | stable_sort用法解析

sort与stable_sort详解

在STL 中, std::sort 是最常用的排序算法之一。位于 <algorithm> 头文件中,提供了一个极其高效且灵活的排序方案。

对于绝大多数应用场景,只需要掌握 std::sort 的两种用法。

std::sort 的基本信息

  • 头文件: 必须包含 #include <algorithm>
  • 作用: 对一个范围内的元素进行排序。
  • 特点:
    • 速度快: 平均时间复杂度为 O(nlog(n))。
    • 不稳定排序: 对于值相等的元素,排序后它们的相对位置不保证与排序前一致。如果需要保持相对位置,请使用 std::stable_sort
    • 通用性强: 它可以排序 std::vector, std::array, std::deque,甚至是普通的 C 风格数组。

一、默认排序(升序)

这是 std::sort 最基本、最常见的用法。不需要任何额外的参数,默认会将范围内的元素按照从小到大的顺序(非降序)排列。

std::sort 接受两个参数:

  1. first: 指向待排序范围的第一个元素的迭代器。
  2. last: 指向待排序范围的最后一个元素的下一个位置的迭代器。

这是一个非常重要的“左闭右开”区间 [first, last) 的概念,在 STL 中非常普遍。

示例:对整数 vector 进行升序排序

#include <iostream>
#include <vector>
#include <algorithm> // 别忘了包含这个头文件

std::vector<int> numbers = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};

// 调用 std::sort 进行默认升序排序
std::sort(numbers.begin(), numbers.end());

二、自定义排序

std::sort 还可以通过提供第三个参数——比较函数(Comparator),实现自定义排序。

这个比较函数本质上是一个回答“谁应该排在前面?”问题的函数。它的规则非常简单:

比较函数 comp(a, b) 必须返回一个 bool 值。如果返回 true,则意味着 a 应该排在 b 的前面;如果返回 false,则 b 应该排在 a 的前面(或保持相对位置)。

方式 1:使用普通函数(降序排序)

这是最直观的方式,我们可以定义一个独立的函数来实现降序排序。

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

// 比较函数:如果 a 大于 b,则 a 应该排在前面
bool compare_desc(int a, int b) {
    return a > b;
}

int main() {
    std::vector<int> numbers = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};
    
    std::sort(numbers.begin(), numbers.end(), compare_desc);

    for (int num : numbers) {
        std::cout << num << " "; // 输出: 9 8 7 6 5 4 3 2 1 0
    }
    std::cout << std::endl;
}

方式 2:使用 Lambda 表达式(现代 C++ 推荐)

在 C++11 及以后,使用 Lambda 表达式是定义临时比较逻辑最简洁、最流行的方式。

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

int main() {
    std::vector<int> numbers = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};
    
    // 使用 Lambda 表达式实现降序排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    for (int num : numbers) {
        std::cout << num << " "; // 输出: 9 8 7 6 5 4 3 2 1 0
    }
    std::cout << std::endl;
}

方式 3:排序自定义结构体

假设我们有一个学生结构体,需要根据学生的分数进行排序。

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

struct Student {
    std::string name;
    int score;
};

int main() {
    std::vector<Student> students = {
        {"张三", 95},
        {"李四", 88},
        {"王五", 100},
        {"赵六", 95}
    };

    // 使用 Lambda 表达式,按分数从高到低排序
    std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
        // 如果分数不同,则分数高的排前面
        if (a.score != b.score) {
            return a.score > b.score;
        }
        // 如果分数相同,可以按名字的字典序排(可选)
        return a.name < b.name;
    });

    std::cout << "按分数降序排序后:" << std::endl;
    for (const auto& s : students) {
        std::cout << "姓名: " << s.name << ", 分数: " << s.score << std::endl;
    }
}

输出:
按分数降序排序后:
姓名: 王五, 分数: 100
姓名: 张三, 分数: 95
姓名: 赵六, 分数: 95
姓名: 李四, 分数: 88

总结

  • 简单升序: std::sort(v.begin(), v.end());
  • 自定义排序: std::sort(v.begin(), v.end(), my_comparator);
  • 比较器核心: comp(a, b) 返回 true 意味着 a 排在 b 前面。
  • 现代首选: 优先使用 Lambda 表达式来编写自定义比较器,尤其是在处理复杂对象时。

顺带一提stable_sort

核心区别:稳定性

稳定性是 stable_sort (稳定排序) 和 sort (不稳定排序) 的唯一但至关重要的区别。

  • 不稳定排序 (std::sort): 当两个元素 A 和 B 在排序规则下被视为“相等”时(例如,两个学生分数都是 95),std::sort 不保证 它们在排序后的相对位置与排序前一致。如果 A 原本在 B 前面,排序后 B 完全有可能跑到 A 的前面。

  • 稳定排序 (std::stable_sort): 当元素 A 和 B 被视为“相等”时,stable_sort 保证 如果 A 在原始序列中位于 B 的前面,那么在排序后的序列中,A 依然会位于 B 的前面。

std::stable_sort 的使用方法和 std::sort 完全一样
它同样接受两个(用于默认升序)或三个(用于自定义比较)参数。

// 默认升序稳定排序
std::stable_sort(v.begin(), v.end());

// 使用自定义比较函数进行稳定排序
std::stable_sort(v.begin(), v.end(), my_comparator);

std::sort 编写的任何比较函数或 Lambda 表达式,都可以无缝地用于 std::stable_sort

性能权衡

为了维持稳定性,stable_sort 的算法实现通常比 sort 更复杂。在最坏的情况下,它可能需要分配额外的内存(比如典型的归并排序实现)。因此,std::stable_sort 的运行速度可能会比 std::sort 稍慢,并且可能消耗更多内存。

一般情况下用sort,有明确稳定排序需求时再用stable_sort

import os import numpy as np import matplotlib.pyplot as plt import re from matplotlib.ticker import MaxNLocator # 解决中文显示问题 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei'] plt.rcParams['axes.unicode_minus'] = False def natural_sort_key(s): """自然排序算法:确保文件名按数字顺序排列""" return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)] def find_stable_intervals(counts, min_window=300, max_window=2000, std_threshold=10.0, merge_gap=300, min_length=500): """ 改进版稳定区间检测:使用标准差作为稳定性指标 :param counts: 预测框数量列表 :param min_window: 最小窗口尺寸 :param max_window: 最大窗口尺寸 :param std_threshold: 标准差阈值(波动范围) :param merge_gap: 相邻区间合并的最大间隔 :param min_length: 最小有效区间长度 :return: 优化后的稳定区间列表 """ n = len(counts) if n == 0: return [] # 1. 自适应窗口机制 window_size = min(max_window, max(min_window, n // 10)) step_size = max(1, window_size // 2) # 50%重叠滑动 # 2. 初始检测稳定区间(使用标准差) base_intervals = [] for i in range(0, n - window_size + 1, step_size): window = counts[i:i + window_size] if len(window) < 2: # 至少需要2个点计算标准差 continue # 计算标准差作为稳定性指标 std_dev = np.std(window) if std_dev < std_threshold: base_intervals.append((i, i + window_size - 1)) # 如果没有检测到任何区间,直接返回 if not base_intervals: return [] # 3. 合并相邻平稳段 base_intervals.sort(key=lambda x: x[0]) # 确保按起始索引排序 merged_intervals = [] current_start, current_end = base_intervals[0] for start, end in base_intervals[1:]: if start - current_end <= merge_gap: # 间隔小于合并阈值 current_end = max(current_end, end) # 扩展当前区间 else: merged_intervals.append((current_start, current_end)) current_start, current_end = start, end merged_intervals.append((current_start, current_end)) # 4. 过滤短时伪平稳段 final_intervals = [ (start, end) for start, end in merged_intervals if (end - start + 1) >= min_length # 区间长度包含两端点 ] return final_intervals def plot_box_count_trend_with_stable_intervals(file_list, box_counts, stable_intervals, output_path): """ 绘制预测框数量变化趋势图并标记稳定区间 :param file_list: 文件名列表 :param box_counts: 预测框数量列表 :param stable_intervals: 稳定区间列表 :param output_path: 输出图片路径 """ plt.figure(figsize=(20, 10)) # 绘制整体趋势 plt.plot(file_list, box_counts, 'b-', linewidth=1.5, label='预测框数量') # 标记稳定区间 for i, (start, end) in enumerate(stable_intervals): interval_files = file_list[start:end + 1] interval_counts = box_counts[start:end + 1] if not interval_counts: # 确保区间有效 continue # 计算区间统计量 avg_count = np.mean(interval_counts) min_count = np.min(interval_counts) max_count = np.max(interval_counts) std_dev = np.std(interval_counts) # 绘制稳定区间 plt.fill_between(interval_files, min_count, max_count, color='green', alpha=0.2, label=f'稳定区间{i + 1}' if i == 0 else "") # 添加区间标注 mid_idx = start + (end - start) // 2 plt.annotate(f"区间{i + 1}: {start + 1}-{end + 1}\n均值: {avg_count:.1f}±{std_dev:.1f}", (file_list[mid_idx], avg_count), xytext=(0, 20), textcoords='offset points', ha='center', fontsize=10, bbox=dict(boxstyle="round,pad=0.3", fc="yellow", alpha=0.7)) # 设置图表属性 plt.title('预测框数量变化趋势及稳定区间分析', fontsize=18) plt.xlabel('图像文件名', fontsize=14) plt.ylabel('预测框数量', fontsize=14) plt.xticks(rotation=90, fontsize=7) plt.grid(True, linestyle='--', alpha=0.6) plt.legend(loc='upper right') # 添加统计信息 stats_text = f"总文件数: {len(file_list)}\n稳定区间数: {len(stable_intervals)}" plt.figtext(0.95, 0.95, stats_text, ha='right', va='top', bbox=dict(facecolor='white', alpha=0.8), fontsize=12) # 限制X轴刻度数量 plt.gca().xaxis.set_major_locator(MaxNLocator(20)) plt.tight_layout() plt.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() # 配置路径 label_dir = "D:/630-3-label-combine" # 替换为您的标签文件夹路径 output_dir = "D:/630-report" # 输出目录 os.makedirs(output_dir, exist_ok=True) # 获取文件列表并按自然顺序排序 file_list = [f for f in os.listdir(label_dir) if f.endswith(".txt")] file_list.sort(key=natural_sort_key) # 提取文件名(不含扩展名) file_names = [os.path.splitext(f)[0] for f in file_list] # 统计每个文件的预测框数量 box_counts = [] for file in file_list: file_path = os.path.join(label_dir, file) count = 0 with open(file_path, 'r') as f: for line in f: if line.strip(): # 非空行 count += 1 box_counts.append(count) # 计算整体统计数据 total_mean = np.mean(box_counts) total_std = np.std(box_counts) # 找出稳定区间(使用标准差作为指标) stable_intervals = find_stable_intervals( box_counts, min_window=300, # 最小检测窗口 max_window=2000, # 最大检测窗口 std_threshold=total_std * 0.5, # 基于整体标准差设置阈值 merge_gap=300, # 合并最大间隔 min_length=500 # 最小有效长度 ) # 生成结果图片 output_path = os.path.join(output_dir, "box_count_stable_intervals_std.png") plot_box_count_trend_with_stable_intervals(file_names, box_counts, stable_intervals, output_path) # 输出详细结果 print(f"分析完成! 共处理 {len(file_list)} 个文件") print(f"整体平均框数: {total_mean:.2f} ± {total_std:.2f}") print(f"发现 {len(stable_intervals)} 个稳定区间:") for i, (start, end) in enumerate(stable_intervals): interval_counts = box_counts[start:end + 1] avg_count = np.mean(interval_counts) std_dev = np.std(interval_counts) print(f"区间{i + 1}:") print(f" - 文件范围: {start + 1}-{end + 1} (共{end - start + 1}个文件)") print(f" - 平均框数: {avg_count:.2f} ± {std_dev:.2f}") print(f" - 最小值: {min(interval_counts)}, 最大值: {max(interval_counts)}") print(f"结果图片已保存至: {output_path}") # 保存区间信息到文本文件 interval_info_path = os.path.join(output_dir, "stable_intervals_report_std.txt") with open(interval_info_path, 'w') as f: f.write(f"稳定区间分析报告(标准差指标)\n") f.write(f"总文件数: {len(file_list)}\n") f.write(f"整体平均框数: {total_mean:.2f} ± {total_std:.2f}\n") f.write(f"稳定区间数: {len(stable_intervals)}\n\n") for i, (start, end) in enumerate(stable_intervals): interval_counts = box_counts[start:end + 1] avg_count = np.mean(interval_counts) std_dev = np.std(interval_counts) f.write(f"区间 {i + 1}:\n") f.write(f" 起始文件索引: {start + 1} ({file_names[start]})\n") f.write(f" 结束文件索引: {end + 1} ({file_names[end]})\n") f.write(f" 文件数量: {end - start + 1}\n") f.write(f" 平均预测框数: {avg_count:.2f} ± {std_dev:.2f}\n") f.write(f" 最小值: {min(interval_counts)}, 最大值: {max(interval_counts)}\n") f.write("-" * 50 + "\n") print(f"详细区间报告已保存至: {interval_info_path}") 将代码中的预测框的数量改成txt文件中的x坐标的均值,y坐标的均值,分别生成图片和文件
最新发布
07-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值