堆排序
基本思想
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
堆的定义下:
具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二 叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。
思想:
初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
示例:
- 首先将序列构建称为大顶堆;(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)
- 取出当前大顶堆的根节点,将其与序列末尾元素进行交换;(此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
- 对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质;
- 重复2.3步骤,直至堆中只有1个元素为止
实现
/**
* 建立堆
* @param a 待建的数组
* @param lastIndex 需要建立的数组的最后一个元素(控制需要建立堆的长度)
*/
private void buildMaxHeap(int[] a, int lastIndex){
// 从lastIndex处节点(最后一个节点)的父节点开始
for(int i = (lastIndex-1)/2; i >=0 ; i--){
// k保存正在判断的节点
int k=i;
// 如果当前的节点的子节点存在
while (k*2+1<=lastIndex){
// biggerIndex 为最大值的索引,先将其赋值为左子节点
int biggerIndex = k*2+1;
// 如果存在右子节点,则需要比较其大小
if(biggerIndex < lastIndex){
// biggerIndex始终为最大的子节点。
if(a[biggerIndex + 1] > a[biggerIndex]){
biggerIndex++;
}
}
// 如果k节点的值小于其较大的子节点的值,则需要交换他们
if(a[biggerIndex] > a[k]){
swap(a, biggerIndex, k);
// 交换后的左子节点,、
// 有可能小于他自己的子节点,
// 所以需要重新进行比较排序,
// 保证最小值在下面的节点
k = biggerIndex;
} else {
break;
}
}
}
}
/**
* 交换
*/
private void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public void heapSort(int[] a){
for(int i=0; i<a.length-1; i++){
buildMaxHeap(a, a.length-1-i);
swap(a, 0, a.length-1-i);
}
}
@Test
public void heapTest(){
int[] a={7,5,3,2,9,10,8,4,6,1};
heapSort(a);
System.out.println(Arrays.toString(a));
}
归并排序
基本思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
操作方法
实现
/**
* 归并排序
* 简介:将两个(或两个以上)有序表合并成一个新的有序表 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列
* 时间复杂度为O(nlogn)
* 稳定排序方式
* @param a 待排序数组
*/
private void mergeSort(int[] a, int low, int high){
if(low < high){
int mid = (low + high) >>> 1;
mergeSort(a, low, mid);
mergeSort(a,mid+1, high);
merge(a, low, mid, high);
}
}
/**
* 将数组中low到high位置的数进行排序
* @param a 待排序数组
* @param low 待排的开始位置
* @param mid 待排中间位置
* @param high 待排结束位置
*/
private void merge(int[] a, int low, int mid, int high){
int[] temp = new int[high -low + 1];
int i = low; // 左指针
int j = mid + 1; // 右指针
int k = 0;
// 将较小的数移到新数组中
while (i <= mid && j <= high){
if(a[i] < a[j]){
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
// 把左边剩余的移到数组中
while (i <= mid){
temp[k++] = a[i++];
}
// 把右边剩余的移到数组中
while (j <= high){
temp[k++] = a[j++];
}
// 把新数组中的数覆盖原数组
for(int k2 = 0; k2 <temp.length; k2++){
a[k2+low] = temp[k2];
}
}
@Test
public void mergeTest(){
int[] a={7,5,3,2,9,10,8,4,6,1};
mergeSort(a, 0, a.length-1);
System.out.println(Arrays.toString(a));
}
希尔排序
基本思想
把记录按步长进行分组,对每组记录采用直接插入的方法进行排序。随着步长的缩小,所分成的组包含的记录就越来越多,当步长的值减小到1时,整个数据合成一组,构成一组有序的记录,则完成排序。
操作方法
- 先将待排序序列按照某一“增量/步长(increment)”,分割成若干个子序列。
- 对每一个子序列做直接插入排序。
- 缩小增量/步长,继续把整个序列按照增量方法分割成若干个子序列。
- 继续对每个子序列做直接插入排序。
- 重复上述步骤,直到增量为1后,也就是最后一次排序,变成了一次直接插入排序。
示例
基本的希尔排序
private void shellSort(int[] a){
for(int increment = a.length/2; increment > 0; increment /= 2){
// 分组
for(int i=increment; i < a.length; i++){
// 组内排序
for(int j=i; j >= increment; j--){
if(a[j] < a[j-increment]){
int temp = a[j];
a[j] = a[j-increment];
a[j-increment] = temp;
} else {
// 因为当一次循环以上时,因为前面已经排好序,
// 所以直接与最近的一个increment增量比较即可。
// 符合继续比较,不符合直接跳过
break;
}
}
}
}
}
带哨兵的希尔排序
减少交换次数,提高效率
/**
* 哨兵希尔排序,就是将位置为j的元素取出来放到一个变量,
* 最后将这个值放到合适的位置
* @param a 待排序数组a
*/
private void shellSort1(int[] a){
int j=0;
int temp = 0;
for(int increment = a.length >>> 1; increment >0; increment = increment >>> 1){
for (int i=increment; i < a.length; i++){
temp = a[i];
for(j=i; j>=increment; j-=increment){
// temp 是a[j-increment] 交换后的值,
// if里面只是将a[j]的值变换了,
// 而a[j-increment]中没有变,
// 因此来减少变换次数
if(temp < a[j-increment]){
a[j] = a[j-increment];
} else {
// 因为当一次循环以上时,因为前面已经排好序,
// 所以直接与最近的一个increment增量比较即可。
// 符合继续比较,不符合直接跳过
break;
}
}
a[j] = temp;
}
}
}
参考:
必须知道的八大种排序算法【java实现】(二) 选择排序,插入排序,希尔算法【详解】
内部排序(二)希尔排序的两种实现
数据结构常见的八大排序算法(详细整理) 图解排序算法(四)之归并排序
有些注解是自己的理解,注解及代码如有不恰之处,欢迎各位留言指正。