常见排序算法

目录

一,排序算法

1,分类方法

(1)内部排序、外部排序

(2)原址排序、非原址排序

(3)稳定排序、非稳定排序

(4)基于计算的排序算法、基于比较的排序算法

2,排序算法测试代码

(1)提供模板函数

(2)约定接口

(3)排序功能测试

(4)排序稳定性测试

3,常见排序算法

(1)插入排序

(2)选择排序

(3)冒泡排序

(4)双向冒泡排序

(5)归并排序

(6)堆排序

(7)快速排序

(8)基数排序

(9)计数排序

(10)桶排序

(11)希尔排序

(12)近似计数排序

二,排序算法效率、组合嵌套

三,OJ实战

力扣 147. 对链表进行插入排序(插入排序)

力扣 148. 排序链表(插入排序)

POJ 2299 Ultra-QuickSort(归并排序)


一,排序算法

1,分类方法

(1)内部排序、外部排序

根据排序数据来自内部存储器还是外部存储器分类的,现在一般都是内部排序,外部排序的场景很少。

(2)原址排序、非原址排序

原址排序一般只需要O(1)的额外空间,包括快速排序、选择排序、插入排序、冒泡排序、希尔排序

非原址排序需要一定的额外辅助空间,包括归并排序、堆排序

(3)稳定排序、非稳定排序

相等元素排序之后的相对次序和原次序相同的排序叫稳定排序,包括插入排序,基数排序,归并排序,冒泡排序,计数排序

否则叫非稳定排序,包括快速排序,希尔排序,选择排序,堆排序

桶排序是一种嵌套排序算法,是否是稳定排序取决于内部嵌套的是哪种排序算法。

(4)基于计算的排序算法、基于比较的排序算法

计数排序、基数排序、桶排序是基于计算的排序算法,其他常见排序算法都是基于比较的。

基于比较的排序算法,适用于任何良序集,只需要自定义cmp函数(如下面代码所示),即可排序,

也就是说,无论待排序的元素是整数,实数甚至复数,无论是字符串,结构体,还是vector等等,只要能定义出合适的cmp函数使得待排序元素构成的集合是良序的即可。

而基于计算的排序算法,基本上只适用于元素是整数或者实数

当然,对于结构体,如果只根据其中一个成员作为比较对象,其实也能用基于计算的排序算法,

但是,用来比较的对象还是只能是整数或者实数,而不像基于比较的算法都支持结构体用多个成员作为比较对象,

而且,计数排序只能对特殊的结构体排序,即满足如果两个结构体变量的key成员相同那么两个结构体就完全相同的结构体,其中key成员指的是用来排序的成员。

接下来准备花点时间,把常见排序算法的代码都写一写。

2,排序算法测试代码

(1)提供模板函数

template<typename T>
bool cmp(T a, T b)
{
    return a < b;
}

template<typename T>
void exchange(T* a, T* b)
{
    T tmp = *a;
    *a = *b;
    *b = tmp;
}

template<typename T>
T GetMax(T* arr, int len)
{
    T ans = arr[0];
    for (int i = 1; i < len; i++)if(cmp(ans,arr[i]))ans=arr[i];
    return ans;
}

这3个基础函数,可以用于各排序算法中。

(2)约定接口

基于比较的排序算法写成泛型接口,提供2个接口版本,即cmp函数指针可以省略

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
}

template<typename T>
void Sort(T* arr, int len)
{
    Sort(arr,len,cmp);
}

基于计算的排序算法直接以int类型作为排序对象类型,以普通小于作为比较函数

void Sort(int* arr, int len)
{
    
}

(3)排序功能测试

搭建一个简单的评测系统,用来判断排序算法是否完成了功能:

#include <iostream>
using namespace std;


int main() {
    const int N = 1000;
    int arr[N];
    for (int i = 0; i < N; i++)arr[i] = rand()%10000;
    Sort(arr, N);
    for (int i = 0; i < N - 1; i++) {
        if (arr[i] > arr[i + 1]) {
            cout << "error! arr[i] = " << arr[i] << ", arr[i+1] = " << arr[i + 1] << endl;
        }
    }
    cout << "end";
    return 0;
}

