当前位置: 首页 > news >正文

【数据结构与算法_学习精华】

一、学习数据结构与算法的框架思维

1、核心结论:

种种数据结构,皆为数组顺序存储)和链表链式存储)的变换。(索引访问与指针)

数据结构的关键点在于遍历访问,具体一点就是:增删查改等基本操作。

种种算法,皆为穷举。穷举的关键点在于无遗漏无冗余

各种数据结构的遍历 + 访问仅两种形式:迭代(for / while)与  递归(函数自调:自己调用自己)

2、迭代与递归

2.1 迭代

迭代(iterative)—— 自己一步一步数台阶脚下踩一根计数器 i=0→1→2…每数一步,状态全在你自己口袋里(循环变量、栈、指针)CPU 只关心“下一步去哪”,不回头找导游

线性结构,适合 循环迭代,以数组为例:

def traverse(arr: List[int]):for i in range(len(arr)):# 迭代访问 arr[i]
2.1.1 数组求和
def sum_iter(a):total = 0for x in a:        # 迭代遍历total += xreturn totalprint(sum_iter([7, 3, 5]))   # 15
2.1.2 链表遍历
# 基本的单链表节点
class ListNode:def __init__(self, val):self.val = valself.next = None#遍历方式1:迭代访问def traverse(head: ListNode) -> None:p = headwhile p is not None:# 迭代访问 p.valp = p.next
2.1.3 图遍历
from typing import List, Dict, Set, Deque
from collections import deque# ---------- 图定义 ----------
Graph = Dict[int, List[int]]   # 邻接表:{节点: [邻居, ...]}# ---------- 广度优先 BFS(迭代) ----------
def bfs(graph: Graph, root: int) -> None:if root not in graph:returnvisited: Set[int] = set()q: Deque[int] = deque([root])visited.add(root)while q:                       # 标准队列迭代node = q.popleft()print(node)                # 访问节点for neighbor in graph[node]:if neighbor not in visited:visited.add(neighbor)q.append(neighbor)

2.2 递归(套娃-自己调自己)

概念理解:递归(recursive)—— 导游们接力,【每层】的【导游-函数】负责一层处理你问导游 A:“到山顶多少阶?”A 只记 “【我这一层的阶数】 + 【下一层导游 B 的答案】”, 然后把问题原样扔给 导游B。每层导游拍照存档自己的阶数,直到最后一阶不再接力。回程时,照片从后往前逐层收集,结果逐层返回。每层只做“自己这一层 + 剩余结果”,而剩余结果交给下一层复制粘贴的这同一段代码去完成,这就是递归。
2.2.1 数组求和-递归方式
def sum_rec(a, i=0):         # 函数签名:a是数组,i是元素索引if i == len(a):          # 基准case:停止条件。到最后一阶直接返回,避免越界return 0             # 避免越界return a[i] + sum_rec(a, i + 1)  # 把当前元素 a[i] “拿在手里”,每层的元素累计操作#如果是return sum_rec(a, i + 1),则表示跳过当前元素,只算“剩余的部分”运行结果:
sum_rec([7,3,5], 0)
= 7 + sum_rec([7,3,5], 1)
= 7 + (3 + sum_rec([7,3,5], 2))
= 7 + 3 + (5 + sum_rec([7,3,5], 3))
= 7 + 3 + 5 + 0          ← i==3 越界,返回 0
= 15

一些递归的概念

1、函数栈帧:就是函数被调用时,在内存里临时建立的一小块“工作台”,(如例子上的 每层的照片)
里面放着:
1)当前函数的局部变量
2)返回地址(调用完后回到哪条指令)
3)参数值
4)一些控制信息(保存的寄存器、上一帧指针等)调用函数 → 压入一帧;返回 → 弹出这一帧。所有帧按“后调用先弹出”的顺序串在系统调用栈上,这就是递归深度过深会爆 StackOverflow 的原因。上面例子中:函数栈帧里只记录“当前这一层”的局部变量名 i 和指向列表 a 的引用(指针),不会把整个列表或元素复制进帧。所以 a[i] 只是通过帧里的引用去堆上读取数据,本身不是栈帧的一部分。但其实就是可以代指栈帧
2.2.1 链表遍历

既可以迭代 又可以递归的,以链表为例:

# 基本的单链表节点
class ListNode:def __init__(self, val):self.val = valself.next = None#遍历方式2:递归访问def traverse(head: ListNode) -> None:if head is None:          # 终止条件returnprint(head.val)           # 访问当前节点traverse(head.next)       # 处理后续链表
2.2.3 二叉树遍历

非线性结构,适合递归,以二叉树为例:

与上面的链表相似

# 基本的二叉树节点
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef traverse(root: TreeNode) -> None:if root is None:        # 终止条件returnprint(root.val)         # 访问当前节点traverse(root.left)     # 递归左子树traverse(root.right)    # 递归右子树
2.2.4 N叉数的遍历

与二叉树相似

