5、堆排序:堆积排序(Heapsort)是指利用堆积树(堆)这种资料结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。堆排序是不稳定的排序方法,辅助空间为O(1), 最坏时间复杂度为O(nlog2n) ,堆排序的堆序的平均性能较接近于最坏性能。 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。直到无序区只有一个元素为止。
大根堆排序算法的基本操作:
① 初始化操作:将R[1..n]构造为初始堆;
② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
- public static void heapSort(int[] arr)
- {
- for(int i=0;i<arr.length;i++)
- {
- createMaxdHeap(arr,arr.length-i-1);
- swap(arr, 0, arr.length-i-1);
- for(int i = 0; i < arr.length; i++)
- System.out.print(arr[i] + "\t");
- }
- }
- public static void createMaxdHeap(int[] arr, int lastIndex)
- {
- for(int i=(lastIndex-1)/2;i>=0;i--)
- {
- int k=i;// 保存当前正在判断的节点
- while (2*k+1<=lastIndex) // 若当前节点的子节点存在
- {
- int biggerIndex=2*k+1; // biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点
- if(biggerIndex<lastIndex)// 若右子节点存在,否则此时biggerIndex应该等于 lastIndex
- if(arr[biggerIndex]<arr[biggerIndex+1])// 若右子节点值比左子节点值大,则biggerIndex记录的是右子节点的值
- biggerIndex++;
- if(arr[k] < arr[biggerIndex]) // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k
- {
- swap(arr, k, biggerIndex);
- k = biggerIndex;
- }
- else
- break;
- }
- }
- }
- public static void swap(int[] arr, int i, int j)
- {
- if(i==j)
- return;
- arr[i] = arr[i] + arr[j];
- arr[j] = arr[i] - arr[j];
- arr[i] = arr[i] - arr[j];
- }
复制代码
6、归并排序
归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。
工作原理:
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4、重复步骤3直到某一指针达到序列尾
5、将另一序列剩下的所有元素直接复制到合并序列尾
- public static void mergeSort(int[] arr)
- {
- sort(arr, 0, arr.length - 1);
- }
- public static void sort(int[] arr, int left, int right)
- {
- if(left>=right)
- return;
- int center=(left+right)/2; // 找出中间索引
- sort(arr,left,center);// 对左边数组进行递归
- sort(arr,center+1,right); // 对右边数组进行递归
- merge(arr,left,center,right);// 合并
- for(int i=0;i<arr.length;i++)
- System.out.print(arr[i] + "\t");
- }
- /*
- 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
- arr:数组对象
- left:左数组的第一个元素的索引
- center:左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
- right:右数组最后一个元素的索引
- */
- public static void merge(int[] arr, int left, int center, int right)
- {
- int[] tmpArr = new int[arr.length];// 临时数组
- int mid = center + 1;// 右数组第一个元素索引
- int third = left; // third 记录临时数组的索引
- int tmp = left;// 缓存左数组第一个元素的索引
- while(left<=center&&mid<=right)
- {
- if(arr[left]<=arr[mid]) // 从两个数组中取出最小的放入临时数组
- tmpArr[third++]=arr[left++];
- else
- tmpArr[third++]=arr[mid++];
- }
- while(mid<=right) // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
- tmpArr[third++]=arr[mid++];
- while(left<=center)
- tmpArr[third++] = arr[left++];
- while(tmp<=right)// 将临时数组中的内容拷贝回原数组中 (原left-right范围的内容被复制回原数组)
- arr[tmp]=tmpArr[tmp++];
- }
复制代码
7、基数排序
基数排序已经不再是一种常规的排序方式,它更多地像一种排序方法的应用,基数排序必须依赖于另外的排序方法。基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。多关键字排序的思路是将待排数据里德排序关键字拆分成多个排序关键字;第1个排序关键字,第2个排序关键字,第3个排序关键字......然后,根据子关键字对待排序数据进行排序。
多关键字排序时有两种解决方案:
最高位优先法(MSD)(Most Significant Digit first)
最低位优先法(LSD)(Least Significant Digit first)
例如,对如下数据序列进行排序。
192,221,12,23
可以观察到它的每个数据至多只有3位,因此可以将每个数据拆分成3个关键字:百位(高位)、十位、个位(低位)。如果按照习惯思维,会先比较百位,百位大的数据大,百位相同的再比较十位,十位大的数据大;最后再比较个位。人得习惯思维是最高位优先方式。
对于多关键字排序来说,程序将待排数据拆分成多个子关键字后,对子关键字排序既可以使用桶式排序,也可以使用任何一种稳定的排序方法。
- public static void radixSort(int[] arr,int radix,int d)
- {
- int[] tmp=new int[arr.length];// 缓存数组
- int[] buckets=new int[radix];// buckets用于记录待排序元素的信息,buckets数组定义了max-min个桶
- for(int i=0,rate=1;i<d;i++)
- {
- Arrays.fill(buckets,0); // 重置count数组,开始统计下一个关键字
- System.arraycopy(arr, 0,tmp,0,arr.length); // 将data中的元素完全复制到tmp数组中
-
- for(int j=0;j<arr.length;j++) // 计算每个待排序数据的子关键字
- {
- int subKey=(tmp[j]/rate)%radix;
- buckets[subKey]++;
- }
- for(int j=1;j<radix;j++)
- buckets[j]=buckets[j]+buckets[j-1];
- for(int m=arr.length-1;m>=0;m--)// 按子关键字对指定的数据进行排序
- {
- int subKey=(tmp[m]/rate)%radix;
- arr[--buckets[subKey]]=tmp[m];
- }
- rate *= radix;
- }
- }
复制代码
|