踏入数据结构与算法的精彩世界

前言

数据结构与算法是计算机科学和编程领域中至关重要的概念。无论你是一名初学者还是经验丰富的开发者,深刻理解数据结构和算法的原理和应用,都将使你的编程能力更上一层楼。本学习笔记将帮助你掌握数据结构的多样性、算法的设计与应用,以及问题解决的关键思维。
欢迎订阅LeetCode高分题解CSDN专栏,每日一题,和博主一起进步
LeetCode专栏
个人题解GitHub连接:LeetCode-Go-Python-Java-C

文章目录

一、探索数据结构

1.1 逻辑结构的多样性

数据结构是计算机科学的基础,它以不同的逻辑结构形式呈现:

1.1.1 集合结构

集合结构中的元素没有明确的顺序关系,各元素平等。

1.1.2 线性结构

线性结构中的元素按照线性顺序排列,包括数组和链表等。

# 示例代码 - 数组的线性结构表示
linear_array = [1, 2, 3, 4, 5]

# 示例代码 - 链表的线性结构表示
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

node1 = Node(1)
node2 = Node(2)
node3 = Node(3)

node1.next = node2
node2.next = node3

1.1.3 树形结构

树形结构中的元素存在层次关系,每个元素可以有多个子元素,但只有一个父元素。

# 示例代码 - 树的结构表示
class TreeNode:
    def __init__(self, data):
        self.data = data
        self.children = []

root = TreeNode("Root")
child1 = TreeNode("Child 1")
child2 = TreeNode("Child 2")

root.children.append(child1)
root.children.append(child2)

1.1.4 图形结构

图形结构中的元素之间存在复杂的连接关系,可以有多个相互关联的元素。

# 示例代码 - 图的结构表示
graph = {
    
    
    'A': ['B', 'C'],
    'B': ['A', 'C'],
    'C': ['A', 'B']
}

1.2 物理结构的多样性

数据结构的物理存储结构有两种主要方式:

1.2.1 顺序存储

顺序存储将元素按照一定顺序依次存储在内存中,适用于线性结构。

# 示例代码 - 数组的顺序存储
array = [1, 2, 3, 4, 5]

1.2.2 链式存储

链式存储使用指针或引用相互连接元素,适用于各种逻辑结构,包括线性、树和图。

# 示例代码 - 链表的链式存储
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

node1 = Node(1)
node2 = Node(2)
node3 = Node(3)

node1.next = node2
node2.next = node3
# 示例代码 - 图的链式存储
class GraphNode:
    def __init__(self, data):
        self.data = data
        self.neighbors = []

node1 = GraphNode('A')
node2 = GraphNode('B')
node3 = GraphNode('C')

node1.neighbors.append(node2)
node1.neighbors.append(node3)

这些示例代码帮助说明了不同逻辑结构和物理结构的多样性和应用。根据具体的问题和需求,选择合适的结构非常重要。

1.3 算法时间复杂度

算法时间复杂度用于衡量算法的运行时间随输入规模增加时的增长趋势。常见的时间复杂度有O(1)、O(n)、O(log n)等,其中O(1)表示常数时间复杂度,O(n)表示线性时间复杂度,O(log n)表示对数时间复杂度。

# 示例代码 - 一个具有线性时间复杂度的查找算法
def linear_search(arr, target):
    for i, num in enumerate(arr):
        if num == target:
            return i
    return -1

1.4 数组的顺序表示与实现

数组是一种线性结构,可以用顺序存储表示。在以行序为主序的二维数组中,任一元素aij的存储位置公式如下:

aij = base + (i * n + j) * size

其中,base为起始地址,n为列数,i和j分别表示行和列,size为元素大小。

# 示例代码 - 计算二维数组元素的存储位置
def calculate_location(base, i, j, n, size):
    return base + (i * n + j) * size

1.5 特殊矩阵和稀疏矩阵的存储

特殊矩阵(对阵矩阵)和稀疏矩阵是矩阵的两种特殊形式。对阵矩阵只需存储一半的元素,而稀疏矩阵中大部分元素为零,可以使用压缩存储方法来节省空间,如三元组表示法。

