首先按频率对字符串中的字符进行排序,然后按字母顺序排序

时间:2013-12-22 13:27:20

标签: c++ sorting vector character frequency

给定一个字符串,我试图计算字符串中每个字母的出现次数,然后将它们的频率从最高到最低排序。然后,对于具有相似出现次数的字母,我必须按字母顺序对它们进行排序。

以下是我迄今为止所做的事情:

  • 我创建了一个大小为26的int数组,对应于字母表中的26个字母,其中各个值表示它在句子中出现的次数
  • 我将此数组的内容推送到成对的向量vintcharint为频率,char对于实际的信件)
  • 我使用std::sort(v.begin(), v.end());
  • 对这对配对进行了排序

在显示频率计数时,我只是从最后一个索引开始使用for循环来显示从最高到最低的结果。但是,对于那些具有相似频率的字母,我遇到了问题,因为我需要按字母顺序显示它们。我尝试使用嵌套的for循环,内循环以最低索引开始,并使用条件语句检查其频率是否与外循环相同。这似乎有效,但我的问题是我似乎无法弄清楚如何控制这些循环,以避免冗余输出。要了解我在说什么,请参阅此示例输出:

Enter a string: hello world

Pushing the array into a vector pair v:
d = 1
e = 1
h = 1
l = 3
o = 2
r = 1
w = 1


Sorted first according to frequency then alphabetically:
l = 3
o = 2
d = 1
e = 1
h = 1
r = 1
w = 1
d = 1
e = 1
h = 1
r = 1
d = 1
e = 1
h = 1
d = 1
e = 1
d = 1
Press any key to continue . . .

正如您所看到的,如果不是由于错误的for循环带来的冗余输出,那就没问题。

如果你能就我的关注建议更有效或更好的实现,那么只要他们不是太复杂或太高级我就会非常感激,因为我只是一个C ++初学者。

如果您需要查看我的代码,请访问:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    cout<<"Enter a string: ";
    string input;
    getline(cin, input);

    int letters[26]= {0};

    for (int x = 0; x < input.length(); x++) {
        if (isalpha(input[x])) {
            int c = tolower(input[x] - 'a');
            letters[c]++;
        }
    }

    cout<<"\nPushing the array into a vector pair v: \n";
    vector<pair<int, char> > v;

    for (int x = 0; x < 26; x++) {
        if (letters[x] > 0) {
            char c = x + 'a';
            cout << c << " = " << letters[x] << "\n";
            v.push_back(std::make_pair(letters[x], c));
        }
    }

    // Sort the vector of pairs.
    std::sort(v.begin(), v.end());

    // I need help here!
    cout<<"\n\nSorted first according to frequency then alphabetically: \n";
    for (int x = v.size() - 1 ; x >= 0; x--) {
        for (int y = 0; y < x; y++) {
            if (v[x].first == v[y].first) {
                cout << v[y].second<< " = " << v[y].first<<endl;
            }
        }
        cout << v[x].second<< " = " << v[x].first<<endl;
    }

    system("pause");
    return 0;
}

4 个答案:

答案 0 :(得分:5)

您可以通过两个步骤简化这一过程:

  1. 首先使用地图计算字符串中每个字符的出现次数:

    std::unordered_map<char, unsigned int> count;
    
    for( char character : string )
        count[character]++;
    
  2. 使用该地图的值作为比较条件:

    std::sort( std::begin( string ) , std::end( string ) , 
               [&]( char lhs , char rhs )
               {
                   return count[lhs] < count[rhs];
               }
             ); 
    
  3. Here是一个在ideone上运行的工作示例。

答案 1 :(得分:3)

如果你想要最高频率然后最低字母,一个简单的方法是存储频率的负值,然后在排序后否定它。更有效的方法是更改​​用于排序的函数,但这样做比较棘手:

struct sort_helper {
   bool operator()(std::pair<int,char> lhs, std::pair<int,char> rhs) const{
     return std::make_pair(-lhs.first,lhs.second)<std::make_pair(-rhs.first,rhs.second);
   }
};
std::sort(vec.begin(),vec.end(),sort_helper());