然后就可以开始写排序算法了。

(4)排序稳定性测试

struct Node {
	int a;
	int b;
	bool operator<(const Node &x) const{
		return a < x.a;
	}
};
template<typename T>
void StableSort(T* arr, int len, bool(*cmp)(T a, T b))
{
	for (int i = 1; i < len; i++) {
		for (int j = i; j > 0; j--) {
			if (cmp(arr[j], arr[j - 1]))exchange(arr + j, arr + j - 1);
			else break;
		}
	}
}
template<typename T>
void StableSort(T* arr, int len)
{
	StableSort(arr, len, cmp);
}
void testStable()
{
	const int N = 12345;
	Node arr[N];
	Node arr2[N];
	for (int i = 0; i < N; i++)arr[i].a = rand() % 1000, arr[i].b = rand(), arr2[i] = arr[i];
	Sort(arr, N);
	StableSort(arr2, N);
	bool flag = true;
	for (int i = 0; i < N; i++)if (arr[i].b != arr2[i].b)flag = false;
	if (flag)cout << "is stable.";
	else cout << "not stable";
}

3,常见排序算法

(1)插入排序

算法思路:先让前i个元素是有序的,然后插入一个元素,让前i+1个元素是有序的。

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
    for(int i=1; i<len; i++){
        for(int j=i; j>0; j--){
            if(cmp(arr[j], arr[j-1]))exchange(arr+j, arr+j-1);
            else break;
        }
    }
}

最坏时间:O(n^2)

平均时间:O(n^2)

稳定性测试结果:稳定

PS:插入排序的时间其实就是Θ(n+逆序对的数目)

(2)选择排序

算法思路:选择未排序的所有元素中的最小元素,直接放到已排序的这一段的后面。

template<typename T>
int getMinId(T* arr, int len, bool(*cmp)(T a, T b))
{
    int ans=0;
    for(int i=1;i<len;i++){
        if(cmp(arr[i], arr[ans]))ans=i;
    }
    return ans;
}

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
    for(int i=0; i<len; i++){
        int minId = getMinId(arr+i, len-i, cmp);
        exchange(arr+i, arr+i+minId);
    }
}

最坏时间:O(n^2)

平均时间:O(n^2)

稳定性测试结果:不稳定

(3)冒泡排序

算法思路:先把n个元素依次扫描一遍,相邻俩元素排序一下,扫描完之后最大元素就会出现在最后的位置,

然后再扫描前n-1个元素...依次类推

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
    for(int i=len-1; i>0; i--){
        for(int j=0;j<i;j++){
            if(cmp(arr[j+1], arr[j]))exchange(arr+j+1, arr+j);
        }
    }
}

最坏时间:O(n^2)

平均时间:O(n^2)

稳定性测试结果:稳定

或者对称的写法:

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	for (int i = 0; i < len; i++) {
		for (int j = len - 1; j > i; j--) {
			if (cmp(arr[j], arr[j - 1]))exchange(arr + j - 1, arr + j);
		}
	}
}

时间复杂度和稳定性不变

(4)双向冒泡排序

也叫鸡尾酒排序,就是把2个方向的冒泡排序结合一下。

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	for (int low = 0, high = len - 1; low < high; low++, high--) {
		for (int j = high; j > low; j--) {
			if (cmp(arr[j], arr[j - 1]))exchange(arr + j - 1, arr + j);
		}
		for (int j = low; j < high; j++) {
			if (cmp(arr[j + 1], arr[j]))exchange(arr + j + 1, arr + j);
		}
	}
}

最坏时间:O(n^2)

平均时间:O(n^2)

稳定性测试结果:稳定

(5)归并排序

归并排序主要是Merge操作和递归操作。

