Java 实现基本的排序算法

用 Java 写的几个排序算法:插入排序,选择排序,冒泡排序,快速排序(双边扫描),归并排序,堆排序。

归并算法

参考了用Java写算法之四:归并排序
MergeSort(归并排序)算法Java实现

归并算法的主要思想是 divide and conquer。将数组从中间分开,直到数组分组中只剩一个数据,此时单个数据可以很轻松的比较大小了。比较完大小后,再两两合并。

归并效率很高,由于递归划分为子序列只需要logN复杂度,而合并每两个子序列需要大约2n次赋值,为O(n)复杂度,因此,只需要简单相乘即可得到归并排序的时间复杂度O(nlog(n))且算法固定,不受输入数据的影响。
但缺点是空间复杂度O(n),需要额外空间。所以当输入数据很大的时候不适合。

堆排序

参考这里堆与堆排序
HEAPSORT 堆排序算法详解(JAVA实现) 这两个讲的都很清楚。

堆是一种类似于树的数据结构。 不是堆栈中的堆

二叉堆分为最大堆和最小堆。最大堆的性质是每个父元素都不小于子元素。最小堆是每个父元素不大于子元素。

利用最大堆可以完成排序。


public class Sort {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int [] arr = {49,11,90,11,120,34,12,7,43,8,23};
        int len = arr.length;
        /*Quick  quick = new Quick();
        quick.quick_sort(arr, 0, len-1);*/

        MergeSort ms = new MergeSort();
        ms.mergeSort(arr);
        //这里还隐含了 数组的是引用的值的传递。调用排序算法后,再输出数组顺序已排好
        for(int i =0 ;i<len;i++){
            System.out.print(arr[i]+", ");
        }
    }

}

//插入排序:向一个有序数列里面插入,默认第一个数是有序的
class InsertSort{
    public void sort(int arr[]){
        //循环里计算arr.length ,浪费时间
        int n=arr.length;
        //默认第一个数是有序的
        for(int i=1;i<n;i++){
            //准备插入的数
            int insertVal=arr[i];
            //insertVal准备与前一个数比较,所以index=i-1
            int index=i-1;
            while(index>=0&&insertVal<arr[index]){
                //要插入的数比前一个数小,则indexVal的位置还没有找好,index应该-1,继续比较。
                arr[index+1]=arr[index];
                index--;
            }
            //将insertVal插入适当位置.判断插入的位置,用两个数1,2判断下即可
            //整个排序也可以用两个数检验
            arr[index+1]=insertVal;
        }
        /*for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+"; ");
        }
        System.out.println();*/
    }
}
//选择:认为每次循环的第一个数是最小数(最大数)
class Select{
    public void sort(int arr[]){
        int temp=0;
        for(int j=0;j<arr.length-1;j++){
            //开始认为第一个数就是最小
            int min=arr[j];
            //记录最小数下标
            int minIndex=j;
            for(int k=j+1;k<arr.length;k++){
                if(min>arr[k]){
                    min=arr[k];
                    minIndex=k;
                }
            }
            //当退出该次循环时就找到了该次的最小值
            temp=arr[j];
            arr[j]=arr[minIndex];
            arr[minIndex]=temp;
        }
        /*for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+"; ");
        }
        System.out.println();*/
    }
}
//冒泡:两两相比较(刚接触的那个两层循环排序),冒泡,小的下沉,第一轮最小的在最后。
class Bubble{
    public void sort(int arr[]){
        int i,j,temp;
        for(i=0;i<arr.length-1;i++){
            for (j=0;j<arr.length-i-1;j++){
                if(arr[j]>arr[j+1]){
                    temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;

                }
            }
        }
        /*for(i=0;i<arr.length;i++){
            System.out.print(arr[i]+"; ");
        }
        System.out.println();*/
    }

}
//快速排序:对冒泡排序的改进,前面的排序都是单进程。
//1,必须先从右边开始,然后进行左边
//2,写分治代码的时候需要加if(l<r)进行判断
//3,每个地方的交换位置
class Quick{
     int AdjustArray(int s[],int l,int r){
         int i=l,j=r;
         int x=s[i];//第一个坑
         while(i<j){
             //从右向左找小于X 的数填s[i]
         while(i<j&&s[j]>=x)
             j--;
         if(i<j){
             s[i]=s[j];

         }
         while(i<j&&s[i]<=x)
             i++;
         if(i<j){
             //i j 交换位置,最后  i 与key 交换位置
             s[j]=s[i];

         }
         }
     //退出时,i等于j,将 X 填到这个坑中去
         s[i]=x;
         return i;
     }
    //再写分治法的代码
     void quick_sort(int s[],int l,int r){
        if(l<r){
           int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]  
            quick_sort(s, l, i - 1); // 递归调用   
            quick_sort(s, i + 1, r); 
        }

     }

}
//归并排序
//divide and conquer思想
class MergeSort{
    public void mergeSort(int[] arr){
        //统一只创建一个临时数组
        int[] temp = new int[arr.length];
        internalMergeSort(arr,temp,0,arr.length-1);
    }

