1533 字
8 分钟
【算法】基础算法模板

基础算法模板#

TIP

Keep on updating…

Mater Theorem#

主定理(Master Theorem)是分析递归法复杂度的重要定理。

Master 公式如下:

T(n)=aT(n/b)+O(nc), where a,b,c is constT(n) = a * T(n/b) + O(n^c),\ \text{where a,b,c is const}
  1. 如果 log(b,a)<c\log(b,a) < c, 那么复杂度为:O(nc)O(n^c)
  2. 如果 log(b,a)>c\log(b,a) > c, 那么复杂度为:O(nlog(b,a))O(n^{\log(b,a)})
  3. 如果 log(b,a)=c\log(b,a) = c, 那么复杂度为:O(nclogn)O(n^c * \log n)

addtion: T(n)=2T(n/2)+O(nlogn)T(n) = 2 * T(n/2) + O(n *\log n) 的时间复杂度为 O(n((logn)2))O(n * ((\log n)^2))

二分查找#

// find max idx of num <= tar
int binary_search() {
int ans = -1;
while (l <= r) {
int m = l + ((r - 1) >> 2); // avoid memory overflow
if (Array[m] >= tar) {
r = m - 1;
ans = m;
}
if (Array[m] < tar) {
l = m + 1;
}
}
return ans;
}

树的遍历#

树的前序,中序,后序遍历

以下图中的树为例

example tree

  • 先序:中,左,右

    期望输出:

    1 2 4 5 3 6 7
  • 中序:左,中,右

    期望输出:

    4 2 5 1 6 3 7
  • 后序:左,右,中

    期望输出:

    4 5 2 6 7 3 1

递归版#

前序遍历:

void printPreorder(treeNode *root) {
if (root == nullptr)
return;
cout << root->val << " ";
printPreorder(root->left);
printPreorder(root->right);
}

中序遍历:

void printInorder(treeNode *root) {
if (root == nullptr)
return;
printInorder(root->left);
cout << root->val << " ";
printInorder(root->right);
}

后序遍历:

void printPostorder(treeNode *root) {
if (root == nullptr)
return;
printPostorder(root->left);
printPostorder(root->right);
cout << root->val << " ";
}

栈实现版#

NOTE

递归法的实现依赖于语言底层实现的系统栈。会将当前没有运行完毕的函数压入系统栈(递归序)。

使用栈的方式实现树的遍历,本质上就是使用内存栈代替系统栈。可以通过理解系统栈的运行机制来实现通过内存栈来代替系统栈。节点入栈等同于将未处理的节点进行记录,通过之后的出栈pop操作再重新处理该节点。

同时还有一个小技巧,上述的“处理节点”,等同于“处理以节点为根节点的子树”。

对于前序遍历,使用栈实现的步骤如下:

  1. 打印栈顶元素,栈中弹出
  2. 如果右节点不为空,压入右节点
  3. 如果左节点不为空,压入左节点
  4. 重复1,2,3直到栈为空

代码如下:

void printPreorder_stack(treeNode *root) {
if (!root)
return;
std::stack<treeNode> st;
st.push(*root);
while (!st.empty()) {
treeNode tmp = st.top();
cout << tmp.val << " ";
st.pop();
if (tmp.right)
st.push(*tmp.right);
if (tmp.left)
st.push(*tmp.left);
}
return;
}
NOTE

在前序遍历中,我们会先优先处理栈顶元素,之后在考虑将栈顶元素的左右节点入栈

对于中序遍历,使用栈实现步骤如下:

  1. 子树左边界进栈,直到左边界为空
  2. 栈中弹出节点,打印,节点右树重复 1
  3. 直到没有子树,且栈为空

代码如下:

void printInorder_stack(treeNode *root) {
if (!root)
return;
std::stack<treeNode> st;
treeNode *head = root; // 认为是子树的头节点
while (!st.empty() || head != nullptr) {
if (head) {
st.push(*head);
head = head->left;
} else {
head = &st.top();
st.pop();
cout << head->val << " ";
head = head->right;
}
}
}
NOTE

