链表 Linked List

链表是一种线性数据结构,它由多个节点(Node)组成,每个节点包含两部分: 数据部分:存储实际的数据。 指针部分:存储指向下一个节点的地址。 链表的结构特点是节点在内存中不一定是连续存储的,而是通过指针将一个个节点连接起来。每个节点可以通过指针找到下一个节点,形成一个链式结构。 链表的类型: 单向链表(Singly Linked List):每个节点只包含指向下一个节点的指针,最后一个节点的指针指向 null。 双向链表(Doubly Linked List):每个节点包含两个指针,分别指向前一个节点和下一个节点,便于从两端遍历。 循环链表(Circular Linked List):链表的最后一个节点的指针指向第一个节点,形成一个环。 链表的特点: 动态大小:链表大小可以动态调整,元素可以随时添加或删除,且不需要提前指定大小。 插入和删除操作高效:链表在任何位置的插入和删除都比数组更高效,因为只需要改变指针的指向即可。 访问不方便:链表的元素不是连续存储的,访问某个特定元素时需要从头遍历链表,效率较低。 链表的缺点: 随机访问差:在链表中,访问某个元素需要从头节点开始逐个遍历,无法像数组那样通过索引直接访问,效率较低。 额外内存开销:每个节点都需要额外的指针空间,增加了内存的消耗。 复杂的实现和维护:链表的插入、删除、遍历等操作涉及到指针操作,相比数组实现较复杂。 链表的操作: 插入操作:在链表的任意位置插入一个新节点,需要更新指针的连接。 删除操作:删除链表中的某个节点时,更新指针,使其跳过被删除的节点。 遍历操作:从头节点开始,顺序访问链表中的所有节点。 查找操作:遍历链表查找特定元素(通常时间复杂度是 O(n))。 示例(C++): #include <iostream> using namespace std; // 定义链表节点结构 struct Node { int data; // 存储数据 Node* next; // 指向下一个节点的指针 }; // 插入节点 void insert(Node*& head, int value) { Node* newNode = new Node(); // 创建新节点 newNode->data = value; // 设置数据 newNode->next = head; // 新节点指向当前头节点 head = newNode; // 更新头节点 } // 打印链表 void printList(Node* head) { Node* temp = head; while (temp != nullptr) { cout << temp->data << " "; temp = temp->next; } cout << endl; } int main() { Node* head = nullptr; // 空链表 // 插入元素 insert(head, 10); insert(head, 20); insert(head, 30); // 打印链表 printList(head); // 输出: 30 20 10 return 0; } 在这个例子中,我们定义了一个链表节点 Node,每个节点包含一个 data 数据部分和一个 next 指针,指向下一个节点。通过 insert 函数将元素插入链表头部,并通过 printList 函数遍历链表输出元素。 ...

二月 18, 2025 · 林墨瀚

数组 array

数组的特点: 固定大小:数组在创建时必须指定大小,一旦定义,大小不能更改。这意味着,如果需要存储更多的数据,必须创建一个新的数组并复制数据。 元素类型相同:数组中的所有元素必须是相同类型的数据(如整数、字符、浮点数等)。 按索引访问:数组的每个元素都有一个唯一的索引,索引从0开始,允许直接访问任何元素。 连续内存:数组的元素在内存中是连续存储的,这使得数组具有较快的访问速度。 数组的缺点: 固定大小:数组一旦创建,其大小是固定的,无法动态扩展。如果数据量超出了原先设定的大小,必须重新分配更大的数组并复制数据,效率较低。 浪费内存:如果你事先定义的数组大小过大,可能会浪费内存空间;如果大小过小,可能会导致无法存储所有数据。 插入和删除效率低:数组中的元素是连续存储的,因此在数组中间插入或删除元素时,需要移动大量的数据,效率较低。 示例(C++): #include <iostream> using namespace std; int main() { // 声明并初始化一个包含5个整数的数组 int arr[5] = {1, 2, 3, 4, 5}; // 访问数组元素 cout << "数组的第一个元素: " << arr[0] << endl; // 输出 1 // 修改数组元素 arr[2] = 10; // 将数组中第三个元素修改为 10 // 输出修改后的数组元素 cout << "修改后的第三个元素: " << arr[2] << endl; // 输出 10 return 0; } 数组的操作: 访问元素:通过索引访问数组中的元素。例如,arr[3] 访问数组 arr 的第四个元素(索引从0开始)。 修改元素:通过指定索引修改数组中的元素。例如,arr[1] = 20 将数组中第二个元素的值改为20。 遍历数组:通过循环遍历数组中的每个元素。例如,使用 for 循环来访问所有元素。 for (int i = 0; i < 5; i++) { cout << arr[i] << " "; } 数组初始化:在声明时,你可以通过花括号 {} 初始化数组,或者使用循环动态赋值。 数组的应用: 存储一组相同类型的数据:最常见的应用是存储同类型的数据集合,例如学生的成绩、多个传感器的测量值等。 例:存储班级学生的数学成绩。 int scores[30]; // 存储30个学生的成绩 实现栈和队列:通过数组可以实现栈(后进先出)和队列(先进先出)的数据结构。 例:使用数组来实现一个队列: int queue[100]; int front = -1, rear = -1; 数据处理和分析:在数据科学和计算机科学中,数组广泛用于存储和处理数据。 例:用数组存储每日气温数据,进行温度变化分析。 排序和查找:数组是许多经典排序和查找算法(如冒泡排序、二分查找)的基础数据结构。 例:使用数组进行排序: for (int i = 0; i < 5; i++) { for (int j = 0; j < 5 - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); } } } 图像处理:在图像处理中,像素值通常以二维数组的形式存储,用于表示图像中的颜色和亮度。 例:一个图像的RGB像素值可以用二维数组表示。 int image[100][100][3]; // 100x100像素的图像,每个像素有RGB三个值

