冒泡排序的交换次数 (树状数组)

计算冒泡排序的交换次数:

逆序数概念:在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序

一个排列中所有逆序个数总和叫做这个排列的逆序数。 所以冒泡排序结束即是所有的逆序数为0

思路

暴力:我们要计算逆序数,即使要统计出该值位置之前有多少个数比他大.我们用 arr[] 数组来表示序列值,则 Posx<Posy , 有Valuex>Valuey .那么可以得到 (O n2) 的方法

for(int Px = 1; Px<=n ; Px++){
    for(int Py = Px-1; Py>0;Py--){
    if(arr[Px]>arr[Py]) ans++;
    }
}
View Code

我们在换个统计的思路,我们用一个 vis[] 数组来记录,在前 i 个位置,有哪些值已经出现。这样,比如当前位置为 3 值为4 ,在此之前已经有5和3被记录,即原序列为 3 5 4 1 2,那么我们
只需要遍历统计值大于 4 的位置之前有多少出现。上例中从4遍历到5则有vis[5]=1,所以逆序就有一个.
而树状数组是一个用于维护前缀和的工具,所以我们维护一个vis[]从 当前值到最大值(最大位置)的一个前缀和(逆向前缀和),即可快速得出答案
当然由于逆向不太好写,我们可以反向来统计,比如上例中 3 5 4 1 2,第一个位置3由于提早出现,之后的1和2会产生一个贡献,所以我们统计该位置为之后位置产生的逆序数贡献就可以正向求前缀和。
而之前则是统计当前位置之前出现的值对该位置的逆序贡献。所以在后面代码中贡献值为 j - bit[i].
而维护前缀和的树状数组复杂度为 O( logn) ,所以将复杂度降为 O( nlogn)

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(0); cin.tie(0);
#define mp make_pair
#define Accept 0
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const double Pi = acos(-1.0);
const double esp = 1e-9;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5+7;
const int maxm = 1e6+7;
const int mod = 1e9+7;
const int MAXL = 1e6+7;
int n;
int bit[maxn];
int arr[maxn];
int vis[maxn]; 
//树状数组维护的前缀和
int sum(int i){ int s = 0; while(i>0){ s += bit[i]; i -= i & -i; } return s; } //单点更新 void add(int i,int x){    while(i<=n){ bit[i] += x; i += i & -i; } } void solve(){ int ans = 0; for(int j = 0;j<n;j++){ ans += j - sum(arr[j]); add(arr[j],1); }
//暴力的解法
// for(int i=0;i<n;i++){ // int cnt = 0; // for(int j=0;j<arr[i];j++){ // if(vis[j]) cnt++; // } // ans += i - cnt; // vis[arr[i]] =1; // } printf("%d\n",ans); } int main(){ memset(vis,0,sizeof(vis)); scanf("%d",&n); for(int i=0;i<n;i++){ scanf("%d",&arr[i]); } solve(); return 0; }
posted @ 2019-09-09 12:26  Tianwell  阅读(2876)  评论(1编辑  收藏  举报