    public void internalMergeSort(int[] arr,int[] temp,int l,int r){
        if(r-l<1) return;

        int mid = (l+r)/2;

        internalMergeSort(arr,temp,l,mid);
        internalMergeSort(arr,temp,mid+1,r);

        Sort(arr,temp,l,mid,r);
    }
    public void Sort(int [] arr,int[] temp,int l,int mid,int r){
        int i = l;
        int j = mid+1;
        int k =l;//记录temp的数组下标,可以设置为0 ,也可以设置为 left

        //将两个数组按照顺序存入 temp 数组中
        while(i<=mid&&j<=r){
            if(arr[i]<=arr[j]){
                temp[k]=arr[i];
                k++;
                i++;
            }else{
                temp[k]=arr[j];
                k++;
                j++;
            }
        }

        while(i<=mid){
            temp[k++]=arr[i++];
        }
        while(j<=r){
            temp[k++]=arr[j++];
        }

        //复制回原来的数组,因为是递归,所以只要将合并后的数组存数其原来的那段位置
        //后面再根据递归,将有序的数组复原出来

        for(int m=l;m<=r;m++){          
            //注意,K++ 后没有满足要求,所以temp的最后一个数下标为 K-1 所以为 m<k 

            arr[m]=temp[m];
            System.out.print(arr[m]+" ");
        }

        System.out.println();
    }

}
class HeapSort{

    /*
     * 错误输出:1, 43, 5, 32, 12, 12, 23, 71, 98, 
     * 
     * 将maxheapnify 中左右比较的if if  换成 if...else 
     * 依旧错误:1, 5, 23, 12, 12, 32, 43, 71, 98, 
     * 
     * 
     * */

    //堆排序本质上是选择排序。每次选择当前无序区的最大放入有序区。步骤:
    //1,保持最大堆性质MaxHeapify 2,构建最大堆 BuildMaxHeap  3,排序HeapSort(依次取出根节点)
    protected int left(int i){
        return (2*i+1);
    }
    protected int right(int i){
        return(2*i+2);
    }
    //注意,堆越往下,下标 i 越大!!
    protected int parent(int i){
        return (i+1)/2-1;
    }

    //这里假设 i 的左右孩子都是最大堆(因为在buildheap的时候是在最大的i,即从下向上build的)
    public void MaxHeapnify(int[]arr,int index,int heapSize){
        int left = left(index);
        int right = right(index);
        //注意,堆是一个数组,表示堆的数组A 有两个属性:
        //length(数组元素的个数);heap-size 存在数组中的堆元素表个数(导论)
        //所以不应该用arr.length 来表示堆的大小
        //int len = arr.length;
        //largest 记录下标,后面再调整最大堆属性时也需要用到此下标(递归MaxHeapnify(arr, largest, heapSize);)
        int largest = index;

        if(left<=heapSize && arr[left]>arr[index]){
            largest=left;           
        }
            /*
             * ~~(>_<)~~ 这里出错了,应该是比较parent与left哪个大
             * parent变成max(parent ,left),再将parent与right比较
             * parent 变成max(parent ,left,right)
             * 
             * */
             //!!right<=heapSize 递归出口!! 当不满足时,递归结束、
        if(right<=heapSize && arr[right]>arr[largest]){
            largest=right;
        }
        if(largest!=index){
            //交换
            int temp = arr[largest];
            arr[largest]=arr[index];
            arr[index]=temp;
            //如果index位置没有交换,则说明index已经找到自己的位置。
            //假设中左右是最大堆,则index为根的树符合了最大堆要求。
            //否则需要继续对该index进程最大堆化,直到largest超出了堆的大小!!
            MaxHeapnify(arr, largest, heapSize);
        }
    }

    //构造最大堆。由于前面的假设,左右子树均为最大堆,则需要由下向上构造
    public void BuildMaxHeap(int [] arr,int heapSize){

        //for(int i=heapSize-1;i>=0;i--){  注意这里不是在heapSize
        //处开始的,因为叶节点没有左右节点!!!

        for(int i=parent(heapSize);i>=0;i--){
            MaxHeapnify(arr, i, heapSize);
        }
    }

    public void heapSort(int [] arr){
        int len = arr.length;
        int heapSize = len-1;

        BuildMaxHeap(arr, heapSize);
        for(int j =0 ;j<len;j++){
            System.out.print(arr[j]+", ");
        }
        System.out.println();
        //取出堆中的元素。
        for(int i=heapSize;i>=1;--i){   
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i]=temp;
            heapSize--;

            for(int j =0 ;j<len;j++){
                System.out.print(arr[j]+", ");
            }
            System.out.println("Step: ");

            MaxHeapnify(arr, 0, heapSize);      
        }
    }

}

关于堆排序:
堆可以视为一颗完全二叉树
假设一个数组{3,7,2,11,3,4,9,2,18,0}
与堆的关系:
这里写图片描述

构建最大堆的步骤:

  • 首先将堆中元素进行MAX-Heapnify 来构建最大堆
  • 将堆首元素依次取出,排序完成
    这里对元素MAX-Heapnify 时,是从第一个有左右子树的节点开始的。即上图中第4个节点开始,依次向上4,3,2,1,0节点进行MAX-Heapnify 。

图中从左向右:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值