template<typename T>
void Merge(T* arr1, int len1, T* arr2, int len2, bool(*cmp)(T a, T b))
{
    T* arr = new T[len1 + len2];
    int i, j;
    for (i = 0, j = 0; i < len1 && j < len2;) {
        if (cmp(arr2[j], arr1[i]))*arr = arr2[j++];
        else *arr = arr1[i++];
        arr++;
    }
    while(i<len1)*(arr++) = arr1[i++];
    while(j<len2)*(arr++) = arr2[j++];
    arr -= len1 + len2;
    for (int i = 0; i < len1 + len2; i++)arr1[i] = arr[i];
}

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
    if (len <= 1)return;
    Sort(arr, len / 2, cmp);
    Sort(arr + len / 2, len - len / 2, cmp);
    Merge(arr, len / 2, arr + len / 2, len - len / 2, cmp);
}

最坏时间:O(nlogn)

平均时间:O(nlogn)

稳定性测试结果:稳定

Merge操作合并2个数组的过程中,包含至多len次cmp操作和2*len次赋值操作,其中len=len1+len2表示2个数组的长度和

(6)堆排序

代码来自:二叉堆、堆排序_nameofcsdn的博客-CSDN博客

int LeftChild(int id)
{
	return id * 2 + 1;
}
int RightChild(int id)
{
	return id * 2 + 2;
}

template<typename T>
void AdjustHeap(T* arr, int rootId, int size, bool(*cmp)(T a, T b))
{
    int largest = rootId, left = LeftChild(rootId), right = RightChild(rootId);
    if (left < size && cmp(arr[largest], arr[left]))largest = left;
    if (right < size && cmp(arr[largest], arr[right]))largest = right;
    if (largest == rootId)return;
    exchange(arr + rootId, arr + largest);
    AdjustHeap(arr, largest, size, cmp);
}
template<typename T>
void InitHeap(T* arr, int size, bool(*cmp)(T a, T b))
{
    for (int i = size / 2; i >= 0; i--)AdjustHeap(arr, i, size, cmp);
}
template<typename T>
void Sort(T* arr, int size, bool(*cmp)(T a, T b))
{
    InitHeap(arr, size, cmp);
    for (int i = size - 1; i > 0; i--) {
        exchange(arr + i, arr);
        AdjustHeap(arr, 0, i, cmp);
    }
}

最坏时间:O(n log n)

平均时间:O(n log n)

稳定性测试结果:不稳定

(7)快速排序

不稳定版:

template<typename T>
void Sort(T* arr, int low, int high, bool(*cmp)(T a, T b))
{
	if (low >= high)return;
	T x = arr[high];
	int id = high;
	for (int i = low; i < id;) {
		if (cmp(arr[i], x))i++;
		else exchange(arr + i, arr + id--);
	}
	arr[id] = x;
	Sort(arr, low, id - 1,cmp);
	Sort(arr, id + 1, high, cmp);
}
template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	Sort(arr, 0, len - 1, cmp);
}

最坏时间:O(n^2)

平均时间:O(n log n)

稳定性测试结果:不稳定

稳定版:

template<typename T>
void Sort(T* arr, T* arr2, int low, int high, bool(*cmp)(T a, T b))
{
	if (low >= high)return;
	int id = low, id2, id3;
	T x = arr[high];
	for (int i = low; i < high; i++) {
		if (cmp(arr[i], arr[high]))arr2[id++] = arr[i];
	}
	id2 = id;
	for (int i = low; i < high; i++) {
		if (!cmp(arr[i], arr[high]) && !cmp(arr[high], arr[i]))arr2[id++] = arr[i];
	}
	arr2[id++] = x;
	id3 = id;
	for (int i = low; i < high; i++) {
		if (cmp(arr[high], arr[i]))arr2[id++] = arr[i];
	}
	for (int i = low; i <= high; i++)arr[i] = arr2[i];
	Sort(arr, arr2, low, id2 - 1, cmp);
	Sort(arr, arr2, id3, high, cmp);
}
template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	T* arr2 = new T[len];
	Sort(arr, arr2, 0, len - 1, cmp);
}

最坏时间:O(n^2)

平均时间:O(n log n)

稳定性测试结果:稳定

(8)基数排序

适应场景:假设排序对象全是自然数