# 示例代码 - 三元组表示法示例
sparse_matrix = [
    (0, 1, 3),
    (1, 2, 4),
    (2, 0, 2),
]

1.6 完全二叉树的性质

完全二叉树是一种特殊的二叉树,具有以下性质:

  • 所有层次的节点都必须填满,除了最后一层,最后一层的节点从左到右填充,不允许有间隔。
# 示例代码 - 创建一个完全二叉树的节点类
class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

1.7 图的连通性问题

图是一种复杂的数据结构,连通性问题是图论中的重要问题。普里姆算法和克鲁斯卡尔算法用于寻找图的最小生成树。

# 示例代码 - 使用普里姆算法找到最小生成树
from queue import PriorityQueue

def prim(graph):
    # 初始化一个优先队列
    pq = PriorityQueue()
    
    # 随机选择一个起始节点
    start_node = list(graph.keys())[0]
    
    # 标记已访问的节点
    visited = set()
    
    # 将起始节点加入队列
    for neighbor, weight in graph[start_node]:
        pq.put((weight, start_node, neighbor))
    
    visited.add(start_node)
    min_spanning_tree = []
    
    while not pq.empty():
        weight, from_node, to_node = pq.get()
        if to_node not in visited:
            visited.add(to_node)
            min_spanning_tree.append((from_node, to_node, weight))
            for neighbor, weight in graph[to_node]:
                pq.put((weight, to_node, neighbor))
    
    return min_spanning_tree

这些概念和示例代码将帮助你更好地理解数据结构和算法的基本原理和应用。继续学习和实践,你将能够更熟练地使用它们解决实际问题。

二、深入学习数据结构

2.1 线性表的奥秘

线性表是一种常见的数据结构,它包括顺序表和链表。让我们深入了解它们:

顺序表

顺序表使用连续的内存空间来存储元素,支持随机访问。它的特性包括:

  • 按照顺序存储元素,可以通过索引直接访问元素。
  • 插入和删除元素需要移动其他元素,时间复杂度较高。
# 示例代码 - 创建一个顺序表
sequence_list = [1, 2, 3, 4, 5]

链表

链表使用节点和指针的方式来存储元素,支持高效的插入和删除操作。它的特性包括:

  • 元素通过节点和指针相互连接,插入和删除元素只需修改指针,不需要移动其他元素。
  • 无需连续内存空间,节省内存。
# 示例代码 - 创建一个链表节点
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

2.2 栈的黑科技

栈是一种重要的线性数据结构,它遵循先进后出(LIFO)的原则。让我们了解栈的定义和一些应用:

栈的定义

栈是一种抽象数据类型,它包括以下操作:

  • 入栈(Push):将元素压入栈顶。
  • 出栈(Pop):从栈顶弹出元素。
  • 获取栈顶元素(Top):查看栈顶元素。
  • 判断栈是否为空(IsEmpty):检查栈中是否还有元素。
# 示例代码 - 实现一个栈
class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()

    def top(self):
        if not self.is_empty():
            return self.items[-1]

    def is_empty(self):
        return len(self.items) == 0

栈的应用

栈在计算机科学中有广泛的应用,例如:

  • 数制转换

栈可用于将十进制数转换为其他进制,如二进制、八进制或十六进制。

# 示例代码 - 利用栈进行数制转换
def decimal_to_binary(decimal_num):
    stack = Stack()
    while decimal_num > 0:
        remainder = decimal_num % 2
        stack.push(remainder)
        decimal_num = decimal_num // 2
    
    binary_num = ""
    while not stack.is_empty():
        binary_num += str(stack.pop())
    
    return binary_num
  • 括号匹配

栈可用于检查表达式中的括号是否匹配,以确保表达式的正确性。

# 示例代码 - 使用栈检查括号匹配
def is_valid_expression(expression):
    stack = Stack()
    for char in expression:
        if char in '([{':
            stack.push(char)
        elif char in ')]}':
            if stack.is_empty():
                return False
            top_char = stack.pop()
            if not is_matching(top_char, char):
                return False
    return stack.is_empty()

