首先按频率对字符串中的字符进行排序,然后按字母顺序排序
Posted
技术标签:
【中文标题】首先按频率对字符串中的字符进行排序,然后按字母顺序排序【英文标题】:Sorting characters in a string first by frequency and then alphabetically 【发布时间】:2014-01-10 20:51:52 【问题描述】:给定一个字符串,我试图计算字符串中每个字母的出现次数,然后将它们的出现频率从高到低排序。然后,对于出现次数相似的字母,我必须按字母顺序对它们进行排序。
这是我目前能够做到的:
我创建了一个大小为 26 的int
数组,对应于字母表中的 26 个字母,其中各个值表示它在句子中出现的次数
我将这个数组的内容推入了一个由int
和char
组成的对向量v
(int
表示频率,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<char, int>
@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::transform
或 std: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::map
和 std::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 字符串进行排序?