算法导论笔记3

摘要

如何设计算法

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

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12434241.html
今日推荐