def is_matching(left, right):
    return (left == '(' and right == ')') or (left == '[' and right == ']') or (left == '{' and right == '}')
  • 逆波兰表达式

栈可用于将中缀表达式转换为后缀表达式,用于计算。

# 示例代码 - 使用栈进行逆波兰表达式转换
def infix_to_postfix(infix_expression):
    stack = Stack()
    postfix = []
    precedence = {
    
    '+': 1, '-': 1, '*': 2, '/': 2, '^': 3}
    
    for token in infix_expression:
        if token.isalnum():  # 操作数直接加入后缀表达式
            postfix.append(token)
        elif token in "+-*/^":
            while (not stack.is_empty() and stack.peek() != '(' and
                   precedence.get(token, 0) <= precedence.get(stack.peek(), 0)):
                postfix.append(stack.pop())
            stack.push(token)
        elif token == '(':  # 左括号入栈
            stack.push(token)
        elif token == ')':  # 右括号将栈内运算符弹出直到遇到左括号
            while not stack.is_empty() and stack.peek() != '(':
                postfix.append(stack.pop())
            if not stack.is_empty() and stack.peek() == '(':
                stack.pop()
    
    while not stack.is_empty():
        postfix.append(stack.pop())
    
    return ''.join(postfix)

这些示例突显了栈在不同领域的实际应用,包括数制转换、括号匹配和表达式转换等。栈是一种非常有用的数据结构,可以简化问题的解决方案。

三、探索树结构

3.1 二叉树的魅力

二叉树是一种常见的树形数据结构,它包括根节点、左子树和右子树。让我们深入了解它:

二叉树可以使用两种常见的存储结构:

  • 顺序存储结构

顺序存储结构使用数组表示二叉树,节点i的左子节点为2i,右子节点为2i+1。

# 示例代码 - 顺序存储结构表示二叉树
binary_tree = [1, 2, 3, 4, 5, 6, 7]
  • 链式存储结构

链式存储结构使用节点和指针相互连接表示二叉树。

# 示例代码 - 链式存储结构表示二叉树
class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

# 创建一个二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)

二叉树的存储方式取决于具体的应用需求和问题特点,选择合适的存储方式可以提高操作效率和代码的可读性。

3.2 遍历二叉树的秘诀

遍历二叉树是了解其结构和数据的关键,常见的遍历方式包括:

先序遍历(Preorder)

先序遍历按照根-左子树-右子树的顺序遍历二叉树。

# 示例代码 - 先序遍历二叉树
def preorder_traversal(node):
    if node:
        print(node.data)
        preorder_traversal(node.left)
        preorder_traversal(node.right)

中序遍历(Inorder)

中序遍历按照左子树-根-右子树的顺序遍历二叉树,可以用于按照从小到大的顺序输出元素。

# 示例代码 - 中序遍历二叉树
def inorder_traversal(node):
    if node:
        inorder_traversal(node.left)
        print(node.data)
        inorder_traversal(node.right)

后序遍历(Postorder)

后序遍历按照左子树-右子树-根的顺序遍历二叉树。

# 示例代码 - 后序遍历二叉树
def postorder_traversal(node):
    if node:
        postorder_traversal(node.left)
        postorder_traversal(node.right)
        print(node.data)

四、图的奇妙世界

4.1 图的存储结构

图是一种复杂的数据结构,它可以使用多种方式进行存储:

  • 邻接矩阵:使用二维数组表示节点之间的连接关系。适用于稠密图,但可能浪费空间。
# 示例代码 - 使用邻接矩阵表示图
graph = [
    [0, 1, 1, 0],
    [1, 0, 1, 1],
    [1, 1, 0, 0],
    [0, 1, 0, 0]
]
  • 邻接表:使用链表表示每个节点的邻居。适用于稀疏图,节省空间。
# 示例代码 - 使用邻接表表示图
graph = {
    
    
    'A': ['B', 'C'],
    'B': ['A', 'C', 'D'],
    'C': ['A', 'B'],
    'D': ['B']
}
  • 逆邻接表:用于有向图,表示每个节点的入度。
