现在的位置: 首页 > 综合 > 正文

POJ 2823 Sliding Window 堆 / 单调队列

2013年08月09日 ⁄ 综合 ⁄ 共 3612字 ⁄ 字号 评论关闭

题意:给定一串数字,一个定长的窗口,窗口每次平移一个位置,输出窗口中的最大值。
题解:

堆与映射
 
平常,我们用堆,最常见的就是随机地加入元素,随机地取最大值或最小值。这些基本的操作C++中的priority_queue和set都能很好的完成,而且C++中还有一个make_heap,效率较前面2个会更高。而且前面提到的STL都是采用红黑树实现的,很具有稳定性。上面的堆虽然使用简单,但功能上还是有些局限。比如前面提到的堆都只能实现删除最值并没有办法删除指定的值。而且一般STL中的堆都是采用数据存储的,但父亲节点和儿子节点需要交换时,我们需要拷贝所存储的整个数据单元,但数据单元较大时,拷贝的效率就低了。因此,这里介绍一中堆变种,在堆中引入了映射。

定义   具有映射功能的堆称为双向映射堆。又其常常是在二分堆的基础上实现的,所以也常常叫映射二分堆。

特点 映射二分堆其与普通堆不同的地方是它的节点并不真正保存数据单元本身,而是保存指向数据单元的指针。因此当需要交换父子节点的数据时,我们可以避免拷贝大量数据所消耗的时间。同时,映射二分堆还有一个功能可以根据具体的数据单元的索引来删除该单元,即使这个单元不是堆中的最值。

实现方法    在实现是,我们可以用数字H[]存储数据单元,然后采用数组ind[i]=j表示H[i]存放的是索引号为j的数据,mp[i]=j表示索引索引为i的元素存储在H[j]中,这样就可以实现双向映射了。插入堆,我们只需要将插入的元素放在堆尾,然后逐级调整,这个跟普通的二叉堆一样样。删除最值,也跟普通的二叉堆一样,先把堆中最后一个元素与最值对调,然后在调整堆。增加一个的是删除固定索引的数据单元,我们可以先通过mp[i],找到其在H[]中存放的位置,然后该位置的值跟堆最后一个元素对调,接着从该节点向上调整堆,此时得到的还不是一个正规的堆,还需要重新堆整个堆进行从上到下调整。上面的3中操作中都提到了堆的调整,还是上面说的,调整时我们不需要拷贝数据单元,我们只需交换ind[]和mp[]2个数组的对应值即可。

#include <cstdio>
#define M 1000005
int n, k;
struct min_Heap
{
	int array[M], pos[M], id[M]; 
        //  id[i]表示堆内第i个元素对应的数组下标,pos[i]表示数组第i个元素在堆内的位置。
	void siftDown ( int start ) //下滑调整,若子女的值小于父节点的值,则父节点上浮,之后继续向下层比较
	{
		int i = start, j = i * 2 + 1;
		int temp = array[start];
		while ( j < k )
		{
			if ( j < k-1 && array[j] > array[j+1] ) ++j;
			if ( temp <= array[j] ) break;
			else { exch( i, j ); i = j; j = j * 2 + 1; }
		}
	}
	void siftUp ( int start ) //上滑调整,若子女的值小于父节点的值则互相交换
	{
		int j = start, i = ( j - 1 ) / 2;
		int temp = array[j];
		while ( j > 0 )
		{
			if ( temp >= array[i] ) break;
			else { exch ( j, i ); j = i; i = ( i - 1 ) / 2; }
		}
	} 	
	void build_minHeap () //建堆
	{
		int current = ( k - 2 ) / 2;
		while ( current >= 0 )
		{
			siftDown ( current );
			current--;
		}
	}	
	void exch ( int x, int y )
	{
		int temp;
		pos[id[x]] = y; pos[id[y]] = x;
		temp = id[x]; id[x] = id[y]; id[y] = temp;
		temp = array[x]; array[x] = array[y]; array[y] = temp;
	}
} minHeap;

