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

Posted

技术标签:

【中文标题】首先按频率对字符串中的字符进行排序,然后按字母顺序排序【英文标题】:Sorting characters in a string first by frequency and then alphabetically 【发布时间】:2014-01-10 20:51:52 【问题描述】:

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

这是我目前能够做到的:

我创建了一个大小为 26 的 int 数组,对应于字母表中的 26 个字母,其中各个值表示它在句子中出现的次数 我将这个数组的内容推入了一个由intchar 组成的对向量vint 表示频率,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;

【问题讨论】:

您可以通过使用自定义 比较器 运行您的排序一步来解决此问题(请参阅en.cppreference.com/w/cpp/algorithm/sort 以获取示例)。 你也可以使用map&lt;char, int&gt; @Gabriel L.,但即使我使用地图,我仍然无法直接对其值进行排序,对吗?谢谢! 【参考方案1】:

你可以简化很多,分两步:

    先用一个map统计字符串中每个字符出现的次数:

    std::unordered_map<char, unsigned int> count;
    
    for( char character : string )
        count[character]++;
    

    使用该地图的值作为比较标准:

    std::sort( std::begin( string ) , std::end( string ) , 
               [&]( char lhs , char rhs )
               
                   return count[lhs] < count[rhs];
               
             ); 
    

Here 是在 ideone 上运行的一个工作示例。

【讨论】:

此示例按字符出现正确计数,但此后不按字母顺序排序! 这段代码有一个错误,我不知道排序到底在哪里失败,但是试试这个字符串“AZBIPTOFTJCJJIK” 输出是 JJJJITTIAZBPOFCK 看到两个不连续的地方插入了“I”字符! 【参考方案2】:

如果您想要最高频率然后最低字母,一种简单的方法是存储频率的负值,然后在排序后将其取反。更有效的方法是更改​​用于排序的函数,但这有点棘手:

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());

【讨论】:

【参考方案3】:

