考研复习 数据结构

目录

算法设计

1: 注意数组是否是有序的

1 链表转置

使用头插法插入到新链表中

2 数组转置

设置头尾两个指针,交换头尾,一共循环n/2次

3 数组连续部分整体移动

考虑用转置法:
1: 该整体转置一次
2: 剩下部分转置一次
3: 整个数组转置一次,注意整体必须在最后转置

4 链表找中点

设置两个指针,一个指针走一步时另一个指针走两步。

Node* p1 = head;
for(Node* p2 = head; p2->next; p2 = p2->next) {
	p1 = p1->next;
	if(p2->next) p2 = p2->next;
}

5 树的高度和深度

如果是需要树的层次,则将level作为参数传入

void DFS(Node* tree, int level) {
	if(!tree) return;
	DFS(tree->left, level+1);
	DFS(tree->right, level+1);
}

如果是计算树的高度,则将level作为返回值

int DFS(Node* tree) {
	if(!tree) return 0;
	int left = DFS(tree->left);
	int right = DFS(tree->right);
	return 1 + MAX(left, right);
}

6 非递归层序遍历

用队列遍历,当每层最后一个访问时,先将该点孩子入队(如果有),再将队列中最后一个元素标记

7 计算节点到根的路径

回溯思想,设置一个全局变量path,一个全局指针cur表示path中的位置

int path[100];
int cur = -1;
int DFS(Node* tree, int dist/*目的地*/) {
	if(!tree) { return 0; }
	path[++cur] = tree->data; //入path
	if(dist == tree->data) { return 1; } //找到了直接返回
	int find = DFS(tree->left, dist);
	if(find) { return 1; } //找到后不出path
	find = DFS(tree->right, dist);
	if(find) { return 1; }
	path[cur--]; //出path;
	return 0;
}

8 奇数移动到偶数前方

两个指针从两侧开始,遇到奇数在右侧偶数在左侧就交换位置

9 求第k小的元素

  1. 对区间进行划分
  2. 如果划分结果=k, 则返回,否则在左区间寻找k,或者在右区间寻找k-(pivot-left)

10 三色划分

  1. 设置三个指针,两个在左侧,一个在右侧
  2. 如果中间指针遇到了两种颜色,则和左侧或者右侧指针的元素交换
  3. 指针移动(注意和右侧交换时候中间指针不能移动,防止换过来的还是该颜色)

11 划分为元素个数相等,但是和相差最大的2个集合

  1. 对区间进行划分
  2. 如果划分结果=中间值,则成功,否则在左区间或者右区间继续划分

12 计算数组元素的排名

  1. 复杂度O(n^2),两个循环,比较任意两个元素
  2. 大的元素count++

1 链表

1.1 有关指针的问题

  1. 画图解决即可
  2. 注意带头节点的循环链表为空时,前指针与后指针指向头节点(p->front->front始终不为空)

1.2 循环链表问题

  1. 如果在头插入或者删除头,一定需要表尾的指针(即使有头节点也一样)

2 栈

2.1 出栈入栈问题

  1. 根据当前指针指向的元素是否为空,出栈入栈,空满判断都不相同!
  2. 对于一个入栈序列,出栈的顺序有 C 2 n n n + 1 \frac{C^{n}_{2n}}{n+1} n+1C2nn
  3. 问出栈顺序的题:
    • 先看 最后一个入栈是不是第一个出栈,此时出栈顺序唯一
    • 在用手动模拟

2.2 共享栈

  1. 栈底在两端
  2. 满时 中间不需要元素分隔

2.3 栈计算表达式

  1. 遇到操作数入栈,遇到操作符弹出两个操作数,注意此时有顺序
  2. 看清操作顺序,是a op b还是 b op a,尤其是减法

3 队列

3.1 出队入队顺序问题

  1. 入队顺序=出队顺序
  2. 应该和栈一起考,出栈后再入队,再出队,还是出栈的顺序

3.2 循环队列问题

  • 入队:(rear+1)%Max (一定是后入)
  • 出队:(front+1)%Max

下面与front和rear指向有关,front指向非空单元,rear指向空单元

  • 长度:(rear-front+Max)%Max
  • 空: rear=front
  • 满: (rear+1)%Max=front

3.3 输入输出受限问题

  1. 如果是输出受限,那么队列中的顺序就是输出顺序,相当于两端不断加入元素
  2. 如果是输入受限很复杂,用穷举法进行模拟

