哈希算法 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
#include
#include
#include
#include
#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     {
        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     {
        display(H,h[i]); 
    }
    /*for(i=0;i     {
        printf("%s %d\n",H->h[i]->s,H->h[i]->num);
    }
    printf("-----------------------\n");*/
    Build(H);
    Hash x;
    /*for(i=0;i     {
        printf("%s %d\n",H->h[i]->s,H->h[i]->num);
    }
    printf("-----------------------\n");*/
    printf("%d\n",num); 
    for(i=0;i     {
        x=Delete(H);
        printf("%d:%s\n",x->num,x->s);
    }
return 0;
}

完毕

关于scanf的特殊用法,我会另开一页专门总结一下,毕竟太碎,太难记了

你可能感兴趣的:(C语言,数据结构,算法)