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

求逆序数的三种数据结构比较

时间:2014-08-23 16:49:51      阅读:238      评论:0      收藏:0      [点我收藏+]

标签:os   使用   io   for   数据   ar   art   cti   amp   

本文比较 数状数组,线段树,还有一种unnamed的数状结构,在求逆序数中的运行效率。

给定数组a,如果有i, j, i < j且a[i] > a[j],我们称之为一个逆序(反序),求逆序数即找出这样的i,j对的数目。如果通过交换相邻元素来对数组排序,那么逆序数正好是最少的交换次数。长度为n的排列,其逆序数的期望是n(n-1)/4,方差是n(2n+5)(n-1)/72。逆序数的平均数可以大概视为n^2/4,标准差大概视为(1/6)n^(3/2)。

经典的求逆序数的算法可以由归并排序给出,但是在实现细节上可能会出错。而使用辅助数据结构则使得实现难度降低。

最简单的是用线段树:从左往右扫描,每扫描到一个数时,则通过线段树求出当前线段树上大于当前元素的元素的个数。然后将当前元素加到线段树中。

在这里,上述算法也可以用数状数组(binary index tree)来实现。在添加元素时,从大到小,可以理解为从最小元素到当前元素添加了一根线段。在查询时,从小到大累加,可以理解为求当前元素被多少个线段覆盖,也就是比当前元素大的元素的数量。

还有一种数状结构,对于[l, r]的一个区间,令mid=l+r>>1用tree[mid]来维护[mid, r]上的元素个数。
插入时,如果需要插入的元素正好是mid,则计数加1,算法结束。
如果在[mid+1, r]上,则计数加1,同时递归到右区间:l = mid + 1。
如果在[l, mid-1]上,则tree[mid]不变,同时递归到左区间上:r = mid - 1;

对于查询基本上和插入一样。


为了简单对运行效率,生成了[1, 10000]的随机数一千万个(这样的测试方法不一定能反应出效率,我只是偷懒,只简单设计这样的方法)。为了方便实现,求的是a[i] >= a[j]的pair数,否则,很多细节需要修改,而修改后,可能对于某些结构,多了一些判断,会影响效率。

对于线段树的实现,分别使用了递归和非递归,其中queryB是递归的,queryBext是非递归的。可以看出unnamed的数状结构和线段树有某些联系。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cassert>
#include <ctime>
using namespace std;
typedef long long int64;
const int maxn = 10001;
int tree[maxn];
int tree1[maxn];
int tree2[maxn*4];

int64 sa, sb, sc;
static inline void insertA(int x)
{
	for (; x; x -= x & -x) ++tree[x];
}

static inline int queryA(int x)
{
	int r = 0;
	for (; x < maxn; x += x & -x) r += tree[x];
	return r;
}

static inline int queryB(int root, int l, int r, int x)
{
	if (l == r) return tree2[root]++;
	int mid = l + r >> 1, lc = root << 1, rc = lc + 1;
	++tree2[root];
	if (x <= mid) return tree2[rc] + queryB(lc, l, mid, x);
	else return queryB(rc, mid+1, r, x);
}

static inline int queryBext(int root, int l, int r, int x)
{
	int ret = 0;
	while (l <= r)
	{
		if (l == r) return ret + tree2[root]++;
		int mid = l + r >> 1, lc = root << 1, rc = lc + 1;
		++tree2[root];
		if (x <= mid)
		{
			ret += tree2[rc];
			r = mid;
			root = lc;
		}
		else
		{
			l = mid + 1;
			root = rc;
		}
	}
	
}

void calA()
{
	srand(123);
	fill(tree, tree+maxn, 0);
	int start = clock();
	for (int i = 0; i < 10000000; ++i)
	{
		int u = rand() % 10000 + 1;
		int a = queryA(u);
		insertA(u);
		sa += a;
	}
	int use = clock() - start;
	cerr << use << endl;
}

void calB()
{
	srand(123);
	fill(tree2, tree2+maxn*4, 0);
	int start = clock();
	for (int i = 0; i < 10000000; ++i)
	{
		int u = rand() % 10000 + 1;
		int a = queryB(1, 1, 10000, u);
		sb += a;
	}
	int use = clock() - start;
	cerr << use << endl;
}

static inline int query_and_insert(int x)
{
	int l = 1, r = 10000;
	int ret = 0;
	while (l <= r)
	{
		int mid = l + r >> 1;
		if (x == mid)
		{
			ret += tree1[x]++;
			break;
		}
		else if (x > mid)
		{
			++tree1[mid];
			l = mid + 1;
		}
		else
		{
			ret += tree1[mid];
			r = mid - 1;
		}
	}
	return ret;
}

void calC()
{
	srand(123);
	fill(tree1, tree1+maxn, 0);
	int start = clock();
	for (int i = 0; i < 10000000; ++i)
	{
		int u = rand() % 10000 + 1;
		sc += query_and_insert(u);
	}
	int use = clock() - start;
	cerr << use << endl;
}

int main()
{
	calA();
	calB();
	calC();
	cerr << sa << " " << sb << " " << " " << sc << endl;
	return 0;
}


在我的计算机上,使用非递归的线段树的结果是:
575
906
842
25006976835482 25006976835482  25006976835482
使用递归的线段树的结果是:
553
939
824
25006976835482 25006976835482  25006976835482


机器和编译器:
win7 64bit, i7 860, gcc version 4.8.1 (tdm64-2), 编译选项-O2

求逆序数的三种数据结构比较

标签:os   使用   io   for   数据   ar   art   cti   amp   

原文地址:http://blog.csdn.net/baihacker/article/details/38779343

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