算法是编程世界的基石,对于Python初学者来说,掌握一些经典算法不仅能够提升编程技能,还能帮助理解问题解决的逻辑。下面,我们将一步步探索并用Python实现这些算法,从基础到进阶,让你的编程之旅更加精彩。
1. 二分查找
概念:在有序数组中查找目标值,每次比较中间元素,缩小搜索范围。
示例代码:
def binary_search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1 # 如果没找到返回-1
arr = [2, 3, 4, 10, 40]
target = 10
print("Element is present at index", binary_search(arr, target))
解释:通过不断将搜索区间分为两半,直到找到目标或区间为空。
2. 冒泡排序
概念:重复遍历要排序的数列,比较相邻元素,如果顺序错误就交换它们。
示例代码:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = bubble_sort(arr)
print("Sorted array is:", sorted_arr)
解释:每一轮遍历让最大的元素“冒泡”到最后。
3. 快速排序
概念:选择一个基准值,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序。
示例代码:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
arr = [10, 7, 8, 9, 1, 5]
sorted_arr = quick_sort(arr)
print("Sorted array is:", sorted_arr)
解释:递归地把小于基准值的元素和大于基准值的元素分开。
4. 插入排序
概念:将未排序序列中的数据依次插入到已排序序列中的适当位置。
示例代码:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i-1
while j >=0 and key < arr[j] :
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
arr = [12, 11, 13, 5, 6]
sorted_arr = insertion_sort(arr)
print("Sorted array is:", sorted_arr)
解释:逐步构建有序序列,适合小数据量排序。
5. 选择排序
概念:每次从未排序的序列中找到最小(大)的元素,存放到排序序列的起始(末尾)位置。
示例代码:
def selection_sort(arr):
for i in range(len(arr)):
min_idx = i
for j in range(i+1, len(arr)):
if arr[min_idx] > arr[j]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
arr = [64, 25, 12, 22, 11]
sorted_arr = selection_sort(arr)
print("Sorted array is:", sorted_arr)
解释:每轮寻找最小值并移动到序列前端。
6. 链表反转
概念:将链表的指向反向,头节点变成尾节点,尾节点变成头节点。
示例代码:
class Node:
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def reverse_list(head):
prev = None
current = head
while current:
next_temp = current.next
current.next = prev
prev = current
current = next_temp
return prev
# 创建链表: 1->2->3
head = Node(1, Node(2, Node(3)))
reversed_head = reverse_list(head)
print("Reversed list:", end=" ")
while reversed_head:
print(reversed_head.data, end=" -> ")
reversed_head = reversed_head.next
解释:通过改变节点的next指针来实现反转。
7. 动态规划 - 最长公共子序列
概念:找到两个字符串中最长的相同子序列。
示例代码:
def lcs(X, Y):
m, n = len(X), len(Y)
L = [[None]*(n+1) for i in range(m+1)]
for i in range(m+1):
for j in range(n+1):
if i == 0 or j == 0:
L[i][j] = 0
elif X[i-1] == Y[j-1]:
L[i][j] = L[i-1][j-1]+1
else:
L[i][j] = max(L[i-1][j], L[i][j-1])
return L[m][n]
X = "AGGTAB"
Y = "GXTXAYB"
print("Length of LCS is", lcs(X, Y))
解释:利用二维数组记录子问题的解,自底向上计算最长公共子序列长度。
8. 深度优先搜索(DFS)
概念:访问图或树中的节点,尽可能深地搜索,访问完当前节点的所有邻接节点后回溯。
示例代码(图的DFS):
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
visited.add(node)
print(node, end=" ")
for neighbour in graph[node]:
if neighbour not in visited:
dfs(graph, neighbour, visited)
graph = {'0': ['1', '2'],
'1': ['0', '3', '4'],
'2': ['0'],
'3': ['1'],
'4': ['1']}
dfs(graph, '0')
解释:递归地访问节点及其邻居,用集合记录已访问节点避免循环。
9. 广度优先搜索(BFS)
概念:从根节点开始,访问最近的邻接节点,然后访问下一层的节点。
示例代码(图的BFS):
from collections import deque
def bfs(graph, root):
visited = set()
queue = deque([root])
while queue:
vertex = queue.popleft()
print(vertex, end=" ")
for neighbour in graph[vertex]:
if neighbour not in visited:
visited.add(neighbour)
queue.append(neighbour)
graph = {'0': ['1', '2'],
'1': ['0', '3', '4'],
'2': ['0'],
'3': ['1'],
'4': ['1']}
bfs(graph, '0')
解释:使用队列保证按层次访问节点。
10. 斐波那契数列
概念:每一项都是前两项之和,通常从0和1开始。
示例代码(递归与迭代):
# 递归
def fib_recursive(n):
if n <= 1:
return n
else:
return (fib_recursive(n-1) + fib_recursive(n-2))
# 迭代
def fib_iterative(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a+b
return a
print("Fibonacci at position 10 using recursion:", fib_recursive(10))
print("Fibonacci at position 10 using iteration:", fib_iterative(10))
解释:递归直观但效率低,迭代效率高且内存占用少。
优化与应用场景
1. 二分查找的扩展
应用场景:适用于有序列表的快速查找,常见于数据库索引、文件系统搜索等。
优化:可以设计为迭代或递归形式,考虑边界条件的优化,如在链表中的实现需要额外的辅助结构。
2. 排序算法的性能对比
快速排序通常在平均情况下最快,但在最坏情况下的时间复杂度为O(n^2)。
归并排序虽然稳定,但需要额外的存储空间,时间复杂度始终为O(n log n)。
插入排序在小数据集或几乎有序的数据集中表现良好,适合局部排序的优化场景。
3. 动态规划与记忆化
LCS问题展示了动态规划的核心思想,通过表格记录子问题的解来避免重复计算。
优化:对于递归实现,可以通过缓存(记忆化)避免重复计算,减少时间复杂度。
4. 图搜索算法的比较
DFS适用于寻找路径、拓扑排序、连通性检查。
BFS更适合寻找最短路径问题,如在无权图中寻找两节点间的最短路径。
优化:在大规模图中,考虑使用并行处理或A*搜索等高级算法以提高效率。
5. 斐波那契数列的高效实现
矩阵快速幂:对于大数字的斐波那契数,可以利用数学中的矩阵乘法进行指数级优化。
空间优化:迭代方法已经很高效,但可以进一步优化,比如仅用两个变量替换数组,节省内存。
实战案例分析
案例:旅行商问题(TSP)的启发式解决方案
旅行商问题是一个经典的组合优化问题,目标是找到访问每个城市一次并返回起点的最短路径。虽然这是一个NP难问题,但可以使用启发式算法如遗传算法、模拟退火或贪心算法找到近似解。
示例代码(简化版贪心算法):
def tsp_greedy(cities, start=0):
unvisited = set(range(len(cities)))
path = [start]
total_distance = 0
while unvisited:
last_city = path[-1]
nearest_city = min(unvisited, key=lambda city: cities[last_city][city])
total_distance += cities[last_city][nearest_city]
path.append(nearest_city)
unvisited.remove(nearest_city)
# Return to start
total_distance += cities[path[-1]][start]
path.append(start)
return path, total_distance
# 假设cities是一个二维列表,表示城市间的距离
cities = [[0, 20, 15, 25], [20, 0, 30, 35], [15, 30, 0, 30], [25, 35, 30, 0]]
shortest_path, shortest_distance = tsp_greedy(cities)
print("Shortest Path:", shortest_path)
print("Distance:", shortest_distance)
分析:虽然贪心算法在TSP中可能不会得到最优解,但它简单且快速,适合快速生成可行解,对于教学和理解启发式算法非常有用。