二月 18, 2025 · 林墨瀚

栈 Stack

栈(Stack)是一种线性数据结构,它遵循“后进先出”(LIFO,Last In First Out)原则,即最后加入的元素最先被取出。栈通常用于实现需要“反向”操作的算法和数据结构。 栈的特点: 后进先出(LIFO):栈的访问顺序遵循后进先出的原则,最后插入的元素最先被移除。 操作限制:栈只允许在一端进行插入和删除操作,这一端通常被称为栈顶(Top)。 动态增长:栈可以根据需要动态地扩展或收缩(对于动态数组实现的栈)。 存储结构:栈可以使用数组或链表来实现。 栈的常见操作: push:将一个元素添加到栈顶。 pop:移除栈顶元素,并返回该元素的值。 peek(或 top):查看栈顶元素的值,但不移除它。 isEmpty:检查栈是否为空。 size:返回栈中元素的个数。 栈的应用: 递归调用的实现:计算机的函数调用使用栈来管理函数的调用和返回。当一个函数调用时,它的返回地址和局部变量会被压入栈中,函数执行完毕后从栈中弹出返回地址。 表达式求值:栈常用于表达式的求值和解析,特别是在中缀表达式、后缀表达式和前缀表达式的转换和计算中。 深度优先搜索(DFS):栈用于深度优先搜索(DFS)算法,通过栈记录搜索路径,实现图的遍历。 回溯算法:在回溯算法中,栈用于记录当前的选择和路径,便于回退到上一步状态。 栈的示例(C++): #include <iostream> #include <stack> // 引入stack头文件 using namespace std; int main() { stack<int> st; // 创建一个空栈 // push操作:压栈 st.push(10); // 压入10 st.push(20); // 压入20 st.push(30); // 压入30 // peek操作:查看栈顶元素 cout << "栈顶元素: " << st.top() << endl; // 输出: 30 // pop操作:弹出栈顶元素 st.pop(); // 弹出30 // 打印栈顶元素 cout << "弹出栈顶元素后, 栈顶元素: " << st.top() << endl; // 输出: 20 // isEmpty操作:检查栈是否为空 if (!st.empty()) { cout << "栈不是空的!" << endl; // 输出: 栈不是空的! } // size操作:获取栈的大小 cout << "栈的大小: " << st.size() << endl; // 输出: 2 return 0; } 代码解析: push:将元素压入栈中。 top:查看栈顶元素的值。 pop:移除栈顶元素。 empty:检查栈是否为空。 size:返回栈的元素数量。 栈的优缺点: 优点: 时间效率高:栈的插入和删除操作通常为 O(1) 时间复杂度,因此在许多需要频繁插入和删除操作的场景中,栈非常高效。 简单的操作:栈的操作非常简单,只需要知道栈顶元素,并根据栈顶进行插入或删除操作。 缺点: 访问受限:栈只允许访问栈顶的元素,不能直接访问栈中的其他元素,因此它并不适用于需要随机访问元素的场景。 空间限制:栈的大小是有限的,如果栈中的元素太多,可能会发生栈溢出(尤其是递归调用时,栈空间可能会用尽)。 栈的应用实例: 括号匹配:栈可以用于检测表达式中的括号是否匹配,特别是在编译器中,栈用于解析代码中的括号、花括号、方括号等。 Undo(撤销)操作:在许多应用程序中,撤销功能通常是通过栈来实现的,每次执行操作时,将当前状态压入栈中,撤销时从栈中弹出之前的状态。 表达式求值:栈用于处理中缀、前缀、后缀表达式的转换和计算。 总结: 栈是一种常见的线性数据结构,具有后进先出的特点,广泛应用于各种计算机科学和算法问题中。它的简单性和高效性使其成为实现许多算法和数据结构的基础。在需要跟踪状态或执行回溯操作的场景中,栈是非常有用的工具。 ...