在中序遍历中,我们会先找到树最深的左节点。通过head指针,可以标记当前正在处理的子树

对于后序遍历,有两种实现方式:两个栈版,一个栈版。前者比较好理解,所以将两种版本都放在这里进行参考:

  • 两个栈版:

先序为中,左,右。很自然能联想到先’序:中,右,左(上述代码中的压入顺序调整一下即可),那么后序就是将先’序倒置,只需要使用另一个栈收集先’的输出,最后将这个收集栈打印出来即可

  1. 将栈顶元素收集到收集栈,弹出
  2. 左节点存在,入栈
  3. 右节点存在,入栈
  4. 重复1,2,3直到栈空

代码如下:

void printPostorder_twoStack(treeNode *root) {
if (!root)
return;
std::stack<treeNode> st;
std::stack<treeNode> collect;
st.push(*root);
while (!st.empty()) {
treeNode tmp = st.top();
collect.push(tmp);
st.pop();
if (tmp.left) {
st.push(*tmp.left);
}
if (tmp.right) {
st.push(*tmp.right);
}
}
while (!collect.empty()) {
cout << collect.top().val << " ";
collect.pop();
}
}
  • 一个栈版本:
  1. 初始化指针 head 指向头节点,cur 指向当前遍历节点,根节点入栈
  2. 判断左树是否处理过,没有处理入栈
  3. 判断右树是否处理过,没有处理入栈
  4. 左右树都处理过了,则打印当前节点,head指向栈顶,重复2,3

代码如下:

void printPostorder_sigStack(treeNode *root) {
if (!root)
return;
std::stack<treeNode *> st;
st.push(root);
treeNode *head = root;
while (!st.empty()) {
treeNode *cur = st.top();
if (cur->left != nullptr && head != cur->left && head != cur->right) {
st.push(cur->left);
} else if (cur->right != nullptr && head != cur->right) {
st.push(cur->right);
} else {
cout << cur->val << " ";
head = st.top();
st.pop();
}
}
}
NOTE

后序遍历与中序遍历相似的地方是,后序遍历也需要先找到最深的根。不同的是中序遍历是找到最深的子树之后,回到根节点,但后序遍历需要先完全处理cur为根节点的子树。因此使用head标记已经处理完的子树,按照先左后右的顺序处理深层的子树。

排序#

归并排序#

归并排序是归并思想在排序上的应用。将排序的复杂度降到了 O(nlogn)O(n\log n)

排序的主要思想:

  1. 将问题从中点拆分
  2. 通过merge合并两部分的结果

以5个数的归并排序为例,可以将问题划分成如下图所示:

归并排序实例

可以看到在划分的最后,节点已经不能再划分,只能向上执行merge操作

归并排序完整代码:

#include <iostream>
using namespace std;
const int N = 10001;
int n;
int arr[N];
int tmp[N];
void merge(int l, int m, int r) {
int lp = l;
int rp = m + 1;
int begin = 0;
while (lp <= m && rp <= r) {
tmp[begin++] = arr[lp] > arr[rp] ? arr[rp++] : arr[lp++];
}
while (rp <= r) {
tmp[begin++] = arr[rp++];
}
while (lp <= m) {
tmp[begin++] = arr[lp++];
}
for (int i = 0; i < begin; i++) {
arr[l + i] = tmp[i];
}
}
void merge_sort(int l, int r) {
if (l >= r)
return;
int m = (l + r) >> 1;
merge_sort(l, m);
merge_sort(m + 1, r);
merge(l, m, r);
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int val;
cin >> val;
arr[i] = val;
}
merge_sort(0, n - 1);
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}

测试链接:洛谷P1177 【模板】排序

【算法】基础算法模板
http://onemom.top/posts/base_algorithm/
作者
onemotre
发布于
2025-11-12
许可协议
CC BY-NC-SA 4.0