堆排序
在堆中,节点和数组中的下标对应关系:
如果元素x的下标为i,那么它的左孩子节点的下标为2i+1,它的右孩子节点的下标为2i+2,它的父节点的坐标为(i-1)/2
- 第一步对数组中所有的元素构造大顶堆
- 默认数组中第一个元素在堆中,第二个元素和父节点比较,如果大于父节点,和父节点交换位置,即元素上移;如果小于等于父节点,元素位置不改变,继续插入下一个元素。
- 元素移动的次数最多为logN次,即和堆的高度有关。
- 数组中所有的元素遍历结束之后,大顶堆构建完成。
- 第二步每次将堆顶元素和有效长度内的最后一个元素交换,
- 假设数组的长度为len,那么第一次将堆顶元素和数组的第len个元素(此处下标从1开始)交换,然后重新对前len-1个元素调整使其成为大顶堆;第二次就将堆顶元素和数组的第len-1个元素交换,重新对前len-2个元素调整使其成为大顶堆;直到将堆顶元素和数组的第2个元素交换结束之后,排序结束。
- 调整堆的过程是使元素下移的过程,如果堆顶元素小于孩子节点那么与其交换,继续判断交换之后与孩子节点的大小关系,直到确定一个位置,在该位置处均大于等于孩子节点的值。
//首先调整该数组构建一个大根堆,每次将堆顶元素和数组的有效范围内的最后一个位置交换
public static void heapSort(int[]arr) {
if(arr==null||arr.length<2)
return;
for(int i=1;i<arr.length;i++)
heapInsert(arr,i);
int size = arr.length;
swap(arr, 0, --size);
while (size > 1) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
//建大根堆:
//在插入的过程中使较大的元素上移,上移的次数和树的高度有关所以复杂度是logN级别。
//当添加一个数具体和哪几个数比较,就是对应的二叉树的高度。节点数为N的话,二叉树的高度为log(N)
public static void heapInsert(int[] arr,int index) {
while(arr[index]>arr[(index-1)/2]) {
swap(arr,index,(index-1)/2);
index=(index-1)/2;
//(0-1)/2也等于0
}
}
//堆的插入,
//调整堆的过程就是当前的堆顶元素不是最大元素,需要下移到合适的位置,使其大于左右孩子节点,移动的次数和树的高度有关。
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
if(arr[largest]<=arr[index])
break;
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
private static void swap(int[] arr, int i, int j) {
// TODO Auto-generated method stub
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
快速排序
- 在数组中随机选择一个元素作为间隔元素,将小于该元素的放在左侧,大于该元素的放在右侧,等于该元素的放在中间;
- 然后递归对该元素左侧的元素和该元素右侧的元素做相同的操作;如果排序数组的左边界下标大于等于右边界的下标,那么排序结束。
- 维护两个变量,less记录小于区域的最后边界,more记录大于区域的头边界
public static void quickSort(int[]arr) {
if(arr==null||arr.length<2)
return;
quickSort(arr,0,arr.length-1);
}
//经典的快速排序是每次选择最后一个位置的元素作为分界元素,
//随机快排的意思是每次随机选择一个位置作为分界元素
private static void quickSort(int[] arr, int i, int j) {
if(i>=j)
return;
int p= i+new Random().nextInt(j-i+1);
swap(arr,p,j);
int less=i-1,more=j,cur=i;
while(cur<more) {
if(arr[cur]<arr[j])
swap(arr,cur++,++less);
else if(arr[cur]>arr[j])
swap(arr,cur,--more);
else
cur++;
}
swap(arr,less+1,j);//将分界元素放到合适的位置
quickSort(arr,i,less);
quickSort(arr,more,j);
}
归并排序
- 每次对数组分成两部分,先对左侧部分排序,再对右侧部分排序,直到左侧边界等于右侧边界时,证明该部分数组已经有序,然后将两部分合并。
- 合并的过程中,定义一个辅助数组,存放合并后排好序的元素,定义两个指针分别指向各部分的头元素,哪一个小将对应的放入辅助数组中,并且下标后移,直到将两部分的元素合并结束,再将辅助数组中的元素拷贝到原数组中,合并结束。
- 归并排序的优势是保留了之前比较的结果,不需要重复去比较,每次合并时不再是相邻之间进行判断,而是跨间隔的判断。
public static void mergeSort(int[] arr) {
if(arr==null||arr.length<2)
return;
mergeSort(arr,0,arr.length-1);
}
private static void mergeSort(int[] arr, int i, int j) {
if(i==j)
return;
int mid=i+(j-i)/2;
mergeSort(arr,i,mid);
mergeSort(arr,mid+1,j);
merge(arr,i,j,mid);
}
private static void merge(int[] arr, int i, int j, int mid) {
int indexi=i,indexj=mid+1;
int []help=new int[j-i+1];
int k=0;
while(indexi<=mid&&indexj<=j) {
if(arr[indexi]<arr[indexj]) {
help[k++]=arr[indexi];
indexi++;
}else {
help[k++]=arr[indexj];
indexj++;
}
}
while(indexi<=mid)
help[k++]=arr[indexi++];
while(indexj<=j)
help[k++]=arr[indexj++];
for(int m=0;m<help.length;m++) {
arr[i+m]=help[m];
}
}
本文深入讲解了三种主要的排序算法:堆排序、快速排序和归并排序。详细介绍了每种算法的工作原理,包括堆排序的大顶堆构建和调整过程,快速排序的随机选择间隔元素和递归操作,以及归并排序的分治策略和合并过程。
2267

被折叠的 条评论
为什么被折叠?



