递归函数一定要弄清楚这个函数是做什么用的,基于什么条件返回什么结果!...

总结一下:写递归时一定要清楚这个递归函数的每个参数是干嘛用的,以及这个递归函数是干什么用的,返回了什么结果。两篇文章都有提到!

So, here are the steps to writing a recursive function.

  1. Write a prototype for the recursive function.
  2. Write a comment that describes what the function does.
  3. Determine the base case (there may be more than one), and its solution(s).
  4. Determine what smaller problem (or problems) to solve. If it makes it easier for you to follow, save the solutions to the smaller problems to local variables (e.g.,smallin thesum()example).

    ASSUME the recursive call works (similar to inductive hypothesis in CMSC 250), i.e., that it will correctly compute the answer.

  5. Use the solutions of the smaller problem to solve the larger problem. (If this is done INCORRECTLY, the solutions of the smaller problems will also be computed incorrectly, thus, the assumption in the previous step will fail).
Mission Statements
A very important thing to consider when writing a recursive algorithm is to have a clear idea of our function's "mission statement." For example, in this case I've assumed that a person shouldn't be counted as reporting to him or herself. This means "countEmployeesUnder('Betty')" will return zero. Our function's mission statment might thus be "Return the count of people who report, directly or indirectly, to the person named in employeeName - not including the person named employeeName ."



C M S C 2 1 4
C o m p u t e r S c i e n c e I I
F a l l 2 0 0 2



Recursive Functions

What's a Recursive Function?

Technically, a recursive functionis a function that makes a call to itself. To prevent infinite recursion, you need an if-else statement (of some sort) where one branch makes a recursive call, and the other branch does not. The branch without a recursive call is usually the base case(base cases do not make recursive calls to the function).

Functions can also bemutually recursive. For example, functionf()can call functiong()and functiong()can call functionf(). This is still considered recursion because a function can eventually call itself. In this case,f()indirectly calls itself.

Functions can betail-recursive. In a tail-recursive function, none of the recursive call do additional work after the recursive call is complete (additional work includes printing, etc), except to return the value of the recursive call.

The following is typical of a tail-recursive function return.

   return rec_func( x, y ) ; // no work after recursive call, just return the value of call
The following is NOT tail-recursive.
   return rec_func( x, y ) + 3 ; // work after recursive call, add 3 to result
because once rec_funcis done, it must add 3, and then return that value. Hence, additional work. Hence, not tail-recursive.

It's common, in tail-recursive functions, to have one of the parameters be pass-by-reference (though this is not necessary), which is used to accumulate the answer. Once the base case is reached, you simply return the parameter value, which has the answer. Thus, tail-recursive functions often require an additional parameter, where non tail-recursive functions do not.

The main drawback with recursion is that it can useO(n)space (stack space) when a simple loop may only useO(1). For example, printing an array should only requireO(1)space in addition to the array. You just need a looping variable, which requiresO(1)space. The recursive solution usesO(n)space.

However, a good compiler can determine if a function is tail-recursive, and internally produce code that runs inO(1)space, thus giving you the benefits of recursion (i.e., recursion is "neat" and compact to write) but with the space efficiency of a loop. Languages other than C++ (say, ML) often require tail-recursion be converted to loops by the compiler. Typical C++ compilers generally (I believe) do not make this optimization.

When to Use Recursion?

Some people think recursion is so neat, they would like to use it all the time. In general, recursion should be used when you know the number of recursive calls isn't excessive. That number depends somewhat on how much memory you have. Usually, a 1000 recursive calls should be fine. Stack sizes can now be several megabytes of memory, which allows recursion to go fairly deep without causing a core dump.

Sometimes recursion is used because it produces a cleaner answer compared to the iterative version. For example, nearly all code written for tree-like structures is recursive. Many sorting algorithms are more naturally written recursively as well.

However, recursive solutions can be VERY inefficient, if you aren't careful. For example, the obvious recursive solution to compute the Nth Fibonacci numbers has exponential running time, even though the loop version runs inO(n). This is one good reason to study algorithms (in CMSC 351)---so you know what the running time of a recursive algorithm is, and decide if that solution is worthwhile.

If it sounds like recursion is always bad, realize that recursion often produces solutions that are very compact (requires few lines of typed code). Code with fewer lines are easier to debug than code with many lines. Writing recursive functions can give you greater confidence that you are coding correctly. Often, writing code that ought to be recursive without recursion (i.e., as loops) produces messy code. You may even have to simulate a stack (recursion uses the program stack behind the scenes) to get the behavior you want.

How to Think Recursively

When you want to write a recursive function, begin by writing its prototype. Define what the recursive function does in English (or whatever language you prefer).

For example, consider the following stand-alone (non-member) function:

 
   // Sums first n elements of arr
   int sum( int arr[], int n ) ;
When you make the call:
 
   sum( arr, 10 ) ;
You know this is summing the first 10 elements of arr.

Writing down the purpose of the function may seem like a trivial step, but it's important. Students often fail to write recursive functions correctly because they forget what the function is trying to do.

Then, repeat the following to yourself several times:Recursion solves a big problem (of sizen, say) by solving one or more smaller problems, andusing the solutionsof the smaller problems, to solve the bigger problem.For example, suppose you are running for some fund-raising marathon. You have collected a large stack of pledges, but want to know what the total amount that people have pledged to you. This stack is rather large, and you'd rather not do the work yourself.

However, you have many willing friends. You divide the stack of pledges and ask your friend "Could you please add up the dollar amount in pledges in this stack? I've only given you half, so there's half the work to do.". As a sneaky person, you give the other half to another friend, and say the same thing. Once both are done, they will give their answer to you, and you add their results.

Thus, you have broken down the problem into two smaller parts, and asked your friends to do the work.

Now those friends are clever, so they divide the stack into two parts (now each has two stacks with size 1/4) and ask two of their friends. When their friends are done, they return their answer, and the result is summed.

Eventually, there is only a stack of two pledges, and these are given to two friends, and those friends, seeing how silly the problem is now, just tell the first friend the only value on the pledge. There's no need to ask any more friends, because you're down to one pledge (this is the base case).

Thus, recursion is all about breaking a problem down, and solving that, and that smaller problem is solved by breaking it down some more, and trying to solve that. Eventually, you reach an easy solution (the base case), and return the solution.

It's interesting to think about what happens to the stack when this happens. As you make each recursive call, the stack grows larger and larger. When you reach the base case, the recursion is done, and the stack becomes smaller and smaller, as it passes the solution back.

Once you have the prototype written, think about solving the next smaller sized problem. Thus, if the call is:

   sum( arr, n ) ;
what's the next smallest size? What about n - 1?
   sum( arr, n - 1 ) ;
Suppose someone gave you the answer to this sum. What would you have? This is where it's important to remember how you defined the function. This would give you the sum of the first n - 1elements.

Now that you have this solution (which you can assume), what's needed to solve the entire problem? Well, you haven't addedarr[ n - 1 ]. So, do that.

Finally, you should deal with the base case, which is the smallest problem (in terms of input size, n) you can solve without any recursive calls. It turns out that

   sum( arr, 0 ) ;
is the smallest input size. While having an array of size 0 may not make sense, it's fine. Just let this value be 0 (since 0, summed to any value, just gives you that value). 0 is the additive identity.

Here's the code:

int sum( int arr[], int n )
{
  if ( n == 0 )  // base case
    return 0 ;   // no recursive call
  else
    {
      int small = sum( arr, n - 1 ) ; // solve smaller problem
      // use solution of smaller to solve larger
      return small + arr[ n - 1 ] ;
    }
}
So, here are the steps to writing a recursive function.
  1. Write a prototype for the recursive function.
  2. Write a comment that describes what the function does.
  3. Determine the base case (there may be more than one), and its solution(s).
  4. Determine what smaller problem (or problems) to solve. If it makes it easier for you to follow, save the solutions to the smaller problems to local variables (e.g.,smallin thesum()example).

    ASSUME the recursive call works (similar to inductive hypothesis in CMSC 250), i.e., that it will correctly compute the answer.

  5. Use the solutions of the smaller problem to solve the larger problem. (If this is done INCORRECTLY, the solutions of the smaller problems will also be computed incorrectly, thus, the assumption in the previous step will fail).

What Makes Recursion Work

Recursion works only when a problem has a recursive structure. This means that the problem's solution is similar for a large input size as well as a small input size. If reducing the input size causes the solution to look different, then it will be hard to use recursion. However, for the same reason, it can be hard to use loops too. Loops also depend on doing the same thing over and over, but on, say, a different index of an array, or a different node in a linked list.

Also, you must be able to use the solutions of the small problem to help solve the larger one. Again, if the solutions aren't useful to you, then recursion isn't going to be useful.

