编程珠玑变位词算法

问题描述

给定一个英语字典,找出其中的所有变位词集合。例如,“pots”,“stop”和“tops”互为变位词,因为每一个单词都可以通过改变其他单词中的字母顺序来得到。

解决思路

首先想到的方法应该是使用一个两层循环,遍历字典,然后再比较每个单词是否是变位词,在比较单词的时候,可以先对每个单词的所有字母进行排序,然后再比较,总的时间复杂度为O(m*logm*n2),也可以使用一个大小为26的字符数组做Hash来进行对比。这样的算法总的时间复杂度为O(m*n2)。对字典使用一个两层循环遍历,可见效率并不高。所以需要再考虑其他算法。

更好些的方法是,我们可以考虑为每个单词增加一个标识,然后再以标识对单词进行排序,这样排序后,相同标识的单词就分在一起,这样就找出了所有单词的变位词集合。可以将该方法分为以下三个步骤:

  1. 为每个单词增加一个标识,这个步骤的关键是怎么找每个单词的标识,使得一个单词的所有的变位词都又相同的标识,相当于找到一个Hash函数,使得一个单词的所有变位词都有相同的Hash值,合适的方式是对单词中的字母进行排序,如“pots”,“stop”和“tops”这三个单词是变位词,他们的标识是“opst”,即对单词中的字母按照字母顺序进行排序,最后得到标识为“opst”。
  2. 以单词的标识对字典中的单词排序,经过上面的处理,就得到了一个单词与标识的二元组,将这个二元组视为一个整体,可以使用一个结构体(或对象)来理解,比如结构体可以定义为:
    struct {
        char identity[MAX_SIZE];
        char word[MAX_SIZE];
    }
    所有的单词经过步骤1后就得到了一个包含标识和单词的数组,这样使用快速排序对这个数组按照标识大小排序。
  3. 汇总单词的变位词,经过步骤2,将有相同标识的单词汇集在一起了,然后再进行汇总。

编码实现

下面编码实现上述算法,实现对于步骤2,编码时不定义结构体,直接使用一个单词数组和一个标识数组来实现,如单词数组中第i个单词的标识保存再标识数组的第i个元素中。
#define MAX_SIZE 100

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>

void str_to_lower(char *str);
void sign(char words[][MAX_SIZE], int length, char sig[][MAX_SIZE]);
int char_compare(const void *c1, const void *c2);
int str_compare(const void *s1, const void *s2);
int partition(char words[][MAX_SIZE], char sig[][MAX_SIZE], int left, int right);
void qsort_str(char words[][MAX_SIZE], char sig[][MAX_SIZE], int left, int right);
void sort(char words[][MAX_SIZE], char sig[][MAX_SIZE], int length);

/**
 * 比较两个字符的大小
 */
int char_compare(const void *c1, const void *c2){
    return *(char *)c1 - *(char *)c2;
}
/**
 * 所有的单词都存在words数组中,为words单词中的所有单词求他的标识,
 * 其中标识按照单词中的字母顺序排列的字母序列
 */
void sign(char words[][MAX_SIZE], int length, char sig[][MAX_SIZE]) {
    int i;
    for(i = 0; i < length; i++) {
        strcpy(sig[i], words[i]);
        qsort(sig[i], strlen(sig[i]), sizeof(char), char_compare); 
    }
}


void str_to_lower(char *str) {
    int i;
    int len = strlen(str);
    for(i = 0; i < len; i++) {
       str[i]  = tolower(str[i]);
    }
}

/**
 * 比较两个字符串的大小,封装之后可以用qsort函数排序字符串
 */
int str_compare(const void *s1, const void *s2) {
    return strcmp((char *)s1, (char *)s2);
}

int partition(char words[][MAX_SIZE], char sig[][MAX_SIZE], int left, int right) {
    char temp_word[MAX_SIZE];
    char temp_sig[MAX_SIZE];
    
    strcpy(temp_sig, sig[left]);
    strcpy(temp_word, words[left]);
       
    while(left < right) {
        while(str_compare(temp_sig, sig[right]) < 0 && left < right) right--;
        strcpy(sig[left], sig[right]);
        strcpy(words[left], words[right]);
    
        while(str_compare(temp_sig, sig[left]) >= 0 && left < right) left++;
        strcpy(sig[right], sig[left]);
        strcpy(words[right], words[left]);
    }
    strcpy(words[right], temp_word);
    strcpy(sig[right], temp_sig);
    return right;
}

void qsort_str(char words[][MAX_SIZE], char sig[][MAX_SIZE], int left, int right) {
    int part;
    char temp[MAX_SIZE];
    if(left >= right) return;
    part = partition(words, sig, left, right);
    qsort_str(words, sig, left, part - 1);
    qsort_str(words, sig, part + 1, right);
}
/**
 * 使用快速排序对字符串数组进行排序
 */
void sort(char words[][MAX_SIZE], char sig[][MAX_SIZE], int length) {
    qsort_str(words, sig, 0, length - 1);    
}
/*汇总变位词*/
void squash(char words[][MAX_SIZE], char sig[][MAX_SIZE], int length) {
    int i;
    char oldsig[MAX_SIZE];
    strcpy(oldsig, sig[0]);
    printf("%-6s:", oldsig);
    for(i = 0; i < length; i++) {
        if(strcmp(oldsig, sig[i]) != 0) {
            strcpy(oldsig, sig[i]);
            printf("\n");
            printf("%-6s:", oldsig);
        }
        printf("%-6s", words[i]);
    }
    printf("\n");

}

void print(char words[][MAX_SIZE], char sig[][MAX_SIZE], int length) {
    int i;
    for(i = 0; i < length; i++) {
        printf("%s %s\n", sig[i], words[i]);
    }

}
    
int main() {
    char words[][100] = {"pans", "pots", "opt", "snap", "stop", "tops"};
    char sig[6][100];
    int i;
    sign(words, 6, sig);//为单词添加标识
    print(words, sig, 6);
    sort(words, sig, 6);//使用标识排序
    printf("****************************************\n");
    print(words, sig, 6);
    printf("----------------------------------------\n");
    squash(words, sig, 6);//汇总变位词
    return 0;
}
以上实现是以6个单词为例来实现,如果字典中单词较多,内存容不下时,可以对上述的3个步骤使用文件来保存临时结果。







你可能感兴趣的:(编程珠玑变位词算法)