# 示例代码 - 使用逆邻接表表示有向图
graph = {
    
    
    'A': [],
    'B': ['A', 'C'],
    'C': ['A'],
    'D': ['B']
}

选择合适的图存储结构取决于图的性质和应用场景。邻接矩阵适用于密集图,而邻接表适用于稀疏图。逆邻接表用于有向图的入度计算。

4.2 深度优先搜索(DFS)和广度优先搜索(BFS)

图的遍历算法是解决许多问题的关键,其中最常见的是深度优先搜索(DFS)和广度优先搜索(BFS)。

深度优先搜索(DFS)

DFS通过递归或栈实现,以深度为优先,访问一个节点后立即访问其相邻未访问节点。它常用于查找路径或遍历连通分量。

# 示例代码 - 使用深度优先搜索遍历图
def dfs(graph, node, visited):
    if node not in visited:
        print(node)
        visited.add(node)
        for neighbor in graph[node]:
            dfs(graph, neighbor, visited)

广度优先搜索(BFS)

BFS使用队列实现,以广度为优先,首先访问一个节点,然后依次访问其所有相邻节点。它常用于寻找最短路径或层次遍历。

# 示例代码 - 使用广度优先搜索遍历图
from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    while queue:
        node = queue.popleft()
        if node not in visited:
            print(node)
            visited.add(node)
            queue.extend(neighbor for neighbor in graph[node] if neighbor not in visited)

4.3 探索更多图算法

除了遍历,还有许多其他图算法,如拓扑排序、最短路径算法(如Dijkstra算法)、最小生成树算法(如Prim算法和Kruskal算法)、哈希表等等。

# 示例代码 - 使用Dijkstra算法寻找最短路径
import heapq

def dijkstra(graph, start):
    distances = {
    
    vertex: float('infinity') for vertex in graph}
    distances[start] = 0
    priority_queue = [(0, start)]
    
    while priority_queue:
        current_distance, current_vertex = heapq.heappop(priority_queue)
        
        if current_distance > distances[current_vertex]:
            continue
        
        for neighbor, weight in graph[current_vertex].items():
            distance = current_distance + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))
    
    return distances

五、排序与搜索

5.1 排序算法

在解决排序问题时,选择合适的排序算法对性能和效率至关重要。以下是一些常见排序算法的比较:

  • 冒泡排序(Bubble Sort):简单但低效,时间复杂度O(n^2)。
  • 快速排序(Quick Sort):快速且通常具有较好的性能,平均时间复杂度O(n log n)。
  • 希尔排序(Shell Sort):插入排序的改进版本,性能中等,取决于间隔序列的选择,平均时间复杂度O(n log n)。
  • 简单选择排序(Selection Sort):简单但低效,时间复杂度O(n^2)。
  • 归并排序(Merge Sort):稳定且性能良好,平均时间复杂度O(n log n)。
  • 堆排序(Heap Sort):利用堆数据结构,性能中等,时间复杂度O(n log n)。
  • 计数排序(Counting Sort):适用于整数排序,性能极高,时间复杂度O(n + k),其中k是范围。

当涉及排序算法时,每个算法都有其独特的代码示例和使用方法。以下是上述排序算法的代码示例,包括输入和输出部分:

冒泡排序(Bubble Sort)

冒泡排序是一种简单但低效的排序算法。它多次遍历数组,比较相邻元素,如果顺序不对则交换它们,直到整个数组排序完成。

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]

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
bubble_sort(arr)
print("冒泡排序结果:", arr)

快速排序(Quick Sort)

快速排序是一种高效的排序算法,它使用分治法将数组分成较小的部分,然后逐步排序。

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    else:
        pivot = arr[0]
        less = [x for x in arr[1:] if x <= pivot]
        greater = [x for x in arr[1:] if x > pivot]
        return quick_sort(less) + [pivot] + quick_sort(greater)

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = quick_sort(arr)
print("快速排序结果:", sorted_arr)

希尔排序(Shell Sort)

希尔排序是插入排序的改进版本,它使用不同的间隔序列来排序子数组。

