目录
- 知识框架
- No.1 分治法基本思想
- No.2 合并排序
- No.3 快速排序
- 一、基本思想
- 三、效率分析
- 四、快速排序不稳定例子
- No.4 二叉树遍历及其相关特性
- 一、基本概念
- 二、中序遍历
- 三、前序遍历
- 四、二叉树的高度计算(高度不是深度)
知识框架
No.1 分治法基本思想
- 将规模为N的问题分解为k个规模较小的子问题,使这些子问题相互独立可分别求解,再将k个子问题的解合并成原问题的解。如子问题的规模仍很大,则反复分解直到问题小到可直接求解为止。
- 在分治法中,子问题的解法通常与原问题相同,自然导致递归过程。
- 总的来说就是:将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。(均分)
- 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
步骤:
- 将问题的实例划分为同一个问题的几个较小的实例,最好拥有同样的规模。
- 对这些较小实例的求解(一般使用递归方法,但在问题规模足够小的时候,有时也会使用一些其他方法)。
- 如果必要的话,合并这些较小问题的解,以得到原始问题的解。
分治法一定能降低复杂度?获取高效率?
并不是分治法在所有情况下都能降低复杂度和提高效率。分治法的关键在于将原问题划分成更小的子问题,然后将子问题独立地解决,最后将子问题的解合并起来得到原问题的解。如果子问题存在重复计算或合并开销过大等问题,分治法可能不如其他算法效率高。
另外,分治法的时间复杂度与子问题的规模有关,因此在某些情况下子问题的规模较小时,分治法并不能使问题得到更好的解决。因此,正确使用分治法需要根据具体问题的特点选择合适的算法。
No.2 合并排序
也就是将将n个元素排成非递减顺序。
主要思想: 若n为1,算法终止;否则,将n个待排元素分割成k(k=2)个大致相等子集合A、B,对每一个子集合分别递归排序,再将排好序的 子集归并为一个集合。
所以:首先是 进行 划分 然后 再是 合并。;先将整个集合 划分均分到1,然后两两合并;、如图1所示
合并算法:拿2389 和 1457 合并
从两个序列的头部开始合并:相当于每次合并都是下面这样的步骤。如图2所示。
2与1比较,1被移到结果序列;
2与4比较,2被移入结果序列;
4与3比较,3被放入结果序列;
4和8比较,4被放入结果序列;
8和5比较;5被放入结果序列;
8和7比较;7被放入结果序列;第一个序列剩下的8和9按顺序放入结果序列。
图1 :此图是表示整个的排序的过程。
图2:表示合并排序中的合并步骤
代码如下:
// 归并排序(C++-递归版)
template<typename T>
void merge_sort_recursive(T arr[], T reg[], int start, int end) {
if (start >= end)
return;
int len = end - start, mid = (len >> 1) + start;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
merge_sort_recursive(arr, reg, start1, end1);
merge_sort_recursive(arr, reg, start2, end2);
int k = start;
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
reg[k++] = arr[start1++];
while (start2 <= end2)
reg[k++] = arr[start2++];
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
// merge_sort
template<typename T>
void merge_sort(T arr[], const int len) {
T reg[len];
merge_sort_recursive(arr, reg, 0, len - 1);
}
算法效率:
平均时间复杂度:O(nlogn)
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)
空间复杂度:O(n)
排序方式:In-place
稳定性:稳定
No.3 快速排序
一、基本思想
对于输入A[0… n-1],按以下三个步骤进行排序:
- 分区:取A中的一个元素为中心点(pivot) 将A[0…n-1]划分成3段: A[0…s-1], A[s ], A[s+1…n-1], 使得
A[0…s-1]中任一元素<=A[s],
A[s+1…n-1]中任一元素 >=A[s]; 下标s 在划分过程中确定。 - 递归求解:递归调用快速排序法分别对A[0…s-1]和A[s+1…n-1]排序。
- 合并:合并A[0…s-1], A[s], A[s+1…n-1]为A[0…n-1]
算法步骤:
三、效率分析
基本操作:比较
最优情况下: 所有分裂点均处中部最坏情况下:所有分裂点均处于极端
在进行了n+1次比较(ij指针交叉)后建立了分区,还会对数组进行排序,继续到最后一个子数组A[n-2…n-1]。总比较次数为:
最坏时间复杂度:O(n^2)
平均时间复杂度:O(nlogn)
辅助空间:O(logn)
稳定性:不稳定
四、快速排序不稳定例子
{6、7、5、2、5、8} 此为初始序列,那么按照快速排序的算法是不稳定的。
No.4 二叉树遍历及其相关特性
一、基本概念
所谓二叉树的遍历指的是遵循某一种次序来访问二叉树上的所有结点,使得树中每一个结点被访问了一次且只访问一次。
由于二叉树是一种非线性结构,树中的结点可能有不止一个的直接后继结点,所以遍历前必须先规定访问的次序。
二、中序遍历
二叉树的中序遍历算法比较简单,使用递归的策略。在遍历以前首先确定遍历的树是否为空,如果为空,则直接返回;否则中序遍历的算法步骤如下:
- 对左子树L执行中序遍历算法
- 对左子树L执行中序遍历算法
- 对右子树R执行中序遍历算法
三、前序遍历
有了上面的中序遍历的过程,前序遍历也是类似的。在遍历以前首先确定遍历的树是否为空,如果为空,则直接返回;否则前序遍历的算法步骤如下:
- 访问输出根结点V的值;
- 对左子树L执行前序遍历算法
- 对右子树R执行前序遍历算法
四、二叉树的高度计算(高度不是深度)
题目:
//输入一棵二叉树T
//输出二叉树的高度
//二叉树高度定义:叶子到树根的最长路径
//如果就是返回 两边哪个大的加本层的。
if T ==NULL return -1;
else return max{H(L),H(R)}+1;
H(T)=1+max{H(2),H(6)}
=1+1+max{H(3),H(4)}
=1+1+1+H(5)
=1+1+1+1+(-1) (深度-1)??
=3
//二叉树高度定义:叶子到树根的最长路径
//如果就是返回 两边哪个大的加本层的。
if T ==NULL return -1;
else return max{H(L),H(R)}+1;
H(T)=1+max{H(2),H(6)}
=1+1+max{H(3),H(4)}
=1+1+1+H(5)
=1+1+1+1+(-1) (深度-1)??
=3