void Sort(int* arr, int len, int key)
{
    int** p = new int*[10];
    for(int i=0;i<10;i++)p[i]=new int[len];
    int num[10]={0};
    for(int i=0;i<len;i++){
        int val = arr[i]/key%10;
        p[val][num[val]++] = arr[i];
    }
    for(int i=0;i<10;i++){
        for(int j=0;j<num[i];j++)*(arr++)=p[i][j];
    }
}

void Sort(int* arr, int len)
{
    int m = GetMax(arr,len);
    for(int key=1;key<=m;key*=10){
        Sort(arr,len,key);
    }
}

最坏时间:O(kn),其中k是最大自然数的10进制的位数

平均时间:O(kn)

稳定性测试结果:用上面的测试代码测不了,实际上是不稳定。

怎么理解这个时间复杂度呢?

从一个角度看,k不会超过10,所以可以理解为O(n)但是常数比较大。

从另外一个角度看,在很多场景下,最大数的数值是比n要大的,那么kn实际上比nlogn还大。

总之,无论如何都有一个共识,虽然看似是O(n)的复杂度,但是常数很大,很多时候很慢。

如果是像我代码这样,每次都要new,那就更慢了,这一步可以优化,但是优化完也比其他很多排序算法慢。

(9)计数排序

适应场景:假设排序对象全是自然数,而且最大数不会太大

void Sort(int* arr, int len)
{
    int m = GetMax(arr, len);
    int* pnum = new int[m + 1];
    for (int i = 0; i <= m; i++)pnum[i] = 0;
    for (int i = 0; i < len; i++)pnum[arr[i]]++;
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j < pnum[i]; j++)*(arr++) = i;
    }
}

最坏时间:O(m+n),其中m是最大自然数

平均时间:O(m+n)

稳定性测试结果:用上面的测试代码测不了,实际上是不稳定。

(10)桶排序

算法思路:先把所有数分成若干个桶,然后每个桶调用别的排序算法进行排序,组装起来就是完整的排序了。

适用场景:待排序元素都是实数,但是这里我们用都是自然数为例来实现代码。

void Sort(int* arr, int len)
{
    int m = GetMax(arr, len);
    const int T = 10; //大于1的任何数都行
    int gap = m / T + 1; //桶的范围大小
    int num[T] = { 0 };
    for (int i = 0; i < len; i++)num[arr[i] / gap]++;
    int id[T] = { 0 };
    for (int i = 1; i < T; i++)id[i] = id[i - 1] + num[i - 1];
    int* p = new int[len];
    for (int i = 0; i < len; i++)p[id[arr[i] / gap]++] = arr[i];
    int s = 0;
    for (int i = 0; i < T; i++) {
        Sort2(p + s, num[i]);
        s += num[i];
    }
    for (int i = 0; i < len; i++)arr[i] = *(p++);
}

其中的sort2是调用别的排序算法,比如算法导论中提到的是插入排序,

甚至这个函数可以改一改,调用本身递归也是可以完成排序的(只针对整数的情况,对实数没法完成排序),这样其实就基本和基数排序一样了,只不过顺序反过来的,

基数排序是先按照低位(个位)排一次,然后再高位排,这里如果调用自身完全排序,那就是先按照高位排,然后再低位排。

我这里是用计数的方法,用一个数组p依次存下了所有的桶,实际上用链表或者vector来存这些桶更方便。

之所以选择插入排序是因为,插入排序在对几乎已经排好序的数据操作时效率高。

如果分桶之后嵌套的是插入排序,那么时间复杂度是:

最坏时间:O(n^2)

平均时间:O(n + n^2 / T)其中T是桶的数量,当T=n时,平均时间是O(n)

这个平均时间的计算比较复杂,算法导论上是用指示器随机变量来算的。

(11)希尔排序

希尔排序_nameofcsdn的博客-CSDN博客

(12)近似计数排序

计数排序只能处理整数的情况,如果用关联容器,可以处理能多的类型。

template<typename T>
class fun
{
public:
    bool operator()(T a, T b) const
    {
        return cmp(a,b);
    }
};

template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
    map<T, int, fun<T>>m;
    for (int i = 0; i < len; i++)m[arr[i]]++;
    for (auto& it : m) {
        for (int j = 0; j < it.second; j++) *(arr++) = it.first;
    }
}