二月 18, 2025 · 林墨瀚

算法题类型

1. 排序与查找 排序问题:将一组数据按照特定顺序(通常是升序或降序)排列。 基础排序:简单直观,适用于小规模数据。 冒泡排序:通过不断交换相邻元素进行排序。(稳定排序) 选择排序:每次选择未排序部分的最小元素放到已排序部分的末尾。(不稳定排序) 插入排序:将每个元素插入到已排序部分的正确位置。(稳定排序) 高级排序:效率较高,适用于大规模数据。 快速排序:通过分治法和基准元素进行排序。(不稳定排序) 归并排序:通过分治法将数据分成小份排序,然后合并。(稳定排序) 堆排序:利用堆这种数据结构进行排序。(不稳定排序) 线性排序:时间复杂度为O(n),适用于特定类型的数据。 计数排序:适用于数据范围较小的整数。(稳定排序) 桶排序:将数据分到不同的桶中,然后分别排序。(稳定排序) 基数排序:按位进行排序,适用于整数或字符串。(稳定排序) 特殊排序:针对特定场景的排序算法。 外部排序:用于处理无法一次性加载到内存的数据。 查找问题:在数据集中寻找特定元素。 线性查找:逐个遍历数据,直到找到目标元素。(时间复杂度O(n)) 二分查找:在已排序数据中,通过不断缩小搜索范围来查找目标元素。(时间复杂度O(log n)) 二分查找变种:查找第一个大于等于目标值的元素、查找最后一个小于等于目标值的元素等。 矩阵中的目标值查找:在二维数组中查找目标元素。 2. 数学与数论 基础数学: 素数判断:判断一个数是否为素数(只能被1和自身整除的数)。 试除法:通过尝试除以小于等于其平方根的所有整数来判断。(时间复杂度O(sqrt(n))) 埃拉托斯特尼筛法:一种高效的筛选素数的方法。(时间复杂度O(n log log n)) 最大公约数与最小公倍数: 欧几里得算法:用于计算两个整数的最大公约数。(时间复杂度O(log(a, b))) 快速幂:高效计算大整数的幂。(时间复杂度O(log n)) 二进制转换: 二进制转十进制 十进制转二进制 数论:研究整数性质的数学分支。 同余问题: 线性同余方程:求解形如ax ≡ b (mod m)的方程。 模运算:求一个数除以另一个数的余数。 数论函数:定义在整数集合上的函数。 欧拉函数:计算小于等于n且与n互质的正整数个数。 莫比乌斯函数:一个数论函数,用于解决一些组合数学问题。 中国剩余定理:求解同余方程组。 矩阵快速幂:用于快速计算矩阵的幂。 组合数学:研究组合问题的数学分支。 组合数与杨辉三角: 组合数:从n个元素中选择k个元素的方案数。 杨辉三角:一个数表,用于计算组合数。 卡特兰数:一个出现在各种组合问题中的数列。 3. 动态规划 (Dynamic Programming, DP) 经典DP问题: 斐波那契数列:一个递归定义的数列,每个数是前两个数之和。(时间复杂度O(n)) 背包问题: 0/1背包:每个物品只能选择一次。(时间复杂度O(n*capacity)) 完全背包:每个物品可以选择多次。(时间复杂度O(n*capacity)) 多重背包:每个物品有有限个。(时间复杂度O(n_capacity_count)) 最长公共子序列(LCS):两个序列的最长公共子序列。(时间复杂度O(m*n)) 最长递增子序列(LIS):一个序列的最长递增子序列。(时间复杂度O(n log n)) 硬币换零钱问题:用最少的硬币凑出指定金额。(时间复杂度O(amount*n)) 矩阵链乘法:找到矩阵相乘的最佳顺序,使得运算次数最少。(时间复杂度O(n^3)) 其他DP问题: 编辑距离问题:将一个字符串转换为另一个字符串所需的最少操作次数。(时间复杂度O(m*n)) 打家劫舍问题:在不能偷窃相邻房屋的情况下,偷窃最多金额。(时间复杂度O(n)) 状态压缩DP:使用位运算来表示状态的动态规划。 数位DP:用于解决与数字位数有关的动态规划问题。 4. 贪心算法 经典贪心问题: 活动选择问题:选择最多的不重叠活动。(时间复杂度O(n log n)) 区间调度问题:选择最多的非重叠时间区间。(时间复杂度O(n log n)) 零钱兑换问题:用最少的硬币凑出指定金额。(不一定能找到最优解) 其他贪心问题: 最小生成树:找到连接所有顶点的最小权值边的集合。 Prim算法(时间复杂度O(n^2)) Kruskal算法(时间复杂度O(m log n)) 霍夫曼编码:用于数据压缩的编码方式。(时间复杂度O(n log n)) 分数背包问题:允许选择物品的一部分。(时间复杂度O(n log n)) Huffman树:一种用于霍夫曼编码的树结构。 5. 回溯法 (Backtracking) 经典回溯问题: 八皇后问题:在棋盘上放置八个皇后,使其互不攻击。(时间复杂度较高) N皇后问题:八皇后问题的推广。(时间复杂度较高) 全排列与组合:生成集合的所有排列和组合。(时间复杂度较高) 数独问题:填充数独游戏。(时间复杂度较高) 其他回溯问题: 矩阵路径问题:在矩阵中寻找满足条件的路径。(时间复杂度较高) 子集和问题:找到一个集合的子集,其元素之和等于目标值。(时间复杂度较高) 6. 分治法 (Divide and Conquer) 经典分治问题: 排序问题: 归并排序(时间复杂度O(n log n)) 快速排序(平均时间复杂度O(n log n),最坏时间复杂度O(n^2)) 逆序对计数:计算数组中的逆序对数。(时间复杂度O(n log n)) 最大子数组和问题:找到数组中和最大的连续子数组。(时间复杂度O(n)) 其他分治问题: 矩阵乘法: Strassen算法(时间复杂度O(n^log2(7))) 7. 图论 (Graph Theory) 图的遍历: 深度优先搜索(DFS):沿着图的深度方向遍历。(时间复杂度O(V+E)) 广度优先搜索(BFS):沿着图的宽度方向遍历。(时间复杂度O(V+E)) 最短路径问题: Dijkstra算法:用于计算非负权图的单源最短路径。(时间复杂度O(E log V)) Bellman-Ford算法:用于计算可以包含负权边的图的单源最短路径。(时间复杂度O(V*E)) Floyd-Warshall 算法:用于计算任意两点之间的最短路径。(时间复杂度O(V^3)) 最小生成树问题: Prim 算法(时间复杂度O(E log V)) Kruskal 算法(时间复杂度O(E log V)) 其他图论问题: 拓扑排序:对有向无环图进行排序,使得所有边都指向后面的顶点。(时间复杂度O(V+E)) 强连通分量:找到图中互相可达的顶点集合。(时间复杂度O(V+E)) 图的二分性判断:判断一个图是否为二分图。(时间复杂度O(V+E)) 网络流:研究网络中流量的分配问题。 二分图匹配:找到二分图中最大的匹配数。 8. 位运算 位运算技巧:直接对二进制位进行操作,效率高。 判断是否为 2 的幂:n & (n - 1) == 0 统计二进制中 1 的个数:Hamming Weight 交换两个数的值:a ^= b; b ^= a; a ^= b; 找出只出现一次的数字:利用异或运算的性质 计算二进制补码 位运算应用: 状态压缩:用二进制位表示状态 快速幂:利用位运算优化幂运算 9. 树与二叉树 二叉树的遍历: 前序、中序、后序遍历:递归或迭代方式访问所有节点 层次遍历:逐层访问节点 二叉树的性质: 判断是否为二叉搜索树:中序遍历结果为升序 二叉树的最大深度:递归或迭代方式求解 路径和问题:寻找满足条件的路径 其他树结构: 平衡二叉搜索树: AVL 树:保持左右子树高度差不超过 1 红黑树:一种自平衡的二叉搜索树 线段树:用于处理区间查询问题 字典树:用于存储和查找字符串 10. 并行与分布式算法 并行算法:利用多核处理器并行计算,提高效率。 并行排序问题 并行搜索问题 分布式算法:在分布式系统中运行的算法,解决大规模数据处理和存储问题。 MapReduce 问题:一种分布式计算框架 分布式一致性问题:Paxos、Raft 等协议 11. 高级数据结构 平衡树: AVL 树 红黑树 其他数据结构: 跳表:一种有序链表,支持快速查找 并查集(Union-Find):用于处理集合的合并和查询操作 LRU 缓存机制:最近最少使用缓存 堆(优先队列):一种特殊的树形数据结构,用于快速访问最大/最小值