def shell_sort(arr):
    n = len(arr)
    gap = n // 2
    while gap > 0:
        for i in range(gap, n):
            temp = arr[i]
            j = i
            while j >= gap and arr[j - gap] > temp:
                arr[j] = arr[j - gap]
                j -= gap
            arr[j] = temp
        gap //= 2

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
shell_sort(arr)
print("希尔排序结果:", arr)

简单选择排序(Selection Sort)

简单选择排序每次选择最小的元素放到已排序部分的末尾。

def selection_sort(arr):
    n = len(arr)
    for i in range(n):
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
selection_sort(arr)
print("简单选择排序结果:", arr)

归并排序(Merge Sort)

归并排序是一种稳定的排序算法,它将数组分成两部分,分别排序后再合并。

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    middle = len(arr) // 2
    left = arr[:middle]
    right = arr[middle:]
    left = merge_sort(left)
    right = merge_sort(right)
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 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.extend(left[i:])
    result.extend(right[j:])
    return result

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = merge_sort(arr)
print("归并排序结果:", sorted_arr)

堆排序(Heap Sort)

堆排序利用堆数据结构来进行排序,它具有中等性能。

def heapify(arr, n, i):
    largest = i
    left = 2 * i + 1
    right = 2 * i + 2

    if left < n and arr[left] > arr[largest]:
        largest = left

    if right < n and arr[right] > arr[largest]:
        largest = right

    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]
        heapify(arr, n, largest)

def heap_sort(arr):
    n = len(arr)
    for i in range(n // 2 - 1, -1, -1):
        heapify(arr, n, i)
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]
        heapify(arr, i, 0)

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
heap_sort(arr)
print("堆排序结果:", arr)

计数排序(Counting Sort)

计数排序适用于整数排序,它具有非常高的性能。

def counting_sort(arr):
    max_val = max(arr)
    min_val = min(arr)
    range_of_elements = max_val - min_val + 1
    count_arr = [0] * range_of_elements
    output_arr = [0] * len(arr)

    for i in range(len(arr)):
        count_arr[arr[i] - min_val] += 1

    for i in range(1, len(count_arr)):
        count_arr[i] += count_arr[i - 1]

    for i in range(len(arr) - 1, -1, -1):
        output_arr[count_arr[arr[i] - min_val] - 1] = arr[i]
        count_arr[arr[i] - min_val] -= 1

    for i in range(len(arr)):
        arr[i] = output_arr[i]

# 示例使用
arr = [64, 34, 25, 12, 22, 11, 90]
counting_sort(arr)
print("计数排序结果:", arr)

这些代码示例展示了各种排序算法的实现和使用方法。你可以根据需要选择适合你问题的排序算法。

5.2 搜索算法

搜索算法用于在数据集合中查找特定元素,其中一些常见的搜索算法包括:

  • 折半查找(Binary Search):适用于有序列表,将列表分成两部分,不断缩小搜索范围。
  • 二叉搜索树(Binary Search Tree):一种用于搜索和排序的树结构。
  • 哈希表(Hash Table):通过哈希函数将键映射到值的数据结构,具有快速查找特性。

5.2.1 折半查找(Binary Search)

折半查找适用于有序列表,将列表分成两部分,不断缩小搜索范围。

# 示例代码 - 折半查找
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

5.2.2 二叉搜索树(Binary Search Tree)

二叉搜索树是一种用于搜索和排序的树结构,左子树的节点值小于根节点,右子树的节点值大于根节点。

# 示例代码 - 二叉搜索树
class TreeNode:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.val = key

def insert(root, key):
    if root is None:
        return TreeNode(key)
    else:
        if root.val < key:
            root.right = insert(root.right, key)
        else:
            root.left = insert(root.left, key)
    return root

def search(root, key):
    if root is None or root.val == key:
        return root
    if root.val < key:
        return search(root.right, key)
    return search(root.left, key)

5.2.3 哈希表(Hash Table)

哈希表通过哈希函数将键映射到值的数据结构,具有快速查找特性。

