1. 程式人生 > >雜湊演算法 C語言 (連結串列 巨量且隨機的查詢)

雜湊演算法 C語言 (連結串列 巨量且隨機的查詢)

7-18 詞頻統計(30 分)

請編寫程式,對一段英文文字,統計其中所有不同單詞的個數,以及詞頻最大的前10%的單詞。

所謂“單詞”,是指由不超過80個單詞字元組成的連續字串,但長度超過15的單詞將只擷取保留前15個單詞字元。而合法的“單詞字元”為大小寫字母、數字和下劃線,其它字元均認為是單詞分隔符。

輸入格式:

輸入給出一段非空文字,最後以符號#結尾。輸入保證存在至少10個不同的單詞。

輸出格式:

在第一行中輸出文字中所有不同單詞的個數。注意“單詞”不區分英文大小寫,例如“PAT”和“pat”被認為是同一個單詞。

隨後按照詞頻遞減的順序,按照詞頻:單詞的格式輸出詞頻最大的前10%的單詞。若有並列,則按遞增字典序輸出。

輸入樣例:

This is a test.

The word "this" is the word with the highest frequency.

Longlonglonglongword should be cut off, so is considered as the same as longlonglonglonee.  But this_8 is different than this, and this, and this...#
this line should be ignored.

輸出樣例:(注意:雖然單詞the也出現了4次,但因為我們只要輸出前10%(即23個單詞中的前2個)單詞,而按照字母序,the
排第3位,所以不輸出。)

23
5:this
4:is

感謝武漢理工大學的郭小兵老師修正測試資料!

根據題目知,單詞最大大小為80 所以如果一個字串長度是90 那麼他可能就是倆個單詞了
單詞只能由大小寫字母 和數字和下劃線組成 其餘被認為是分隔符
這次個人感覺還是用陣列 Hash 比較好,但是做的過程中覺得連結串列較好 O(∩_∩)O;
還有大小寫轉換 巴拉巴拉;
補充一個小知識點,scanf("%[a-z,A-Z,0-9]")真的是可以為所欲為的  scanf 有很多用法和擴充套件 真的很好用,我應該會總結一篇過於scanf 的用法拓展吧;

此題我用到  雜湊 .最大堆和SCANF的特殊輸入 


