《数据结构和Java集合框架第三版》读书笔记(十一)归并排序

排序能有多快用决策树分析。排序n个元素,每种排列有一个叶结点,决策树t总共n!个叶结点。n!≤2^height(t)

所以树的高度height(t) ≥log2(n!)O(log(n!))=O(nlogn)

所以对于基于比较的排序,最好时间不可能比O(nlogn)小。

平均时间也是不可能比O(nlogn)小,不证明了。


归并排序Merge Sort

算法思想:分治法——将原问题划分成n个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后合并其结果,就得到原问题的解。

分解:将n个元素分成各含n/2个元素的子数组

解决:用归并排序法对两个子数组递归地排序

合并:合并两个已排序的子数组以得到排序结果。

对Object数组进行排序

public static void sort (Object[ ] a){
	Object aux[ ] = (Object [ ])a.clone();
	mergeSort (aux, a, 0, a.length);
} 

将数组a的元素复制到数组aux(auxiliary:辅助的),调用递归方法mergeSort排序aux数组中的元素然后返回到a数组中去。

对于mergeSort(Object[] src, Object[] dest, int off, int len)方法,如果数组长度小于7就使用插入排序;否则,就将dest数组分裂成两半,递归调用mergeSort方法。

mergeSort(dest, src, off, halfLen);

mergeSort(dest, src, mid, halfLen);

注意dest和src反过来了。即递归的每一层的dest总是上一层的src。

递归直到直到分裂后的数组元素长度小于7为止,对此时的dest数组进行排序,假设现在是第N层,然后返回上一层N-1层,这时相当于N-1层的src数组排序完毕了,归并,复制给dest数组。即第N-1层的dest数组排序完毕。再返回上一层N-2层。

这样相当于递归的每一层的dest数组都被排好序了。

如图所示


	public static void mergeSort(Object[] src, Object[] dest, int off, int len) {
		if (len < 7) {// 元素个数小于7时采用插入排序
			insertSort(dest, off, len);
			return;
		}
		// 元素个数不小于7时递归
		int halfLen = len / 2;
		int mid = off + halfLen;
		int high = off + len;
		mergeSort(dest, src, off, halfLen);
		mergeSort(dest, src, mid, halfLen);
		// 只有数组分裂到元素个数小于7时递归才会结束,层层向上返回。
		// 然后对分别排序完毕的两半src数组进行归并。
		// 这里有一个小小的优化,如果左数组的最大的元素不大于右边数组的最小的元素,两个数组直接连接在一起就行了。
		if (((Comparable<Object>) src[mid - 1]).compareTo((src[mid])) <= 0) {
			System.arraycopy(src, off, dest, off, len);
			return;
		}
		for (int i = off, p = off, q = mid; i < high; i++) {
			if (q >= high
					|| (p < mid && ((Comparable) src[p]).compareTo(src[q]) <= 0))
				dest[i] = src[p++];
			else
				dest[i] = src[q++];
		}
	}

	public static void insertSort(Object[] x, int off, int len) {
		for (int i = off; i < off + len; i++) {
			for (int j = i; j > off
					&& ((Comparable<Object>) x[j]).compareTo(x[j - 1]) < 0; j--)
				swap(x, j, j - 1);
		}
	}

	private static void swap(Object[] x, int a, int b) {
		Object tmp = x[a];
		x[a] = x[b];
		x[b] = tmp;
	}

最差时间、平均时间:O(nlog(n/7))。空间开销workspace:O(n)

二,原地归并排序

核心是手摇算法

归并排序在利用分治思想将数组分为左右两部分分别递归后,需要将两部分排序后的数组合并在一起。如果不利用辅助数组的话,就需要在数组本身原地归并。

    public static void mergeSort(int[] array){
        if (array == null || array.length == 0)
            return ;
        mergeSort(array,0,array.length-1);
    }

    public static void mergeSort(int[] array, int start, int end) {
        if (start<=end){
            int mid=(start+end)>>>1;
            mergeSort(array,start,mid);
            mergeSort(array,mid+1,end);
            merge(array,start,mid,end);
        }
    }

假设现在有数组array,左右两部分已经排好序,需要合并merge(array,start,mid,end);