# 基本的 N 叉树节点
from typing import Listclass TreeNode:val: intchildren: List['TreeNode']def traverse(root: TreeNode) -> None:if root is None:                # 基准:空树returnprint(root.val)                 # 先访问当前节点(前序)for child in root.children:     # 再依次递归每棵子树traverse(child)
2.2.5 图的遍历

与N叉数相似,对于环,要单独使用

from typing import List, Dict, Set, Deque
from collections import deque# ---------- 图定义 ----------
Graph = Dict[int, List[int]]   # 邻接表:{节点: [邻居, ...]}# ---------- 深度优先 DFS(递归) ----------
def dfs(graph: Graph, root: int, visited: Set[int]) -> None:if root not in graph:          # 停止条件:空图或孤立点returnprint(root)                    # 访问当前节点(前序)visited.add(root)for neighbor in graph[root]:   # 依次递归每条边if neighbor not in visited:dfs(graph, neighbor, visited)

带环的图,需要使用 布尔数组 visited 做标记

from typing import Dict, List, Set, Deque
from collections import dequeGraph = Dict[int, List[int]]# ---------- DFS(递归) ----------
def dfs(graph: Graph, root: int, visited: Set[int]) -> None:if root not in graph:returnprint(root)visited.add(root)for nxt in graph[root]:if nxt not in visited:   # 环被这一步剪掉dfs(graph, nxt, visited)

3、数据结构根本

3.1 数组

连续存储,可通过索引随机访问元素。内存空间需要一次性分配好。如果需要扩容,时间复杂度是O(N),如果是在数组中间插入/删除元素,时间复杂度也是O(N)。因为都需要移动其他元素保证连续存储。

3.2 链表

非连续存储,所以不能随机访问元素。需要指针指向下一元素。删除/插入元素,只需要操作某一元素的前后指针,所以时间复杂度是O(1)。因为需要存储前后指针,需要额外的存储空间。

4、数据结构基本类型

4.1、数组

4.2、链表

4.3、哈希表

通过散列函数把键映射到一个大数组里。

拉链法需要链表特性。

线性探查法需要数组连续访问特性。

4.4、队列

可以使用链表也可以使用数组实现。

用数组实现,就要处理扩容缩容的问题;
用链表实现,没有扩/缩容问题,但需要更多的内存空间存储节点指针

1

4.5、栈

可以使用链表也可以使用数组实现。

用数组实现,就要处理扩容缩容的问题;
用链表实现,没有扩/缩容问题,但需要更多的内存空间存储节点指针

可以使用链表也可以使用数组实现用数组实现,就要处理扩容缩容的问题;
用链表实现,没有扩/缩容问题,但需要更多的内存空间存储节点指针

4.6、树

用数组实现的是:完全二叉树、二叉堆

用链表实现的是:二叉搜索树、红黑树、B数、AVL树

4.7、图

图的两种存储方式,邻接表就是链表,邻接矩阵就是二维数组。

5、算法的本质

计算机算法,最笨但最通用的是:穷举。然后可以变形成聪明的穷举

大部分开发岗位工作中都是基于现成的开发框架做事,不怎么会碰到底层数据结构和算法相关的问题,但另一个事实是,只要你想找技术相关的岗位,数据结构和算法的考察是绕不开的,因为这块知识点是公认的程序员基本功。为了区分,不妨称算法工程师研究的算法为「数学算法」,称刷题面试的算法为「计算机算法」,我们的目标主要聚焦的是「计算机算法」。找一份开发岗位的工作,所以你真的不需要有多少数学基础,只要学会用计算机思维解决问题就够了

5.1 排列组合问题

排列组合问题抽象成一棵树,要精确地使用代码遍历这棵树的所有节点,不能漏不能多,才能写出正确的代码

5.2 有序数组中,寻找一个元素

在有序数组中寻找一个元素,用一个 for 循环暴力穷举谁都会,但 二分搜索算法 就是更聪明的穷举方式,拥有更好的时间复杂度

5.3 动态规划

动态规划是无冗余地穷举所有解,然后找一个最值

5.4 贪心算法

贪心算法就是在题目中发现一些规律(专业点叫贪心选择性质),使得你不用完整穷举所有解就可以得出答案

5.5 计算连通分量

想判断图中的两个节点是否连通,用 DFS/BFS 暴力搜索(穷举)肯定可以做到,但 Union Find 算法硬是用数组模拟树结构,把连通性相关的操作复杂度给干到 O(1)

6、常见的算法技巧

链表和数组

1、单链表常考的技巧就是双指针

判断单链表是否成环,暴力解是用一个 HashSet 之类的数据结构来缓存走过的节点,遇到重复的就说明有环。

但用快慢指针可以避免使用额外的空间,这就是聪明地穷举

2、数组常用的技巧也是双指针

3、二分搜索技巧

可以归为两端向中心的双指针。