(代表 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。

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

【讨论】:

【参考方案4】:
// CODE BY VIJAY JANGID in C language
// Using arrays, Time complexity - ( O(N) * distinct characters ) 
// Efficient answer

#include <stdio.h>

int main() 

    int iSizeFrequencyArray= 58;
    //  122 - 65 = 57  for A to z
    int frequencyArray[iSizeFrequencyArray]; 

    int iIndex = 0;

    // Initializing frequency to zero for all
    for (iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++) 
        frequencyArray[iIndex] = 0;
    

    int iMyStringLength = 1000;
    char chMyString[iMyStringLength];

    // take input for the string
    scanf("%s", &chMyString);

    // calculating length
    int iSizeMyString;
    while(chMyString[++iSizeMyString]);

    // saving each character frequency in the freq. array
    for (iIndex = 0; iIndex < iSizeMyString; iIndex++) 
        int currentChar = chMyString[iIndex];
        frequencyArray[currentChar - 65]++;
    

    /* // To print the frequency of each alphabet
    for (iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++) 
        char currentChar = iIndex + 65;
        printf("\n%c - %d", currentChar, frequencyArray[iIndex ]);
    
    */

    int lowestDone = 0, lowest = 0, highestSeen = 0;

    for( iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++ ) 
        if(frequencyArray[iIndex] > highestSeen) 
            highestSeen = frequencyArray[iIndex];
        
    

    // assigning sorted values to the current array
    while (lowest != highestSeen) 

        // calculating lowest frequency
        for( iIndex = 0; iIndex < iSizeFrequencyArray; iIndex++ ) 

            if( frequencyArray[iIndex] > lowestDone &&
               frequencyArray[iIndex] < lowest) 
                lowest = frequencyArray[iIndex]; // taking lowest value
            
        

        // printing that frequency
        for( iIndex =0; iIndex < iSizeFrequencyArray; iIndex++ ) 

            // print that work for that times
            if(frequencyArray[iIndex] == lowest)
                char currentChar = iIndex + 65;
                int iIndex3;
                for(iIndex3 = 0; iIndex3 < lowest; iIndex3++)
                    printf("%c", currentChar);
                
            
        

        // now that is done, move to next lowest
        lowestDone = lowest;

        // reset to highest value, to get the next lowest one
        lowest = highestSeen+1;

    

    return 0;


说明:

    首先创建数组来存储重复大小 (112 - 65) 以存储从 A 到 z 的 asci 字符。 通过在每次出现时递增来存储每个字符的频率。 现在找到最高频率。 运行一个循环,条件为(最低!= 最高),其中最低 = 0。 现在在每次迭代中打印频率等于最低的字符。它们将自动按字母顺序排列。 最后找到下一个更高的频率并打印,以此类推。 当最低到达最高时,然后打破循环。

【讨论】:

【参考方案5】:

按照@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

【讨论】:

【参考方案6】:
#include<stdio.h>

// CODE BY AKSHAY BHADERIYA

char iFrequencySort (char iString[]);
void vSort (int arr[], int arr1[], int len);

int
main ()

  int iLen, iCount;
  char iString[100], str[100];
  printf ("Enter a string : ");
  scanf ("%s", iString);

  iFrequencySort (iString);

  return 0;



char
iFrequencySort (char iString[])

  int iFreq[100] =  0 ;
  int iI, iJ, iK, iAsc, iLen1 = 0, iLen = 0;

  while (iString[++iLen]);

  int iOccurrence[94];
  int iCharacter[94];

  for (iI = 0; iI < iLen; iI++)
                   //frequency of the characters
      iAsc = (int) iString[iI];
      iFreq[iAsc - 32]++;
    



  for (iI = 0, iJ = 0; iI < 94; iI++)
                   //the characters and occurrence arrays
      if (iFreq[iI] != 0)
    
      iCharacter[iJ] = iI;
      iOccurrence[iJ] = iFreq[iI];
      iJ++;
    
    
  iLen1 = iJ;

  vSort (iOccurrence, iCharacter, iLen1);   //sorting both arrays

  /*letter array consists only the index of iFreq array.
     Converting it to the ASCII value of corresponding character */
  for (iI = 0; iI < iLen1; iI++)
    
      iCharacter[iI] += 32;
    
  iK = 0;
  for (iI = 0; iI < iLen1; iI++)
                   //characters into original string
      for (iJ = 0; iJ < iOccurrence[iI]; iJ++)
    
      iString[iK++] = (char) iCharacter[iI];
    
    
  printf ("%s", iString);


void
vSort (int iOccurrence[], int iCharacter[], int len)

  int iI, iJ, iTemp;
  for (iI = 0; iI < len - 1; iI++)
    
      for (iJ = iI + 1; iJ < len; iJ++)
    
      if (iOccurrence[iI] > iOccurrence[iJ])
        
          iTemp = iOccurrence[iI];
          iOccurrence[iI] = iOccurrence[iJ];
          iOccurrence[iJ] = iTemp;

          iTemp = iCharacter[iI];
          iCharacter[iI] = iCharacter[iJ];
          iCharacter[iJ] = iTemp;
        
    
    

【讨论】:

【参考方案7】:

给出一个答案,一个被接受。我想给出一个额外的答案,显示此任务的标准方法。

通常需要先对事物进行计数,然后再取回它们的排名或某些最高值或其他信息。

最常见的解决方案之一是为此使用所谓的关联容器,特别是std::map,甚至更好的是std::unordered_map。这是因为我们需要一个键值,上面描述的方式是一个字母和一个关联的值,这里是这个字母的计数。钥匙是独一无二的。同一个字母不能超过一个。这当然没有任何意义。

关联容器通过键值访问元素非常有效。

好的,有 2 个。 std::mapstd::unordered_map。一种使用树以排序方式存储键,另一种使用快速散列算法访问键值。由于我们稍后对排序的键不感兴趣,但对出现的排序计数感兴趣,我们可以选择std::unordred_map。另一个好处是,这将使用提到的哈希算法快速访问密钥。

地图还有一个巨大的优势。有一个索引运算符[],它对于键值看起来非常快。如果找到,它将返回对与键关联的值的引用。如果没有找到,它将创建一个键并使用默认值(在我们的例子中为 0)初始化它的值。然后计算任意键就像map[key]++ 一样简单。

但是,后来,我们在这里经常听到:但它必须按计数排序。这当然不起作用,因为我的计数有重复的值,并且地图只能包含唯一的键值。所以,不可能。

解决方案是使用第二个关联容器std::multiset,它可以有更多相同的键和自定义排序运算符,我们可以在其中根据值进行排序。在这里,我们不是将键和值存储为 2 个元素,而是将两个值存储为 std::pair。我们按对的第二部分排序。

首先我们不能使用std::multi:set,因为我们需要唯一的键(在本例中是字母)。

上述方法为我们提供了极大的灵活性和易用性。我们基本上可以用这个算法计算任何东西

例如可以看下面的简洁代码:

#include <iostream>
#include <string>
#include <utility>
#include <set>
#include <unordered_map>
#include <type_traits>
#include <cctype>

// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<char, unsigned int>;

// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;

// Sorted values will be stored in a multiset
struct Comp  bool operator ()(const Pair& p1, const Pair& p2) const  return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second;  ;
using Rank = std::multiset<Pair, Comp>;
// ------------------------------------------------------------


// --------------------------------------------------------------------------------------
// Compact function to calculate the frequency of charcters and then get their rank
Rank getRank(std::string& text) 

    // Definition of our counter
    Counter counter;

    // Iterate over all charcters in text and count their frequency
    for (const char c : text) if (std::isalpha(c)) counter[char(std::tolower(c))]++;
    
    // Return ranks,sorted by frequency and then sorted by character
    return  counter.begin(), counter.end() ;

// --------------------------------------------------------------------------------------
// Test, driver code
int main() 
    // Get a string from the user
    if (std::string text; std::getline(std::cin, text))

        // Calculate rank and show result
        for (const auto& [letter, count] : getRank(text))
            std::cout << letter << " = " << count << '\n';

请查看使用的最少语句。非常优雅。


但我们经常看到数组被用作关联容器。它们还有一个索引(一个键)和一个值。缺点可能是未使用密钥的空间开销。此外,这只适用于已知量级的东西。例如 26 个字母。其他国家的字母可能有更多或更少的字母。那么这种解决方案就没有那么灵活了。反正也经常用也OK。

因此,您的解决方案可能稍微复杂一些,但当然仍然有效。

让我再举一个例子来获取任何容器的最高值。在这里您将看到,这样的解决方案是多么灵活。

对不起,它有点高级。 . .

#include <iostream>
#include <utility>
#include <unordered_map>
#include <queue>
#include <vector>
#include <iterator>
#include <type_traits>
#include <string>


// Helper for type trait We want to identify an iterable container ----------------------------------------------------
template <typename Container>
auto isIterableHelper(int) -> decltype (
    std::begin(std::declval<Container&>()) != std::end(std::declval<Container&>()),     // begin/end and operator !=
    ++std::declval<decltype(std::begin(std::declval<Container&>()))&>(),                // operator ++
    void(*std::begin(std::declval<Container&>())),                                      // operator*
    void(),                                                                             // Handle potential operator ,
    std::true_type);
template <typename T>
std::false_type isIterableHelper(...);

// The type trait -----------------------------------------------------------------------------------------------------
template <typename Container>
using is_iterable = decltype(isIterableHelper<Container>(0));

// Some Alias names for later easier reading --------------------------------------------------------------------------
template <typename Container>
using ValueType = std::decay_t<decltype(*std::begin(std::declval<Container&>()))>;
template <typename Container>
using Pair = std::pair<ValueType<Container>, size_t>;
template <typename Container>
using Counter = std::unordered_map<ValueType<Container>, size_t>;
template <typename Container>
using UnderlyingContainer = std::vector<Pair<Container>>;

// Predicate Functor
template <class Container> struct LessForSecondOfPair 
    bool operator () (const Pair<Container>& p1, const Pair<Container>& p2)  return p1.second < p2.second; 
;
template <typename Container>
using MaxHeap = std::priority_queue<Pair<Container>, UnderlyingContainer<Container>, LessForSecondOfPair<Container>>;


// Function to get most frequent used number in any Container ---------------------------------------------------------
template <class Container>
auto topFrequent(const Container& data) 

    if constexpr (is_iterable<Container>::value) 

        // Count all occurences of data
        Counter<Container> counter;
        for (const auto& d : data) counter[d]++;

        // Build a Max-Heap
        MaxHeap<Container> maxHeap(counter.begin(), counter.end());

        // Return most frequent number
        return maxHeap.top().first;
    
    else
        return data;

// Test
int main() 
    std::vector testVector 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 ;
    std::cout << "Most frequent is: " << topFrequent(testVector) << "\n";

    double cStyleArray[] =  1.1, 2.2, 2.2, 3.3, 3.3, 3.3 ;
    std::cout << "Most frequent is: " << topFrequent(cStyleArray) << "\n";

    std::string s "abbcccddddeeeeeffffffggggggg" ;
    std::cout << "Most frequent is: " << topFrequent(s) << "\n";

    double value = 12.34;
    std::cout << "Most frequent is: " << topFrequent(value) << "\n";

    return 0;

【讨论】:

以上是关于首先按频率对字符串中的字符进行排序,然后按字母顺序排序的主要内容,如果未能解决你的问题,请参考以下文章

首先按布尔列对数组进行排序,然后按字符串列排序

首先按字母顺序对对象数组进行排序,然后按数字排序

java - 如何仅使用Java中的util.Scanner包按字母顺序对单个字符串进行排序?

如何在 swift 中按字母顺序对 JSON 字符串进行排序?

text 按字母顺序对字符串中的字符进行排序#array #sort

使用 Linq 按名称频率对列表进行排序