二月 5, 2025 · 林墨瀚

《Hello 算法》第二章·算法复杂度

当我们提到算法复杂度时,可以把它当作衡量完成一项任务效率的标准。想象你和朋友一起比赛,看谁能更快地完成整理书架的任务。你们有两种不同的做法,最终谁更快完成任务,谁就是胜利者。通过算法复杂度,我们能够评估不同方法在完成任务时的效率,帮助我们在各种情况下选择最佳的方案。 时间复杂度:任务做得有多快? 时间复杂度就像是完成任务所需要的时间。假设你有100本书要整理,每次拿一本书,你都需要检查它放对了没,直到所有书都摆放好了。那么,整理书架的时间就跟书的数量成正比。 在计算机科学中,算法的时间复杂度通过大O符号表示,用来描述算法的运行时间随输入值大小的变化趋势。它通常忽略低阶项和系数,只关注输入规模趋近无穷时的表现。时间复杂度常用来衡量算法在最坏情况下的执行时间,也可用来分析平均情况。通过估算算法操作单元的数量来计算时间复杂度,常见的分类包括线性时间算法(O(n))和指数时间算法(O(Mn))。(总结自Wikipedia) 常见的时间复杂度 O(1) — 常数时间复杂度 如果不管有多少本书,任务所需时间都一样(比如,找几本书),这就是常数时间复杂度。就像你每次做的动作都很简单,任务的大小对时间没有太大影响。 O(n) — 线性时间复杂度 假设你是一本一本地整理书,每增加一本书,整理的时间就多一点。这就是线性时间复杂度,任务量增加,所需时间也按比例增加。 O(n²) — 平方时间复杂度 如果你需要比别人做得更复杂,比如检查每本书与其他每本书的位置是否匹配,时间就会随着任务数量的增加加速增加。每本书要和其他所有书比对一次,这就是平方时间复杂度。 O(log n) — 对数时间复杂度 这种复杂度表示每次操作都会将问题规模缩小一半,导致时间增长相对较慢。比如,二分查找就是这种算法类型,每次将搜索范围减半,大大提升了效率。 O(n log n) — 对数线性时间复杂度 这种复杂度常见于高效的排序算法,比如快速排序和归并排序。它的时间复杂度优于O(n²),但没有O(n)那样直接。 空间复杂度:做任务需要多少空间? 空间复杂度就像是你整理书架时需要的额外空间。假设你有一张大桌子用来暂时放书,那么空间复杂度就是你需要多少桌子来摆放这些书。 在计算机科学中,空间复杂度描述了一个算法或程序执行时所需的存储空间大小,通常以输入值长度为函数。类似于时间复杂度,空间复杂度也使用大O符号表示,如 O(n)、O(n log n)、O(2^n) 等,其中n代表输入长度,影响算法的空间需求。空间复杂度计算时不考虑算法的执行时间。(总结自Wikipedia) 常见的空间复杂度 O(1) — 常数空间复杂度 如果你只需要一个小桌子就能完成任务,不管书有多少,空间需求都一样。这就是常数空间复杂度,表示你对空间的需求是固定的,不会随任务大小变化。 O(n) — 线性空间复杂度 如果你需要一张桌子来放每一本书,那么空间复杂度就是线性的。随着书的增多,你需要的空间也会增加。 生活中的例子: O(1):找固定的物品(比如桌子上的一支笔),不管桌子多大,时间都差不多。 O(n):检查书包里的所有物品,时间随着物品数量的增加而增加。 O(n²):如果你需要检查每两件物品之间的匹配情况,随着物品数量增加,任务变得更加复杂。 大O符号 在算法分析中,我们使用大O符号来描述算法的时间或空间复杂度,特别是在最坏情况下。它帮助我们理解数据量增大时,算法性能会如何变化。通过大O符号,我们可以评估不同算法在处理大数据时的效率。 大O符号(Big-O notation)是用来描述算法时间复杂度或空间复杂度的数学符号,表示随着输入数据规模增大,算法的性能或资源需求的增长趋势。大O符号关注的是输入规模趋近无穷时算法的增长速率,忽略常数因子和低阶项,旨在提供算法在最坏情况下的表现。常见的时间复杂度有O(1)(常数时间)、O(n)(线性时间)、O(n²)(平方时间)等,它们帮助我们评估不同算法的效率和适应性,尤其在处理大数据时。(来源:ChatGPT) 时间复杂度分析 O(1):常数时间复杂度,执行时间不随输入规模的变化而变化。 O(n):线性时间复杂度,时间随输入规模成正比增加。 O(n²):平方时间复杂度,时间随输入规模的平方增长,常见于双重循环。 O(log n):对数时间复杂度,时间增长较慢,常用于二分查找、平衡树等算法。 O(n log n):对数线性时间复杂度,通常出现在高效排序算法中,如快速排序、归并排序等。 空间复杂度分析 O(1):常数空间复杂度,额外的空间需求不随输入规模的变化而变化。 O(n):线性空间复杂度,额外空间需求与输入规模成正比。 总结 & 共勉 希望通过这个简洁的介绍,你对算法复杂度有了更加清晰的理解。如果你有任何问题或想深入探讨某个算法复杂度,随时欢迎提问! ...

