排序
1.经典快排
经典快排与上一篇中荷兰国旗问题的解决方法是一致的。在荷兰国旗问题中,把小于 num的数放在数组左边,等于num的数放在数组中间,大于num的数放在数组右边;在经典快排中,这个num设置为数组中的最后一个元素,之后的操作与荷兰国旗问题的操作一致即定义小于区域,等于区域和大于区域,以及相应的三个指针(具体细节见上一篇荷兰国旗问题)。在划分完小于区域,和大于区域后,接着分别在小于区域和大于区域中取最后一个位置的数为num,对其继续使用荷兰问题解决方法(递归),直到划分区域只剩一个元素。
在这里插入代码片
import java.util.Arrays;
public class QuickSort {
//首先解决荷兰国旗问题
public static int [] partition(int []arr,int L,int R) {
int less=L-1;
int more=R;
//当L==R结束
//注意这是小于more 因为more在递减,R为最后一个数
while(L<more) {
if(arr[L]<arr[R]) {
swap(arr,++less,L++);
}else if(arr[L]>arr[R]) {
swap(arr,--more,L);
}else {
L++;
}
}
swap(arr,more,R);
return new int[] {less+1,more};
}
public static void swap(int [] arr,int a, int b) {
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
public static void quickSort(int []arr,int L,int R) {
//递归终止条件 这个L<R是因为分割数组 直至不能分割
if(L<R) {
int []p=partition(arr,L,R);
quickSort(arr,L,p[0]-1);
quickSort(arr,p[1]+1,R);
}
}
public static void main(String []args) {
int []arr= {2,4,5,3,8,3,0,5};
quickSort(arr,0,arr.length-1);
System.out.println("排序后数组为"+Arrays.toString(arr));
}
}
2.随机快排
在经典快排中,每次都是与数组的最后一个元素进行比较。特例:当数组arr=[1,2,3,4,5]时,num=5,此时只有小于区域和等于区域没有大于区域,遍历了一次,只搞定了一个数字,其时间复杂度为:
T(N)=2T(N/2)+O(N)=O(N*logN)
,空间复杂度为:log(N).相比较于经典快排,随机快排每次都是与数组中随机的元素的比较。代码如下:
import java.util.Arrays;
//随机快排 主要是比较的数字是随机选的
//具体来说就是将最后一个数和前面的数进行交换
public class RandomQuickSort {
//首先还是解决荷兰国旗问题
public static int[] paratition(int []arr, int L,int R) {
int less=L-1;
int more=R;
while(L<more) {
if(arr[L]<arr[R]) {
swap(arr,++less,L++);
}else if(arr[L]>arr[R]) {
swap(arr,--more,L++);
}else {
L++;
}
}
swap(arr,R,more);
return new int[] {less+1,more};
}
public static void swap(int []arr,int L,int R) {
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
public static void randomQuickSort(int []arr,int L, int R) {
if(L<R) {
//Math.random()产生0到1之间的数
swap(arr,R,L+(int)(Math.random())*(R-L+1));//注意起始索引为L;
int []p=paratition(arr,L,R);
randomQuickSort(arr,L,p[0]-1);
randomQuickSort(arr,p[1]+1,R);
}
}
public static void main(String []args) {
int []arr= {1,2,8,3,2,5,7};
randomQuickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
3.堆排
3.1基础知识
堆是一完全二叉树。
满二叉树:一棵深度为k且有 2k-1个结点的二叉树称为满二叉树(左右节点都存在).
完全二叉树:如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。对于一个节点i,其左叶节点为 2*i+1,其右叶节点为 2*i+2;其父节点(i-1)/2.
大根堆:父节点的值大于或等于子节点的值
小根堆:父节点的值小于或等于子节点的值
3.2数组形成大根堆
数组中每个索引为i的值都与其父节点的值(索引为(i-1)/2)进行比较,如果小于,则交换两节点值,继续向上比较,如果大于则满足大根堆要求。具体见下图
3.2heapify 过程
在数组按照3.1过程形成大根堆后,其根节点的值为最大值,将根节点与最底部的最右节点值进行交换,然后移除最右节点值,从根到底部,通过比较根节点索引为i 与左子节点i*2+1和右子节点i*2+2的值,再次形成大根堆。具体见下图
3.3代码如下
import java.util.Arrays;
public class StackSort {
//首先遍历整个数组 把这个数组的元素变为大根堆
public static void main(String[] args) {
int[] arr=new int[] {8,3,5,6,5,11,23,13};
for(int i=0;i<arr.length;i++) {
heapInsert(arr,i);
}
//经过了大根堆只是把最大值给求了出来,将根节点和最后一个节点交换然后进行构建大根堆
int heapSize=arr.length;
//将根节点元素和最后一个节点元素进行交换
swap(arr,0,--heapSize);
//这个是为了逐次构建大根堆
//由于最后一个节点元素与根节点元素进行了交换,因此每次heapify 都从0开始。
while(heapSize>0) {
heapify(arr,0,heapSize);
swap(arr,0,--heapSize);
}
System.out.println(Arrays.toString(arr));
}
//比较子节点 和父节点的大小
public static void heapInsert(int[] arr,int index) {
//已知子节点的索引为i,对应父节点 的索引为(i-1)/2;
while(arr[index]>arr[(index-1)/2]) {
//交换两者的位置
swap(arr,index,(index-1)/2);
//此时 向上继续比较
index=(index-1)/2;
}
}
//交换两者
public static void swap(int []arr,int a,int b) {
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
//重新构建大根堆
public static void heapify(int []arr,int index,int heapSize) {
//这里index 为根节点 其左节点为index*2+1;
int left=index*2+1;//计算下左节点
while(left<heapSize) {
//比较左右节点 的值的大小
int largest=(left+1<heapSize)&&(arr[left+1]>arr[left])?(left+1):left;
//比较 largest 与 根节点的值
largest=arr[largest]>arr[index]?largest:index;
if(largest==index)
break;
swap(arr,largest,index);
index=largest;//继续下沉比较
//注意此时 left 的值也需要变换
left=2*index+1;
}
}
}
总结:堆排的时间复杂度为O(N*logN),空间复杂度为O(1).
4.归并排序:
在归并排序中,会先找到一个数组的中间下标mid,然后以这个mid为中心,对两边分别进行排序,之后我们再根据两边已排好序的子数组,重新进行合并。(对一个数组不断以中间位置划分,直到只有一个数字划分结束,然后排序,合并划分的数组)
import java.util.Arrays;
public class MergeSort {
public static void main(String[]args){
int arr[]={8,3,4,2,6,9};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void mergeSort(int[] arr){
if(arr.length<2){
return;
}
sortProcess(arr,0,arr.length-1);
}
public static void sortProcess(int[] arr,int L,int R){
//当划分后只有一个数时停止
if(L==R){
return;
}
//划分为两部分 R和L中间位置的索引
int mid=L+((R-L)>>1);
sortProcess(arr,L,mid);
sortProcess(arr,mid+1,R);
//用来合并被分掉的数组
merge(arr,L,mid,R);
}
public static void merge(int[]arr,int L,int mid,int R){
int p1=L; //左指针
int p2=mid+1;//右边的指针
int[] help=new int[R-L+1];//定义一个数组用来存储合并后的数组
int i=0;
while(p1<=mid&&p2<=R){
help[i++]=arr[p1]>arr[p2]?arr[p2++]:arr[p1++];
}
//当从第一个while循环出来后必然是有一个>R 或mid
while(p1<=mid){
help[i++]=arr[p1++];//限制性help[i]=arr[p1]在分别执行++
}
while(p2<=R){
help[i++]=arr[p2++];
}
//再把help数组赋值给原数组:
for (int j=0;j<i;j++) {
arr[L+j] = help[j];
}
}
}