数据结构---归并排序和外部排序

内部排序
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。

外部排序
若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。

就地排序
若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1),称为就地排序。

稳定排序
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序后,这些记录的相对次序保持不变,即在原序列中 ri=rj, ri 在 rj 之前,而在排序后的序列中,ri 仍在 rj 之前,则称这种排序算法是稳定的;否则称为不稳定的。

排序序列分布
排序需要考虑待排序关键字的分布情况,这会影响对排序算法的选择,通常我们在分析下列算法时都考虑关键字分布是随机分布的,不是按照某种规律分布的,比如正态分布等。

待排序序列
排序序列中,剩余即将要排序的序列部分。

已排序序列
排序序列中,已经排序好的序列部分

首先我们了解一下归并排序的特性:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定
    归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤
  • 将我们的排序的数组进行我们的重点划分为两个部分
    例如left和right此时的区间是[left,right),制定一个中点mid = (left+right)/2。
  • 利用分治算法继续将我们左右两个继续进行划分,
  • 知道我们的left == right 或者left+1 == right,也就是只有一个元素的时候和一个元素也没有的时候我们就认为这个序列是有序的
  • 然后将我们的有序的数组进行合并,达到我们最后的时候就合成了有序的数组。
    在这里插入图片描述
    就上图一样,我们将数组每次拆解成两个部分,知道达到有序数组的条件的时候就开始合并我们的数组。这就是我们二路归并
    如下图所示,初始状态时,a序列[2,3,5]和b序列[2,9]为已排序好的子序列,现在利用二路归并,将a和b合并为有序序列 r,初始时,i指向a的第一个元素,j指向b的第一个元素,k初始值等于0。
    说明,r中最后一个元素起到哨兵的作用,灰色显示。

在这里插入图片描述

第一步,比较a[i]和b[j],发现相等,如果规定相等时,a的先进入r,则如下图所示,i, k分别加1,为了形象化,归并后的元素不再绘制。

在这里插入图片描述

第二步,继续比较,此时b[j]小,所以b的元素2进入r,则如下图所示,j, k分别加1,
在这里插入图片描述

第三步,继续比较,此时a[i]小,所以a的元素3进入r,则如下图所示,i, k分别加1,

在这里插入图片描述

第四步,继续比较,此时a[i]小,所以a的元素5进入r,则如下图所示,i, k分别加1,此时序列a的3个元素已经归并完,b中还剩下一个,这个可以通过k可以看出,它还没有到达个数5。
在这里插入图片描述

第五步,将序列b中的所有剩余元素直接放入r中即可,不用做任何比较了,直至b变空,二路归并结束。

在这里插入图片描述

这其实也就是和我们的第一个图解是一样的思想
完整代码

#include <iostream>
using namespace std;
void Swap(int arr[], int a, int b){
	int temp = arr[a];
	arr[a] = arr[b];
	arr[b] = temp;
}
void combindArray(int array[], int left, int right, int mid){
	//将[left,mid) 和数组[mid,right)进行合并
	int* p = new int[right - left];
	int i = 0;
	int start = left;
	int end = right;
	int port = mid;
	while (left < mid&&port < right){
		if (array[left] < array[port]){
			p[i++] = array[left++];
		}
		else{
			p[i++] = array[port++];
		}
	}
	while (left < mid){
		p[i++] = array[left++];
	}
	while (port < right){
		p[i++] = array[port++];
	}
	i = 0;
	while (start < end){
		array[start++] = p[i++];
	}
	delete[]p;
}
//1.平均切割区间
//2.分治处理左右两个小区间,直到size ==0 或者size ==1
//3.合并左右两个有序数组
void MergeSortInner(int array[], int left, int right){//此时我们的区间是[left,right)
	if (left == right){
		return;//size=0;
	}
	if (left + 1 == right){
		return;
	}
	int mid = (left + right) / 2;
	MergeSortInner(array, left, mid);
	MergeSortInner(array, mid, right);
	//合并两个区间的元素
	combindArray(array, left, right, mid);
}
//归并排序就是将两个有序的区间进行合并,采用分治算法
void MergeSort(int array[], int size){
	MergeSortInner(array, 0, size);
}
void printSort(int array[], int size){
	for (int i = 0; i < size; i++){
		cout << array[i] << " ";
	}
	cout << endl;
}
int main(){
	int arr[] = { 5, 6, 8, 9, 5, 4, 2, 3, 1, 6 };
	int size = sizeof(arr) / sizeof(arr[0]);
	MergeSort(arr, size);
	printSort(arr, size);

	system("pause");
	return EXIT_SUCCESS;
}


