1. 程式人生 > 其它 >統計檔案裡出現次數前10的單詞

統計檔案裡出現次數前10的單詞

技術標籤:# 演算法題

統計” The_Holy_Bible_Res.txt “ 中字元的個數,行數,單詞的個數,統計單詞的詞頻並列印輸出詞頻最高的前 10 個單詞及其詞頻

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>	
#include <string.h>
#define MAXKEY 10000
#define SWAP(a,b) {pInfo_t t=a;a=b;b=t;}

int hash(char *key) {//雜湊函式:輸入字串的地址返回字串對應的雜湊值
	int h = 0 , g;
	while (*key) {
		h = (h << 4) + *key++;
		g = h & 0xf0000000;
		if (g)
			h ^= g >> 24;
		h &= ~g;
	}
	return h % MAXKEY;
}
typedef struct info
{
	int num;//單詞詞頻
	char *address;//單詞地址
	struct info *next;//指向雜湊衝突連結串列的下一個結點
}Info_t , *pInfo_t;

int isLetterofAlphabet(char c)//判斷小寫字母
{
	if (c <= 122 && c >= 97)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

//陣列下標從0開始
//left是堆頂的下標,編號是left+1
//左孩子編號2*left+2,下標是2*left+1
//右孩子編號2*left+3,下標是2*left+2
void adjustMinHeap(pInfo_t *p , int left , int right)//調整為小頂堆
{//自上而下調整
	int father = left;
	int son = 2 * father + 1;//son指向更小的孩子
	while (son <= right)
	{
		if (son + 1 <= right && (p[son]->num > p[son + 1]->num))//son+1<=right保證了沒有右孩子就不會比較兩個孩子
		{
			son = son + 1;//右孩子小就指向右孩子
		}
		if (p[father]->num > p[son]->num)
		{
			SWAP(p[father] , p[son]);
			father = son;
			son = 2 * father + 1;
		}
		else
		{
			break;
		}
	}
}
void buildMinHeap(pInfo_t *p , int left , int right)//建立小頂堆
{
	int lastFather = (right - 1) / 2;//最後一個分支結點
	for (int i = lastFather; i >= left; i--)//自下而上建堆
	{
		adjustMinHeap(p , i , right);
	}
}

/*
沒有用到這個函式,只是提供了一種找出檔案裡的單詞的思路
缺點:如果指標所指向的是檔案裡面第一個字元,那麼p[-1]就錯了
*/
int isWord(char *p)	
{//檔案指標此時指向的是字母,如果前面不是字母,說明這是一個單詞
	if (p[-1] < 97 || p[-1]>122)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}


//2
int main(int argc , char *argv[])
{
	FILE *fp = fopen(argv[2] , "rb");
	if (fp == NULL)
	{
		perror("fp.fopen:");
		return -1;
	}
	pInfo_t hashTable[MAXKEY] = { NULL };
	char c;
	int n1=0 , n2=1 , n3=0;//字元的個數,行數,單詞的個數	
	while ((c = fgetc(fp)) != EOF)
	{
		/* 錯誤:在檔案裡單詞的結尾不是'\0',這樣把單詞地址輸入hash函式得到的雜湊值錯
		if (c <= 122 && c >= 97)//小寫字母
		{
			if (isWord(&c))//是單詞
			{
				n3++;
				pInfo_t p = (pInfo_t) calloc(1 , sizeof(Info_t));
				p->num++;
				p->address = &c;
				//錯:每遇到一個單詞就創新結點p,用p->num==1去判斷單詞以前是否出現過是不合理的
				if (hashTable[hash(&c)] != NULL&&p->num==1)
				{//這個結點第一次出現並且發生了衝突
					pInfo_t q = hashTable[hash(&c)];
					while (q->next!=NULL)
					{
						q = q->next;
					}
					q->next = p;
				}
		*/
		if (isLetterofAlphabet(c))//小寫字母
		{
			n3++;
			n1++;
			int i = 0;
			//錯誤的寫法:char word[100] = {0}; 
			char *word = (char*) calloc(1 , 100);
			word[i++] = c;
			while ((c = fgetc(fp)) != EOF && isLetterofAlphabet(c))
			{
				word[i++] = c;
				n1++;
			}
			if (hashTable[hash(word)] != NULL)
			{//單詞發生了衝突或者是單詞之前出現過
				pInfo_t q = hashTable[hash(word)];
				//比較雜湊元素對應的單詞是否和word相同
				while (q->next != NULL && strcmp(q->address , word)!=0)
				{
					q = q->next;
				}
				if (strcmp(q->address , word) == 0)
				{
					q->num++;//衝突連結串列裡有word這個單詞,詞頻+1
				}
				else
				{
					pInfo_t p = (pInfo_t) calloc(1 , sizeof(Info_t));
					p->address = word;
					p->num = 1;
					q->next = p;
				}
			}
			else
			{//單詞沒發生衝突並且是第一次出現
				pInfo_t p = (pInfo_t) calloc(1 , sizeof(Info_t));
				p->address = word;
				p->num = 1;
				hashTable[hash(word)] = p;
			}
			if (c == EOF)//到達檔案尾
			{
				break;
			}
		}
		if(c=='\n')//遇到'\n'
		{
			n2++;
		}
	}
	fclose(fp);
	printf("字元數%d,行數%d,單詞數%d\n" , n1 , n2 , n3);

	pInfo_t A[10] = {NULL};//存放前10大詞頻的結點指標
	int flag = 0;//標識是否建立了初始的前10個結點指標的小頂堆
	//掃描雜湊連結串列
	for (int i = 0,j=0; i < MAXKEY; i++)
	{	
		if (hashTable[i]!=NULL)
		{
			pInfo_t t= hashTable[i];
			while (t!=NULL)
			{
				if (j<10)//把前10個結點指標存進A	
				{
					A[j++] = t;
				}
				else if(j==10&&flag==0)
				{
					buildMinHeap(A , 0 , 9); //建立小頂堆
					flag = 1;
				}
				else
				{
					if (A[0]->num < t->num)
					{
						//錯:SWAP(A[0],t);  影響t = t->next;
						A[0] = t;
						adjustMinHeap(A , 0 , 9);
					}	
				}
				t = t->next;
			}
		}
	}

	for (int i = 0; i < 10; i++)
	{//列印輸出詞頻最高的前 10 個單詞及其詞頻
		printf("%s:%d\n" , A[i]->address , A[i]->num);
	}
	return 0;
}