# 示例代码 - 哈希表
class HashTable:
    def __init__(self):
        self.size = 10
        self.table = [[] for _ in range(self.size)]

    def _hash_function(self, key):
        return hash(key) % self.size

    def put(self, key, value):
        index = self._hash_function(key)
        for entry in self.table[index]:
            if entry[0] == key:
                entry[1] = value
                return
        self.table[index].append([key, value])

    def get(self, key):
        index = self._hash_function(key)
        for entry in self.table[index]:
            if entry[0] == key:
                return entry[1]
        return None

这些搜索算法适用于不同的情况和数据结构,选择合适的搜索算法可以提高查找效率。

六、算法设计与应用

在这一部分,我们将讨论一些常见的算法设计和应用,它们解决了各种实际问题。

6.1 队列的抽象数据类型定义及操作算法实现

队列是一种常见的数据结构,它遵循先进先出(FIFO)的原则,可以通过抽象数据类型和操作算法来定义和实现。

6.1.1 链队列

链队列是一种基于链表实现的队列,包括入队和出队操作。

# 示例代码 - 链队列
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class Queue:
    def __init__(self):
        self.front = None
        self.rear = None

    def is_empty(self):
        return self.front is None

    def enqueue(self, data):
        new_node = Node(data)
        if self.rear is None:
            self.front = self.rear = new_node
            return
        self.rear.next = new_node
        self.rear = new_node

    def dequeue(self):
        if self.is_empty():
            return None
        data = self.front.data
        self.front = self.front.next
        if self.front is None:
            self.rear = None
        return data

6.1.2 循环队列

循环队列是一种基于数组实现的队列,通过循环利用数组空间来实现入队和出队操作。

# 示例代码 - 循环队列
class CircularQueue:
    def __init__(self, size):
        self.size = size
        self.queue = [None] * size
        self.front = self.rear = -1

    def is_empty(self):
        return self.front == -1

    def is_full(self):
        return (self.rear + 1) % self.size == self.front

    def enqueue(self, data):
        if self.is_full():
            return "Queue is full"
        if self.is_empty():
            self.front = self.rear = 0
        else:
            self.rear = (self.rear + 1) % self.size
        self.queue[self.rear] = data

    def dequeue(self):
        if self.is_empty():
            return "Queue is empty"
        data = self.queue[self.front]
        if self.front == self.rear:
            self.front = self.rear = -1
        else:
            self.front = (self.front + 1) % self.size
        return data

6.2 二叉树的中序遍历与统计叶节点个数

二叉树是一种常见的树形数据结构,中序遍历可以按照左根右的顺序遍历树,统计叶节点个数是一个常见的操作。

# 示例代码 - 二叉树的中序遍历与统计叶节点个数
class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

def inorder_traversal(root):
    if root:
        inorder_traversal(root.left)
        print(root.data, end=" ")
        inorder_traversal(root.right)

def count_leaves(root):
    if root is None:
        return 0
    if root.left is None and root.right is None:
        return 1
    return count_leaves(root.left) + count_leaves(root.right)

6.3 单源最短路径–迪杰斯特拉算法(Dijkstra’s Algorithm)

迪杰斯特拉算法用于查找图中从单个源节点到所有其他节点的最短路径。

# 示例代码 - 迪杰斯特拉算法
import heapq

def dijkstra(graph, start):
    distances = {
    
    vertex: float('infinity') for vertex in graph}
    distances[start] = 0
    priority_queue = [(0, start)]

    while priority_queue:
        current_distance, current_vertex = heapq.heappop(priority_queue)

        if current_distance > distances[current_vertex]:
            continue

        for neighbor, weight in graph[current_vertex].items():
            distance = current_distance + weight

            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))

    return distances

6.4 哈希表的设计与应用

哈希表是一种高效的数据结构,它通过哈希函数将键映射到值,常用于快速查找。

