Data Structure and Algorithm (Golang Implementation) (8.2) Basic Knowledge-Divide and Conquer and Recursion

Divide and conquer and recursion

In computer science, the divide and conquer method is a very important algorithm.

The literal interpretation is 分而治之to divide a complex problem into two or more identical or similar sub-problems.

Until the last sub-problem can be solved directly, the solution of the original problem is the combination of the sub-problems.

The divide-and-conquer approach generally uses recursion to solve problems.

1. Recursion

Recursion is to continuously call the function itself.

For example, we find the factorial 1 * 2 * 3 * 4 * 5 *...* N:

package main
import "fmt"

func Rescuvie(n int) int {
    if n == 0 {
        return 1
    }

    return n * Rescuvie(n-1)
}

func main() {
    fmt.Println(Rescuvie(5))
}

Will repeatedly enter a function, its process is as follows:

Rescuvie(5)
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120

The function constantly calls itself, and it is also multiplied by a variable: n * Rescuvie(n-1)This is a recursive process.

It is easy to see that because the recursive type uses operators, each repeated call makes the chain of operations continue to lengthen, and the system has to use the stack to save and restore data.

If you need to operate on a longer and longer chain every time you recurse, it is extremely slow, and the stack may overflow, causing the program to crash.

So there is another way of writing, called tail recursion:

package main
import "fmt"

func RescuvieTail(n int, a int) int {
    if n == 1 {
        return a
    }

    return RescuvieTail(n-1, a*n)
}


func main() {
    fmt.Println(RescuvieTail(5, 1))
}

His recursive process is as follows:

RescuvieTail(5, 1)
RescuvieTail(4, 1*5)=RescuvieTail(4, 5)
RescuvieTail(3, 5*4)=RescuvieTail(3, 20)
RescuvieTail(2, 20*3)=RescuvieTail(2, 60)
RescuvieTail(1, 60*2)=RescuvieTail(1, 120)
120

Tail recursion means that the recursive function directly returns its value after calling itself, without adding operations to it, the efficiency will be greatly improved.

If all recursive calls in a function appear at the end of the function, we call this recursive function tail-recursive. When the recursive call is the last statement executed in the entire function body and its return value is not part of the expression, the recursive call is tail recursion. The characteristic of tail recursive functions is that no operation is required during the regression process. This feature is important because most modern compilers will use this feature to automatically generate optimized code. -From Baidu Encyclopedia.

Tail recursive functions, some high-level language compilers will optimize to reduce unnecessary stack generation, so that the program stack maintains a fixed number of layers, and there will be no stack overflow.

We will give several examples to illustrate.

2. Example: Fibonacci sequence

The Fibonacci sequence refers to a sequence in which the latter number is the sum of the first two numbers. as follows:

1 1 2 3 5 8 13 21 ... N-1 N 2N-1

The solution to tail recursion is:

package main
import "fmt"


func F(n int, a1, a2 int) int {
    if n == 0 {
        return a1
    }

    return F(n-1, a2, a1+a2)

}

func main() {
    fmt.Println(F(1, 1, 1))
    fmt.Println(F(2, 1, 1))
    fmt.Println(F(3, 1, 1))
    fmt.Println(F(4, 1, 1))
    fmt.Println(F(5, 1, 1))
}

Output:

1
2
3
5
8

The n=5current recursive process is as follows:

F(5,1,1)
F(4,1,1+1)=F(4,1,2)
F(3,2,1+2)=F(3,2,3)
F(2,3,2+3)=F(2,3,5)
F(1,5,3+5)=F(1,5,8)
F(0,8,5+8)=F(0,8,13)
8

3. Example: binary search

Find a certain number in a sequence that has been sorted, such as:

1 5 9 15 81 89 123 189 333

Find the number from the ordered sequence above 189.

The idea of ​​binary search is to 189compare the median of the ordered sequence first with the target number , and if it just matches the target, end.

If the median is larger than the target number, because it is already sorted, so the numbers on the right of the median are definitely larger than the target number, then look for it from the left of the median.

If the median is smaller than the target number, because the numbers are already sorted, the numbers to the left of the median are definitely smaller than the target number, then look for it from the right of the median.

This kind of divide-and-conquer, the split search is called the binary search algorithm.

Recursive solution:

package main

import "fmt"

// 二分查找递归解法
func BinarySearch(array []int, target int, l, r int) int {
    if l > r {
        // 出界了,找不到
        return -1
    }

    // 从中间开始找
    mid := (l + r) / 2
    middleNum := array[mid]

    if middleNum == target {
        return mid // 找到了
    } else if middleNum > target {
        // 中间的数比目标还大,从左边找
        return BinarySearch(array, target, 1, mid-1)
    } else {
        // 中间的数比目标还小,从右边找
        return BinarySearch(array, target, mid+1, r)
    }

}

func main() {
    array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
    target := 500
    result := BinarySearch(array, target, 0, len(array)-1)
    fmt.Println(target, result)

    target = 189
    result = BinarySearch(array, target, 0, len(array)-1)
    fmt.Println(target, result)
}

Output:

500 -1
189 7

It can be seen that 189this number is at the subscript 7of the sequence , but 500this number cannot be found.

Of course, recursive solutions can be converted to non-recursive, such as:

package main

import "fmt"

// 二分查找非递归解法
func BinarySearch2(array []int, target int, l, r int) int {
    ltemp := l
    rtemp := r

    for {
        if ltemp > rtemp {
            // 出界了,找不到
            return -1
        }

        // 从中间开始找
        mid := (ltemp + rtemp) / 2
        middleNum := array[mid]

        if middleNum == target {
            return mid // 找到了
        } else if middleNum > target {
            // 中间的数比目标还大,从左边找
            rtemp = mid - 1
        } else {
            // 中间的数比目标还小,从右边找
            ltemp = mid + 1
        }
    }
}

func main() {
    array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
    target := 500
    result := BinarySearch2(array, target, 0, len(array)-1)
    fmt.Println(target, result)

    target = 189
    result = BinarySearch2(array, target, 0, len(array)-1)
    fmt.Println(target, result)
}

Many computer problems can be solved using recursion. In theory, all recursive methods can be converted into non-recursive methods, but using recursion, the code is more readable.

Series article entry

I am the star Chen, Welcome I have personally written data structures and algorithms (Golang achieve) , starting in the article to read more friendly GitBook .

Published 12 original articles · Likes0 · Visits 90

Guess you like

Origin blog.csdn.net/m0_46803965/article/details/105595077