二月 1, 2025 · 林墨瀚

P1255 数楼梯

题目 题目描述 楼梯有 N 阶,上楼可以一步上一阶,也可以一步上二阶。 编一个程序,计算共有多少种不同的走法。 输入格式 一个数字,楼梯数。 输出格式 输出走的方式总数。 样例 #1 样例输入 #1 4 样例输出 #1 5 提示 对于 60% 的数据,N≤50; 对于 100% 的数据,1≤N≤5000。 问题分析 我们可以用递归角度解决: 当青蛙跳到第 n 级楼梯,可以选择: 从第 n-1 级跳到第 n 级(跳 1 级)。 从第 n-2 级跳到第 n 级(跳 2 级)。 递归关系: f(n)=f(n−1)+f(n−2) 其中,f(n) 代表跳到第 n 级楼梯的方法总数。 并且: 如果 n=0,即青蛙在第 0 级楼梯,则 f(0)=0(即什么都不做)。 如果 n=1,即青蛙跳到第 1 级楼梯,那么只能跳 1 级,则 f(1)=1。 问题的递归式: 基于以上推理,我们可以列出以下递归关系式: 解题 伪代码: F(n): 如果 n == 0: 返回1 // 起始点,不需要跳跃,只有一种方式 如果 n == 1: 返回 1 // 只有一种方式跳到第1阶 // 从n-1阶跳到n阶,或从n-2阶到n阶 返回 CountWays(n - 1) + CountWays(n - 2) Python 实现: 初版: ...