Fortunately, many problems exhibit this kind of behavior. If you can write it in a loop, there are ways to convert it to recursion (and vice versa).

Misconceptions about Recursion

Suppose function f()calls g()and then function g()calls h()which then calls i(). What happens when i()is done?

As you know, the flow of execution goes back toh()and when that's done, it goes back tog(), when wheng()is done, it goes back tof().

This is how the call stack works. Each function call causes some memory to be pushed on a stack. Once the call is done, that memory is popped off the stack, and the flow of control goes back to the function that made the call.

Still, even though most students in 214 have no trouble understanding this idea of a function call and its return, they think that recursion is somehow different. In particular, they think that once the base case is reached, the function is magically done. They think no return occurs, and the previous recursive calls disappear. It doesn't. In this respect, recursive calls are just like regular function calls. In fact, they are not implemented any different (except possibly that they can be optimized) from regular function calls.

For example, if functionf()callsf(), which callsf(). Then, when the last call tof()is completed (presumably the base case), you return back to the second call tof(), and when that is done, it goes back to the original call. This is just like non-recursive function calls.

I call this process winding and unwinding. Each recursive call is like walking up one step. Eventually you reach the top step (the base case). When you're done, you back down one step at a time, until you are back at the bottom step (the original call).

Don't forget that even recursive functions return back, just like non-recursive functions do.

The second misconception about recursion is that the base case is only called when the value passed in as argument is the base case value. Technically, this is not wrong. This is when the base case is run.