答案 2 :(得分:1)

按照@ Manu343726的建议,使用unordered_map来计数字符是一个好主意。但是,为了产生排序的输出,需要执行另一步。

我的解决方案也在C++11中,并且使用了lambda expression。这样,您既不需要定义自定义结构也不需要比较函数。代码几乎完成了,我只是跳过了阅读输入内容:

#include <unordered_map>
#include <iostream>
#include <set>

int main() {
    string input = "hello world";

    unordered_map<char, unsigned int> count;
    for (char character : input)
        if (character >= 'a' && character <= 'z')
            count[character]++;

    cout << "Unsorted list:" << endl;
    for (auto const &kv : count)
        cout << kv.first << " = " << kv.second << endl;

    using myPair = pair<char, unsigned int>;
    auto comp = [](const myPair& a, const myPair& b) {
        return (a.second > b.second || a.second == b.second && a.first < b.first);
    };
    set<myPair, decltype(comp)> sorted(comp);
    for(auto const &kv : count)
        sorted.insert(kv);

    cout << "Sorted list according to frequency then alphabetically:" << endl;
    for (auto const &kv : sorted)
        cout << kv.first << " = " << kv.second << endl;

    return 0;
}

输出:

  

未排序列表:
  r = 1
  h = 1
  e = 1
  d = 1
  o = 2
  w = 1
  l = 3
  根据频率对列表进行排序,然后按字母顺序排列:
  l = 3
  o = 2
  d = 1
  e = 1
  h = 1
  r = 1
  w = 1

注1:与其将unordered_map中的每个元素插入set中,使用函数std::transformstd:copy可能会更有效,但是我的代码至少短。

注2:与其使用自定义排序的set来保持所需顺序,不如使用成对向量并最后对其进行一次排序可能会更有效,但是您的解决方案已经类似于这个。

Code on Ideone

答案 3 :(得分:1)

(代表OP发布。)

多亏了Stack Overflow出色的人员的回应,我终于能够解决我的问题。这是我的最终代码,以防万一有兴趣的人或将来可能被困在同一条船上的人参考:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

struct Letters
{
    Letters() : freq(0){}
    Letters(char letter,int freq) {
        this->freq = freq;
        this->letter = letter;
    }
    char letter;
    int freq;
};

bool Greater(const Letters& a, const Letters& b)
{
    if(a.freq == b.freq)
        return a.letter < b.letter;

    return a.freq > b.freq;
}

int main () {

    cout<<"Enter a string: ";
    string input;
    getline(cin, input);

    vector<Letters> count;
    int letters[26]= {0};

    for (int x = 0; x < input.length(); x++) {
        if (isalpha(input[x])) {
            int c = tolower(input[x] - 'a');
            letters[c]++;
        }
    }

    for (int x = 0; x < 26; x++) {
        if (letters[x] > 0) {
            char c = x + 'a';
            count.push_back(Letters(c, letters[x]));
        }
    }

    cout<<"\nUnsorted list..\n";
    for (int x = 0 ; x < count.size(); x++) {
        cout<<count[x].letter<< " = "<< count[x].freq<<"\n";
    }

    std::sort(count.begin(),count.end(),Greater);

    cout<<"\nSorted list according to frequency then alphabetically..\n";
    for (int x = 0 ; x < count.size(); x++) {
        cout<<count[x].letter<< " = "<< count[x].freq<<"\n";
    }

    system("pause");
    return 0;
}

示例输出:

Enter a string: hello world

Unsorted list..
d = 1
e = 1
h = 1
l = 3
o = 2
r = 1
w = 1

Sorted list according to frequency then alphabetically..
l = 3
o = 2
d = 1
e = 1
h = 1
r = 1
w = 1
Press any key to continue . . .

我基本上只是遵循@OliCharlesworth的建议,并通过本指南A Function Pointer as Comparison Function实现了一个自定义比较器。

尽管我很确定我的代码仍然可以提高效率,但是我仍然对结果感到满意。