二月 1, 2025 · 林墨瀚

《Hello 算法》第一章 · 初识算法

算法 算法定义 算法(algorithm)是指在有限时间内通过一组明确的步骤或指令来解决特定问题的过程。它具有以下基本特性: 明确性:问题及其输入输出是清晰定义的,确保在不同的执行环境下可以得到一致的结果。 可行性:算法能够在有限的步骤、时间和内存空间内完成,保证其可以在实际系统中执行。 确定性:每个步骤有清晰的定义,并且在相同的输入和运行条件下,算法的输出始终一致,避免了不确定性和随机性。 最优性:在满足问题要求的前提下,算法能够实现最高效的执行,例如最短的时间复杂度和最少的空间消耗。 实例: 排序算法,如冒泡排序(Bubble Sort)和快速排序(Quick Sort),用于将一组无序数据排列为有序数据。快速排序通常被认为是最优的,因为它的平均时间复杂度是 O(n log n),而冒泡排序的时间复杂度是 O(n²)。 算法复杂度 算法复杂度(algorithm complexity)是用来衡量算法执行效率的指标,主要包括时间复杂度和空间复杂度。它反映了算法执行时所需资源的消耗,通常用于评估算法的优劣。 时间复杂度:描述算法执行所需时间的增长情况,通常使用大 O 表示法(Big-O notation)。例如,O(n)、O(log n)、O(n²) 等表示算法随着输入规模的增加,运行时间是如何变化的。 空间复杂度:描述算法执行时所需内存空间的增长情况。与时间复杂度类似,空间复杂度也可以用大 O 表示法来表示。 常见的时间复杂度: O(1):常数时间复杂度,表示算法的执行时间不随输入规模的增加而变化。 O(log n):对数时间复杂度,表示算法的执行时间随着输入规模的增加而缓慢增长。例如,二分查找算法。 O(n):线性时间复杂度,表示算法的执行时间与输入规模成正比。 O(n²):平方时间复杂度,表示算法的执行时间随着输入规模的平方增长。例如,冒泡排序、插入排序等。 O(n log n):对数线性时间复杂度,表示算法的执行时间比线性增长快,但比平方增长慢。例如,快速排序和归并排序。 这一部分内容将在第二章细讲 实例: 在查找某个元素时,线性查找(O(n))的时间复杂度较高,适用于小规模数据。而二分查找(O(log n))则适用于已经排序好的数据,效率要高得多。 数据结构 数据结构定义 数据结构(data structure)是指组织和存储数据的方式,它涉及数据内容、数据间的关系以及对数据的操作方法。良好的数据结构设计能够有效提高程序的性能,达到高效处理数据的目的。数据结构的设计目标包括: 空间效率:尽量减少内存占用,节省计算机资源。 操作效率:使数据的访问、插入、删除、更新等操作尽可能快速,提升算法的执行效率。 简洁性:提供简洁且直观的数据表示和逻辑结构,以便算法能高效运行。 平衡性:数据结构的设计是一个权衡过程,为了在某一方面取得优化,往往需要在其他方面做出妥协。例如,哈希表能够提供快速的查找操作,但它的空间消耗可能较大。 常见的数据结构: 线性结构:如数组、链表、栈、队列。 树形结构:如二叉树、平衡二叉树、B 树。 图形结构:如无向图、有向图、加权图。 哈希结构:哈希表用于快速查找。 数据结构与算法的关系 数据结构与算法是紧密相关的,它们的结合体现在以下几个方面: 数据结构是算法的基础:数据结构为算法提供了有序的存储结构和方法,通过结构化的存储方式组织数据,并为算法提供快速的访问和修改操作。例如,数组和链表是最常见的数据结构,分别适合不同的算法需求。 算法为数据结构赋予功能:数据结构本身仅存储数据,只有与算法结合,才能发挥其应有的作用。通过不同的算法,可以对同一数据结构进行各种操作,如排序、查找、遍历等。 数据结构的选择影响算法的效率:同一问题可以通过不同的数据结构来实现,选择合适的数据结构能够显著提高算法的效率。例如,图的遍历可以通过邻接矩阵或邻接表实现,选择不同的数据结构会影响算法的性能。 实例: 在实现图的最短路径算法时,使用邻接矩阵的空间复杂度较高,但适合密集图;而使用邻接表则适用于稀疏图,能够节省内存并提高效率。 在实现深度优先搜索(DFS)时,栈(Stack)是一个重要的辅助数据结构,它能够帮助我们跟踪节点的遍历路径。 总结 数据结构与算法是计算机科学的核心,良好的数据结构设计能够帮助我们高效地存储和操作数据,而合理选择和优化算法可以显著提高程序的性能。理解它们之间的关系,能够帮助我们在实际编程中做出更合适的选择,提高代码的执行效率和可维护性。

