迭代回溯法

467 阅读6分钟

回溯是用于解决各种约束满足问题的算法技术之一。在这篇文章中,我们将在迭代(迭代回溯)的帮助下探索回溯的概念,并举例说明。时间和空间的复杂性将在文章的最后讨论。

目录

  1. 迭代简介
  2. 迭代的优点
  3. 逆向追踪简介
  4. 迭代逆向追踪与递归逆向追踪的比较
  5. 时间和空间复杂度

迭代简介

迭代是重复执行一组指令的过程,直到控制循环的条件变为错误。它包括初始化、比较、迭代中的语句执行和更新控制变量。在迭代过程中,拥有正确的控制条件是至关重要的;否则,程序可能会进入无限循环。例如,我们可以使用forwhile 循环,重复运行一连串的指令。

让我们来看看一个使用迭代计算n! 的例子。例如,如果我们设置n=4n_factorial 函数将在for loop 的范围内进行1 to 4

def n_factorial(n):
   result = 1
   for i in range(1, n+1):
       result = result * i
   return result

在每个迭代中,该函数将通过将两个数字相乘来进行计算,并将结果传递给下一个迭代。在我们下面的例子中,n = 4 ,在第一次迭代中,计算的结果是1 ,并传递给下一次迭代。在下一次迭代中,计算的结果是2 ,同样,这个结果也被传递到下一次迭代。接下来我们得到的结果是6 。在最后一次迭代时,n_factorial 函数返回的结果是24
iteration

迭代的优点

与递归相比,迭代更快、更有效。这是因为迭代可以用来重复执行一组语句,而没有函数调用的开销或对堆栈内存的利用。无限的递归会导致系统崩溃,而迭代的无限重复只是使用CPU周期。与递归相比,迭代不需要那么多内存或处理能力。迭代代码更容易优化,具有多项式时间复杂度。

逆向追踪的介绍

回溯是一种一般的算法,它在所有潜在的解决方案中对一个特定问题的解决方案进行系统的搜索。

在回溯中,我们通常从几个可获得的可能性中的一个潜在选择开始,然后逐步尝试解决问题。如果我们能够用所选的选择/动作来解决这个问题,我们可以简单地返回解决方案。否则,我们可以回溯并在尝试解决问题之前挑选另一种选择。在这方面,我们可以考虑以下情况。

  • 找到问题的解决方案,然后直接返回结果。
  • 如果我们穷尽所有的可能性而没有找到解决方案,我们就可以得出结论,给定的问题没有解决方案。

回溯为我们提供了很多的可能性,可以从中挑选。在选择了一个选项之后,我们又会看到一组新的可能性。这个过程不断重复,直到我们找到问题的解决方案。如果用一组特定的选项/动作不能发现解决方案,我们可以回溯并尝试另一组选项或动作。这个过程不断重复,直到我们解决约束满足问题。

让我们来看看没有相等的相邻子串问题以及回溯如何帮助我们找到解决方案。在下面的例子中,现在的数字串是01020 ,接下来可以用0, 1, 2 ,这样就没有相等的相邻子串了,可以填入什么数字。

如果X=0,它将与方案A相冲突
如果X=1,它将不会与A to C 中的任何方案相冲突。
我们将设置X为1,新的字符串现在是010201

A. 0102 0 X
B. 201 02 0X
C. 010 20X

如果X=0,它将与情景A相冲突;
如果X=1,它将与情景B相冲突;
如果X=2,它将与情景D相冲突

A. 010201 0 X
B. 0102 01 0X  
C. 01 020 10X
D. 0102 010X

在这种情况下,我们已经把所有可以分配给X的值都用完了。现在我们将回溯并尝试为X分配一个新的值。
如果X=1,它将与情景A冲突

如果X=2,它将不会与A to C
我们将X设置为2,现在新的字符串是0102012

A. 010201 X
B. 010 20 1X
C. 0 102 01X

如果X=0,它将不会与下面的任何方案相冲突。现在的字符串将变成01020120

A. 010201 2 X
B. 0102 01 2X
C. 01 020 12X
D. 0102 012X

让我们仔细看一下这个算法。

伪代码
def backtrack(largest_value, is_safe_function, total_slots_available):
    def backtrack_from(position):
        while True:
            if is_safe_function(solution, position):
                if position >= num_slots:
                    return solution
                position += 1
                solution[position] = 0
            else:
                # Backtrack
                while (solution[position] == max_val):
                    solution[position] = None
                    position -= 1
                if position < 0:
                    break
                solution[position] += 1      
        return None if no solution

    Return the solution as a list of values.
实施

让我们看看我们将如何实现我们问题的解决方案:

  • max_val - 我们将分配给槽的值将从 到0 max_val
  • is_safe - 这指的是no_adjacent 函数。它将接受两个参数,解决方案和位置。
  • num_slots - 我们要分配值的 "槽 "的总数。
  • solution - 该函数将以数值列表的形式返回解决方案。
def backtrack(max_val, is_safe, num_slots):

    solution = [None] * num_slots
    def backtrack_from(position):
        while True:
            if is_safe(solution, position):

如果值被分配到所有可用的槽中,函数将返回解决方案。

                if position >= num_slots-1:
                    return solution
                position += 1
                solution[position] = 0
            else:

如果没有可能的值可以被分配,我们将在这里进行回溯。

                while (solution[position] == max_val-1):
                    solution[position] = None
                    position -= 1
                if position < 0:
                    break
                solution[position] += 1

如果函数回溯超过了起始位置,这意味着我们无法找到一个有效的解决方案。


        return None
    solution[0] = 0
    return backtrack_from(0)

在这里,函数no_adjacent() ,检查数字是否满足约束条件,也就是说,它没有任何相邻的子串。测试约束条件的子串长度从1开始到整个字符串长度的一半。一旦发现一个相等的相邻子串,该函数将返回False。


def no_adjacent(string, up_to_index):

    length = up_to_index+1
    for j in range(1, length//2+1):
        if (string[length-2*j:length-j] == string[length-j:length]):
            return False
    return True

下面是驱动代码,用于打印出我们解决方案的输出。

print(''.join(str(v) for v in backtrack(3, no_adjacent, 10)))

一旦你运行该程序,你应该看到以下输出。

0102012021

整个代码被复制到下面供你参考

def backtrack(max_val, is_safe, num_slots):
    solution = [None] * num_slots
    def backtrack_from(position):
        while True:
            if is_safe(solution, position):
                if position >= num_slots-1:
                    return solution
                position += 1
                solution[position] = 0
            else:
                while (solution[position] == max_val-1):
                    solution[position] = None
                    position -= 1
                if position < 0:
                    break
                solution[position] += 1
        return None
    solution[0] = 0
    return backtrack_from(0)

def no_adjacent(string, up_to_index):
    length = up_to_index+1
    for j in range(1, length//2+1):
        if (string[length-2*j:length-j] == string[length-j:length]):
            return False
    return True

print(''.join(str(v) for v in backtrack(3, no_adjacent, 10)))

迭代逆向追踪与递归逆向追踪的比较

在迭代回溯中,我们使用forwhile 循环来解决约束条件满足问题。对于递归回溯,它使用递归来解决这个问题。由于递归的工作方式,它可能会遇到内存不足的问题。因此,它不断地递归调用函数而不进行任何计算。尽管如此,递归可以对复杂的问题产生惊人的简单答案。

时间和空间复杂度

时间复杂性。O(N²) 有k个嵌套循环,时间复杂度为O (N^k)。在我们的回溯函数中,k=2,因为有一个循环嵌套在第一个循环中。

空间复杂度:O(N) 。 通过OpenGenus的这篇文章,你一定对迭代回溯如何帮助我们解决约束满足问题有了完整的认识。