码迷,mamicode.com
首页 > 其他好文 > 详细

每日一题26:求逆序对数目与求和

时间:2015-05-22 09:52:32      阅读:221      评论:0      收藏:0      [点我收藏+]

标签:逆序   两数之和   

求逆序对问题与解决方案原理

在一个数列中,如果规定从小到大为正序,那么如果排在后面的某个元素比前面的某一个元素小,那么就称这两个数构成一个逆序对,例如,数列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
技术分享

每日一题26:求逆序对数目与求和

标签:逆序   两数之和   

原文地址:http://blog.csdn.net/liao_jian/article/details/45896671

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!