目录
数据结构
第一章 绪论
算法复杂度
T ( n ) = { 1 a × T ( n b ) + c × n k T(n)=\left\{\begin{aligned} &1\\ &a\times T(\cfrac nb)+c \times n^k \end{aligned}\right. T(n)=⎩⎪⎨⎪⎧1a×T(bn)+c×nk
⇓ \Downarrow ⇓
T ( n ) = { n log b ( a ) , a > b k n k × log b ( n ) , a = b k n k , a < b k T(n)=\left\{\begin{aligned} &n ^{\log_b(a)}, &a>b^k\\ &n^k \times \log_b(n),&a=b^k\\ &n^k , &a<b^k\\ \end{aligned}\right. T(n)=⎩⎪⎪⎨⎪⎪⎧nlogb(a),nk×logb(n),nk,a>bka=bka<bk
第三章 栈、队列和数组
栈
出栈不同排列个数 卡特兰数: C 2 n n n + 1 \cfrac{C^n_{2n}}{n+1} n+1C2nn
第四章 串
模式匹配KMP
const int Size = 1e6 + 10;
int mynext[Size];
void get_next(string t) {
t += ' ';
int n = t.size(), i = 0, j = -1;
mynext[0] = -1;
while (i < n) {
if (j == -1 || t[i] == t[j]) {
mynext[++i] = ++j;
}
else j = mynext[j];
}
}
vector<int> get_all_point(string s, string t) {
vector<int> ans;
int i = 0, j = 0;
int sn = s.size(), tn = t.size();
t += ' ';
while (i < sn && j <= tn) {
if (j == -1 || s[i] == t[j]) {
i++; j++;
}
else {
j = mynext[j];
}
if (j == tn)ans.push_back(i - tn);
}
return ans;
}
第五章 树和二叉树
基本性质
假设总结点数是 n n n,度为 0 0 0的点有 n 0 n_0 n0个,度为 1 1 1的点有 n 1 n_1 n1个,度为 x x x的点有 n x n_x nx个:
- n = n 0 + n 1 + n 2 + . . . + n x n=n_0+n_1+n_2+...+n_x n=n0+n1+n2+...+nx
- n − 1 = n 1 + 2 n 2 + 3 n 3 + . . . + x n x n-1=n_1+2n_2+3n_3+...+xn_x n−1=n1+2n2+3n3+...+xnx
第二条等式成立的理由:
左边=边数=总结点n-1
右边=度为0的点往下无边,度为1的结点往下连着一条边,度为2的结点往下连着两条边,度为x的结点往下连着x条边
当该树为二叉树时:
- n = n 0 + n 1 + n 2 , n − 1 = n 1 + 2 n 2 n=n_0+n_1+n_2,n-1=n1+2n_2 n=n0+n1+n2,n−1=n1+2n2 ⇒ \Rightarrow ⇒ n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
高度为h的m叉树的最多结点个数n:
n
=
m
h
−
1
m
−
1
n=\cfrac {m^h-1}{m-1}
n=m−1mh−1
m叉树的结点个数为n,最低高度h:
令
n
=
m
h
−
1
m
−
1
n=\cfrac {m^h-1}{m-1}
n=m−1mh−1可得
h
=
log
m
(
n
(
m
−
1
)
)
h=\log_m(n(m-1))
h=logm(n(m−1))
先中后序非递归遍历
二叉树定义
// Definition for a binary tree node.
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
先序
vector<int> preorderTraversal(TreeNode* root) {
TreeNode* p = root;
vector<int>ans;
stack< TreeNode*>sk;
while (p || sk.size()) {
while (p) {
sk.push(p);
ans.push_back(p->val);
p = p->left;
}
p = sk.top();
sk.pop();
p = p->right;
}
return ans;
}
中序
vector<int> inorderTraversal(TreeNode* root) {
TreeNode* p = root;
vector<int>ans;
stack< TreeNode*>sk;
while (p || sk.size()) {
while (p) {
sk.push(p);
p = p->left;
}
p = sk.top();
sk.pop();
ans.push_back(p->val);
p = p->right;
}
return ans;
}
后序
待续
树、森林、二叉树的转换
规则:左孩子右兄弟
性质:树T转成对应的二叉树BT,则T的后序遍历和BT的中序遍历相同。
第六章 图
性质:假设无权图 G G G的邻接矩阵为 A A A,则 A x A^x Ax的第i行第j列表示的含义是 i 到 j 的 走 k 步 的 路 径 数 量 = A i j i到j的走k步的路径数量=A_{ij} i到j的走k步的路径数量=Aij
第七章 查找
二叉平衡搜索树
高为h的二叉平衡搜索树最少的结点数:
当它的一棵子树高度为h-1,另一棵子树高度为h-2,既平衡又结点最少
int f(int n)
{
if (n <= 1)
return n;
else
return 1 + f(n - 1) + f(n - 2);
}
以上代码可用矩阵快速幂优化:
[
1
1
1
1
0
1
0
0
1
]
×
[
f
n
−
1
f
n
−
2
1
]
=
[
f
n
−
1
+
f
n
−
2
+
1
f
n
−
1
1
]
=
[
f
n
f
n
−
1
1
]
\left[ \begin{matrix} 1 &1 &1 \\1 & 0 &1 \\ 0&0&1 \end{matrix} \right] \times \left[ \begin{matrix} f_{n-1}\\f_{n-2} \\1 \end{matrix} \right]=\left[ \begin{matrix} f_{n-1}+f_{n-2}+1\\f_{n-1} \\1 \end{matrix} \right] =\left[ \begin{matrix} f_{n}\\f_{n-1} \\1 \end{matrix} \right]
⎣⎡110100111⎦⎤×⎣⎡fn−1fn−21⎦⎤=⎣⎡fn−1+fn−2+1fn−11⎦⎤=⎣⎡fnfn−11⎦⎤
红黑树
B树
高、关键字数和结点数的关系
m阶B树 ,每个结点关键字范围:
①根:
[
1
,
m
−
1
]
[1,m-1]
[1,m−1]
②非根:
[
⌊
m
−
1
2
⌋
,
m
−
1
]
[\lfloor \cfrac{m-1}{2}\rfloor,m-1]
[⌊2m−1⌋,m−1]
令
x
=
⌊
m
−
1
2
⌋
x=\lfloor \cfrac{m-1}{2}\rfloor
x=⌊2m−1⌋,x为非根最少关键字,则其有(x+1)个子树
m阶h高B树,总结点数范围:
[
1
+
2
×
(
x
+
1
)
h
−
1
−
1
x
,
m
h
−
1
m
−
1
]
[1 + 2 \times \cfrac{(x+1)^{h-1}-1}{x},\cfrac{m^{h}-1}{m-1}]
[1+2×x(x+1)h−1−1,m−1mh−1]
说明
最少:
第一层: 1 1 1
第二层: 2 2 2
第三层: 2 × ( x + 1 ) 2 \times (x+1) 2×(x+1)
第四层: 2 × ( x + 1 ) × ( x + 1 ) 2 \times (x+1)\times (x+1) 2×(x+1)×(x+1)
第 h 层: 2 × ( x + 1 ) h − 1 2 \times (x+1)^{h-1} 2×(x+1)h−1
最多:
第一层: 1 1 1
第二层: m m m
第三层: m × m m\times m m×m
第 h 层: m h − 1 m^{h-1} mh−1
m阶h高B树,总关键字n数范围:
[
2
×
(
x
+
1
)
h
−
1
−
1
,
m
h
−
1
]
[2 \times(x+1)^{h-1}-1,m^{h}-1]
[2×(x+1)h−1−1,mh−1]
说明
最少:
第一层: 1 1 1
第二层: 2 × x 2\times x 2×x
第三层: 2 × ( x + 1 ) × x 2 \times (x+1)\times x 2×(x+1)×x
第四层: 2 × ( x + 1 ) × ( x + 1 ) × x 2 \times (x+1)\times (x+1)\times x 2×(x+1)×(x+1)×x
第 h 层: 2 × ( x + 1 ) h − 1 × x 2 \times (x+1)^{h-1}\times x 2×(x+1)h−1×x
最多:
第一层: 1 × ( m − 1 ) 1\times(m-1) 1×(m−1)
第二层: m × ( m − 1 ) m\times(m-1) m×(m−1)
第三层: m × m × ( m − 1 ) m\times m\times(m-1) m×m×(m−1)
第 h 层: m h − 1 × ( m − 1 ) m^{h-1}\times(m-1) mh−1×(m−1)
分别令
n
=
2
×
(
x
+
1
)
h
−
1
−
1
,
m
h
−
1
n=2 \times(x+1)^{h-1}-1,m^{h}-1
n=2×(x+1)h−1−1,mh−1,可得m阶n个关键字B树,高度h范围:
[
log
m
(
n
+
1
)
,
log
x
+
1
(
n
+
1
2
)
+
1
]
[\log _{m} (n+1),\log _{x+1} (\cfrac{n+1}{2})+1]
[logm(n+1),logx+1(2n+1)+1]
插入
待续
删除
待续
第八章 排序
内 部 排 序 { 插 入 排 序 { 直 接 插 入 排 序 折 半 交 换 排 序 希 尔 排 序 交 换 排 序 { 冒 泡 排 序 快 速 排 序 选 择 排 序 { 简 单 选 择 排 序 堆 排 序 归 并 排 序 基 数 排 序 内部排序\left\{\begin{aligned} &插入排序\left\{\begin{aligned} &直接插入排序\\ &折半交换排序\\ &希尔排序\\ \end{aligned}\right.\\ &交换排序\left\{\begin{aligned} &冒泡排序\\ &快速排序\\ \end{aligned}\right.\\ &选择排序 \left\{\begin{aligned} &简单选择排序\\ &堆排序\\ \end{aligned}\right.\\ &归并排序\\ &基数排序\\ \end{aligned} \right. 内部排序⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧插入排序⎩⎪⎨⎪⎧直接插入排序折半交换排序希尔排序交换排序{冒泡排序快速排序选择排序{简单选择排序堆排序归并排序基数排序
各种算法性质
算法种类 | 时间复杂度 | 空间复杂度 | 稳定性 | ||
---|---|---|---|---|---|
最好情况 | 平均情况 | 最坏情况 | |||
直接插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(nlogn) | O(n^2) | O(n^1.3) | O(1) | 不稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) | 稳定 |
直接插入排序
选取第一个无序的数,插入到有序的数中
稳定,越顺越快
void InsertSort(int n) {
//n个数,从0到n-1
int t, i, j;
for (i = 0; i < n; i++) {
t = num[i];
for (j = i; j > 0 && t < num[j - 1]; j--)
num[j] = num[j - 1];
num[j] = t;
}
}
冒泡排序
从前往后或者从后往前,大的数到后面或者小的数到前面
稳定,越顺越快
简单选择排序
选取一个最小的数(或最小)和当前位置交换
不稳定,时间固定
希尔排序
根据增量分组,在小组中排序(小组中排序采用直接插入排序),持续缩小增量,直到增量为1
稳定,越顺越快
void ShellSort(int n) {
//n个数,从0到n-1
int d, i, j, t;
for (d = n / 2; d >= 1; d--) {
for (i = 0; i < n; i++) {
t = num[i];
for (j = i; j - d >= 0 && t < num[j - d]; j -= d) {
num[j] = num[j - d];
}
num[j] = t;
}
}
}
快速排序
选取第一个数为哨兵,小的放到左,大的到右
不稳定,越顺越慢
void QuickSort(int l, int r) {
//对[l,r]进行排序
if (l >= r)return;
int t = num[l], i = l , j = r;
while (i < j) {
while (i < j && t <= num[j])j--;
if (i < j)num[i++] = num[j];
while (i < j && num[i] <= t)i++;
if (i < j)num[j--] = num[i];
}
num[i] = t;
QuickSort(l, i - 1);
QuickSort(i + 1, r);
}
堆排序
构建大根堆或者小根堆,首次调整从最后一个非叶结点开始(从右往左,从下到上)调整(和子节点比较),向上至根节点进行调整
不稳定
void HeapJust(int x, int n) {
//自顶向下调整
//父x->子y=2*x+1,2*x+2
//子y->父x=(y-1)/2
int y;
while (x * 2 + 1 <= n - 1) {
if (2 * x + 2 > n - 1 || num[2 * x + 1] > num[2 * x + 2])y = 2 * x + 1;
else y = 2 * x + 2;
if (num[x] > num[y])break;
swap(num[x], num[y]);
x = y;
}
}
void HeapSort(int n) {
//n个数,从0到n-1
//从小到大排序,大根堆
int i;
for (i = n / 2 - 1; i >= 0; i--) {
HeapJust(i, n);
}
for (i = n - 1; i >= 0; i--) {
swap(num[i], num[0]);
HeapJust(0, i);
}
}
归并排序
划分两个区间,先将这两个区间排序,再将两个区间归并
稳定
int num2[Size];
void MergeSort(int l, int r) {
if (l >= r)return;
int mid = (l + r) / 2;
MergeSort(l, mid);
MergeSort(mid + 1, r);
int i, l1 = l, r1 = mid, l2 = mid + 1, r2 = r, k = l;
for (i = l; i <= r; i++)num2[i] = num[i];
while (l1 <= r1 || l2 <= r2) {
if (l2 > r2 || l1 <= r1 && num2[l1] < num2[l2])num[k++] = num2[l1++];
else num[k++] = num2[l2++];
}
}
基数排序
从按位排序,从低位到高位
稳定,和位数有关
//不超过十位的整数排序
void BaseSort(int n) {
//n个数,从0到n-1
int minx = num[0], maxx = num[0],k;
int i;
for (i = 1; i < n; i++) {
minx = min(minx, num[i]);
maxx = max(maxx, num[i]);
}
for (i = 0; i < n; i++) {
num[i] -= minx;
}
int wei[15] = { 1,10,100,1000,10000 ,100000,1000000,10000000,100000000,1000000000 };
for (int j = 0; j < 10; j++) {
vector<vector<int>>base(10);
for (i = 0; i < n; i++) {
base[(num[i] / (wei[j ]))%10].push_back(num[i]);
}
k = 0;
for (auto v : base) {
for (auto x : v) {
num[k++] = x;
}
}
}
for (i = 0; i < n; i++) {
num[i] += minx;
}
}
多路平衡归并与败者树
假设初始归并段个数为
r
r
r,归并路数为
k
k
k,有
n
n
n个数需要归并,
S
S
S为归并趟数
直接选择排序比较次数
S
(
n
−
1
)
(
k
−
1
)
=
log
k
r
(
n
−
1
)
(
k
−
1
)
=
log
2
r
log
2
k
(
n
−
1
)
(
k
−
1
)
S(n-1)(k-1)=\log_kr(n-1)(k-1)=\cfrac{\log_2r}{\log_2k}(n-1)(k-1)
S(n−1)(k−1)=logkr(n−1)(k−1)=log2klog2r(n−1)(k−1)
败者树深度
log
2
k
\log_2k
log2k
败者树比较次数
S
(
n
−
1
)
log
2
k
=
(
n
−
1
)
log
r
k
=
(
n
−
1
)
log
2
r
S(n-1)\log_2k=(n-1)\log_rk=(n-1)\log_2r
S(n−1)log2k=(n−1)logrk=(n−1)log2r
置换选择排序
用来产生更长的初始归并段
最佳归并树
(多叉哈夫曼树
用于组织长度不等的初始归并段的归并顺序,使得WPL最小。
{
n
=
n
0
+
n
k
n
−
1
=
k
n
k
⇒
n
0
−
1
=
(
k
−
1
)
n
k
⇒
(
n
0
−
1
)
%
(
k
−
1
)
=
0
\left\{\begin{aligned} n=n_0+n_k\\ n-1=kn_k\\ \end{aligned}\right. \Rightarrow n_0-1=(k-1)n_k \Rightarrow (n_0-1)\%(k-1)=0
{n=n0+nkn−1=knk⇒n0−1=(k−1)nk⇒(n0−1)%(k−1)=0