設\(A\)為一個有\(n\)個數字的有序集\((n>1)\),其中所有數字各不相同。
如果存在正整數\(i,j\)使得\(1 ≤ i < j ≤ n\)而且\(A[i] > A[j]\),則\(<A[i], A[j]>\)這個有序對稱為\(A\)的一個逆序對,也稱作逆序數。
1.氣泡排序
思想
我們的氣泡排序的思想十分簡單,我們每一次迴圈都將最大的數排到最後面。我們每交換一次就是一個逆序對。
我們氣泡排序有一個簡單的小優化:如果我們某一輪沒有交換了,我們的序列即為有序。
程式碼:
for(int i=1;i<=n;i++) {
bool flag=1;
for(int j=1;j<=n-i;j++)
if(a[j]>a[j+1]) swap(a[j],a[j+1]),flag=0,ans++;
if(flag) break;
}
時間複雜度\(O(n^2)\)。
2.歸併排序
思想
我們首先來了解一下歸併排序的思想:二分和合並。
三部曲:
1.劃分:把序列分成元素個數儘量相等的兩半
2.遞迴求解:把兩半元素分別排序
3.合併問題:把兩個有序表合併為一個
合併:每次只需把左右兩邊序列的最小元素進行比較,把其中較小的元素加入到合併後的輔助陣列中。
void msort(int s,int t) {
if(s==t) return ;
int mid=(s+t)>>1;
msort(s,mid),msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid && j<=t) {
if(a[i]<=a[j]) r[k]=a[i],k++,i++;
else r[k]=a[j],k++,j++;
}
while(i<=mid) r[k]=a[i],k++,i++;
while(j<=t) r[k]=a[j],k++,j++;
for(int i=s;i<=t;i++) a[i]=r[i];
return ;
}
我們可以假設左邊序列為\({3,4,7,9}\),右邊為\({1,5,8,10}\)
我們的第一步就是比較\(1\)和\(3\),因為\(1<3\),所以\(1\)入預備陣列。
我們這時候可以發現,我們的\(1\)與左邊序列的\(3\)和\(3\)之後的數都是逆序對,一共就\(4\)對了。這也是歸併排序找逆序對快的原因。
我們只需要在\(a[i]>a[j]\)時,答案加上\(mid-i+1\)。
程式碼
void msort(int s,int t) {
if(s==t) return ;
int mid=(s+t)>>1;
msort(s,mid),msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid && j<=t) {
if(a[i]<=a[j]) r[k]=a[i],k++,i++;
else r[k]=a[j],k++,j++,ans+=mid-i+1;
}
while(i<=mid) r[k]=a[i],k++,i++;
while(j<=t) r[k]=a[j],k++,j++;
for(int i=s;i<=t;i++) a[i]=r[i];
return ;
}
時間複雜度\(O(n\ log\ n)\)。
3.樹狀陣列
樹狀陣列
我們的樹狀陣列也是可以求逆序對的。
思想
實際上就是統計當前元素的前面有幾個比它大的元素的個數,然後把所有元素比它大的元素總數累加就是逆序對總數。
程式碼
#include <bits/stdc++.h>
using namespace std;
int n,a[1005],lsh[1005],BIT[1005];
int lowbit(int x) { return x & -x; }
void update(int k,int x) {
for(int i=k;i<=n;i+=lowbit(i)) BIT[i]+=x;
return ;
}
int ask(int x) {
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=BIT[i];
return ans;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),lsh[i]=a[i];
sort(lsh+1,lsh+n+1);//離散化
unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
int ans=0;
for(int i=1;i<=n;i++) {
update(a[i],1);//在a[i]這個位置上加1
ans+=i-ask(a[i]);
}
printf("%d",ans);
return 0;
}
時間複雜度\(O(n\ log\ n)\)。
4.線段樹
思想
逆序對可以表示成一個數前面有幾個比這個數大的數,就表示這個數所形成的逆序對數。
我們查詢就是:
找\(a[i]+1\)~\(Max\)的值。
我們加入就是:
\(a[i]\)這個位置的數量加\(1\)。
我們最後把查詢到的所有對數輸出即可。
但我們的數可能很大,我們這時就需要使用到離散化了。
程式碼
#include <bits/stdc++.h>
using namespace std;
int n,a[100005];
struct SementTree{ int l,r,sum; }t[400005];
void read(int &x) {
int f=1; x=0; char ch=getchar();
while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0' && ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
x*=f;
}
void build(int p,int l,int r) {
t[p].l=l,t[p].r=r,t[p].sum=0;
if(l==r) { return ; }
int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
return ;
}
void update(int p,int u) {
if(t[p].l==t[p].r) { t[p].sum++; return ; }
int mid=(t[p].l+t[p].r)>>1;
if(u<=mid) update(p<<1,u);
else update(p<<1|1,u);
t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
return ;
}
int ask(int p,int l,int r,int ll,int rr) {
if(ll<=l && r<=rr) return t[p].sum;
int mid=(l+r)>>1;
int ans=0;
if(ll<=mid) ans+=ask(p<<1,l,mid,ll,rr);
if(mid<rr) ans+=ask(p<<1|1,mid+1,r,ll,rr);
return ans;
}
int main() {
read(n);
for(int i=1;i<=n;i++) read(a[i]);
build(1,0,100000);
long long ans=0;
for(int i=1;i<=n;i++) {
ans+=ask(1,0,100000,a[i]+1,100000);
update(1,a[i]);
}
printf("%lld",ans);
return 0;
}
5.動態開點
#include <bits/stdc++.h>
using namespace std;
int n,tot;
long long ans;
struct SementTree{ int l,r,lc,rc,sum; }t[200005];
int build() {
++tot;
t[tot].lc=t[tot].rc=t[tot].sum=0;
return tot;
}
void update(int p,int l,int r,int k) {
t[p].l=l,t[p].r=r;
if(l==r) { t[p].sum++; return; }
int mid=(l+r)>>1;
if(k<=mid) {
if(!t[p].lc) t[p].lc=build();
update(t[p].lc,l,mid,k);
}
else{
if(!t[p].rc) t[p].rc=build();
update(t[p].rc,mid+1,r,k);
}
t[p].sum=t[t[p].lc].sum+t[t[p].rc].sum;
}
int ask(int p,int l,int r){
if(t[p].l>=l && t[p].r<=r) { return t[p].sum; }
int value=0,mid=(t[p].l+t[p].r)>>1;
if(l<=mid && t[p].lc) value+=ask(t[p].lc,l,r);
if(r>=mid+1 && t[p].rc) value+=ask(t[p].rc,l,r);
return value;
}
int main() {
scanf("%d",&n);
int root=build();
for(int i=1,x;i<=n;i++) {
scanf("%d",&x);
ans+=ask(1,x+1,100000);
update(1,1,100000,x);
}
printf("%lld",ans);
return 0;
}