# 示例代码 - 哈希表
class HashTable:
    def __init__(self):
        self.size = 10
        self.table = [[] for _ in range(self.size)]

    def _hash_function(self, key):
        return hash(key) % self.size

    def put(self, key, value):
        index = self._hash_function(key)
        for entry in self.table[index]:
            if entry[0] == key:
                entry[1] = value
                return
        self.table[index].append([key, value])

    def get(self, key):
        index = self._hash_function(key)
        for entry in self.table[index]:
            if entry[0] == key:
                return entry[1]
        return None

哈希表在实际应用中具有广泛的用途,例如实现缓存、数据库索引等,它可以快速查找和存储数据。

6.5 图的遍历:深度优先搜索(DFS)与广度优先搜索(BFS)

图是一种复杂的数据结构,深度优先搜索(DFS)和广度优先搜索(BFS)是两种常见的图遍历算法。

6.5.1 深度优先搜索(DFS)

深度优先搜索通过递归或栈来遍历图的节点,沿着一条路径尽可能深入,直到无法继续为止。

# 示例代码 - 深度优先搜索(DFS)
def dfs(graph, node, visited):
    if node not in visited:
        print(node, end=" ")
        visited.add(node)
        for neighbor in graph[node]:
            dfs(graph, neighbor, visited)

# 图的表示方式,使用字典表示邻接关系
graph = {
    
    
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

visited = set()  # 用于记录已访问的节点
dfs(graph, 'A', visited)  # 从节点 'A' 开始进行深度优先搜索

6.5.2 广度优先搜索(BFS)

广度优先搜索通过队列来遍历图的节点,先访问离起始节点最近的节点,然后依次访问距离逐渐增加的节点。

# 示例代码 - 广度优先搜索(BFS)
from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])

    while queue:
        node = queue.popleft()
        if node not in visited:
            print(node, end=" ")
            visited.add(node)
            queue.extend(neighbor for neighbor in graph[node] if neighbor not in visited)

# 图的表示方式,使用字典表示邻接关系
graph = {
    
    
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

bfs(graph, 'A')  # 从节点 'A' 开始进行广度优先搜索

深度优先搜索和广度优先搜索在解决图相关的问题时非常有用,它们可以用于路径查找、连通性检测等应用。

6.6 拓扑排序

拓扑排序是对有向无环图(DAG)进行排序的算法,它用于表示任务之间的依赖关系,确保没有循环依赖。

# 示例代码 - 拓扑排序
from collections import defaultdict

class Graph:
    def __init__(self, vertices):
        self.graph = defaultdict(list)
        self.V = vertices

    def add_edge(self, u, v):
        self.graph[u].append(v)

    def topological_sort_util(self, v, visited, stack):
        visited[v] = True
        for i in self.graph[v]:
            if visited[i] is False:
                self.topological_sort_util(i, visited, stack)
        stack.insert(0, v)

    def topological_sort(self):
        visited = [False] * self.V
        stack = []
        for i in range(self.V):
            if visited[i] is False:
                self.topological_sort_util(i, visited, stack)
        return stack

# 创建一个有向图
g = Graph(6)
g.add_edge(5, 2)
g.add_edge(5, 0)
g.add_edge(4, 0)
g.add_edge(4, 1)
g.add_edge(2, 3)
g.add_edge(3, 1)

print("拓扑排序结果:", g.topological_sort())  # 输出拓扑排序结果

拓扑排序可用于任务调度、依赖关系分析等场景,确保任务按照依赖关系有序执行。

这些算法设计与应用示例涵盖了数据结构与算法的多个方面,帮助解决各种实际问题。深入理解并掌握这些算法将为你的编程和问题解决能力提供强大的支持。

总结

数据结构和算法是计算机科学中的基础,是每个程序员必须掌握的核心知识之一。通过本学习笔记,你已经了解了不同逻辑结构和物理结构,掌握了多种排序和搜索算法的实现,理解了二叉树和图的基本原理,并学会了如何应用算法解决实际问题。

继续深入学习和实践,将进一步提高你的编程技能,并使你能够更自信地应对各种复杂的计算机科学问题。数据结构和算法不仅仅是学科,更是一种思维方式,它将帮助你成为更出色的程序员。

猜你喜欢

转载自blog.csdn.net/qq_42531954/article/details/133151598