尋找最大的K個數,Top K問題的堆實現


 

尋找最大的K個數,如果所有的數據全部可以放入內存,就可以使用random select算法在線性時間內尋找第K大的數,再得到最大的K個數。

參考:http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html

如果不能把所有數據的數據都一次性放入內存,就可以維護一個大小為K的堆,找最大的K個數,就維護一個小根堆,堆頂元素為這最大的K個數中的最小元素。具體實現參考下面兩個例子:

2億個整數中求最大的100萬之和

題目:有一個文件中保存了2億個整數,每個整數都以' '分隔。求最大的100萬個整數之和。

算法:
1. 首先建立一個容量為100萬(Top K)的int數組,從文件讀取整數填充。
2. 利用堆維護該100萬條記錄(確保堆頂元素為最小值)
3. 從文件中讀取一個整數與堆頂元素比較,如果大於堆頂元素則替換該元素,並調整堆的結構。
4. 重復步驟3一直到數據讀取完
5. 將數組中的元素全部相加,得到結果

參考代碼:

View Code
#include <iostream>   
using namespace std;  
  
template<class T>  
class CTopK  
{  
public:  
    CTopK();  
    ~CTopK();  
    T*  m_Data;  
    int GetTopK(const char* sFile, int& nTop);  
private:  
    void Clear();  
    void HeapAdjust(int nStart, int nLen);  
    void BuildHeap(int nLen);  
};  
  
template<class T>  
CTopK<T>::CTopK()  
{  
    m_Data = NULL;  
}  
  
template<class T>  
CTopK<T>::~CTopK()  
{  
    Clear();  
}  
  
template<class T>  
void CTopK<T>::Clear()  
{  
    if (NULL != m_Data)  
    {  
        delete[] m_Data;  
        m_Data = NULL;  
    }  
}  
  
//獲取前top的k個數   
template<class T>  
int CTopK<T>::GetTopK(const char* sFile, int& nTop)  
{  
    FILE* fp = NULL;  
    T fData;  
    int i = 0;  
  
    //判斷輸入參數   
    if ( (NULL == sFile) || (nTop <= 0) )  
    {  
        cout << "error parameter" << endl;  
        return -1;  
    }  
  
    //清除操作   
    Clear();  
  
    //打開文件   
    fp = fopen(sFile, "r");  
    if (NULL == fp)  
    {  
        cout << "open file failed!" << endl;  
        return -1;  
    }  
  
    //分配空間   
    m_Data = new T[nTop];  
    if (NULL == m_Data)  
    {  
        cout << "new operator failed!" << endl;  
        return -1;  
    }  
  
    cout << "please wait..." << endl;  
  
    //讀取前nTop個數據,注意數據的類型T   
    for (i = 0; i < nTop; i++)  
    {  
        if (EOF != fscanf(fp, "%d", &fData))  
        {  
            m_Data[i] = fData;  
        }  
        else  
        {  
            break;  
        }  
    }  
  
    //最大的個數小於nTop,求前i個數據   
    if (i < nTop)  
    {  
        nTop = i;  
    }  
    else  
    {  
        BuildHeap(nTop);//建立小頂堆   
  
        while (EOF != fscanf(fp, "%d", &fData))  
        {  
            if (fData > m_Data[0])  
            {  
                //交換並調整堆   
                m_Data[0] = fData;  
                HeapAdjust(0, nTop);  
            }  
        }  
    }  
  
    return 0;  
}  
  
//調整小根堆,堆頂為Top K最小   
template<class T>  
void CTopK<T>::HeapAdjust(int nStart, int nLen)  
{  
    int nMinChild = 0;  
    T fTemp;  
  
    while ( ( 2 * nStart + 1) < nLen)  
    {  
        nMinChild = 2 * nStart + 1;  
        if ( (2 * nStart + 2) < nLen)  
        {  
            //比較左子樹和右子樹,記錄最小值的Index   
            if (m_Data[2 * nStart + 2] < m_Data[2 * nStart + 1])  
            {  
                nMinChild = 2 * nStart + 2;  
            }  
        }  
  
        //change data   
        if (m_Data[nStart] > m_Data[nMinChild])  
        {  
            //交換nStart與nMaxChild的數據   
            fTemp = m_Data[nStart];  
            m_Data[nStart] = m_Data[nMinChild];  
            m_Data[nMinChild] = fTemp;  
  
            //堆被破壞,需要重新調整   
            nStart = nMinChild;  
        }  
        else  
        {  
            //比較左右孩子均大則堆未破壞,不再需要調整   
            break;  
        }  
    }  
}  
  
