归并排序(2012/5/6 百度实习笔试题)

数组al[0,mid-1] 和 al[mid,num-1],都分别有序。将其merge成有序数组al[0,num-1],要求空间复杂度O(1)

首先回忆一下概念:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。算法如下:

// 将有二个有序数列a[first...mid]和a[mid+1...last]合并。
	private static void mergearray(int a[], int first, int mid, int last,
			int temp[]) {
		int i = first, j = mid + 1;
		int m = mid, n = last;
		int k = 0;
		while (i <= m && j <= n) {
			if (a[i] < a[j])
				temp[k++] = a[i++];
			else
				temp[k++] = a[j++];
		}
		while (i <= m)
			temp[k++] = a[i++];
		while (j <= n)
			temp[k++] = a[j++];
		for (i = 0; i < k; i++)
			a[first + i] = temp[i];
	}

	private static void merge(int a[], int first, int last, int temp[]) {
		if (first < last) {
			int mid = (first + last) / 2;
			merge(a, first, mid, temp); // 左边有序
			merge(a, mid + 1, last, temp); // 右边有序
			mergearray(a, first, mid, last, temp); // 再将二个有序数列合并
		}
	}

	public static void mergeSort(int a[]) {
		int n = a.length;
		int[] p = new int[n];
		merge(a, 0, n - 1, p);
	}
好了,介绍了最基本的归并知识,会到本题


第一种:先看一种最简单、也最容易想到的方法:开辟一个辅助数组c以下所有算法假设数组都是正有序)

// 将有序数组a[]和b[]合并到c[]中
	public static int[] mergeSort(int a[], int b[], int c[]) {
		int i, j, k;
		int n = a.length;
		int m = b.length;
		i = j = k = 0;
		while (i < n && j < m) {
			if (a[i] < b[j]) {
				c[k++] = a[i++];
			} else {
				c[k++] = b[j++];
			}
		}
		while (i < n) {
			c[k++] = a[i++];
		}
		while (j < m) {
			c[k++] = b[j++];
		}
		return c;
	}

这种方法的时间复杂度、空间复杂度都为O(n),显然不符合题目要求。


第二种:不开辟另一块内存,直接在原数组上操作。

/**
	 * 将a[0...m-1]和a[m,n-1]归并;时间复杂度为O(m*n),空间复杂度为O(1).
	 * 把后半部分插到前半部分
	 * 
	 * @param a 待排序数组
	 * @param m 
	 */
	public static void merge(int[] a, int m) {
		int i = 0;
		int j = m;
		int n = a.length;
		while (j < n && i < j) {
			if (a[j] >= a[j - 1]) {
				break;
			} else if (a[j] < a[i]) {
				int temp = a[j];
				for (int k = j - 1; k >= i; k--) {
					a[k + 1] = a[k];
				}
				a[i] = temp;
				i++;
				j++;
			} else {
				i++;
			}
		}
	}

此时,空间复杂度为O(1),符合要求,但增加了对前半部分的移动,增加了时间复杂度。在最好情况下,时间复杂度O(1),在最坏情况下,时间复杂度为O(m*n),典型的以时间换空间。此时,所耗费的时间代价太大,还是不合适,有没有更好的呢


第三种:我们知道,堆排序的空间复杂度为O(1),时间复杂度为O(nlgn),比原来那个好,试一下:

/**
	 * 堆排序
	 * @param a
	 */
	private static void heapSort(int[] a) {
		int n = a.length;
		for (int i = n / 2 - 1; i >= 0; i--) {
			build(a,n,i);
		}
		for(int i=0;i<n;i++){
			//将第一个根节点和最后n-1-i个叶子结点交换
			int temp = a[0];
			a[0] = a[n-1-i];
			a[n-1-i] = temp;
			//调整堆
			build(a,n-1-i,0);
		}
	}

	/**
	 * 建一个小根堆
	 * @param a
	 * @param n
	 * @param index
	 */
	static void build(int[] a, int n, int index) {
		int i = index;
		int j = 2 * i + 1;//根节点i的左子节点
		while (j < n) {
			 if(j+1<n && a[j]>a[j+1]){//如果左节点比右结点大,则左节点满足要求
				 j++;
			 }
			 if(a[i]<=a[j]){//如果父节点大于或等于右结点,则满足要求;此子大根堆完成(等于号会影响该算法的稳定性)
				 break;
			 }else{
				 int temp = a[i];
				 a[i] = a[j];
				 a[j] = temp;			 
				 i=j;
				 j = 2* i +1;
			 }
		}
		return;
	}

时间复杂度得到了降低(当然也可以把数组后半部分建堆,前半部分和堆的根节点比较交换,然后调整堆,时间可以得到优化,为nlg(n/2),但时间复杂度仍然不变)。


但题目要求的是用归并,有没有这样的归并呢?

第四种:在第二种的基础上加以优化

不需要辅助数组即可归并。

关键在于merge这个函数。两段递增的子数组arr[begin…mid-1]和arr[mid…end],i=begin,j=mid,k=end

image

 

i往后移动,找到第一个arr[i]>arr[j]的索引

image

j往后移动,再找第一个arr[j]>arr[i]的索引

image

附:上面的分析图来自http://www.cnblogs.com/daniagger/archive/2012/07/25/2608373.html

然后我们将i到mid的部分和mid到j-1的部分对调,较小的部分就调到前面去了,然后从后面的部分与j到k的部分又是两个递增的子数组,继续迭代即可。

/**
	 * 此算法是不稳定的
	 * 
	 * @param a
	 * @param start
	 * @param mid
	 * @param end
	 */
	private static void in_place_mergeSort(int[] a, int start, int mid, int end) {
		if (a[mid] >= a[mid - 1]) {
			return;
		}
		int i = start, j = mid;
		int step = 0;
		while (i < mid && j < end) {
			while (i < mid && j < end && a[i] <= a[j]) {
				i++;
			}
			while (i < mid && j < end && a[i] >= a[j]) {// 此等号破坏了该算法的稳定
				j++;
				step++;
			}
			int n=j-1;
			exchange(a,i,mid,n);
			i += step+1;
			mid += step;
			j = mid;
			step=0;
			if (j>=end || a[mid] >= a[mid - 1]) {
				break;
			}
		}
	}
	
	private static void exchange(int[] a,int start,int mid,int end){
		reverse(a, start, mid-1);
		reverse(a, mid, end);
		reverse(a, start, end);
	}

	private static void reverse(int[] a, int start, int end) {
		int i = start, j = end;
		for (; i < j; i++, j--) {
			int temp = a[i];
			a[i] = a[j];
			a[j] = temp;
		}
	}
在最好情况下,时间复杂度为O(1),在最坏情况下,最外层while循环次数为end-mid,和第二种方法的效果一样;reverse方法中for循环次数为mid,时间复杂度仍然为O(m*n),但是在一般情况下,经过测试,效率要比第二种高一倍。



转载请声明出处,http://blog.csdn.net/gaopo_y/article/details/8086143

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值