3.4 中缀表达式转为后缀

方法1:

  1. 遇到操作数直接输出,遇到操作符入栈
  2. 入栈时该操作符优先级必须压住栈顶(优先级最高),否则将出栈,直到该操作符压住栈顶
  3. 相同符号,栈中优先级高(栈中先出现),括号在栈外优先级最高,栈中优先级最低

方法2:

  1. 求出后缀表达式,然后用字符和中缀表达式对齐
  2. 扫描后缀表达式,消去后缀中的字符。
    • 是否能够消去中缀栈中的字符
    • 不能的话是否可以消去中缀表达式中字符
    • 中缀无法被消掉的必须入栈

演示:a/b+(c*d-e*f),用树求后缀:ab/cd*ef*-+

后缀ab/cd*ef*-+
中缀a/b+(c*d-e*f)
[/][/][+(][+(][*+(][*+(][-+(][-+(][*-+(]
第几条规则2321,323232321,2

4 数组

4.1 矩阵压缩

  1. 如果是表达式,直接带a[1][1]
  2. 如果是较小的数(不超过10):直接画图
  3. 如果是较大的数,则:
    • 看清是行优先还是列优先
    • 不管下标从0还是1开始,计算该元素前面有几个元素
    • 如果是n个元素,从0开始为b[n+0]=b[n], 从1开始为b[n+1]

5 串

5.1 求next数组

  1. 先画基本的表格,前两个固定是0, 1
  2. 第三个开始比较前一列上下两个数字代表的字符是否相等:
    • 相等:下面的值+1
    • 不相等:替换下面的数字,用该列下面的数字对应的列替换,重复2
    • 如果用0替换,则无法继续替换,直接+1
  3. 如果下标从0开始,则上下数字均减去1

演示:abaa

1234
abaa
0112
第1条第1条2,1不等,
用第1列下面的0替换,
然后+1
1,3相等,直接+1

5.2 模式比较算法

  1. 需要有一个指针i指向主串,j指向模式串
  2. 如果失配,则主串i不动,j=next[j]
  3. 如果匹配或者j=0(下标从1开始,否则是-1), 则i++, j++

5.3 求nextval数组

从第3列开始,如果该列Next代表的符号=该列符号,将Next对应的列的Next值拷贝到该列

1234
abaa
0102
0,1相等,改为04,2不等,不用改

6 树

6.1 概念:

  • 节点的度:树的孩子个数
  • 树的度:最大的节点的度
  • 分支节点,非终结节点:度>0
  • 叶子节点,终端节点,度=0
  • 高度:叶子=0,非叶子为最大子树高度+1
  • 节点路径长度:根到节点的边个数
  • 树的路径长度:根到每个节点的路径之和

计算:

  • 度之和=n-1
  • 边个数=n-1

6.2 2叉树

  1. 二叉树和度为2的树区别:二叉树可以为空,但是2度树至少有3个节点

  2. 计算:

    • 根从1开始编号,则左孩子为2i, 右孩子为2i+1(根据最后一个节点判断是左孩子还是右孩子)
    • n0=n2+1,因为有n0+n1+n2-1=n1+2n2=> n0=n2+1
    • 只规定了叶子个数和总个数:使用特殊值法,下面是7个节点,4个叶子的树 在这里插入图片描述
  3. 完全二叉树计算:

    • 第一个非叶子节点:[n/2]
    • 计算叶子的个数:n-[n/2]
    • n1=1或者0

6.3 树的遍历

  1. 如果 前序与后序完全相反,则说明树是一根线,没有左子树或者右子树

  2. 前中后序,叶子的相对位置不变

  3. 已知中序+前后序画树:

    • 根据前序得到树的根
    • 在中序中画出根的位置,分出左右子树
    • 找到左右子树在前序中的位置,重新找根
  4. 已知前后序画树:

    • 如果前序中有xxABxx,后序中有xxxBAxx,则说明A是B的父节点
    • 如果前序中有xxABCDxx,后序中有xxxBCDAxxx,则说明BCD都是A的子树
  5. 通过先序确定的二叉树个数: C 2 n n n + 1 \frac{C^{n}_{2n}}{n+1} n+1C2nn,相当于先序入栈,出栈顺序为中序

  6. 前序+中序+后续可以用递归实现,区别在于访问时间,

  7. 栈实现遍历:

    • 前序:入栈访问,
    • 中序:出栈访问
    • 后序:节点必须设置一个flag,当栈顶是该节点时,flag=1,可以出栈并访问,flag=0,将右子树入栈,设置flag=1

6.4 线索二叉树

  1. 问线索二叉树的线索次序:

    • 根据前序或者后序写出遍历结果,
    • 问哪个节点,左子树为null,则指向遍历序列中前一个,右子树为null,则指向遍历序列中后一个
  2. 线索二叉树的线索个数:叶子2个,非叶子1个=n1+2n0=n1+n0+n2+1=n+1

  3. 空链域问题:只有2个节点的链域可能是空:

    • 第一个遍历的节点是否有左子树,
    • 最后一个遍历的节点是否有右子树
  4. 线索树不能解决的遍历问题:前序的前继,后序的后继

6.5 森林

  1. 森林遍历:从左到右遍历每一颗树
  2. 森林的树个数=森林点数-边数

6.6 树的二叉化

  1. 无左右孩子问题:
    • 非叶节点 【二叉化】产生 一个无右孩子的节点(+最右节点也无右孩子)
    • 叶节点 【二叉化】 产生一个无左孩子的节点
  2. 树的遍历问题
    • 树的中/后根序 【对应的二叉树】中序
    • 树的前根序 【对应的二叉树】前序

6.7 WPL

  1. WPL指的是带权路径值
  2. 计算方式,根到该节点的路径长度*该点的节点权值

6.8 并查集

  1. 用孩子指向父节点的树
  2. Union时候,首先检查根是否相同,然后必须将根进行合并(而不是该节点直接指向另一个节点,该节点可能已经指向一个节点)
  3. 使用:最小生成树(克鲁斯卡尔算法)、寻找联通分量、判断是否是连通图

6.9 哈夫曼树

  1. 只有度为0和k的树
  2. 叶子节点为n时,总结点为2n-1(需要自己算)
  3. 哈夫曼树虚节点计算 n0是需要归并的段个数,k是k路归并:
    1. 列出度数和节点数的关系 n 0 + n k − 1 = k ⋅ n k n_0+n_k-1=k\cdot n_k n0+nk1=knk
    2. 表示出nk: n k = ( n 0 − 1 ) / ( k − 1 ) . . . . u n_k=(n_0-1)/(k-1)....u nk=(n01)/(k1)....u,余的u个表示多出来的段
    3. 增加k个节点,1个节点用叶子的值填充,u个用多出来的段填充,k-u-1个就是虚节点

7 图

7.1 概念

  • 完全图:任意两个点之间存在一条边,无向完全图边个数:C(n,2), 有向完全图边的个数2C(n,2)
  • 子图:顶点和边的集合属于原来的图(边集合和顶点集合属于原来的图并不一定是子图,可能边和顶点不搭配)
  • 生成子图:要求顶点集和原来的图一样,边集属于原来的图
  • 极大联通子图(联通分量):加入任意一个点将不连通。联通图只有一个联通分量(本身),非联通图有多个联通分量
  • 极小联通子图:删去任意一条边将不连通。联通图极小联通子图就是生成树
  • 点联通:点A到点B之间存在路径
  • 联通图(无向图概念):任意两点之间存在一条路径,边的个数至少n-1(生成树),最大为C(n,2)
  • 强连通图(有向图概念):任意两点之间存在一条路径,A->B, B->A,强联通图必有环,边的个数最少n,最大为2C(n,2)
  • 强联通分量:任意两点之间存在一条路径,必定存在环。计算方法:拓扑排序法,统计遇到的环的个数
  • DAG图:有向无环图
  • AOV网: 一种DAG图,表示活动网络

7.2 计算

  1. 有向图:出度之和=入度之和=e,无向图:度之和=2e
  2. 一个点的出度不一定等于入度,但是图的出度=入度

7.3 至少有几条边,至少有几个点问题

  1. 联通图考虑:
    • 边的个数最大: 完全图 C(n,2)
    • 边的个数最小:生成树 n-1
    • 确保联通:构造去掉一个点的完全图:C(n-1, 2)+1
  2. 非联通图考虑:
    • 最大边数/最少点数:构造去掉一个点的完全图,但是不连上 C(n-1, 2)

7.4 拓扑排序

  1. 用邻接矩阵存储,对角线以下为0的上三角阵一定有拓扑排序
    如果可以拓扑排序,则不一定为上三角阵,但是可以通过修改编号变为上三角阵

7.5 邻接表和邻接矩阵

  1. 存储比较
    有向图无向图
    邻接表每一条边存放一次每一条边存放两次
    邻接矩阵边的个数为矩阵中1的个数,
    主对角线为0,a[i][i]=0
    1的个数一定是偶数,
    边=1的个数/2

7.6 图操作的时间复杂度

操作邻接表邻接矩阵
DFS/BFSO(E+V)O(V^2)
拓扑排序O(E+V)O(V^2)

7.7 图的遍历

  1. DFS:和树的先序相同,使用栈或者递归
int visit[V] = {0};
void DFS(int node) {
	visit[node] = 1;
	for(int i = 0; i < V; i++) {
		if(A[i][j] && !visit[j]) { DFS(j); }
	}
}
  1. BFS:和树的层序相同,使用队列,不能使用递归

7.8 求联通分量个数

  1. DFS:最外层调用DFS的次数
  2. 并查集:统计根节点的个数(父节点为-1的点)

7.9 回路检测

  1. DFS:如果检测到访问了已经到达的点,说明存在回路
  2. 拓扑排序:如果存在没有被排序的点,但是不存在入度为0的点,说明存在环路

7.10 判断是否是树

  1. 回路检测
  2. 边的个数是n-1+DFS遍历了所有的点

7.11 最小生成树

  1. 是否唯一:
    如果有权值相同的边,则可能不唯一。
    如果不存在权值相同的边,则一定唯一。唯一不一定权值全部不同。
  2. PK算法:
    Prim普里姆算法Kruskal克鲁斯卡尔算法
    描述找到距离点集最近的点加入树找到最小的边加入树,不能构成回路
    复杂度V^2ElogE
    实现并查集+堆寻找最小边

7.12 最短路径

Dijkstra迪杰斯特拉算法Floyd弗洛伊德算法BFS
描述
  • 访问距离源点最近的点
  • 根据该点修改未访问的点到源点距离
  • 共进行V轮,每轮选择一个点作为中间点,
  • 遍历a[i][j],更新a[i][j] < a[i][k]+a[k][j],(k是否等于i,j均可)
一个外部数组记录距离,该节点的距离等于父节点距离+1
复杂度V^2V^3V^2
问题负权图不行,因为无法修改已经访问的点允许有负权,不能有负权的环。可用于无向图权值必须相同

7.13 拓扑排序

  1. 删去入度为0的点,可以用于环路检测
  2. 删除出度为0的点,可以变为逆拓扑序
  3. 拓扑排序不唯一
  4. 复杂度:使用邻接矩阵V^2, 使用邻接表E+V

7.14 关键路径

  1. AOV网中最长的路径
  2. 关键路径不唯一
  3. 减小关键路径不一定减小工期,增大关键路径一定增大工期
  4. 事件(点):
    • 最早开始:多个汇入选择最长的(前一个点+权值)
    • 最晚开始:多个发出选择最小的(前一个点-权值)
  5. 活动(边):
    • 最早开始:起点的最早开始事件
    • 最晚开始:终点最晚开始-该活动的长度
  6. 拓扑排序求解最早开始时间,逆拓扑排序求解最晚开始时间。
  7. 关键活动:最早开始=最晚开始的活动
  8. 关键节点:最早开始=最晚开始的节点
  9. 求关键路径时,首先计算节点的关键路径,同时标记到底从哪条边汇入的。

7.15 DAG表达式

  1. 叶子节点必须重新利用
  2. 先画出完整的图,然后再消去相同的。

8 查找

8.1 概念

  • 平均查找长度:查找到该点的比较次数乘以该点的概率,分为查找成功时的和查找失败时的。

8.2 顺序查找

  1. 查找成功的次数(无序表和有序表相同):第一个元素1,第二个元素2,…一共是1+…+n
  2. 查找失败的次数:无序表每个元素均是n, 有序表查找失败次数更小。

8.3 折半查找

  1. 判定树是平衡二叉树而不是完全二叉树,因为可能有偶数个元素
  2. 树节点是查找不成功的结果
  3. 问折半查找的平均查找长度需要画树
  4. 查找最多次数和最少次数最多相差1,如果问查找成功的次数,则画图做。
    • 首先画出一个总结点个数最接近的满二叉树,然后在最左侧或者最右侧添加节点
      (注意只能往一个方向添加,做到左子树始终大于右子树或者右子树始终大于左子树)
    • 如果给查找树按照中序遍历可以得到节点的值
  5. 折半查找树形状判定:
    • 左子树节点个数>右子树节点个数,或者相反。不能同时出现。
    • 或者用中序标号,用两个点计算父节点,是否都是上取整或者下取整

8.4 分块查找

  1. 块长取 n \sqrt{n} n

8.5 BST二叉查找树

  1. 插入:插入到第一个查找失败的位置
  2. 删除:
    • 叶子:直接删除
    • 一个孩子:孩子替换
    • 2个孩子:用右子树中序第一个节点进行替换
  3. 操作复杂度:平均log2n, 最坏n(退化为一个链)
  4. 删除后插入:删除叶子:不变,删非叶子:变
  5. 有序插入时退化为一条链,复杂度为n
  6. 合法的查找顺序:对于abABCD, 如果a>b,则b在a的左子树,ABCD也必须小于a。如果a<b, b在a的右子树,则ABCD也必须大于a

8.6 平衡二叉树

  1. 插入和删除本质是先插入/删除,再修改问题节点,因此从问题节点从最长路径进行修改。
  2. 修改方法:RR/LL: 中间节点为根,LR: RL:最下方节点为根,其他节点按照二叉树的规则挂上去。
  3. 平衡二叉树最少节点: n h = n h − 1 + n h − 2 + 1 , n 0 = 0 , n 1 = 1 , n 2 = 2 n_h=n_{h-1}+n_{h-2}+1, n_0=0, n_1=1, n_2=2 nh=nh1+nh2+1,n0=0,n1=1,n2=2
  4. 平衡因子:NULL节点:-1, 叶节点:0,非叶节点:MAX{left, right}+1,平衡因子为左子树高度-右子树高度
  5. 删除后插入:不管是叶子还是非叶,都可能变,也可能不变。要看是否进行了调整。

8.7 红黑树

  1. 黑节点个数>红节点个数

    • 根到叶子最长路径<= 2*最短路径
    • 2*黑节点>= 总节点
    • 红黑树的最大高度:按照全部为黑节点的满二叉树计算,然后高度*2
  2. 红黑树适合于较多的插入删除操作,AVL树适合于较多查找操作

  3. 插入:插入总是调整红红冲突

    • 叔叔为红色,则两个节点同时变黑,爷爷变红,循环调整
    • 叔叔为黑色,则和AVL调整相同,父+两个红色节点(RR/LL,RL/LR),调整后子树的根为黑色,两个孩子为红色

    删除:

    • 两个孩子:用右子树中序第一进行替换,转而删除该节点
    • 一个孩子:孩子替代,强制变为红色。
    • 0个孩子:红节点直接删除,黑节点,兄必须是红色

8.8 B树

  1. m阶即树的度
  2. 如果给出树型问度:
    • 看最大的子树的个数
    • 看节点中关键词最多个数+1
  3. 节点中的关键词有序
  4. 度:关键词个数为度-1
    • 根:度为0, 2, 3, 4, …
    • 非根:度为 ⌈ m ⌉ \lceil m\rceil m到m
  5. 根非常特殊,如果提到非叶节点,需要考虑是不是根
  6. 非叶节点中也存放关键词,叶节点都在同一层
  7. h层节点:
    • 最多节点数: m h − 1 m^h-1 mh1,每一个节点都为m-1个
    • 最少节点数: 2 ⌈ m 2 ⌉ h − 1 − 1 2\lceil \frac m2\rceil^{h-1}-1 22mh11,根节点1个,然后分为两个子树,每个节点都是m/2个
  8. n个关键词:n+1个叶节点(n+1)种失败的可能
  9. 节点的个数:
    • 最多 1 + ( n − 1 ) ⌈ m 2 ⌉ = 关键词个数 1+(n-1)\lceil\frac m2\rceil=关键词个数 1+(n1)2m=关键词个数,根为1个key, 其他都是m/2个
    • 最少: ( m − 1 ) n = 关键词个数 (m-1)n=关键词个数 (m1)n=关键词个数
    • 节点的个数必须凑好,否则可能不能成树,因此尽可能画图
  10. 阶数如果是3或者4,则每个节点关键词最少可以只有1个(关键词数=节点数),变为一个满二叉树
  11. 插入:插入后如果关键词变为m个,则分裂。中间一个关键词插入到父节点。查看父节点是否需要分裂。
  12. 删除:
    • 非叶节点:用左孩子最大值或者右孩子最小值替换,递归向下删除,直到叶子
    • 叶子节点:如果删除后关键词小于 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2个,则:
      • 左右兄弟借:左右兄弟够借,则一个key去父节点,父节点的key下降到该节点
      • 左右兄弟不够借:父节点key下降,和左右孩子节点合并。递归删除父节点中的key
  13. B树内存相关:
    • 计算内存大小:B树的每个节点都需要记录文件,因此任意一个节点:m-1个索引值+m-1个索引指向的地址+m个指向孩子的指针,
    • 计算节点个数:最小:总记录数/(m-1),最大:总记录数/([m/2]-1)

8.9 B+ 树

  1. 阶数=度=key的最大个数
  2. 叶子节点之间用指针相连,可以进行顺序查找
  3. key指向一个节点,该key是节点中最大的key,因此节点中的key和子树个数相等

8.10 散列查找

  1. 概念:

    • 填装因子:元素个数/表长,对于拉链法这个计算方式可能会>1(不懂)
    • 开放地址:地址对于同义词和非同义词均开放
    • 聚集:指同义词在相邻的位置上聚集,降低查找效率(多在线性探测时),原因:处理冲突的方式不当
  2. 删除:

    • 开放地址:删除时必须使用伪删除
    • 拉链法:可以物理删除
  3. 发生冲突的可能性和表长无关,只和填装因子有关。

  4. 查找长度:

    • 查找失败的长度:看Hash函数值有几个,计算到查找到该函数值的时候需要比较几次到空元素(所有的探测法都是如此),和空元素比较也算1次
  5. 伪随机序列法:di必须是确定的,预先算出的,而不是每调用一次Hash函数计算一次random()

  6. 是否是Hash函数:

    • 要求对于同一个key返回同一个值
    • 要求返回值不超过0-n-1
  7. 如果Hash(key)=key%p,需要自己选取Hash函数,则p选择最大的素数

9 排序

9.1 概念

  • 稳定性:排序关键词相等时,排序之和元素的相对顺序不变
  • 内部排序:排序时元素都在内存中
  • 外部排序:排序时元素在内存和外存中移动

9.2 插入排序

  1. 每趟针对每个元素,都向前比较最少1次,最多n-1次,并移动,共n-1趟
  2. 最好最差情况:
    • 如果元素有序,需要比较n次,复杂度O(n)
    • 如果元素无序,需要比较/移动:0+1+2+…n-1次,复杂度O(n^2)
  3. 如果最小的元素在最后一个位置上,则最后一趟之前,没有一个元素在正确位置上
  4. 分辨:前i个是有序的,虽然不是最小的。

9.3 希尔排序

  1. 每趟对a[i], a[i+d], a[i+2d],…a[i+kd]进行插入排序,即第1个元素开始,往后数d个(第1个元素不算),再往后数d个,形成子表进行排序,每趟修改d的个数
  2. 不稳定,因为相同元素会进入不同的子表
  3. 复杂度:无法确定,但是有序时会退化成O(n)
  4. 只能在顺序表实现
  5. 分辨:寻找交换了位置的多对元素,如果间隔相等说明是。如果求d,则可能是间隔的因数,对题目中的选项逐个验证。

9.4 冒泡排序

  1. 每趟从最后一个元素向前交换,直到进入有序表,共n-1趟
  2. 最好最差情况:
    • 如果有序,则只需要比较n-1次,进行一趟
    • 如果无序,需要比较/交换 n-1+n-2+…+1次
  3. 分辨:第i趟最前面有i个最小值,或者最后面有i个最大值(从前面开始冒泡)

9.5 快排

  1. 每趟:
    • 把a[left]拷贝到pivot,
    • right不断减小,直到找到小于pivot的值,拷贝到a[left],
    • left不断增加,直到找到大于pivot的值,拷贝到a[right]
    • 重复直到left>=right, 然后把pivot拷贝到a[left]
  2. 空间复杂度:使用到栈,需要O(logn)~O(n)
  3. 时间复杂度:
    • 最好情况:无序,pivot两侧元素个数基本相等:O(nlogn)
    • 最坏情况:有序或者逆序,退化为O(n^2)
  4. 每趟都有一个元素到达正确位置,分辨时先排好序,然后比较
  5. 可以使用队列替换栈,区别在于处理子区间的顺序
    while(!empty()) {
    	left,right = pop();
    	int pivot = partition(left, right);
    	push(left, pivot-1);
    	push(pivot+1, right);
    }
    

9.6 选择排序

  1. 每趟都从i+1到n-1中挑选最小的和i+1交换,共n-1趟
  2. 复杂度总是O(n^2),比较总是O(n^2),交换为1-n,交换次数最少的算法
  3. 分辨:第i趟,前面i个是最小的

9.10 堆排序

  1. 堆:
    • 完全二叉树
    • 父节点大于两个子节点a[i]>a[2i], a[i]>a[2i+1]
  2. 建堆:
    • 从n/2到1的顺序检测节点
    • 节点和子节点比较,选择较大的换上去
    • 换下来的父节点需要递归的向下更换
  3. 插入:
    • 插入到最后并且向上调整
  4. 删除:
    • 堆顶和堆中最后一个元素交换,然后向下调整
  5. 复杂度:建堆O(n),插入/删除O(log2n),排序O(nlog2n)

9.11 归并排序

  1. 两个元素之间相互归并,然后是4个元素,8个元素…
  2. 复杂度:
    • 归并:归并最坏复杂度: m+n-1,情况是一个表前m-1个元素都小于另一个表第一个元素,而最后一个元素大于另一个表所有元素
    • 趟数 ⌈ log ⁡ k n ⌉ \lceil\log_kn\rceil logkn
  3. 辅助空间:O(n)
  4. 复杂度和子排列无关

9.12 基数排序

  1. 从低位开始排(LSD)或者高位开始(MSD),分配+收集
  2. 辅助空间O(r),r是数字的范围比如0-9
  3. 复杂度:d趟(d是key的位数),每趟分配: n,每趟收集: r,共d(n+r)
  4. 注意排序时如果位数不足需要补足,比如2补为 002

9.13 内部排序性质总结

  1. 稳定性:归并,插入,基数,冒泡(跪插鸡毛)
  2. 辅助空间:冒泡(n~logn),归并O(n)
  3. 复杂度和初始状态有关:插入,冒泡,快排,希尔排序
  4. 趟数和初始值有关:冒泡
  5. 一趟后可能没有一个元素在正确位置上:归并,插入
  6. 一趟后总有元素在正确位置上:选择,冒泡,快排,堆排序

9.14 外部归并

  1. 外部排序分为两部分,首先生成最大归并段,然后将归并段进行归并,假设可用内存为M
    • 最大归并段长度为M,由此可以计算出归并段的个数
    • 内存中可以放入m块,则m-1个为输入,1个为输出,即归并路数为m-1
  2. 读写次数计算:需要m趟,则读+写:(m*磁盘块数+磁盘块数)2,第一部分是外部排序,第二部分是内部排序,最后是读写2
  3. 归并趟数计算: ⌈ log ⁡ k r ⌈ \lceil \log_kr\lceil logkr,k是归并路数,r是磁盘总块数
  4. 输入输出块计算:
    • k路归并,需要1个输出块和k个输入块,
    • 如果是并行,则至少2个输出+2k输入,
    • 如果是已知总数,则1个为输出,m-1个为输入,也就是归并路数
  5. 计算归并段的长度:归并段长度就是最大内存值。

9.15 败者树

  1. 用于找出k个元素中最小的元素。
    • 对于r个磁盘块,k路归并,则趟数为 ⌈ log ⁡ k r ⌉ \lceil\log_kr\rceil logkr
    • 每趟需要寻找n-1个最小元素(n是总块数),
    • 寻找一个最小元素,普通比较法需要k-1次比较,而败者树需要 log ⁡ 2 k \log_2k log2k次比较。
  2. 叶节点为缓存块,非叶节点为失败者的块号(大的值),在根上方写入冠军的块号(最小值)
  3. 败者树比较次数和路数k无关

9.16 置换选择算法

  1. 生成较长的有序文件用于归并
  2. 生成方法:
    • 工作区空了就用输入文件填充
    • 工作区中找到比上个输入大的最小元素
    • 如果找不到则结束输入,重新输出到一个新的文件
      例如:17,21,05, 44, 10, 3,工作区长度为3
      [17,21,5], 输出5, [17,21,44], 输出17, [10,21,44], 此时必须输出大于17的最小值21

9.17 最佳归并树

  1. 构造哈夫曼树(将段长作为权值,度为归并路数K,叶节点为归并段数r)
  2. 当段个数不够时设置虚段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值