了解外部排序
数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
比如我们对1000000个数据进行排序,此时内存不够的时候我们就不能进行内部排序,此时应该使用我们的外部排序的思想去进行排序。
外部排序也是指的是大文件的排序,当待排序的文件很大时,无法将整个文件的所有记录同时调入内存进行排序,只能将文件存放在外存,这种排称为外部排序。外部排序的过程主要是依据数据的内外存交换和“内部归并”两者结合起来实现的。
一般来说外排序分为两个步骤:预处理和合并排序。首先,根据可用内存的大小,将外存上含有n个纪录的文件分成若干长度为t的子文件(或段);其次,利用内部排序的方法,对每个子文件的t个纪录进行内部排序。这些经过排序的子文件(段)通常称为顺串(run),顺串生成后即将其写入外存。这样在外存上就得到了m个顺串(m=[n/t])。最后,对这些顺串进行归并,使顺串的长度逐渐增大,直到所有的待排序的记录成为一个顺串为止。
1.预处理阶段
最重要的事情就是选择初始顺串。通常使用的方法为置换选择排序,它是堆排序的一种变形,实现思路同STL的partial_sort。步骤如下:
(1)初始化堆
从磁盘读入M个记录放到数组RAM中;
设置堆末尾标准LAST=M-1;
建立最小值堆。
(2)重复以下步骤直到堆为空
把具有最小关键码值的记录Min也就是根节点送到输出缓冲区;
设R是输入缓冲区中的下一条记录,如果R的关键码大于刚刚输出的关键码值Min,则把R放到根节点,否则使用数组中LAST位置的记录代替根节点,并将刚才的R放入到LAST所在位置,LAST=LAST-1;
(3)重新排列堆,筛出根节点。
如果堆的大小是M,一个顺串的最小长度就是M个记录,因为至少原来在堆中的那些记录将成为顺串的一部分,如果输入时逆序的,那么顺串的长度只能是M,最好情况输入是正序的,有可能一次性就能把整个文件生成一个顺串,由此可见生成顺串的长度是大小不一的,但是每个顺串却是有序的,利用扫雪机模型能够得到平均顺串的长度为2M。
外部排序最常用的算法是多路归并排序,即将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序。然后,对已经排序的子文件进行归并排序。
2. 二路合并
(1) 二路合并排序
二路合并是最简单的合并方法,合并的实现与内排序中的二路归并算法并无本质区别,下面通过具体例子,分析二路合并外部排序的过程。
有一个含有9000个纪录的文件需要排序(基于关键字)。假定系统仅能提供容纳1800个纪录的内存。文件在外存(如磁盘)上分块存储,每块600个纪录。外部排序的过程分为生成初始顺串和对顺串进行归并排序两个阶段。在生成初始顺串阶段,每次读入1800个纪录(即3段)待内存,采用内排序依次生成顺串依次写入外存储器中。
顺串生成后,就开一开始对顺串进行归并。首先将内存等分成3个缓冲区,和,每个缓冲区可容纳600个纪录,其中和为输入缓冲区,为输出缓冲区,每次从外存读入待归并的块到和,进行内部归并,归并后的结果送入,中的几率写满时再将其写入外存。若(或)中的纪录为空,则将待归并顺串中的后续块读入和(或)中进行归并,直到待归并的两个顺串都已归并为止。重复上述的归并方法,由含有5块(每块上限1800个记录)的顺串经二路归并的一边归并后生成含有3块(每块上限3600个记录)的顺串,再经过第二遍……第s遍(s=[],m为初始顺串的个数),生成含有所有记录的顺串,从而完成了二路归并外部排序。
对文件进行外部排序的过程中,因为对外存的读写操作所用的操作的时间远远超过在内存中产生顺串和合并所需的时间,所以常用对外存的读写操作所用的时间作为外部排序的主要时间开销。分析一下上述二路归并排序的对外存的读写时间。初始时生成5个顺串的读写次数为30次(每块的读写次数为2次)。
类似地,可得到二路、三路……多路合并方法。
(2) 多路替代选择合并排序
采用多路合并技术,可以减少合并遍数,从而减少块读写次数,加快排序速度。但路数的多少依赖于内存容量的限制。此外,多路合并排序的快慢还依赖于内部归并算法的快慢。
设文件有n个纪录,m个初始顺串,采用k路合并方法,那么合并阶段将进行遍合并。k路合并的基本操作是从k个顺换的第一个纪录中选出最小纪录(即关键字最小的纪录),把它从输入缓冲区移入输出缓冲区。若采用直接选择方式选择最小元,需要k-1次比较,遍合并共需n(k-1)=次比较。由于随k的增长而增长,则内部归并时间亦随k的增大而增大,这将抵消由于增大k而减少外存信息读写时间所得的效果。若在k个纪录中采用树形选择方式选择最小元,则选择输出一个最小元之后,只需从某叶到根的路径上重新调整选择树,即可选择下一个最小元。重新构造选择书仅用O()次比较,于是内部合并时间O(n)=O(),它与k无关,不再随k的增大而增大。
常见的有基于“败者树”的多路替代选择合并排序方法。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 中处理大数据量排序的方法有多种,下面是几种常见的方法: 1. 快速排序:快速排序是一种分治算法,它将一个数组分成两个部分,左边的数都比右边的数小,再对左右两部分分别进行快速排序,递归进行下去,直到整个数组有序。 2. 归并排序归并排序是一种分治算法,它将两个已经排序的数组归并成一个更大的有序数组。 3. 堆排序:堆排序是一种选择排序,它通过建立大根堆或小根堆来解决排序问题。 4. 计数排序:计数排序是一种线性排序,它对于数据有一定的值域限制,能够快速地给数据进行排序。 这几种方法各有优缺点,应该根据具体的情况选择合适的排序方法。 ### 回答2: 在Java中,针对大数据量的排序方法有许多种。以下是几种常用的排序方法: 1. 快速排序(Quick Sort):快速排序是一种基于分治策略的排序算法,它通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小。然后分别对这两部分记录继续进行快速排序,以达到整个数据序列有序的目的。快速排序是一种原地排序算法,平均时间复杂度为O(nlog n),适用于大数据量的排序。 2. 归并排序(Merge Sort):归并排序是一种基于分治策略的排序算法,它将待排序的数据序列分成两个子序列,分别进行递归地排序,然后再将两个有序子序列合并为一个有序序列。归并排序的时间复杂度为O(nlog n),适用于大数据量的排序,但相对于快速排序归并排序需要额外的内存空间。 3. 堆排序(Heap Sort):堆排序是一种基于堆数据结构排序算法,它通过将待排序的数据构建成一个最大堆或最小堆,然后将堆顶元素与最后一个元素交换,再对剩余的n-1个元素重新构建堆,直至所有元素都有序。堆排序的时间复杂度为O(nlog n),适用于大数据量的排序,但相对于快速排序归并排序,堆排序的常数项较大。 4. 外部排序:如果待排序的数据量过大无法全部加载到内存中进行排序,可以使用外部排序算法。外部排序通过将数据分成小块,在内存中逐个块进行排序,然后再将排好序的块合并成一个有序的结果。外部排序常用的算法包括多路归并排序、置换选择排序等。 总之,对于大数据量的排序,可以根据具体情况选择合适的排序方法。如果内存空间较大,可以使用快速排序归并排序或堆排序;如果内存空间有限,可以考虑使用外部排序算法。 ### 回答3: Java中处理大数据量的排序方法一般有以下几种: 1. 内存排序:对于能够直接放入内存的数据量,可以利用Java标准库中的排序算法(如Arrays.sort()方法)进行排序。这种方法简单方便,适用于小规模数据。 2. 外部排序:对于无法直接放入内存的大数据量,可以使用外部排序算法。外部排序将数据划分为多个小块,在内存中进行部分排序后,再通过归并等方法将这些有序小块合并成最终的有序结果。常见的外部排序算法有归并排序、多路归并排序等。 3. 分布式排序:当数据量非常庞大,并且无法由单台机器处理时,可以采用分布式排序。分布式排序将数据划分为多个部分,并由多台计算机同时进行排序。这需要借助于分布式计算框架,如Hadoop、Spark等。 4. 基数排序:基数排序是一种通过将数据按照位数进行排序的方法。对于大数据量,可以通过将数据按照某个位数进行划分,并分别进行排序,然后再合并排序结果。重复这个过程,直到所有位数排序完成。基数排序适用于数据量大且位数少的情况。 综上所述,Java可以通过内存排序外部排序、分布式排序和基数排序等方法来处理大数据量的排序问题。具体选择哪种方法,取决于数据量的大小、可用内存大小、计算机集群的规模等因素。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值