标准库的函数

sort()

std::sort() 通常是快速排序或其变种(如内省排序、三路快速排序等),具体的实现会因编译器和标准库的不同而有所差异。以下是快速排序的几种简单优化方法,一些编译器和标准库可能用了以下方法:

当然快速排序还能进一步优化,直接使用 std::sort() 无法通过洛谷 P1177 【模板】排序,这道题需要手搓快速排序算法。

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
    std::sort(vec.begin(), vec.end());
    // vec = {1, 1, 2, 3, 4, 5, 6, 9}
    return 0;
}

stable_sort()

std::stable_sort() 函数也用于对给定范围内的元素进行排序,但它保证排序是稳定的。
它通常使用归并排序算法。

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
    std::stable_sort(vec.begin(), vec.end());
    // vec = {1, 1, 2, 3, 4, 5, 6, 9}
    return 0;
}

qsort()

与C++标准库的 std::sort() 函数不同,qsort() 并不会根据数据特性来选择最佳的排序算法。它使用的是快速排序的一种简化版本,不具备自适应性和优化特性。因此,qsort() 并不会根据数据特性动态选择排序算法。

C中,使用 qsort() 函数时,用户必须要提供一个比较函数来定义排序规则。该比较函数必须符合 qsort() 函数指定的格式,以便 qsort() 能够正确地比较数组中的元素。用户可以通过提供不同的比较函数来改变排序的规则,但是选择的排序算法仍然是快速排序。

#include <stdio.h>
#include <stdlib.h>

int compare_function(const void *a, const void *b) {
    int num1 = *((int *)a), num2 = *((int *)b);
    if (num1 < num2)
        return -1;
    else if (num1 > num2)
        return 1;
    else
        return 0;
}

int main() {
    int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
    int n = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, n, sizeof(int), compare_function);
    for (int i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

Tim 排序

wikipedia - Tim排序

Tim排序是一种结合了归并排序和插入排序的稳定排序算法,由Tim Peters于2002年提出。它的性能在实际应用中表现良好,特别是在处理大型数据集时。Tim排序的主要思想是通过先使用插入排序对小规模的子数组进行排序,然后再使用归并排序对已经部分有序的子数组进行合并。

适用

Tim排序在以下情况下通常更好:

但是,Tim排序可能不如其他排序算法在以下情况下:

分析

  1. 插入排序阈值选择:
    Tim排序首先确定一个阈值,通常在32到64之间。这个阈值的作用是确定何时切换到插入排序。如果待排序数组的大小小于这个阈值,就会采用插入排序来处理。
  2. 分区:
    将待排序的数组分成若干个小块(称为run),每个小块的大小不超过插入排序的阈值。通常,使用插入排序对每个小块进行排序,从而得到一些已经部分有序的run。
  3. 归并排序:
    使用归并排序将已经排序好的run逐步合并,直到整个数组排序完成。归并的过程是基于稳定的归并操作,确保了排序的稳定性。
  4. 合并策略:
    Tim排序使用一种特殊的合并策略,称为归并树(merge tree)或Tim树。在这个合并策略中,首先对相邻的run进行归并,然后逐级向上合并,直到整个数组都被合并为止。这种策略可以有效地利用CPU缓存,从而提高了排序的性能。
  5. 优化:
    Tim排序通常会采用一些优化技巧,比如对已经有序的run进行标记,以减少不必要的归并操作。此外,还可以通过多线程等方法加速排序过程。
    总的来说,Tim排序通过结合插入排序和归并排序的优点,以及优化归并过程,实现了高效的稳定排序算法。它的性能在大部分情况下都比较优秀,并且对于大规模数据集的排序表现尤为突出。

代码实现

#include <bits/stdc++.h>
using namespace std;
const int INSERTION_SORT_THRESHOLD = 64;

void insertionSort(vector<int>& arr, int left, int right) {
	for (int i = left + 1; i <= right; ++i) {
		int key = arr[i];
		int j = i - 1;
		while (j >= left && arr[j] > key) {
			arr[j + 1] = arr[j];
			--j;
		}
		arr[j + 1] = key;
	}
}

void merge(vector<int>& arr, int left, int mid, int right) {
	int n1 = mid - left + 1;
	int n2 = right - mid;
	vector<int> L(n1), R(n2);
	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];
	int i = 0, j = 0, k = left;
	while (i < n1 && j < n2) {
		if (L[i] <= R[j]) {
			arr[k] = L[i];
			++i;
		} else {
			arr[k] = R[j];
			++j;
		}
		++k;
	}
	while (i < n1) {
		arr[k] = L[i];
		++i;
		++k;
	}
	while (j < n2) {
		arr[k] = R[j];
		++j;
		++k;
	}
}