一月 21, 2025 · 林墨瀚

算法自学笔记专栏总序

欢迎来到我的《算法自学笔记》专栏!这是我在自学算法过程中的一个学习日志,旨在记录并分享我对常见算法和数据结构的理解、学习过程中的思考以及探索到的有用资源。我希望通过这个专栏,能帮助自己更好地总结学习成果,同时为其他对算法感兴趣的同学提供一些启发和参考。 专栏内容概览 本专栏将涵盖以下几个主要方面: 基本数据结构:包括数组、链表、栈、队列、哈希表、树、图等常见数据结构。 排序与查找算法:涉及快速排序、归并排序、二分查找、线性查找等常用算法。 动态规划:深入讲解背包问题、最短路径问题、最长公共子序列等经典问题的解法。 图算法:介绍深度优先搜索、广度优先搜索、最小生成树、最短路径等图相关算法。 其他算法:包括贪心算法、回溯算法、分治算法等常见的高级算法。 专栏目标 整理学习笔记:将学习过程中遇到的关键点、算法实现和常见问题进行总结,并加以归纳整理。 分享学习经验:记录并分享自己的自学心得,帮助更多对算法感兴趣的学习者更高效地入门和进阶。 持续更新:随着学习的深入,我将持续更新专栏内容,不断补充新的算法和数据结构,力求使本专栏内容更加丰富和完善。 专栏发展方向 目前,我已完成了常见数据结构和部分经典算法的学习笔记。未来,我会继续扩展更深入的算法和数据结构内容,并根据实践经验不断优化和完善笔记。 此外,专栏的配套网站也在开发中,未来将提供更加直观的学习资料,并结合在线编程和算法演示,进一步增强互动性和学习体验。 如何使用本专栏 仓库地址 浏览笔记:可以随时浏览并参考专栏中的各类学习笔记,帮助你理解算法的核心思想及其实现方式。 贡献代码:如果你在某个算法或数据结构上有新的见解或改进,欢迎通过提交 Pull Request 参与贡献。 参与讨论:如果你在学习过程中有问题或想法,可以在 Issues 区域提出,我们一起讨论、解决。 关于我 我的背景 我是一个初二学生,虽然算法学习起步较晚,但我一直对编程和算法充满浓厚兴趣。如今,我正通过自学不断提升自己的算法水平,并希望能通过实践提升自己的编程能力。 为什么专注算法学习 在我的学习经历中,我曾对编程语言产生浓厚兴趣,并开始尝试学习一些编程语言。然而,老师建议我先专注于算法学习,因为掌握算法的核心思维将帮助我更好地理解编程语言的底层原理。因此,我决定将语言学习暂时搁置,集中精力攻克算法,培养扎实的逻辑思维能力。这一决定,也为我后续的编程之路打下了坚实的基础。 我的目标 我的目标是通过不断学习算法和数据结构,在信息奥赛(NOI)中争取获得省赛一等奖。我相信,通过扎实的算法基础,不仅能提升我的编程水平,也能帮助我在将来的编程竞赛中脱颖而出。 学习资源与参考资料 在这条自学算法的道路上,我参考了以下书籍和资料: 《hello 算法》:为初学者提供了很多实用的算法学习资料和代码示例,帮助我从零基础入门。 CS自学指南:为自学计算机科学的学生提供了清晰的学习路径,帮助我规划和组织学习内容。 HackWay技术学习路线:提供了从入门到进阶的技术学习路线,帮助我更系统地提升技能。 《算法基础:打开算法之门》:为算法入门提供了详细的讲解,帮助我理解基础概念。 《算法导论》:经典教材,深入探讨了算法设计和分析,是我的重要参考书。 《数据结构、算法与应用:C++语言描述》:结合C++语言讲解数据结构和算法的实现,帮助我理解实际应用。 许可证 此专栏内容采用 CC BY-NC-SA 4.0 许可证,您可以在非商业用途下使用和分享本专栏的内容,但需要注明出处,并且分享的内容须遵循相同的许可协议。 致谢 特别感谢所有在算法和编程领域做出贡献的前辈,正是因为有了他们的知识和经验,我们这些自学者才能更容易获得进步。希望通过我的努力,这些宝贵的知识能帮助更多的学习者。 这是我自学算法过程中的一个小小总结和记录,希望你在这个专栏中能收获知识与灵感。欢迎你在学习中提出建议,或者与我共同探讨算法的奥妙!

