尋找最大的k個數,TopK問題的C++實現

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

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

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

參考源代碼:

#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;
}

 

搜索引擎熱門查詢統計

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

    解決方法:hash表+堆

    第一步、先對這批海量數據預處理,在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個單詞。複雜問題簡單化了之後,編寫代碼實現也相對輕鬆多了,如下:

//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/andylin02/archive/2008/11/28/3401123.aspx

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章