逆序數:
也是就說,對於n個不同的元素,先規定各元素之間有一個標准次序(例如n個 不同的自然數,可規定從小到大為標准次序),於是在這n個元素的任一排列中,當某兩個元素的先后次序與標准次序不同時,就說有1個逆序。一個排列中所有逆序總數叫做這個排列的逆序數。
在一個排列中,如果一對數的前后位置與大小順序相反,即前面的數大於后面的數,那么它們就稱為一個逆序。一個排列中逆序的總數就稱為這個排列的逆序數。逆序數為偶數的排列稱為偶排列;逆序數為奇數的排列稱為奇排列。如2431中,21,43,41,31是逆序,逆序數是4,為偶排列。
如數列 3 5 4 8 2 6 9
(5,4)是一個逆序對,同樣還有(3,2),(5,2),(4,2)等等。
什么是逆序數:
跟標准列相反序數的總和
比如說
標准列是1 2 3 4 5
那么 5 4 3 2 1 的逆序數算法:
5之前沒有數,記為0.
看第二個,4之前有一個5,在標准列中5在4的后面,所以記1個
類似的,第三個 3 之前有 4 5 都是在標准列中3的后面,所以記2個
同樣的,2 之前有3個,1之前有4個
將這些數加起來就是逆序數=1+2+3+4=10
再舉一個 2 4 3 1 5
4 之前有0個
3 之前有1個
1 之前有3個
5 之前有0個
所以逆序數就是1+3=4
逆序數的求法:
1.一個一個的數
最簡單也是最容易想到的方法就是,對於數列中的每一個數a[i],遍歷數列中的數a[j](其中j<i),若a[i]<a[j],則逆序數加1,這樣就能統計出該數列的逆序數總和
該方法的時間復雜度為O(n^2)
2.歸並思想
有一種排序的方法是歸並排序,歸並排序的主要思想是將整個序列分成兩部分,分別遞歸將這兩部分排好序之后,再和並為一個有序的序列.
在合並的過程中是將兩個相鄰並且有序的序列合並成一個有序序列,如以下兩個有序序列
Seq1:3 4 5
Seq2:2 6 8 9
合並成一個有序序:
Seq:2 3 4 5 6 8 9
對於序列seq1中的某個數a[i],序列seq2中的某個數a[j],如果a[i]<a[j],沒有逆序數,如果a[i]>a[j],那么逆序數為seq1中a[i]后邊元素的個數(包括a[i]),即len1-i+1,
這樣累加每次遞歸過程的逆序數,在完成整個遞歸過程之后,最后的累加和就是逆序的總數
再如:
0 1 2 3 4
2 3 4 1 5
mid=2;
seq1:2 3 4
seq2:1 5
2>1 count=mid-i+1=2-0+1=3;所以,逆序數為3.
#include<iostream> #include<climits> using namespace std; int count=0; void merge(int arr[],int first,int mid,int last) { int *tmpArr=new int[last-first+1]; int i=first,j=mid+1; int cur=0; while(i<=mid && j<=last) { if(arr[i]<arr[j]) { tmpArr[cur++]=arr[i++]; } else { tmpArr[cur++]=arr[j++]; count += mid-i+1;//只增加這一句便可求逆序數 } } if(i<=mid) { while(i<=mid) tmpArr[cur++]=arr[i++]; } else { while(j<=last) tmpArr[cur++]=arr[j++]; } for(int k=0;k<cur;k++) { arr[first++]=tmpArr[k]; } delete[] tmpArr; tmpArr=NULL; } void mergeSort(int arr[],int first,int last) { if(first==last) return; int mid=(first+last)/2; mergeSort(arr,first,mid); mergeSort(arr,mid+1,last); merge(arr,first,mid,last); } int main() { //int a1[]={7,8,9,1,2,3,5}; //int a1[]={5,4,3,2,1}; //count=10 int a1[]={2,3,4,1,5}; //count=3 int len=sizeof(a1)/sizeof(a1[0]); mergeSort(a1,0,len-1); cout<<count<<endl; }
整個代碼只是在歸並排序基礎上加了一句話而已。
方法3:用樹狀數組
還是以剛才的序列
3 5 4 8 2 6 9
大體思路為:新建一個數組,將數組中每個元素置0
0 0 0 0 0 0 0
取數列中最大的元素,將該元素所在位置置1
0 0 0 0 0 0 1
統計該位置前放置元素的個數,為0
接着放第二大元素8,將第四個位置置1
0 0 0 1 0 0 1
統計該位置前放置元素的個數,為0
繼續放第三大元素6,將第六個位置置1
0 0 0 1 0 1 1
統計該位置前放置元素的個數,為1
…
…
這樣直到把最小元素放完,累加每次放元素是該元素前邊已放元素的個數,這樣就算出總的逆序數來了
在統計和計算每次放某個元素時,該元素前邊已放元素的個數時如果一個一個地數,那么一趟復雜度為O(n),總共操作n趟,復雜度為O(n^2),和第一種方法的復雜度一樣了,那我們為什么還用這么復雜的方法
當然,在每次統計的過程中用樹狀數組可以把每一趟計數個數的復雜度降為O(logn),這樣整個復雜度就變為O(nlogn)
樹狀數組是一種很好的數據結構,這有一篇專門描述樹狀數組的文章
將序列中的每個數按照從大到小的順序插入到樹狀數組中,給當前插入節點及其父節點的個數加1,然后統計該節點下邊及右邊放置元素的個數
具體代碼如下:
//用樹狀數組求逆序數 const int Length=8; struct Node { int data; int pos; }; int LowBit(int num) { return num&(num^(num-1)); } //自定義排序方法 int cmp(const void * num1,const void * num2) { return ((Node*)num2)->data-((Node *)num1)->data; } //統計pos節點下方及右方節點元素的個數 int sum(int *TreeArray,int pos) { int result=0; while (pos) { result+=TreeArray[pos]; pos-=LowBit(pos); } return result; } //使pos位置的節點及其父節點的元素個數加1 void INC(int *TreeArray,int pos) { while(pos<Length) { TreeArray[pos]++; pos+=LowBit(pos); } } void insertNum(int *TreeArray,Node * seq,int &reverseNum) { for (int i=1;i<Length;i++) { reverseNum+=sum(TreeArray,seq[i].pos); //累加逆序數 INC(TreeArray,seq[i].pos); //將該節點及父節點的數加1 } } int main(int argc, char* argv[]) { int array[]={3,5,4,8,2,6,9}; Node seq[Length]; int TreeArray[Length]; memset(TreeArray,0,sizeof(TreeArray)); for (int i=1;i<Length+1;i++) { seq[i].data=array[i-1]; seq[i].pos=i; } //從大到小排序 qsort(seq+1,Length-1,sizeof(Node),cmp); int reverseNum=0; //邊插入邊計數 insertNum(TreeArray,seq,reverseNum); cout<<reverseNum<<endl; return 0; }
轉自:http://blog.csdn.net/dlengong/article/details/7594919
可以看以前的:http://www.cnblogs.com/youxin/p/3352861.html