However, any recursive call (that doesn't end in infinite recursion), eventually calls the base case. Thus, when you call
sum( arr, 10 )the base case is eventually called. Some people think the base case only gets called when you call
sum( arr, 0 ), and while this is true,sum( arr, 0 )is eventually called when you callsum( arr, 10 ).

The lesson? Base case contributes to the solution ofanyrecursive call (if it doesn't, you have infinite recursion, or a core dump).

Helper Functions

Recursive functions rely heavily on parameters and return values of functions to do their work. Indeed, the basic unit of a recursive function is a function. This may seem obvious, but it really isn't. If you only use loops, then theoretically, you can write one large main()and never have to write another function again. Loops don't really require functions. Recursion does.

When writing recursive functions, you have to write functions (I suppose one could makemain()recursive, and let it do all the work, but it's not very elegant). This has one good side-effect. Functions tend to be shorter if written recursively than if written using loops.

However, because recursive functions rely on parameters and return values, what happens if the function you write doesn't have the parameters you need.

For example, consider a singly linked list. You wish to print out all the values in the list. The prototype in the class header file looks like:

   void print() ;
Without any parameters, you can't write this recursively. What do you do? You write a recursive helper function.
   void printRec( Node * curr ) ; // helper
print()calls printRec( first ), where printRec()is the true recursive function.

Often, you need such helpers so you can make recursive calls. The helpers have the parameters you need to make it work. So,print(), while not recursive, makes a call to a helper function which is recursive, and does all the work.print()is basically a "bootstrap" function, which calls the recursive helper function with the correct initial value as argument.

Common Mistakes With Helpers

Just because it's useful to write recursive functions with helpers, don't overdo it. The most common mistake when writing helper functions is to use the EXACT SAME PROTOTYPE.

Thus, you haverec_funcwhich takes two parameter types, which callsrec_func_helpwhich takes the same two parameter types! Every semester, someone does this on an exam (writes a recursive function, which calls a helper with the exact same parameter list), leaving graders to scratch their heads wondering why students are doing this.

Why bother writing a helper when the original function would have been fine? Write helpers only because you need a different parameter list. Otherwise, don't use helper functions.

Avoiding Static/Global Variables

Generally, I put static variables in the same category as global variables, and prefer to avoid them when I can. In particular, using static variables to do recursion can lead to problems. For example, static variables are initialized when the function is initially called for the first time. However, it's hard to re-initialize them afterwards.

Consider

  int sum ( int arr[], int n )
  {
     int result = 0 ;
     if ( n == 0 )
        return result ;
     else {
        result += arr[ n - 1 ] ;
        sum( arr, n - 1 ) ;
     }
  }
This should work fine the first time you call it. However, the second time, resultis not 0, and your answer will be wrong.

Instead, you should always update arguments to the recursive function.

You can "cheat" somewhat by using a reference parameter. This isn't a static variable, nor is it global, though it behaves like a global variable. The "pure" form of recursion avoids those kinds of references (arrays are usually fine because arrays are often not modified in the recursive call).

Nevertheless, you should know how to use reference parameters. Almost always, you need helper recursive functions to use reference parameters. The initial function is not recursive, but declares some local variables and initializes it, then calls the recursive function and passes the variables to the reference parameters.

Converting a Loop to Recursion

Converting a loop to recursion is not as hard as it seems. This is nothow you would normally write a recursive function. To write a recursive function, you generally want to think "recursively".

As mentioned earlier, thinking recursively means solving a large instance of a problem by solving a smaller instance of the problem, assuming that the smaller instance can be solved, and using that solution to solve the bigger problem.

Nevertheless, it's fairly easy to convert afor-loopto recursion, should you choose to do so.

Here's a typical loop.

   for ( <init> ; <cond> ; <update> )
      <body>
This can be converted to two functions: the main function and the helper recursive function.
void recFunc() {
   int loopVar = <init>
   
   recHelperFunc( loopVar );
}

void recHelperFunc( int loopVar ) {
   if ( <cond> ) {
      <body>
      <update>
      recHelperFunc( loopVar ); // recursive call
   }
}
The loop has been replaced by an if-statement. The recursive call at the end replaces the loop. Since the looping variable is being updated, you can save some memory by passing the variable by reference.

However, the looping variablecanbe passed by value as well. Sometimes, it's nicer to pass by value, because if you pass by reference, then you must pass something that looks like a variable. Passing an expression (e.g.,i + 1) is not allowed for a non-const reference, but is permitted for a value parameter.

Let's look at how to convert a simple loop.

   for ( int i = 0 ; i < n ; i++ )
     cout << i << endl;
Suppose that nis a variable, then, you need two parameters in the recursive helper function.
void recFunc() {
   int i = 0 ;
   recHelperFunc( i, n ) ; // recursive call
}

void recHelperFunc( int i, int n ) {
   if ( i < n ) {  // condition of loop
      cout << i << endl ;
      i++;
      recHelperFunc( i, n ) ; // recursive call
   }
}
In general, you should avoid using ++ on variables that are passed as arguments. In particular, don't use post-increment or post-decrement, since, in principle, the update won't occur until AFTER the function call, and at that point, you will cause infinite recursion.

It's better to update the value prior to calling the function, and pass the variable in. Alternately, if you are passing by value, use an expression that is not side-effecting.i++has a side effect (it updatesi), where asi + 1is not side effecting (it doesn't updatei).

Recall that when you pass an argument by value, a copy is created. Thus, every single timerecHelperFunc(), a new copy ofiandnare created.

Admittedly, this is somewhat of a waste becausennever changes, but recursion often does this.

"Real" Recursion

We can try to write this function more "recursively". That is, to think about how to solve the smaller problem. Initially, you'd think about what kind of function you want. The goal is to print the numbers from 0 to n - 1. So you can imagine having a function to do this:
void printIt( int n );
The recursive call passes in n.

Now, what would be a smaller recurisve call?

void printIt( n - 1 );
This function would print out 0 up to n - 2. If this occurred, what would we do to print the rest? You would print n - 1. The base case is when n == 0.

So, here's how you would write the code in a more traditional recursive way.

void printIt( int n ) {
   if ( n == 0 ) { // base case
      cout << 0 << endl;
   }
   else { // recursive case
      printIt( n - 1 );  // recursive call
      cout << n - 1 << endl;
   }
}
Notice that you pass one fewer parameter this way, although it doesn't match up with the loop nearly as well. In general, you would "prefer" to do it this way, because it's more "natural" when writing recursive functions.

Here's an analogy. There are two ways to learn a foreign language. Either learn a foreign language by learning it like native speakers speak it, or learn to translate every word in your native language to the foreign language. The second method works, somewhat, but isn't as "fluent" as the first.

For coding, of course, it doesn't matter which method, as long as it works, but nevertheless, the second method we looked at it more "fluent" since the problem is solved from the usual methodology of solving smaller problems and using it to solve bigger problems.

Occasionally, you must use the loop method to come up with a recursive call, because not all problems are nearly as easy to break down into smaller cases.



http://www.cs.umd.edu/class/fall2002/cmsc214/Tutorial/recursion2.html

http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=recursionPt1

http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=recursionPt2




<think>嗯,我现在需要弄清楚SQL中的REVERSE函数的作用。我对这个函数不太熟悉,但记得在SQL中有一些字符串处理函数,比如SUBSTRING、UPPER、LOWER之类的,可能REVERSE也是类似的。那它的作用是什么呢? 首先,我应该回忆一下是否在某个数据库系统中见过这个函数。比如,MySQL有没有REVERSE?或者SQL Server、PostgreSQL?不同的数据库系统有时候有不同的内置函数。比如,我记得在MySQL中,确实有一个REVERSE函数,它用来反转字符串。例如,把'hello'变成'olleh'。这可能就是它的基本功能。 那其他数据库系统呢?比如SQL Server,有没有这个函数?或者Oracle?比如,在SQL Server中,可能没有内置的REVERSE函数,或者有的话用法是否相同?或者是否在其他扩展中?可能用户的问题可能指的是某个特定数据库系统的函数,比如MySQL的,所以需要指出这可能因数据库而异。 所以,REVERSE函数的作用应该是将字符串的字符顺序反转。例如,输入'12345',输出就是'54321'。这可能在某些情况下有用,比如生成反向的字符串用于某些特定的查询,或者处理数据时需要反转字符串。 那应用场景有哪些呢?比如,可能需要检查回文,或者隐藏某些信息的时候反转显示。或者处理某些编码的情况。或者有时候需要将字符串倒序排列以便进行比较或匹配。 不过需要注意的是,这个函数可能在不是所有的数据库系统中都存在。比如,在MySQL中存在,但其他如PostgreSQL可能使用其他方法,或者没有这个函数。因此,在回答时应该提到这一点,说明该函数的可用性取决于具体的数据库系统。 另外,有没有例子可以说明?比如,在MySQL中,执行SELECT REVERSE('abcde')会返回'edcba'。而如果数据库不支持这个函数,可能需要用其他方法,比如递归查询或者自定义函数来实现。 此外,有没有什么注意事项?比如,处理多字节字符(如UTF-8中的中文或特殊符号)时,是否会影响反转的结果?比如,如果字符串包含多字节字符,REVERSE函数是否能正确处理?比如,在MySQL中,如果字符集是utf8,反转一个包含多字节字符的字符串可能会出错,因为函数是按字节反转而不是按字符反转的,这样可能导致乱码。例如,'你好'反转后可能变成不可读的字符。这一点可能需要提醒用户注意。 总结来说,REVERSE函数的作用是反转字符串中的字符顺序,但具体实现和可用性取决于数据库系统,使用时需注意字符编码的问题。</think>在 SQL 中,`REVERSE` 函数的作用是**将字符串的字符顺序反转**。例如,输入 `'Hello'`,输出为 `'olleH'`。不过需注意以下几点: --- ### 核心功能 - **字符串反转**:将输入字符串的字符顺序从后向前重新排列。 - 示例:`SELECT REVERSE('123')` 返回 `'321'`。 - 用途:可用于生成反向文本、检查回文、特定格式处理等场景。 --- ### 数据库兼容性 - **MySQL**:明确支持 `REVERSE()`,如 `SELECT REVERSE('abc');` → `'cba'`。 - **SQL Server**:也支持该函数,用法类似。 - **PostgreSQL**:不直接提供 `REVERSE()`,但可通过组合其他函数(如 `ARRAY` 和 `STRING_AGG`)实现。 - **Oracle**:无内置 `REVERSE`,需用 `UTL_RAW` 或自定义函数实现。 --- ### 注意事项 1. **多字节字符问题**: - 如果字符串包含多字节字符(如中文、Emoji),部分数据库(如 MySQL)可能按字节而非字符反转,导致乱码。 - 示例:`REVERSE('你好')` 可能返回无效字符(取决于字符集和数据库实现)。 2. **性能**: - 对长文本频繁使用可能影响效率,建议谨慎处理大数据量场景。 --- ### 示例(以 MySQL 为例) ```sql SELECT REVERSE('数据库'); -- 可能输出无效字符(若字符集处理不当) SELECT REVERSE('hello'); -- 输出 'olleh' ``` --- ### 替代方案 如果数据库不支持 `REVERSE`,可通过其他方式实现,如: - **PostgreSQL**: ```sql SELECT STRING_AGG(ch, '') FROM ( SELECT ch FROM regexp_split_to_table('hello', '') AS ch ORDER BY ROW_NUMBER() OVER () DESC ) AS t; ``` --- 总结:`REVERSE` 是便捷的字符串反转工具,但使用前需确认数据库支持性,并注意多字节字符的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值