排序算法(六)、归并排序

1、二路归并排序

“归并”即“合并”,是指将两个或者两个以上有序表组合成一个有序表。假如待排序表含有 n 个记录,即可以视为 n 个有序的子表。每个子表长度为1,然后两两归并,得到 n/2 个长度为 2 或者 1 的有序表,然后,再两两归并,。。。。如此重复,直到合并成一个长度为 n 的有序表为止。这种排序方法称为“二路归并排序”。

递归形式的二路归并算法,主要包含两个步骤:

1)、分解:将长度为 n 的待排序表分解成两个 n/2 大小的子表,然后,递归地对两个子表进行排序。。

2)、合并:将子表合并。

(1)部分代码如下:

void MergeSort(int a[], int low, int high)
{
	if (low < high)
	{
		int mid = (low + high) / 2;
		MergeSort(a,low,mid);
		MergeSort(a,mid+1,high);
		Merge(a,low,mid,high);

	}
}

(2)部分代码如下:

// MergeSort 归并排序

void Merge(int a[], int low, int mid, int high)
{
	int i = low, j = mid + 1, k = 0;    // j 是从 mid + 1 开始的。
	int *b = new(nothrow) int[high - low + 1];
	if (!b)  
	{
		cout << "分配失败" << endl;
		return;
	}

	while (i <= mid && j <= high)  // 分两路进行比较,把数据较小的放到中介数组
	{
		if (a[i] <= a[j])
			b[k++] = a[i++];
		else
			b[k++] = a[j++];
	}

	while (i <= mid)    // 若某个区间的数据仍然有剩余,就直接把剩余的数据复制到中介数组
		b[k++] = a[i++];// 这两个循环只会执行其中一个。。因为肯定不会两边都有剩余
	while (j <= high)
		b[k++] = a[j++];

	for (int i = low,k=0; i <= high; i++,k++) // 将 b 数组的元素复制到原数组
		a[i] = b[k];

	delete[]b;

}

测试:

int main()
{
	int a[] = { 1, 5, 3, 4, 12, 35, 21, 9 };
	mergesort(a,0,7);

	for (int i = 0; i < 8; i++)
		cout << a[i] << endl;
	return 0;
}

对于非递归的形式,只需要更改 merge() 即可

void MergeSort(int arr[], int n)//n代表数组中元素个数,数组最大下标是n-1   
{
	int size = 1, low, mid, high;
	while (size <= n - 1)  // 使用步长来控制
	{
		low = 0;
		while (low + size <= n - 1)
		{
			mid = low + size - 1;
			high = mid + size;
			if (high>n - 1)//第二个序列个数不足size   
				high = n - 1;
			Merge(arr, low, mid, high);//调用归并子函数   
			//cout << "low:" << low << " mid:" << mid << " high:" << high << endl;//打印出每次归并的区间   
			low = high + 1;//下一次归并是第一关序列的下界   
		}
		size *= 2;//范围扩大一倍   
	}
}

举例说明一下:

// 非递归版本的 MergeSort
// 使用一个例子来解释这个排序,假设有 8 个数据,[48],[38],[65],[97],[76],[13],[27],[33],元素个数 n = 8
// 初始时,size = 1,此时,size <= 7,
// 第一次内循环时,即 low + size <= n - 1,因为每次 low 都会递增,所以要保证不越界
// 一开始,low = 0,mid = 0,high = 1,这可以看成是元素的下标。调用归并函数,将 [48],[38]变成 [38,48].
// low = high +1 = 2,mid = 2,high = 3,调用归并函数,将 [69],[57]变成 [57,69].
//low = high +1 = 4,mid = 4,high = 5,调用归并函数,将 [76],[13]变成 [13,76].
//low = high +1 = 6,mid = 6,high = 7,调用归并函数,将 [27],[33]变成 [27,33].,这样,序列变成了 [38,48],[57,69],[13,76],[27,33]


// 第二次,size = 2,size <=7
//一开始,low = 0,mid = 1,high = 3,[38,48],[57,69]变成了 [38,48,57,69]
//然后,low = 3+1 = 4, mid = 5, high = 7, [13,76],[27,33]变成了[13,27,33,76]


// 第三次,size = 4,size <=7
//一开始,low = 0,mid = 3,high = 7,这样 [38,48,57,69],[13,27,33,76] 就变成了 [13,27,33,38,48,57,69,76]

性能分析:

空间复杂度:上面的 merge () 函数需要申请辅助单元, n 个单元。但是,每一趟归并以后,这些空间就被释放了,所以时间复杂度为 O(n)

时间复杂度: 每一趟归并的复杂度为 O(n),总共进行 log2 (n) 趟归并。所以时间复杂度为 O(nlog2 (n))。


升级——原地归并排序

既然 merge() 函数需要辅助单元,那么,不使用额外的存储空间可以吗?




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值