//建立堆   
template<class T>  
void CTopK<T>::BuildHeap(int nLen)  
{  
    int i = 0;  
    T nTemp;  
  
    //將m_Data[0, Len-1]建成小根堆,這里只維護一個小根堆,不排序   
    for (i = nLen / 2  - 1; i >= 0; i--)  
    {  
        HeapAdjust(i, nLen);  
    }  
}  
  
int main(int argc, char* argv[])  
{  
    char   szFile[100] = {0};  
    int     nNum = 0;  
    CTopK<int> objTopSum;  
  
    cout << "please input count file name:" << endl;  
    cin >> szFile;  
    cout << "please input top num:"<< endl;  
    cin >> nNum;  
    objTopSum.GetTopK(szFile, nNum);  
  
    int fSum = 0;  
    for (int i = 0; i < nNum; i++)  
    {  
        cout<<objTopSum.m_Data[i]<<" ";  
        fSum += objTopSum.m_Data[i];  
    }  
    cout << "\ntop " << nNum << " value = " << fSum << endl;  
  
    return 0;  
}  

測試數據:生成1000萬個不重復的隨機數

View Code
//生成隨機的不重復的測試數據   
#include <iostream>   
#include <time.h>   
#include <assert.h>   
using namespace std;  
  
//產生[l,u]區間的隨機數   
int randint(int l, int u)  
{  
 return l+(RAND_MAX*rand()+rand())%(u-l+1);  
}  
  
 //1000W的int,大約4M的數據,如果放在mian內,在我的機子上好像是棧溢出了,放在全局空間就沒問題   
const int size = 10000000;  
int num[size];  
  
int main()  
{  
    int i, j;  
    FILE *fp = fopen("data.txt", "w");  
    assert(fp);  
    for (i = 0; i < size; i++)  
        num[i] = i+1;  
    srand((unsigned)time(NULL));  
    for (i = 0; i < size; i++)  
    {  
        j = randint(i, size-1);  
        int t = num[i]; num[i] = num[j]; num[j] = t;  
        //swap(num[i], num[j]);   
    }  
    for (i = 0; i < size; i++)  
        fprintf(fp, "%d ", num[i]);  
    fclose(fp);  
    return 0;  
}  

1000萬個數據太大,打開文件很慢,可能會看不到,可以測試1萬個數據中找最大的10個。

搜索引擎熱門查詢統計

題目描述:
搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節。
假設目前有一千萬個記錄,這些查詢串的重復度比較高,雖然總數是1千萬,但如果除去重復后,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就是越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。

解決方法:hash表+堆,去重復后不超過300萬個,總大小不超過300萬*255B=765MB,內存使用不超過1G。

第一步:先對這批海量數據預處理,在O(N)的時間內用Hash表完成去重復操作。
第二步:借助堆這個數據結構,找出Top K,時間復雜度為NlogK。即借助堆結構,可以在log量級的時間內查找和調整/移動。因此,維護一個K(該題目中是10)大小的小根堆(Kmin設為堆頂元素),然后遍歷300萬的Query,分別和Kmin進行對比比較(若X>Kmin,則更新並調整堆,否則,不更新),最終的時間復雜度是:O(N)+ N'*O(logK),(N為1000萬,N’為300萬)。

為了降低實現上的難度,假設這些記錄全部是一些英文單詞,即用戶在搜索框里敲入一個英文單詞,然后查詢搜索結果,最后,要你統計輸入單詞中頻率最大的前K個單詞。復雜問題簡單化了之后,編寫代碼實現也相對輕松多了,如下:

View Code
//copyright@yansha &&July     
//July、updated,2011.05.08     
    
//題目描述:     
//搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的     
//長度為1-255字節。假設目前有一千萬個記錄(這些查詢串的重復度比較高,雖然總數是1千萬,但如果     
//除去重復后,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就是越熱門),     
//請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。     
    
#include <iostream>     
#include <string>     
#include <assert.h>     
using namespace std;    
    
#define HASHLEN 2807303     
#define WORDLEN 30     
    
// 結點指針     
typedef struct node_no_space *ptr_no_space;    
typedef struct node_has_space *ptr_has_space;    
ptr_no_space head[HASHLEN];    
    
struct node_no_space     
{    
    char *word;    
    int count;    
    ptr_no_space next;    
};    
    
struct node_has_space    
{    
    char word[WORDLEN];    
    int count;    
    ptr_has_space next;    
};    
    
// 最簡單hash函數     
int hash_function(const char *p)    
{    
    int value = 0;    
    while (*p != '\0')    
    {    
        value = value * 31 + *p++;    
        if (value > HASHLEN)    
            value = value % HASHLEN;    
    }    
    return value;    
}    
    
