1. 程式人生 > >堆處理海量資料----求前k個最小的數--時間複雜度(n * log k)

堆處理海量資料----求前k個最小的數--時間複雜度(n * log k)

通過閱讀July的書籍,發現裡面求前k個最小數有很多方法。但在面對處理海量資料處理的時候,不能 把全部資料都放在電腦記憶體中。這時用堆來處理,並把資料放在外存中,通過讀取檔案的方式來讀取。感覺該方法十分巧妙,時間複雜度(n*log k)。程式碼如下:

#include<iostream>
#include<assert.h>
using namespace std;
void MaxHeap(int heap[],int i,int len);

void BuildHeap(int heap[],int len)
{
	if(heap == NULL)
		return;
	int index = len/2;
	for(int i = index;i >= 1; i--)
		MaxHeap(heap,i,len);
}
void MaxHeap(int heap[],int i,int len)
{
	int largeIndex = -1;
	int left = i * 2;
	int right = i *2 + 1;
	if(left<=len && heap[left]>heap[i])
		largeIndex = left;
	else
		largeIndex = i;
	if(right<=len && heap[right]>heap[largeIndex])
		largeIndex = right;
	if(largeIndex != i)
	{
		swap(heap[i],heap[largeIndex]);
		MaxHeap(heap,largeIndex,len);
	}

}

int main()
{
	int k;
	cout<<"求最小的k個數,請輸入k的值:";
	cin>>k;
	int *heap = new int [k+1];

	FILE *fp = fopen("data.txt","r");
	assert(fp);
	for(int i = 1;i<=k;i++)
	
	{
		fscanf(fp,"%d",&heap[i]);
	}
	BuildHeap(heap,k);

	int newData;
	while(fscanf(fp,"%d",&newData)!=EOF)
	{
		if(newData<heap[1])
		{
			heap[1]=newData;
			MaxHeap(heap,1,k);
		}
	}
	for(int j=1;j<=k;j++)
		cout<<heap[j]<<" ";
	cout<<endl;
	fclose(fp);
	return 0;
	
}

關於assert函式用法

assert巨集的原型定義在<assert.h>中,其作用是如果它的條件返回錯誤,則終止程式執行,原型定義:

#include <assert.h>void assert( int expression );

  assert的作用是現計算表示式 expression ,如果其值為假(即為0),那麼它先向stderr列印一條出錯資訊,然後通過呼叫 abort 來終止程式執行。請看下面的程式清單badptr.c:

複製程式碼
#include <stdio.h>
#include <assert.h>
#include 
<stdlib.h>int main( void ) { FILE *fp; fp = fopen( "test.txt", "w" );//以可寫的方式開啟一個檔案,如果不存在就建立一個同名檔案 assert( fp ); //所以這裡不會出錯 fclose( fp ); fp = fopen( "noexitfile.txt", "r" );//以只讀的方式開啟一個檔案,如果不存在就開啟檔案失敗 assert( fp );
//所以這裡出錯 fclose( fp ); //程式永遠都執行不到這裡來return0; }
複製程式碼

[[email protected] error_process]# gcc badptr.c 
[[email protected] error_process]# ./a.out 
a.out: badptr.c:14: main: Assertion `fp' failed.

  已放棄使用assert()的缺點是,頻繁的呼叫會極大的影響程式的效能,增加額外的開銷。在除錯結束後,可以通過在包含#include <assert.h>的語句之前插入 #define NDEBUG 來禁用assert呼叫,示例程式碼如下:

#include <stdio.h>#define NDEBUG
#include <assert.h>

用法總結與注意事項:

  1)在函式開始處檢驗傳入引數的合法性如:

複製程式碼
int resetBufferSize(int nNewSize)
{
  //功能:改變緩衝區大小,
  //引數:nNewSize 緩衝區新長度
  //返回值:緩衝區當前長度 
  //說明:保持原資訊內容不變     nNewSize<=0表示清除緩衝區  assert(nNewSize >=0);
  assert(nNewSize <= MAX_BUFFER_SIZE);
  ...
}
複製程式碼

  2)每個assert只檢驗一個條件,因為同時檢驗多個條件時,如果斷言失敗,無法直觀的判斷是哪個條件失敗,如:

  不好:

assert(nOffset>=0&& nOffset+nSize<=m_nInfomationSize);

  好:

assert(nOffset >=0);
assert(nOffset+nSize <= m_nInfomationSize);

  3)不能使用改變環境的語句,因為assert只在DEBUG個生效,如果這麼做,會使用程式在真正執行時遇到問題,如:

  錯誤:

assert(i++<100);

  這是因為如果出錯,比如在執行之前i=100,那麼這條語句就不會執行,那麼i++這條命令就沒有執行。

  正確:

 assert(i <100);
 i++;

  4)assert和後面的語句應空一行,以形成邏輯和視覺上的一致感。

  5)有的地方,assert不能代替條件過濾。

還可以使用STL -- nth_element來逐步求前k個最小的值,簡單的例子說明如下:

#include<iostream>
#include<algorithm> //必須的
using namespace std;


int main()
{


	int a[]={3,2,6,1,8};
	for(int i=2;i>=0;i--)
	{
		nth_element(a,a+i,a+4);//求前3個最小的數
		cout<<a[i]<<endl;
	}
	return 0;
}