把cmp函数封装成了函数对象。

耗时主要在map的插入操作,log1 + log2 + log3 + ... + logn = n logn

所以时间复杂度是O(n logn)

二,排序算法效率、组合嵌套

评价一个排序算法的时间效率,主要看最坏时间、平均时间、常数

比如归并排序的最坏时间是O(nlogn),但是常数大,插入排序的最坏时间是O(n^2),但是常数小,所以数组较小时用插入排序可能更快。

又比如快速排序的平均时间是同类算法中最快的,但是最坏时间是O(n^2)。

因为有这2种情况的存在,所以把两种及以上的排序算法进行组合嵌套,能达到更好的效果。

而桶排序、希尔排序,本身就是嵌套了其他排序算法的排序算法。

算法导论习题:

答:总时间是Θ(k^2)* n/k = Θ(nk)

答:上面提到,Merge操作合并2个数组的时间是Θ(len),其中len=len1+len2表示2个数组的长度和

n/k个数组需要log(n/k) / log2轮合并,每一轮的时间都是Θ(n),所以总时间是Θ(nlog(n/k))

答:log n

答:这个主要就看各个常数了,这个比较复杂,不过k应该是不超过10的

三,OJ实战

链表适合插入排序,统计逆序数适合归并排序。

力扣 147. 对链表进行插入排序(插入排序)

对链表进行插入排序。


插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

插入排序算法:

插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
 

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4
示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

思路一:插入排序

class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        if(!head)return NULL;
        ListNode* p=head;
        while(p && p->next){
            ListNode*pn=p->next;
            if(p->val<=pn->val){
                p=pn;
                continue;
            }
            p->next=pn->next;
            if(pn->val<head->val)pn->next=head,head=pn;
            else{
                ListNode* h =head;
                while(h->next->val<pn->val)h=h->next;
                pn->next=h->next,h->next=pn;
            }
        }
        return head;
    }
};

思路二:

直接调用我的ListSort函数模板:ACM模板

力扣 148. 排序链表(插入排序)

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

进阶:

你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
 

示例 1:


输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:


输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:

输入:head = []
输出:[]
 

提示:

链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105

思路一:插入排序

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(!head)return NULL;
        ListNode* p=head;
        while(p && p->next){
            ListNode*pn=p->next;
            if(p->val<=pn->val){
                p=pn;
                continue;
            }
            p->next=pn->next;
            if(pn->val<head->val)pn->next=head,head=pn;
            else{
                ListNode* h =head;
                while(h->next->val<pn->val)h=h->next;
                pn->next=h->next,h->next=pn;
            }
        }
        return head;
    }
};

440 ms

思路二:归并排序


//把两个升序的链表合并为一个升序的链表
ListNode* mergeTwoUpLists(ListNode* p, ListNode* q) {
    if(!p)return q;
    if(!q)return p;
    ListNode *head;
    if(p->val < q->val)head=p,p=p->next;
    else head=q,q=q->next;
    ListNode *ans=head;
    while(p && q)
    {
        if(p->val < q->val)ans->next=p,ans=p,p=p->next;
        else ans->next=q,ans=q,q=q->next;
    }
    if(p)ans->next=p;
    else ans->next=q;
    return head;
}

class Solution {
public:
    ListNode* sortList(ListNode* head,int len) {
        if(len<2)return head;
        int k=len/2;
        ListNode* p=head;
        for(int i=0;i<k-1;i++)p=p->next;
        ListNode* p2=sortList(p->next,len-k);
        p->next=NULL;
        p=sortList(head,k);
        return mergeTwoUpLists(p,p2);
    }
    ListNode* sortList(ListNode* head) {
        int len=LinkGetLength(head);
        return sortList(head,len);
    }
};

132 ms

思路三:

直接调用我的ListSort函数模板:ACM模板(9)链表

POJ 2299 Ultra-QuickSort(归并排序)

题目:

Description

In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacent sequence elements until the sequence is sorted in ascending order. For the input sequence  9 1 0 5 4 ,Ultra-QuickSort produces the output  0 1 4 5 9 .
Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.
Input