struct max_Heap
{
	int array[M], pos[M], id[M];
	void siftDown ( int start )
	{
		int i = start, j = i * 2 + 1;
		int temp = array[start];
		while ( j < k )
		{
			if ( j < k-1 && array[j] < array[j+1] ) ++j;
			if ( temp >= array[j] ) break;
			else { exch( i, j ); i = j; j = j * 2 + 1; }
		}
	}
	void siftUp ( int start )
	{
		int j = start, i = ( j - 1 ) / 2;
		int temp =array[j];
		while ( j > 0 )
		{
			if ( temp <= array[i] ) break;
			else { exch( j, i ); j = i; i = ( i - 1 ) / 2; }
		}
	} 
	void build_maxHeap ()
	{
		int current = ( k - 2 ) / 2;
		while ( current >= 0 )
		{
			siftDown ( current );
			current--;
		}
	}
	void exch ( int x, int y )
	{
		int temp;
		pos[id[x]] = y; pos[id[y]] = x;
		temp = id[x]; id[x] = id[y]; id[y] = temp;
		temp = array[x]; array[x] = array[y]; array[y] = temp;
	}
} maxHeap;

int main()
{
	int i, t;
	scanf("%d%d",&n,&k);
	for ( i = 0; i < n; ++i )
	{
		scanf("%d", &minHeap.array[i]);
		maxHeap.array[i] = minHeap.array[i];
		minHeap.pos[i] = minHeap.id[i] = i;
		maxHeap.pos[i] = maxHeap.id[i] =i;
	}	
	minHeap.build_minHeap ();	
        printf("%d ", minHeap.array[0] );
	for ( i = k; i < n; ++i )
	{
		t = minHeap.pos[i-k];
		minHeap.exch( i, t );
		minHeap.siftUp ( t );
		minHeap.siftDown ( t );
		printf("%d ", minHeap.array[0] );
	}
	putchar('\n');

	maxHeap.build_maxHeap();
	printf("%d ", maxHeap.array[0] );
	for ( i = k; i < n; ++i )
	{
		t = maxHeap.pos[i-k];
		maxHeap.exch( i, t );
		maxHeap.siftUp ( t );
		maxHeap.siftDown ( t );
		printf("%d ", maxHeap.array[0] );
	}
	putchar('\n'); 
	return 0;
}

下面是单调队列的解法。 基本仿照http://blog.chinaunix.net/space.php?uid=22753395&do=blog&cuid=2208489的写法

#include <iostream>
using namespace std;

const int M = 1000005;
int a[M], que[M], Min[M], Max[M], id[M];
int n, k;

void get_min()
{
	int i, head = 1, tail = 0;
	for ( i = 1; i < k; ++i )
	{
		while ( head <= tail && que[tail] > a[i] )
			--tail;
		 ++tail;
		 que[tail] = a[i];
		 id[tail] = i;
	}
	for ( i = k; i <= n; ++i )
	{
		while ( head <= tail && que[tail] > a[i] )
			--tail;
		++tail;
		que[tail] = a[i];
		id[tail] = i;
		while ( id[head] <= i-k )
			++head;
		Min[i-k] = que[head];
	}
}

void get_max ()
{
	int i, head = 1, tail = 0;
	for ( i = 1; i < k; ++i )
	{
		while ( head <= tail && que[tail] < a[i] )
			--tail;
		++tail;
		que[tail] = a[i];
		id[tail] = i;
	}
	for ( i = k; i <= n; ++i )
	{
		while ( head <= tail && que[tail] < a[i] )
			--tail;
		++tail;
		que[tail] = a[i];
		id[tail] = i;
		while ( id[head] <= i-k )
			++head;
		Max[i-k] = que[head];
	}
}

int main()
{
	int i;
	scanf("%d%d", &n, &k );
	for ( i = 1; i <= n; ++i )
		scanf("%d",a+i);

	get_min();
	get_max();

	for ( i = 0; i < n-k+1; ++i )
		printf("%d ", Min[i] );
	putchar('\n');
	
	for ( i = 0; i < n-k+1; ++i )
		printf("%d ", Max[i] );
	putchar('\n');

	return 0;
}

抱歉!评论已关闭.