<pre name="code" class="java"> public static void mergeSort(int[] array){
        if (array == null || array.length == 0)
            return ;
        mergeSort(array,0,array.length-1);
    }

    public static void mergeSort(int[] array, int start, int end) {
        if (start<end){
            int mid=(start+end)>>>1;
            mergeSort(array,start,mid);
            mergeSort(array,mid+1,end);
            merge(array,start,mid,end);
        }
    }

    /**
     * array左右两部分已经分别排序完毕,将他们合并成一个有序的数组。
     * @param array  数组
     * @param start  左半部分起始位置
     * @param mid 左半部分结束位置
     * @param end  右半部分结束位置
     */
    private static void merge(int[] array, int start, int mid, int end) {
        int leftStart=start;
        int rightStart=mid+1;//右半部分起始位置
        while (leftStart<rightStart&&rightStart<=end){
            //左指针自增直到左指针到达右指针或者左指针元素大于右边起点元素位置
            while (leftStart<rightStart&&array[leftStart]<=array[rightStart]){
                leftStart++;
            }
            if (leftStart==rightStart)//左指针等于右指针的话说明不需要调整顺序了
                break;
            //如果左指针小于右指针,则左指针之前的元素<右指针元素<左指针元素,左指针之前的元素是整个待合并数组里最小的元素,不需要参加合并。
            // 记录此时的右指针
            int oldRightStart=rightStart;
            //右指针向右移动,直到元素比左指针大为止。
            while (rightStart<=end&&array[rightStart]<=array[leftStart]){
                rightStart++;
            }
            //[左指针~(旧右指针-1)]>[旧右指针元素~(新右指针-1)的元素],这两段需要调整
            exchange(array,leftStart,oldRightStart-1,rightStart-1);
                //排序完毕后,leftStart要指向被交换过来的小元素区域右边那个元素,这样leftStart新的位置的左边区域都是排过序的小元素
            leftStart+=rightStart-oldRightStart;
        }
    }

    /**
     * 数组分为两段,左段大于右段,需要调整顺序
     * 利用手摇算法三段逆序来完成
     * f(AB)f(CD)——>(BA)(DC)
     * f(f(AB)f(CD))——>f((BA)(DC))——>DCBA
     * 也可以证明为f(f(AB)f(CD))——>f(CD)f(AB)——>DCBA
     * @param array    数组
     * @param start     左端起点
     * @param mid       左段终点
     * @param end       右段终点
     */
    private static void exchange(int[] array, int start, int mid, int end) {
        reverse(array,start,mid);
        reverse(array,mid+1,end);
        reverse(array,start,end);
    }

    /**
     * 逆序
     */
    private static void reverse(int[] array, int start, int end) {
        //双指针向中间靠拢,交换元素
        while (start<end){
            swap(array,start++,end--);
        }
    }

    private static void swap(int[] array, int j, int i) {
        if (i==j)
            return;
        int k = array[i];
        array[i] = array[j];
        array[j] = k;
    }

 

三,单链表的归并排序

Sort a linked list in O(n log n) time using constant space complexity.

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode sortList(ListNode head) {
        if(head==null||head.next==null) return head;
        int length=getLength(head);
        return mergeSort(head,length);
              
        
    }
    private ListNode mergeSort(ListNode head,int length){
        if(length==1) return head;
        ListNode firstBegin=head,firstLast=head,secondBegin,secondLast,p=head;
        int mid=(length-1)>>>1;
        for(int i=0;i<length-1;i++){
            if(i==mid) firstLast=p;//得到第一段链表最后一个元素
            p=p.next;
        }//p最终指向第二段链表最后一个元素
        secondLast=p;
        secondLast.next=null;
        secondBegin=firstLast.next;//将second指向第二段链表第一个元素
        firstLast.next=null;
        firstBegin=mergeSort(firstBegin,mid+1);//递归第一段链表
        secondBegin=mergeSort(secondBegin,length-mid-1);//递归第二段链表
        firstLast=getListNode(firstBegin,mid);
        secondLast=getListNode(secondBegin, length-mid-2);
        //归并
        return merge(firstBegin,firstLast,secondBegin,secondLast);
        
    }
    private ListNode merge(ListNode firstBegin,ListNode firstLast,ListNode secondBegin,ListNode secondLast){
        if(firstLast.val<=secondBegin.val){
            firstLast.next=secondBegin;
            return firstBegin;
        }
        else if(secondLast.val<=firstBegin.val){
            secondLast.next=firstBegin;
            return secondBegin;
        }
        ListNode min=firstBegin,max=secondBegin,tmp,lastMin=null;
        while(min!=null&&max!=null){
            if(min.val>max.val){
                tmp=min;
                min=max;
                max=tmp;
            }
            if(lastMin!=null)
                lastMin.next=min;
            else
                firstBegin=min;
            lastMin=min;
            min=min.next;
        }
        lastMin.next=max;
        return firstBegin;
    }
    private ListNode getListNode(ListNode head,int index){
    	ListNode tmp=head;
    	for(int i=0;i<index;i++){
    		tmp=tmp.next;
    	}
    	return tmp;
    }
    private int getLength(ListNode head){
        ListNode p=head;
        int length=0;
        while(p!=null){
            length++;
            p=p.next;
        }
        return length;
    }
    

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值