The input contains several test cases. Every test case begins with a line that contains a single integer n < 500,000 -- the length of the input sequence. Each of the the following n lines contains a single integer 0 ≤ a[i] ≤ 999,999,999, the i-th input sequence element. Input is terminated by a sequence of length n = 0. This sequence must not be processed.
Output

For every input sequence, your program prints a single line containing an integer number op, the minimum number of swap operations necessary to sort the given input sequence.
Sample Input

5
9
1
0
5
4
3
1
2
3
0
Sample Output

6
0

题意很好理解,输入一个数列,输出它的逆序数。

我的原始代码(超时):
 

#include<iostream>
using namespace std;

int n;
int c[500005];
int num[500005];

int sum(int i)
{
	int s = 0;
	while (i)
	{
		s += c[i];
		i -= (i&(-i));
	}
	return s;
}

void add(int i, int x)
{
	while (i <= n)
	{
		c[i] += x;
		i += (i&(-i));
	}
}

int findmax()
{
	int max = 1;
	for (int j = 2; j <= n; j++)if (num[max] < num[j])max = j;
	return max;
}

int main()
{	
	ios_base::sync_with_stdio(false);
	long long s;
	while (cin >> n)
	{
		if (n == 0)break;
		for (int i = 1; i <= n; i++)
		{
			cin >> num[i];
			c[i] = 0;
		}
		s = 0;
		for (int i = 1; i <= n; i++)
		{
			int j = findmax();
			num[j] = -1;
			s += sum(j);
			add(j, 1);
		}
		cout << s << endl;
	}
	return 0;
}

很明显它的时间是n*n,所以超时了。

但是好在思路差的不是很多,稍微改了下就对了。

代码:

#include<iostream>
#include<algorithm>
using namespace std;
 
struct node
{
	int num;
	int index;
};
 
int n;
int c[500005];
node nod[500005];
 
bool cmp(node a, node b)
{
	return a.num > b.num;
}
 
int sum(int i)
{
	int s = 0;
	while (i)
	{
		s += c[i];
		i -= (i&(-i));
	}
	return s;
}
 
void add(int i, int x)
{
	while (i <= n)
	{
		c[i] += x;
		i += (i&(-i));
	}
}
 
int main()
{	
	ios_base::sync_with_stdio(false);
	long long s;
	while (cin >> n)
	{
		if (n == 0)break;
		for (int i = 1; i <= n; i++)
		{
			cin >> nod[i].num;
			nod[i].index = i;
			c[i] = 0;
		}
		s = 0;
		sort(nod + 1, nod + 1 + n, cmp);
		for (int i = 1; i <= n; i++)
		{
			int j = nod[i].index;
			s += sum(j);
			add(j, 1);
		}
		cout << s << endl;
	}
	return 0;
}

这个虽然过了,但是很慢,我猜想手写快速排序或者归并排序应该会比较快。

普通的归并排序,只需要加一句sum += mid - i + 1;就可以变成边排序边统计逆序数。

代码:

#include<iostream>
using namespace std;
 
int n;
long long sum;
int num[500005];
int copynum[500005];
 
void merge(int low, int high)
{
	int mid = (low + high) / 2;
	int i = low, j = mid + 1, k = low;
	while (i <= mid && j <= high)
	{
		if (num[i] < num[j])copynum[k++] = num[i++];
		else
		{
			copynum[k++] = num[j++];
			sum += mid - i + 1;
		}
	}
	while (i <= mid)copynum[k++] = num[i++];
	while (j <= high)copynum[k++] = num[j++];
	for (int i = low; i <= high; i++)num[i] = copynum[i];
}
 
void sort(int low, int high)
{
	if (low == high)return;
	int mid = (low + high) / 2;
	sort(low, mid);
	sort(mid + 1, high);
	merge(low, high);
}
 
int main()
{	
	ios_base::sync_with_stdio(false);
	while (cin >> n)
	{
		if (n == 0)break;
		for (int i = 1; i <= n; i++)cin >> num[i];
		sum = 0;
		sort(1, n);
		cout << sum << endl;
	}
	return 0;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值