Java 递归算法和非递归算法的性能,大O表示法
想象一下,我必须检查一个字符串的所有字母是否都在另一个字符串中。我想比较两种实现,一种是尾部递归,另一种是使用hashMap。以下是两种实现:Java 递归算法和非递归算法的性能,大O表示法,java,algorithm,performance,recursion,big-o,Java,Algorithm,Performance,Recursion,Big O,想象一下,我必须检查一个字符串的所有字母是否都在另一个字符串中。我想比较两种实现,一种是尾部递归,另一种是使用hashMap。以下是两种实现: private boolean isPossible(final String left, final String right) { boolean toReturn = false; if (left.isEmpty()) { toReturn = true; } else { char cha
private boolean isPossible(final String left, final String right) {
boolean toReturn = false;
if (left.isEmpty()) {
toReturn = true;
} else {
char charAt = left.charAt(0);
final int index = right.indexOf(charAt);
toReturn = index != -1 ? isPossible(left.substring(1), stringWithoutIndex(right, index)) : false;
}
return toReturn;
}
以及hashMap解决方案:
public boolean isPossible(String left, String right) {
HashMap<Character, Integer> occurrencesMap = createOccurrenceMapFor(left);
HashMap<Character, Integer> withTheLettersInRightRemoved = removeLettersFoundIn(right, occurrencesMap);
return checkThatWeCanWriteTheMessage(withTheLettersInRightRemoved);
}
private HashMap<Character, Integer> removeLettersFoundIn(final String string, final HashMap<Character, Integer> occurrencesMap) {
HashMap<Character, Integer> lettersRemoved = new HashMap<>(occurrencesMap);
for (char c : string.toCharArray()) {
if (lettersRemoved.containsKey(c))
lettersRemoved.put(c, lettersRemoved.get(c).intValue() - 1);
}
return lettersRemoved;
}
private HashMap<Character, Integer> createOccurrenceMapFor(String string) {
HashMap<Character, Integer> occurrencesMap = new HashMap<>();
for (char c : string.toCharArray()) {
if (occurrencesMap.containsKey(c))
occurrencesMap.put(c, occurrencesMap.get(c).intValue() + 1);
else
occurrencesMap.put(c, 1);
}
return occurrencesMap;
}
private boolean checkThatWeCanWriteTheMessage(HashMap<Character, Integer> occurrencesMap) {
for (char c : occurrencesMap.keySet()){
if (withTheLettersInMagazineRemoved.get(c) > 0) {
return false;
}
}
return true;
}
公共布尔值是可能的(字符串左、字符串右){
HashMap OccurrenceMap=CreateCurrenceMapfor(左);
删除了TTERSINRIGHT的HashMap=删除了TTERSINRFOUND(右侧,发生率MAP);
返回我们可以写入邮件的支票(删除邮件右侧);
}
私有HashMap RemoveletterFoundin(最终字符串字符串,最终HashMap发生映射){
HashMap-lettersRemoved=新的HashMap(发生率映射);
for(char c:string.toCharArray()){
如果(已删除的信件,包括(c))
lettersRemoved.put(c,lettersRemoved.get(c).intValue()-1);
}
退回信件;
}
私有HashMap CreateCurrenceMapfor(字符串){
HashMap OccurrenceMap=新HashMap();
for(char c:string.toCharArray()){
if(发生率MAP.containsKey(c))
occurrencesMap.put(c,occurrencesMap.get(c.intValue()+1);
其他的
发生率map.put(c,1);
}
返回发生率MAP;
}
我们可以写入消息的私有布尔检查(HashMap occurrencesMap){
for(字符c:occurrencesMap.keySet()){
如果(带文本的InmagazineRemoved.get(c)>0){
返回false;
}
}
返回true;
}
我认为这两种解决方案都有O(n)性能,因为它们都没有for循环,等等。但是,一旦我比较了时间,我就会发现hashMap解决方案比递归解决方案快得多。当然,这是有道理的,但我想知道为什么,因为在理论上,两者都有O(n)。我说的对吗?第一个解决方案会检查第一个字符串中的每个字符,即O(N),但对于每个字符,它会搜索第二个字符串中的匹配字符,这会给出另一个内部/嵌套的O(N)和O(N^2) 第二个解决方案迭代第一个字符串O(N),然后迭代第二个字符串O(N),最后迭代hashmap,hashmap只包含有限的字符范围(一些常量)。总数为O(N)+O(N)+C=O(N)
aString。indexOf(aChar)
使用源字符串中字符的循环实现;因此它是O(aString.length
)。而stringWithoutIndex(aString,anIndex)
不能在优于O的(aString.length
)中实现。在最坏的情况下(当left
中的所有字符出现在right
中时),您将执行O(left.length
*right.length
)操作
您的第一个代码片段相当于:
private boolean isPossible(String left, String right) {
// loop will repeat left.length times at most
while (true) {
if (left.isEmpty()) {
return true;
} else {
char first = left.charAt(0);
left = left.substring(1);
// indexOf: O(right.length)
int index = -1;
for (int i=0; i<right.length; i++) {
if (right.charAt(i) == first) {
index = i;
break;
}
}
if (index >= 0) {
// stringWithoutIndex: concatenating strings is O(size of result)
right = right.substring(0, index) + right.substring(index+1);
} else {
return false;
}
}
}
}
私有布尔值是可能的(字符串左、字符串右){
//循环最多将重复左.长次
while(true){
if(left.isEmpty()){
返回true;
}否则{
char first=left.charAt(0);
左=左。子字符串(1);
//索引of:O(右。长度)
int指数=-1;
对于(int i=0;i=0){
//stringWithoutIndex:串联字符串为O(结果大小)
右=右。子字符串(0,索引)+右。子字符串(索引+1);
}否则{
返回false;
}
}
}
}
我只将递归转换为迭代,并扩展了indexOf
和stringWithoutIndex
——这使得复杂度更容易计算,因为循环很容易看到
请注意,从递归到迭代的机械转换(反之亦然)不会改变复杂度类;虽然,当代码可以编写为尾部递归时(如本例中的isPossible
),迭代代码可以更快一些,并且不会耗尽堆栈(因为它不使用它)。因此,许多编译器在幕后将尾部递归转换为迭代
对于内联函数也可以提出类似的论点(内联通常更快,但将保持在同一个大o复杂度类中),不过还有一个额外的大小权衡:如果代码在许多地方使用,内联会使编译的程序更大。第一个算法:o(n*m)
第二个算法:O(n)-您将看到可以从m
的大小中提取
请参见下面的反馈
第一个节目 递归函数不是尾部递归函数。没有尾声。 但我认为可以通过分离
return
语句将其转换为尾部递归函数。例如(我缩短了您的代码,但请验证)
不确定stringWithoutIndex(右,index)
到底是什么,但最好知道时间复杂度。我假设它只返回右字符串,而不返回给定索引处的字符,即O(m)
(m
是右字符串的长度)
我用数字表示函数中的代码行(1、2、3、4和5
)。我假设左字符串的长度为n
,右字符串的长度为m
O(1)
O(1)
O(m)
,因为在最坏的情况下,您会在最后一个索引处找到字符O(1)
O(m)
,删除右字符串中的一个字符O(m)
。由于迭代次数与左字符串中的字符相同,因此该算法的时间复杂度为O(m*n)
- 如果
,则它小于m
O(n^2)
- 如果
,那么它就是m=n
O(n^2)
- 如果
,那么它就是m>n
O(n*m)
O(
private boolean isPossible(final String left, final String right) {
if (left.isEmpty()) return true;
char firstChar = left.charAt(0);
int index = right.indexOf(charAt);
if (index == -1) return false;
return isPossible(left.substring(1), stringWithoutIndex(right, index));
}
O(n*k' + m*k+n + (n-m)*l)