1.堆排序
此处的堆其实就是以数组形式存储的完全二叉树。堆排序如果从时间复杂度的角度上来看是最稳定的排序方法,无论最好最坏,时间复杂度都是O(nlogn)。
堆有两种堆,需要我们注意,一种是大顶堆,即所有父节点比其子节点的值都要大(a[K] > a[2K+1] && a[K] > a[2K+2]);另一种便是小顶堆,即所有父节点比其子节点的值都要小(a[K] < a[2K+1] && a[K] < a[K2+2])。由此不难理解,大堆顶的顶值是最大的,小顶堆的顶值是最小的。
使用堆排序,一共有两步:
**第一步:**如果是升序,首先要建立大顶堆;如果是降序,首先要建立小顶堆。无论是建立大顶堆还是小顶堆,都要自下向上建立,每次添加一个父节点的时候,都要再次自上而下的进行判断,这样才能保证建立的堆是大顶堆或者小顶堆。
**第二步:**堆顶元素与堆尾的元素交换,由此可以知道,交换后,堆尾的元素是有序,其余剩下的元素还是无序的。所以只要重新再调整剩下的无序元素形成我们想要的大顶堆或者小顶堆。对于调整来讲不用再自下而上,因为除了交换后的堆顶,其余的都已经排好了,所以只要再次将堆顶元素落到合适的位置即可。所以调整时,用自上而下的顺序即可。
/*
堆排序
*/
public int[] HeapSort(int[] a){
//建立大顶堆
buildBigStack(a,0,a.length-1);
int length = a.length;
//将堆顶的元素与堆尾的元素交换
swap(a,0,length-1);
//开始递归调整大顶堆,最终形成一个有序的小顶堆,即排好序
sort02(a,0,length-2);
return a;
}
private void HeapAdjust(int[] a, int start, int end) {
while (2*start+1 <= end){//循环终止条件,即当元素没有子节点的时候终止
int biggerIndex = 2*start+1;//用来记录子节点中值较大的子节点的索引
//如果存在子节点,且左儿子的值比右儿子小,修改索引值
if(2*start+2 <= end && a[2*start+1] < a[2*start+2]){
biggerIndex = 2*start + 2;
}
//如果父节点的值小于子节点的值
if(a[start] < a[biggerIndex]){
//交换父子节点
swap(a,start,biggerIndex);
//并且保存子节点的索引值
start = biggerIndex;
}
//如果父节点的值大于子节点的值,就直接跳出循环即可,因为调整的堆已排好序了
else
break;
}
}
//递归调整
private void sort02(int[] a, int start, int end) {
//调整元素
HeapAdjust(a,start,end);
//交换堆顶和堆尾的元素
swap(a,0,end);
if(end > 1)//如果仅剩一个元素,不再进行比较
sort02(a,0,end-1);
}
//用来交换元素
public void swap(int[] a, int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public int[] buildBigStack(int[] a, int start, int end) {
//最后一个有子节点的父节点
int lastFatherIndex = (end - 1)/2;
//从最后一个拥有儿子的父节点开始建立
for(int i = lastFatherIndex; i >= start ; i--){
HeapAdjust(a,i,end);
}
return a;
}
2.快速排序
快速排序的基本思想是,取出数组的一个数,设置为基数,用来判断其他数和它的关系,这里设置每段要排序数组的起点为基数。
对每段要排序的数组, 再设置要排序数组的起点(start)和终点(end)。当两者不同时,即不指向同一个元素时,表明要排序的数组已排好。
所以每次排序分两步:
**第一步:**因为将数组的起点设置为基数,所以第一步先从后往前判断与基数的大小,如果比基数大的话,说明其位置正确,所以只要end–即可,直至出现比基数小的数或者是,start == end。如果比基数小的话,说明其位置不正确,需要交换基数和此数的位置。
第二步: 承接上一步交换位置后的基数,再从前往后判断,直至碰到第一个比基数大的数,再交换基数和此数的位置。
如果start < end ,则继续重复以上两步。
时间复杂度取决于树的深度,最好的情况下每次等长分配,类似于折半查找,时间复杂度是O(nlogn),最坏的情况下是一个已排好序的数组,所以树的深度n,时间复杂度为O(N2)
public int[] fastSort(int[] a){
sort(a,0,a.length-1);
return a;
}
public void sort(int[] a,int start, int end){
int left = start, right = end;
int key = a[left];
while (left < right){
while(left < right && a[right] >= key)
right--;
if(a[right] < key){
a[left] = a[right];
a[right] = key;
}
while (left < right && a[left] <= key)
left++;
if(a[left] > key){
a[right] = a[left];
a[left] = key;
}
}
if(left > start) sort(a,start,left-1);
if(right < end) sort(a,right+1,end);
}
3.归并排序
归并排序的基本思想是可以将序列看成有序序列的不断合并。
所以归并排序分为两步:
第一步: 将数组递归划分,直至每个子序列只含有一个数。
第二步: 合并
public int[] mergingSort (int[] a){
//定义一个用于存放最后结果的数组
int[]temp = new int[a.length];
//递归分组,注意end是length-1
merging(a,0,a.length-1,temp);
return temp;
}
public void merging(int[]a,int start,int end,int[] temp){
//如果起点和终点不相同,即子序列包含一个以上的元素
if(start < end){
int mid = (start+end)/2;
merging(a,start,mid,temp);
merging(a,mid+1,end,temp);
group(a,start,mid,end,temp);//合并
}
}
public int[] group(int[]a, int start,int mid, int end, int[] temp){
int i = start;
int k = start;
int j = mid + 1;
while (i <= mid && j <= end){
if(a[i] < a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= mid) {
temp[k++] = a[i++];
}
while (j <= end){
temp[k++] = a[j++];
}
//注意!!!每次合并完了以后需要将原始数组a,
// 重调位置,变成刚合并成的有序序列。
for(int w = start; w <= end;w++){
a[w] = temp[w];
}
return temp;
}