摘要
如何设计算法
2.3 设计算法
算法设计有很多种,插入排序使用增量方法,在排序子数组A[1..j-1]后将单个元素插入适当位置,产生子数组A[1..j]。下面思考另一种方法分治法,分治法在最坏的情况运行时间比插入排序少很多,优点之一是往往很容易确定运行时间。
2.3.1分治法
许多有用的算法在结构上是递归的。分治法的思想:将原问题分解为几个规模较小但类似原问题的子问题,递归求解这些子问题,再合并这些子问题的解来建立原问题的解。
分治在每层递归有三个步骤:
分解原问题为若干子问题,解决子问题,合并子问题的解成原问题的解。
归并排序遵循分治法:
分解待排序的n个元素成2个具有n/2个元素的子序列。使用归并排序递归排序两个子序列,合并两个已排序的子序列产生已排序的答案。
核心代码
package notes.javase.algorithm.sort;
public class MergeSort {
public void Merge(int[] array, int low, int mid, int high) {
int i = low; // i是第一段序列的下标
int j = mid + 1; // j是第二段序列的下标
int k = 0; // k是临时存放合并序列的下标
int[] array2 = new int[high - low + 1]; // array2是临时合并序列
// 扫描第一段和第二段序列,直到有一个扫描结束
while (i <= mid && j <= high) {
// 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
if (array[i] <= array[j]) {
array2[k] = array[i];
i++;
k++;
} else {
array2[k] = array[j];
j++;
k++;
}
}
// 若第一段序列还没扫描完,将其全部复制到合并序列
while (i <= mid) {
array2[k] = array[i];
i++;
k++;
}
// 若第二段序列还没扫描完,将其全部复制到合并序列
while (j <= high) {
array2[k] = array[j];
j++;
k++;
}
// 将合并序列复制到原始序列中
for (k = 0, i = low; i <= high; i++, k++) {
array[i] = array2[k];
}
}
public void MergePass(int[] array, int gap, int length) {
int i = 0;
// 归并gap长度的两个相邻子表
for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
Merge(array, i, i + gap - 1, i + 2 * gap - 1);
}
// 余下两个子表,后者长度小于gap
if (i + gap - 1 < length) {
Merge(array, i, i + gap - 1, length - 1);
}
}
public int[] sort(int[] list) {
for (int gap = 1; gap < list.length; gap = 2 * gap) {
MergePass(list, gap, list.length);
System.out.print("gap = " + gap + ":t");
this.printAll(list);
}
return list;
}
// 打印完整序列
public void printAll(int[] list) {
for (int value : list) {
System.out.print(value + "t");
}
System.out.println();
}
public static void main(String[] args) {
int[] array = {
9, 1, 5, 3, 4, 2, 6, 8, 7
};
MergeSort merge = new MergeSort();
System.out.print("排序前:tt");
merge.printAll(array);
merge.sort(array);
System.out.print("排序后:tt");
merge.printAll(array);
}
}
大专栏 算法导论笔记3" title="归并排序的效率:">归并排序的效率:
时间复杂度:归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(n*log2n)。
空间复杂度:由前面的算法说明可知,算法处理过程中,需要一个大小为n的临时存储空间用以保存合并序列。
算法稳定性:在归并排序中,相等的元素的顺序不会改变,所以它是稳定的算法。
归并排序和堆排序、快速排序的比较
- 若从空间复杂度来考虑:首选堆排序,其次是快速排序,最后是归并排序。
- 若从稳定性来考虑,应选取归并排序,因为堆排序和快速排序都是不稳定的。
- 若从平均情况下的排序速度考虑,应该选择快速排序。
练习
1. 二分查找
给出递归算法,显然最坏的情况运行时间为O(log2n)
/**
* 二分查找,找到该值在数组中的下标,否则为-1
*/
static int binarySerach(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] == key) {
return mid;
}
else if (array[mid] < key) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return -1;
}
2.用二分查找改善插入排序能够提高总运行时间
//二分查找
public static int binarySearch(int array[],int low,int high,int temp)
{
int mid=0;
while(low<=high)
{
mid=(low+high)/2;
if(array[mid]<temp&&temp<=array[mid+1])
return (mid+1);
else if(array[mid]<temp)
low = mid + 1;
else
high = mid -1;
}
return high;
}
//二分排序
public static void binarySort(int array[],int size)
{
int i,j,k,temp;
for(i=1;i<size;i++)
{
temp=array[i];
if(array[i]<array[0])
k=0;
else
k = binarySearch(array,0,i,temp);
for(j=i;j>k;j--)
{
array[j]=array[j-1];
}
array[k]=temp;
System.out.println(Arrays.toString(array));
}
}
在最坏的情况下,二分查找的时间复杂度是nlgn,但插入时数组移动的时间复杂度仍然是n2。故总体运行时间不能改善为O(nlgn),但若排序中采用链表的数据结构,则可以改善。
扫描二维码关注公众号,回复:
9642670 查看本文章
3.描述一个运行时间为O(nlgn)的算法,给定n个整数的集合S和另一个整数x,该算法能确定S中是否存在两个其合刚好为x的元素
输入:数组A,整数值x
算法伪代码:
A=Sort(A)//对数组A排序
n=length[A]//n为数组A的长度
for i=0 to n-1 do
if A[i]>=0 and Binary_search(A,x-A[i],1,n) then //用二分查找看x-A[i]是否在数组A中
return true
end if
end for
return false