#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#define MAX 1000000   
typedef struct Node *Hash;   /**雜湊的結構體**/
struct Node{
    char *s;
    int num;
    Hash next;
};
typedef struct node *heap;  /**最大堆的結構體**/
struct node{
    Hash *h;
    int size;
};
int num=0;   
int nextprme(int n); /**本來應該找到一個合適的大小來建立雜湊陣列的 但是題目沒有給出單詞個數,
再掃一遍輸入內容來找單詞個數,代價太高,所以 不如直接定義最大的指標陣列 用連結串列來做**/
int deal(char *s) /**雜湊日常處理。。。把資料對映成相應下標 ,這個函式可以根據自己喜好 自己設定處理方式,只要能通過的話。。。(●'◡'●)**/
{
    /**此方法是資料結構課本上一個移位法,就是把字串每一位當一個32進位制處理,但是最多能移位處理12次,所以我們需要改進一下**/
    int i,   len=strlen(s);
    unsigned int h=0;
    if(len<=12)
    {
        while(*s!='\0')
            h=((h<<5)+*(s++))%MAX;
    }
    else        /**ARE YOU KIDDING ME? ARE THEY DIFFERENT? **/ // enummm....嗯~ o(* ̄▽ ̄*)o
    {
         while(*s!='\0')
            h=((h<<5)+*(s++))%MAX;
    }
    return h;
}
Hash insert(Hash h,char *s) /**雜湊連結串列的老朋友 沒有不行**/
{
    Hash p=h;              /**先判斷是不是空**/
    while(p)
    {
        if(strcmp(p->s,s)==0)   /**不是空就看是不是相等**/
        {
            p->num++;
            return h;
        }
        else p=p->next;    /**否則next**/
    }
    p=(Hash)malloc(sizeof(struct Node)); /**是空 就好說了,在頭指標那插一個就好了,在後面插可以但是 程式碼會長那麼一點**/
    p->s=(char*)malloc(strlen(s)*sizeof(char));   /**分配相應的字串空間**/
    strcpy(p->s,s);   /**複製**/
    p->num=1; 
    p->next=h;
    num++;
    //printf("%s %d\n",p->s,p->num);    /**測試 點  看看插入的是啥 ,感興趣的可以\\去掉**/
    return p;
}
void display(heap H,Hash h)    /**這是為了測試程式碼正確  可忽略這函式 嗎?!(13:56) 不 ! 經過我的改進 他成為 了最大堆的輸入函式 (●'◡'●)(16:42)**/
{
    if(h==NULL)
        return;
    else
        while(h)
        {
            H->h[++H->size]=h;
            h=h->next;
        }
}
void shift(char *s)  /**轉換 將讀到的大寫字母轉換成小寫字母**/
{
    int i;
    for(i=0;i<strlen(s);i++)
    {
        s[i]=tolower(s[i]);
    }
}
/**下面是最大堆的函數了**/
heap Creatheap(int p)
{
    heap he=(heap)malloc(sizeof(struct node));   /** 建立最大堆**/
    he->h=(Hash*)malloc((p+5)*sizeof(Hash));    /**建立 一個 由雜湊指標 組成的 陣列**/
    he->size=0; 
    return he;  
}
int max (Hash x,Hash y)       /**  s 是單詞 ,num 是 出現次數**/
{
    if(x->num==y->num)
    {
        if(strcmp(x->s,y->s)<0) return 1;
        else return 0;
    }
    else if(x->num>=y->num) return 1;
    else return 0;
}
void perdown(heap H,int p)       /**這屬於最大堆的,說不好說 ,如果用筆 按照二叉樹那樣畫一畫 就明白了 ,不知道二叉樹的,先畫個TREE 再旋轉180° 不知道TREE是啥的,出門右拐加擡頭。**/
{
    int parent,child;                              
    Hash x=H->h[p];                  
    for(parent=p;parent*2<=H->size;parent=child)
    {
        child = parent*2;
        if(child!=H->size&&!(max(H->h[child],H->h[child+1])))
            child++;
        if(max(x,H->h[child])) break;
        else
            H->h[parent] = H->h[child];
    }
    H->h[parent]=x;
}
void Build(heap H) /**這個要和上面那個函式連在一起 ,他們就成了(最大堆的建立)**/
{
    int i;
    for(i=H->size/2;i>0;i--)
        perdown(H,i);
}
Hash Delete(heap H)  /**最大堆的刪除 其實就是把最大值取出來,就跟取抽紙一樣,抽完,下一個最大值又到剛才的位置了**/
{
    int parent,child;        
    Hash MaxHash,x;
    MaxHash=H->h[1];    /**將最大值 抽出,最大值就是 排在頭上的第一個的the first ’s**/

    x=H->h[H->size--];       /**然後把欺負最小的,把老末放在[1]中,然後經過各種比他大的大大蹂躪,到達一個新的位置**/
    for(parent =1;parent*2<=H->size;parent=child) /**蹂躪開始**/
    {
        child = parent*2;   
        if((child !=H->size)&&!(max(H->h[child],H->h[child+1])))  
            child++;
        if(max(x,H->h[child])) break;   /**蹂躪結束**/
        else
            H->h[parent]=H->h[child];   /**下一個繼續蹂躪**/
    }
    H->h[parent]=x;     /**到達不會被蹂躪的位置**/
    return MaxHash;    /**把那個快被遺忘的最大值返回 (可算有存在感了。。。)**/
}
Hash h[MAX]={0}; /**建立一個雜湊指標陣列,其實 一個迴圈 把每個都賦值為NULL比較好 ,我這種寫法是因為me lan**/
int main(){
    int p=MAX;
    char arr[80];
    int f;
    while(1)
    { 

/**下面 是scanf 的特殊使用**/
        f=scanf("%80[A-Za-z0-9_]",arr); /**這是最靚的地方 最多輸80個 而且只能輸入[A-Z a-z 0-0 _]中的字元,而且 如果scanf會返回一個已經讀入的(連續的東西的)個數 ,注意是連續,80個字串的讀入也算只讀入一個東西**/
        arr[15]='\0';                                 /**超過15的單詞擷取前15個 那就在第16個那個地方 賦值一個終止符**/
        if(getchar()=='#')          /**將結束上次輸入的字元GET掉 還有讀入# 就**/
        {
            break;
        }
        if(f==0) continue;     /**如果上次沒有輸入成功 那就不插入了,top(如果上次沒輸入成功,arr還儲存這上上次輸入的資料)**/
        shift(arr);            /**變變變,大寫變小寫**/
        int pos = deal(arr);      /**把資料處理一下,得到一個地址**/
        h[pos]=insert(h[pos],arr);    /**根據地址插入雜湊連結串列**/
    }                               /**到此為止雜湊的連結串列差不多就實現了**/
/**接下來就是輸出了,最大堆準備***/
    int i;
    heap H = Creatheap( num );   /**這裡就知道上上上面的那個num 的用處了吧,根據單詞個數,建立最合適堆,不浪費太多空間**/
    for(i=0;i<MAX;i++)  /**將連結串列中的東西先全搬進堆裡**/
    {
        display(H,h[i]); 
    }
    /*for(i=0;i<num;i++)  /**測試點,看看搬進了啥**/
    {
        printf("%s %d\n",H->h[i]->s,H->h[i]->num);
    }
    printf("-----------------------\n");*/
    Build(H);
    Hash x;
    /*for(i=0;i<num;i++)  /**測試點,看看從陣列變成最大堆後,成啥樣了,(看了也白看,就看個熱鬧,看不出他是不是最大堆了,得用手畫一下才行。。)**/
    {
        printf("%s %d\n",H->h[i]->s,H->h[i]->num);
    }
    printf("-----------------------\n");*/
    printf("%d\n",num); 
    for(i=0;i<num/10;i++) /**可以把10去掉,看看是不是完整的從大到小輸出**/
    {
        x=Delete(H);
        printf("%d:%s\n",x->num,x->s);
    }
return 0;
}

完畢

關於scanf的特殊用法,我會另開一頁專門總結一下,畢竟太碎,太難記了