408-数据结构-归并排序&外部排序&外部排序优化

归并排序

算法思想:将需要排序序列分为多个小块,两个两个小块进行归并操作,也就是完成两个小块归并成一个大的有序块。直到完成整个序列的归并操作。

归并操作:需要开辟一个辅助空间,两个指针指向两个小块,两个指针指向的数据进行比较,将小的或者大的放入辅助空间,循环直到其中一个小块没有数据。最后将另外一个块中的数据添加到辅助空间最后即可。

一般归并排序采取二路归并排序,也就是两个两个小块进行合并,也可以是多路归并,也就是n个小块进行合并。

正经排序算法:
将序列分割为单独块,两个两个进行内部排序也就是完成一次归并操作,合并为一个大块,如果最后存在没有满的块也视为一个块。在归并操作时候,由于完成归并操作是在另外一个数组,所以可以开辟辅助数组,先将数组复制到辅助数组,然后归并回原有数组。一次一次归并,块内所含有的元素个数不断递增1,2,4,8…直到整个序列排序完毕。

空间复杂度:O(n)
时间复杂度:O(nlogn)
稳定性:稳定
可以支持链表

int m[N]; 	//辅助空间
void mergeUnit(int a[], int low, int mid, int high){
	for (int i = low ; i <= high ; i++)
		m[i] = a[i];
	int i = low, j = mid+1, k = low;
	while (i <= low && j <= high){
		if (m[i] <= m[j])
			a[k++] = m[i++];
		else
			a[k++] = m[j++];
	}
	while (i <= low)
		a[k++] = m[i++];
	while (j <= high)
		a[k++] = m[j++];
	return;
}

void mergeSort(int a[], int low, int high){
	int mid = (low + high) / 2;
	mergeSort(a, low, mid);
	mergeSort(a, mid+1, high);
	mergeUnit(a, low, mid, high);
}

外部排序

外部排序主要针对不能一次性将所有排序数据加载进内存,部分数据依旧在外存呆着。由于操作系统文件系统已经将外存数据进行分块管理,所以可以认为这些一个一个块就是归并排序一个小单元,所以可以将几个的块加载进内存,将他们进行归并排序,然后重新写会外存。这就可以达到排序的目的。

第一次可以两个文件块加载进入内存,然后对他们进行归并操作,但需要注意的是达到了文件块的大小就可以将他们写会外存,然后进行继续归并操作。这样可以将两个文件块变为有序的,称为归并段。
第二次类似,可以读取两个归并段的头部文件块,然后进行归并操作。注意点:如果有某一个归并段的文件块为空,需要进行度操作将这个归并段的下一个文件块读入内存继续归并。
一直操作就可以将外存中的数据排序成有序序列。

显然,每一次整体扩大归并段,都会进行总体的文件块数的读或者写操作,也就是2*总体文件块数I/O操作。整个归并算法过程可以理解为:生成归并段,第一次归并操作,第二次归并操作…

总体I/O操作:2总体文件块数+2总体文件块数*归并次数。

第一个优化思路是可以降低归并次数,来减少总体I/O次数,降低归并次数可以通过采取多路归并操作
对于r个初始归并段归并可以用K叉树表示,那么归并次数h-1 = log_k®向上取整。所以可以通过提高k或者减少r来减少归并次数。

但k路归并会导致内存开销变大,同时选最优时候需要判断k-1次。

败者树

在多路归并时候选择最优需要判断k-1次,而如果采用败者树可以将判断次数压缩为logk次。
败者树主要思想:通过两个比较选择较优,然后两个较优的继续跟其他组较优比较…,然后可以获得最优。这个过程可以用二叉树来进行展现。在多路归并需要剥夺掉最大的那个,也就是树中最高的那个点,那么新加进来一个选择最优就只需要将新加进来的重新一个原来剥夺掉最优的经过的比较对象,如果他比每一个大那么新加进来的就是最优,如果劣于其中任何一个,那就是上一轮败者是最优。这个比较次数显然最多就是树的高度也就是logn

置换选择排序

在上面说明了另外一个可以降低归并次数的降低初始归并段。
显然在外部排序时候,内存的空闲空间决定了初始归并段的个数。因为将外存数据读出然后沾满内存空间就会得到一个初始归并段。
所以初始归并段个数=所有数据N/内存空闲空间向上取整。

通过置换选择排序可以突破内存空间的限制,减少初始归并段的个数。

置换选择排序算法过程:
将外存数据读到内存填满空闲的内存空间,然后选择内存空间最小的加入输出缓冲区,然后再去数据区读一个数据,每一次都将内存缓冲区最小的加入输出缓冲区,但必须要大于输出缓冲区最后一个值,让输出缓冲区呈现递增有序。直到内存空间中每一个数值都比缓冲区最后一个数小,结束。这时候输出缓冲区就是一个归并段。

这样做可以让初始归并段突破原有内存限制,但也会让每一个归并段的大小不一,但这不重要。

按照归并排序,会产生一个归并二叉树,会发现总的I/O磁盘总数会刚好等于这个二叉树的wpl两倍。所以为了减少I/O次数可以改变原有归并策略,让这个二叉树的wpl达到最小。

当然二叉树是因为采取二路归并,如果使用m路归并就是m叉树。
要使wpl最小那么就需要使用哈夫曼树。
唯一一个要注意的是m叉树如果需要使用哈夫曼树,如果最后存在不满于m个结点情况需要进行补充权重为0的结点使得最后达到m个结点。

结论:

  1. 初始归并段树量-1恰好为k-1的倍数不需要添加
  2. 补充至为其倍数。

你可能感兴趣的:(408-数据结构,数据结构,算法,c语言,c++)