void timSort(vector<int>& arr, int left, int right) {
	if (right - left + 1 <= INSERTION_SORT_THRESHOLD) {
		insertionSort(arr, left, right);
		return;
	}
	int mid = left + (right - left) / 2;
	timSort(arr, left, mid);
	timSort(arr, mid + 1, right);
	merge(arr, left, mid, right);
}

void timSort(vector<int>& arr) {
	timSort(arr, 0, arr.size() - 1);
}

int main() {
	vector<int> arr = {3, 5, 3, 8, 6, 2, 7, 1, 4};
	cout << "Original Array:" << endl;
	for (int num : arr)
		cout << num << " ";
	cout << endl;
	timSort(arr);
	cout << "Sorted Array:" << endl;
	for (int num : arr)
		cout << num << " ";
	cout << endl;
	return 0;
}

排序总结

alt text
© Nail_ 2024 from 124.220.68.222
*Copyright © Nail_ 2024 All rights reserved. This image is the property of Nail_. Reproduction of this image is permitted with proper attribution to Nail_ as the source, along with this copyright notice. Unauthorized distribution, including failure to provide proper attribution and copyright notice, is strictly prohibited.

举例

洛谷 P3366 【模板】最小生成树
使用插入排序(46-53行标注处)有三个测试点TLE,使用C++标准库快速排序(16-18, 55行标注处)全部AC。

#include <bits/stdc++.h>
using namespace std;
#define ulli unsigned long long int
#define lli long long int
//const int MAX_npoint=20;
const int MAX_npoint=5003+9;
const int MAX_nline=200005+9;
int datum[MAX_npoint][MAX_npoint];
int father[MAX_npoint];
struct node{    //手搓从小到大放[^1]
	int length;
	int a,b;
};
vector<node> shortest;

bool cmp(node a,node b){
	return a.length<b.length;
}

int ffindfa(int fn){
	if(father[fn]==fn)
		return fn;
	else{
		return father[fn]=ffindfa(father[fn]);
	}
}

int main(){
	int npoint,nline;
	scanf("%d %d",&npoint,&nline);
	int tempia,tempib,tempic;
	for(int i=1;i<=nline;i++){
		scanf("%d %d %d",&tempia,&tempib,&tempic);
		int isChange=datum[tempib][tempia];
		if(datum[tempia][tempib]==0)
			datum[tempia][tempib]=datum[tempib][tempia]=tempic;
		else
			datum[tempia][tempib]=datum[tempib][tempia]=min(datum[tempib][tempia],tempic);
		node tempnode;
		tempnode.length=tempic;
		tempnode.a=tempia; tempnode.b=tempib;
		if(isChange!=datum[tempib][tempia]){
//			if(shortest.size()==0)
//				shortest.push_back(tempnode);
//			else{
//				//[^1]: 插入排序
//				ulli j;
//				for(j=0;j<shortest.size();j++)
//					if(shortest[j].length>=tempnode.length) break;
//				if(j==shortest.size())
//					shortest.push_back(tempnode);
//				else
//					shortest.insert(shortest.begin()+j,tempnode);
//			}
			shortest.push_back(tempnode);
		}
	}
	sort(shortest.begin(),shortest.end(),cmp);
	
	for(int i=1;i<=npoint;i++)
		father[i]=i;
	int res=0,lineTimes=0;
	for(ulli i=0;i<shortest.size();i++){
		int pa=shortest[i].a,pb=shortest[i].b;
		int fatherpa=ffindfa(pa),fatherpb=ffindfa(pb);
		if(fatherpa!=fatherpb){
			father[fatherpa]=fatherpb;
			res+=shortest[i].length;
			lineTimes++;
		}
	}
	
	if(res==0||lineTimes!=(npoint-1))
		printf("orz");
	else
		printf("%d",res);
	return 0;
}

标准库的函数

Tim 排序