// 添加單詞到hash表     
void append_word(const char *str)    
{    
    int index = hash_function(str);    
    ptr_no_space p = head[index];    
    while (p != NULL)    
    {    
        if (strcmp(str, p->word) == 0)    
        {    
            (p->count)++;    
            return;    
        }    
        p = p->next;    
    }    
        
    // 新建一個結點     
    ptr_no_space q = new node_no_space;    
    q->count = 1;    
    q->word = new char [strlen(str)+1];    
    strcpy(q->word, str);    
    q->next = head[index];    
    head[index] = q;    
}    
    
    
// 將單詞處理結果寫入文件     
void write_to_file()    
{    
    FILE *fp = fopen("result.txt", "w");    
    assert(fp);    
        
    int i = 0;    
    while (i < HASHLEN)    
    {    
        for (ptr_no_space p = head[i]; p != NULL; p = p->next)    
            fprintf(fp, "%s  %d\n", p->word, p->count);    
        i++;    
    }    
    fclose(fp);    
}    
    
// 從上往下篩選,保持小根堆     
void sift_down(node_has_space heap[], int i, int len)    
{    
    int min_index = -1;    
    int left = 2 * i;    
    int right = 2 * i + 1;    
        
    if (left <= len && heap[left].count < heap[i].count)    
        min_index = left;    
    else    
        min_index = i;    
        
    if (right <= len && heap[right].count < heap[min_index].count)    
        min_index = right;    
        
    if (min_index != i)    
    {    
        // 交換結點元素     
        swap(heap[i].count, heap[min_index].count);    
            
        char buffer[WORDLEN];    
        strcpy(buffer, heap[i].word);    
        strcpy(heap[i].word, heap[min_index].word);    
        strcpy(heap[min_index].word, buffer);    
            
        sift_down(heap, min_index, len);    
    }    
}    
    
// 建立小根堆     
void build_min_heap(node_has_space heap[], int len)    
{    
    if (heap == NULL)    
        return;    
        
    int index = len / 2;    
    for (int i = index; i >= 1; i--)    
        sift_down(heap, i, len);    
}    
    
// 去除字符串前后符號     
void handle_symbol(char *str, int n)    
{    
    while (str[n] < '0' || (str[n] > '9' && str[n] < 'A') || (str[n] > 'Z' && str[n] < 'a') || str[n] > 'z')    
    {    
        str[n] = '\0';    
        n--;    
    }    
        
    while (str[0] < '0' || (str[0] > '9' && str[0] < 'A') || (str[0] > 'Z' && str[0] < 'a') || str[0] > 'z')    
    {    
        int i = 0;    
        while (i < n)    
        {    
            str[i] = str[i+1];    
            i++;    
        }    
        str[i] = '\0';    
        n--;    
    }    
}    
    
int main()    
{    
    char str[WORDLEN];    
    for (int i = 0; i < HASHLEN; i++)    
        head[i] = NULL;    
        
    // 將字符串用hash函數轉換成一個整數並統計出現頻率     
    FILE *fp_passage = fopen("string.txt", "r");    
    assert(fp_passage);    
    while (fscanf(fp_passage, "%s", str) != EOF)    
    {    
        int n = strlen(str) - 1;    
        if (n > 0)    
            handle_symbol(str, n);    
        append_word(str);    
    }    
    fclose(fp_passage);    
        
    // 將統計結果輸入文件     
    write_to_file();    
        
    int n = 10;    
    ptr_has_space heap = new node_has_space [n+1];    
        
    int c;    
        
    FILE *fp_word = fopen("result.txt", "r");    
    assert(fp_word);    
    for (int j = 1; j <= n; j++)    
    {    
        fscanf(fp_word, "%s %d", &str, &c);    
        heap[j].count = c;    
        strcpy(heap[j].word, str);    
    }    
        
    // 建立小根堆     
    build_min_heap(heap, n);    
        
    // 查找出現頻率最大的10個單詞     
    while (fscanf(fp_word, "%s %d", &str, &c) != EOF)    
    {    
        if (c > heap[1].count)    
        {    
            heap[1].count = c;    
            strcpy(heap[1].word, str);    
            sift_down(heap, 1, n);    
        }    
    }    
    fclose(fp_word);    
        
    // 輸出出現頻率最大的單詞     
    for (int k = 1; k <= n; k++)    
        cout << heap[k].count << " " << heap[k].word << endl;    
        
    return 0;    
}    

參考:http://blog.csdn.net/v_JULY_v/archive/2011/05/08/6403777.aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM