作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
️ CSDN实力新星,社区专家博主
博客主页:https://blog.csdn.net/Long_xu
递归算法的核心思想就是函数自己调用自己。
递归算法包括两个部分:基本情况和递归情况。基本情况是指一些简单、不需要继续拆分的子问题,而递归情况则是通过反复调用自身来处理较为复杂的子问题。
一个典型的递归函数通常包括以下几个要素:
递归算法的优点在于,它可以极大地简化程序的设计和实现,同时能够解决许多复杂的问题。然而,如果不合理使用递归可能会导致内存溢出等问题。因此,在实际编程中需要根据具体情况来选择使用递归或者非递归方法来解决问题。
递归算法包括三个基本要素:基线条件、递归条件和自我调用。下面分别来介绍一下这三个概念。
优点:
缺点:
使用递归算法需要注意控制好递归深度和程序运行效率。在实际开发中,可以根据具体情况采用迭代算法、动态规划等其他方式来替代或优化递归算法。
深度优先搜索(DFS)和广度优先搜索(BFS)是两种常用的图形遍历算法,它们都可以用递归实现。下面分别介绍一下这两种算法的原理、应用场景及如何使用递归来实现。
(1)深度优先搜索(Depth-First Search, DFS)是一种图形遍历算法。从起点出发,访问第一个相邻节点,然后递归地访问该节点的第一个未被访问过的相邻节点,直到所有能够到达的节点都被访问为止。
深度优先搜索应用场景:DFS 通常用于解决连通性问题,比如求解图中的连通分量、判断是否存在环等。
深度优先搜索的递归实现:
def dfs(node, visited):
# 如果当前节点已经被访问,则返回
if node in visited:
return
# 标记当前节点为已访问
visited.add(node)
# 访问当前节点的所有相邻节点
for neighbor in node.neighbors:
dfs(neighbor, visited)
(2)广度优先搜索(Breadth-First Search, BFS)是一种图形遍历算法。从起点出发,按照距离由近及远依次访问每个节点的所有相邻节点,直到所有能够到达的节点都被访问为止。
BFS 通常用于解决最短路径问题,比如在图中求两点之间的最短路径、求解迷宫等。
广度优先搜索的实现:由于 BFS 本质上是一种“广度优先”的遍历方式,因此使用递归并不是很自然。我们可以使用队列(Queue)来实现非递归的 BFS 算法。
def bfs(start, end):
queue = [(start, [start])]
while queue:
(node, path) = queue.pop(0)
for neighbor in node.neighbors:
if neighbor not in path:
if neighbor == end:
return path + [neighbor]
else:
queue.append((neighbor, path + [neighbor]))
二叉树是一种常见的数据结构,在实际开发中经常会使用到。通过递归算法,可以轻松地解决二叉树遍历、树的深度、节点个数等问题。
(1)前序遍历指先访问节点,然后依次访问其左子树和右子树。可以使用递归算法来实现前序遍历。
def preorder_traversal(root):
if root is None:
return []
result = [root.val]
left = preorder_traversal(root.left)
right = preorder_traversal(root.right)
return result + left + right
(2)中序遍历指先访问节点的左子树,然后访问节点本身,最后访问其右子树。同样可以使用递归算法来实现中序遍历。
def inorder_traversal(root):
if root is None:
return []
left = inorder_traversal(root.left)
result = [root.val]
right = inorder_traversal(root.right)
return left + result + right
(3)后序遍历指先访问节点的左子树和右子树,最后访问节点本身。同样可以使用递归算法来实现后序遍历。
def postorder_traversal(root):
if root is None:
return []
left = postorder_traversal(root.left)
right = postorder_traversal(root.right)
result = [root.val]
return left + right + result
树的深度指从根节点到最远叶子节点的距离,可以使用递归算法来计算树的深度。
def tree_depth(root):
if root is None:
return 0
left_depth = tree_depth(root.left)
right_depth = tree_depth(root.right)
return max(left_depth, right_depth) + 1
节点个数指二叉树中所有节点的数量,可以使用递归算法来计算节点个数。
def node_count(root):
if root is None:
return 0
left_count = node_count(root.left)
right_count = node_count(root.right)
return left_count + right_count + 1
快速排序、归并排序等排序算法以及二分查找等查找算法使用了递归思想。
快速排序(Quick Sort)是一种基于分治策略的高效排序算法。具体做法是:从数组中选择一个元素作为“枢轴”(pivot),然后将数组中比枢轴小的元素移到枢轴左边,比枢轴大的元素移到枢轴右边,最后再对左右两部分分别进行同样的操作。通过不断地切割数组,最终得到有序数组。
可以使用递归算法来实现快速排序。
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
left = [x for x in arr[1:] if x < pivot]
right = [x for x in arr[1:] if x >= pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
归并排序(Merge Sort)是一种稳定的、外部排序算法。其基本思想是:将待排数据分成若干个大小相等(或相近)子集合,并对每个子集合进行同样的操作,最后合并所有子集合成为有序数组。
可以使用递归算法来实现归并排序。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
二分查找算法也可以使用递归思想来实现。下面是一个使用递归思想的二分查找算法:
def binary_search(arr, target):
if len(arr) == 0:
return -1
mid = len(arr) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
res = binary_search(arr[mid+1:], target)
return res + mid + 1 if res != -1 else -1
else:
return binary_search(arr[:mid], target)
在这个算法中,首先判断数组是否为空,如果为空则返回-1。接着计算出数组的中间位置mid,并判断arr[mid]是否等于目标值target。如果相等,则直接返回mid;否则继续进行下一步判断。
如果arr[mid]小于目标值target,则说明目标值可能在右边的部分数组中,此时就需要对右半部分进行递归查找。在递归函数返回后,需要将返回结果加上mid+1(因为右半部分的第一个元素索引为mid+1)得到最终结果。
如果arr[mid]大于目标值target,则说明目标值可能在左边的部分数组中,此时就需要对左半部分进行递归查找。
通过以上递归过程,最终能够找到目标元素所在的位置或者确定该元素不存在于数组中。
动态规划问题需要将大问题拆分为若干个子问题,并通过合并子问题的解得到整个问题的解。这种分治思想与递归思想非常相似,因此可以使用递归来实现。
动态规划问题可以使用递归算法进行求解。实际上,许多动态规划问题都可以被转换为递归问题,并且在递归的过程中进行记忆化搜索(Memorization Search),从而避免重复计算。
下面以一个简单的例子来说明如何使用递归算法求解动态规划问题:
假设有n个物品和一个容量为C的背包,每个物品i的重量是wi,价值是vi。现在需要将这些物品放入背包中,在不超过容量限制的情况下,使得背包中所装物品的总价值最大。
可以定义一个函数f(i,j),表示将前i个物品放入容量为j的背包中所能得到的最大价值。则对于第i个物品,如果将其放入背包,则有两种情况:放或者不放。因此状态转移方程可以表示为:
f(i,j) = max(f(i-1, j), f(i-1, j-wi) + vi)
其中,第一项f(i-1, j)表示不放第i个物品,则只考虑前i-1个物品;第二项f(i-1, j-wi)+vi表示将第i个物品放入背包,则考虑前i-1个物品并且剩余空间为j-wi。
接下来就可以用递归算法来实现:
def knapsack(i, j):
if i == 0 or j == 0:
return 0
elif w[i] > j:
return knapsack(i-1, j)
else:
return max(knapsack(i-1, j), knapsack(i-1, j-w[i]) + v[i])
在这个递归函数中,首先判断i和j是否为0,如果为0则返回0。如果第i个物品的重量大于当前容量j,则只能考虑前i-1个物品,因此递归调用knapsack(i-1,j)。否则就需要比较放和不放两种情况所得到的最大价值,即max(knapsack(i-1,j), knapsack(i-1,j-w[i])+v[i])。
需要注意的是,在上述递归过程中会存在大量的重复计算,因此可以使用记忆化搜索来优化算法性能。具体做法是,在每次计算f(i,j)时,将其结果存储下来并加以利用。当再次需要计算f(i,j)时,直接返回已经计算过的结果即可。
回溯算法是一种求解决策问题的通用算法,在很多 NP 难题中都有应用。回溯算法采用试错的思想,需要借助于递归函数进行状态转移。
分治设计模式也是一种常见的编程技巧,它将一个大问题拆分成小问题来求解,并通过合并小问题得到大问题的解。这种思想与递归非常相似。
递归算法是一种常用的算法设计技巧,在编程中具有重要性和应用价值: