雜湊演算法 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的特殊用法,我會另開一頁專門總結一下,畢竟太碎,太難記了