一月 21, 2025 · 林墨瀚

技研录,开站!

我创建这个网站的初衷源于对编程、语言学习和历史的浓厚兴趣。作为一名热衷于探索技术与人文领域的学生,我希望在这片数字空间里记录自己的成长与探索,并通过这个平台展示自己的编程项目和学习成果,同时与志同道合的人分享思考与见解。我的网站将展示我在编程、英语学习、日语以及历史研究等方面的积累与思考。从简单的代码到深刻的思辨,每一项成果都是我成长的一部分。希望你能在这里找到与你志同道合的灵感与启发。 人生是一段不断挑战自我、追求卓越的旅程。无论是编程的代码,语言的表达,还是历史的学习,每一步都需要耐心与坚持。通过这个平台,我希望与大家共同进步、互相激励。未来的路上,让我们继续学习、探索、不懈追求,成为更好的自己。 我的未来仍然很长,充满了无限的可能与机会。在这段不断学习与成长的旅程中,我相信每一次努力和突破,都会带来更多选择与机遇。无论是技术创新,还是人生的其他领域,我都愿意保持积极向上的心态,勇敢追逐梦想,迎接新的挑战。未来,我将继续积累经验、拓宽视野,把握每一个机会,成就更好的自己。

一月 20, 2025 · 林墨瀚