每日一題26:求逆序對數目與求和

求逆序對問題與解決方案原理

在一個數列中,如果規定從小到大爲正序,那麼如果排在後面的某個元素比前面的某一個元素小,那麼就稱這兩個數構成一個逆序對,例如,數列5,4,3,2,1中,任一個數都與它前面的數構成逆序對,這個序列中一共就有1+2+3+4=10個逆序對,數列23541中有5個逆序對,那麼任給定一個數列,如何知道有多少個逆序對呢?簡單的方法是將每個元素與它後面的元素比較,然後就可以累加出總的逆序對數目,這種算法的複雜度是O(n^2)。另一種比較快的方法是利用歸併排序,得到的算法複雜度爲O(nlgn)。想法是這樣的,歸併算法合併階段,分界點前後兩個子序列都是有序的,當我們把兩個子序列寫會結果數組時,如果左子序列剩餘的第一個元素小於或等於右子序列剩餘的第一個元素,那麼就選擇左子序列的第一個元素寫會結果數組中,否則選擇右子序列剩餘元素的第一個元素寫會結果數組。被選擇的元素小於或等於它所在子序列剩餘的元素,也小於另一個子序列剩餘的元素,但不同的是,如果選擇的是左子序列的元素,那麼這個元素在爲合併之前是位於當前剩餘元素的前面的,所以不存在逆序對,如果選擇的是右子序列的元素,那麼這個元素在爲合併之前是位於當前剩餘元素的後面的,所以它存在逆序對,那麼是多少呢,答案是左子序列當前還剩餘的元素個數。在一次合併過程中的情況已經討論清楚了,把所有的合併階段聯合起來,就可以看成是我們對每一個元素從距離它比較近的地方開始尋找它的逆序對數,但不是一次就計算完它的逆序對數,而是有間隔地計算,另一點考慮是,在一次合併過程中所涉及的元素序列之前的序列中的元素位置的交換並不會影響這個元素的逆序對,因爲它們位置變化了,但還是在這個元素之前。而在一個元素後面的元素本來就不參與這個元素逆序對的計算。因此,上面的算法恰好能計算出所有的逆序對數目。

求和問題與解決方案原理

給定一個數列,在給定一個sum,判斷在數列中是否存在兩個數之和是sum,這樣的數對有幾個。簡單方法是對每一個元素到它後面的元素中查找是否有另一個數與之相加之和等於sum,如果找到這樣的一個數,標記這個數已經使用過,然後在考察其他元素,時間複雜度是O(n^2)。如果序列是有序的問題就會簡單點了,設兩個指針分別指向數組的首位元素,然後將兩個指針指向的元素相加,如果相加之和大於sum,那麼後指針向前推薦一個位置,如果相加之和小於sum,前指針向後推進一個位置,如果相加之和等於sum,增加一個滿足條件的數對,然後後指針向前推進一個位置,前指針向後推進一個位置(假設用過的值不能重複使用),繼續需找下一對滿足條件的數對,知道兩個指針相遇或交叉。

代碼與結果:

#include "stdafx.h"
#include "../Tool/Sorter.h"
#include <iostream>
using namespace MyTools;
using namespace std;

//數組中是否存在相加之和等於sum的數對,返回滿足條件的數對個數
int CanFindSum(int A[], int start, int end, int sum)
{
    //自己寫的排序算法類,一共有十二中排序算法
    //但目前註釋還沒寫完,暫不發表
    Sorter<int> sorter;
    //使用三路劃分快排算法先對序列排序
    //後面就是對算法描述的翻譯了
    sorter.ThreeWayQuickSort(A, start, end);
    int p = start, q = end;
    int count = 0;
    while (p < q)
    {
        int s = A[p] + A[q];
        if (s > sum) --q;
        if (s < sum) ++p;
        else
        {
            ++count;
            --q;
            ++p;
        }
    }
    return count;
}

//歸併算法合併階段程序增加了計算逆序數的三行代碼和一個參數
void merge_for_reverse_order(int A[], int p, int q, int r, int& count)
{
    int length1 = q - p + 1, length2 = r - q;
    //其實只需要把左子序列的元素複製出來就夠了,具體原因
    //會在排序的博客中闡述
    int * left = new int[length1],
        *right = &A[q + 1];
    for (int i = p, j = 0; j < length1;)
    {
        left[j++] = A[i++];
    }
    //第1行
    int left_count = length1;
    int i = 0, j = 0;
    while (i < length1 && j < length2)
    {
        if (left[i] <= right[j])
        {
            A[p++] = left[i++];
            //第2行
            --left_count;
        }
        else
        {
            A[p++] = right[j++];
            //第3行
            count += left_count;
        }
    }
    while (i < length1) A[p++] = left[i++];
    delete left;
}

//歸併排序算法,增加了一個參數
void getInverseOrderCount(int A[], int start, int end,int &count)
{
    if (start < end)
    {
        int q = (start + end) / 2;
        getInverseOrderCount(A, start, q, count);
        getInverseOrderCount(A, q + 1, end, count);
        merge_for_reverse_order(A, start, q, end, count);
    }
}

int GetInverseOrderCount(int A[], int start, int end)
{
    int count = 0;
    getInverseOrderCount(A, start, end, count);
    return count;
}

接下來是測試代碼:

int _tmain(int argc, _TCHAR* argv[])
{
    int v1[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    int sum = 100, sum1 = 11;
    cout <<"逆序對數:"<< GetInverseOrderCount(v1,0,9) << endl;
    cout << "相加之和等於100的數對個數:"<<CanFindSum(v1, 0, 9, sum) << endl;
    cout << "相加之和等於11的數對個數:" << CanFindSum(v1, 0, 9, sum1) << endl;
    cout << endl;
    return 0;
}

程序運行截圖:
這裏寫圖片描述
程序驗證:

正好四對數字之和等於11,而沒有數對之和等於100
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章