如果在数组中搜索元素,一个 for 循环花 O(N)时间穷举肯定能搞定,但是二分搜索告诉你,如果数组是有序的,它只要 O(logN)的复杂度,这就是一种更聪明的搜索方式。

4、滑动窗口算法

典型的快慢双指针。用嵌套 for 循环花 O(N^2) 的时间肯定可以穷举出所有子数组。但是滑动窗口算法表示,在某些场景下,它可以用一快一慢两个指针,只需 O(N) 的时间就可以找到答案,这就是更聪明地穷举方式。

5、前缀和 -技巧

频繁地让你计算子数组的和,每次用 for 循环去遍历肯定没问题,但前缀和技巧预计算一个 preSum 数组,就可以避免循环。

6、差分数组技巧

频繁地让你对子数组进行增减操作,也可以每次用 for 循环去操作,但差分数组技巧维护一个 diff 数组,也可以避免循环。

二叉树系列

二叉树模型几乎是所有高级算法的基础。叉树题目的递归解法可以分两类思路:

第一类是遍历一遍二叉树得出答案:回溯算法

第二类是通过分解问题计算出答案:动态规划算法

1、遍历二叉树最大深度

这个逻辑就是用 traverse 函数遍历了一遍二叉树的所有节点,维护 depth 变量,在叶子节点的时候更新最大深度。

class Solution:def __init__(self):# 记录最大深度self.res = 0# 记录当前遍历节点的深度self.depth = 0def maxDepth(self, root: TreeNode) -> int:self.traverse(root)return self.resdef traverse(self, root: TreeNode) -> None:if not root:# 到达叶子节点self.res = max(self.res, self.depth)return# 前序遍历位置self.depth += 1self.traverse(root.left)self.traverse(root.right)# 后序遍历位置self.depth -= 1
2、全排列问题:

本质就是多叉树的遍历,所以说回溯算法本质就是遍历多叉树,只要能把问题抽象成树结构,就一定能用回溯算法解决。

class Solution:def permute(self, nums: List[int]) -> List[List[int]]:# 记录所有全排列res = []# 记录当前正在穷举的排列track = []# track 中的元素会被标记为 true,避免重复使用used = [False] * len(nums)# 主函数,输入一组不重复的数字,返回它们的全排列def backtrack(nums):# 到达叶子节点,track 中的元素就是一个全排列if len(track) == len(nums):res.append(track[:])returnfor i in range(len(nums)):# 排除不合法的选择if used[i]:# nums[i] 已经在 track 中,跳过continue# 做选择track.append(nums[i])used[i] = True# 进入递归树的下一层backtrack(nums)# 取消选择track.pop()used[i] = Falsebacktrack(nums)return res
3、二叉树最大深度 --分解问题
# 定义:输入根节点,返回这棵二叉树的最大深度
def maxDepth(root: TreeNode) -> int:if root is None:return 0# 递归计算左右子树的最大深度leftMax = maxDepth(root.left)rightMax = maxDepth(root.right)# 整棵树的最大深度就是左右子树的最大深度加一res = max(leftMax, rightMax) + 1return res

http://www.dtcms.com/a/491066.html

相关文章:

  • 第18讲:C语言内存函数
  • 外贸网站如何做推广论坛网站 备案
  • 深圳网站建设首选h5case是什么网站
  • Matlab的交通标志定位实现
  • 课堂网站开发企业管理培训班哪个好
  • 记录一个自动学习的脚本开发过程
  • h5游戏免费下载:一起蛙蛙跳
  • chrome中的axure插件提示无法不受支持
  • 石家庄住房城乡建设厅网站著名企业vi设计
  • 深圳做网站的公司排名开个人网站如何赚钱
  • centos 生产环境搭建最佳实践 (一)
  • RocketMQ 实战:马拉松系统异步化与延时任务落地(含死信队列处理)
  • 通达信指标平台
  • 网站建设及推广培训网站备案查询站长工具
  • MATLAB2025B版本新特点
  • Node.js使用Express+SQLite实现登录认证
  • 仿百度百科网站源码设计类专业学校有哪些
  • 重庆建站多少钱一年工业产品设计培训
  • 【IEEE出版 | 早鸟优惠本周截止】人工智能驱动图像处理与计算机视觉技术国际学术研讨会 (AIPCVT 2025)
  • 网站开发案例教程东营网站建设服务商
  • 基于微信小程序的垃圾分类管理系统【2026最新】
  • SSM高校教室申请管理系统yf80k(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 创建一个SpringBoot项目(连接数据库)
  • 飞沐网站设计大鹏网站建设建站好不好
  • 淮南专业网站建设怎样保存网站资料 做证据
  • 如何在WPF中实现ComboBox多选
  • 单北斗GNSS变形监测是什么?主要用于大坝及桥梁安全监测吗?
  • 网站建设公司的服务器建设网站申请书
  • 如何加强省市级门户网站的建设太原网站优化服务
  • YOLO-V1 与 YOLO-V2 核心笔记