常用经典排序算法总结Java实现

简介

这是一份常用经典排序算法的Java代码实现,可运行。

算法包括:冒泡排序、插入排序、希尔排序、快速排序、选择排序、归并排序、堆排序。

复杂度

算法复杂度,稳定性:
《常用经典排序算法总结Java实现》
忘记说希尔排序了~

希尔排序是不稳定的。因为,虽然一次插入排序是稳定的,但是分区后的插入可能导致不同分区之间的数相对位置发生改变,因此最终是不稳定的。

希尔排序的时间复杂度没有明确的说法,有的说平均复杂度是O(nlogn),有的说是O(n^1.3),总之就是比O(n^2)小啦。

希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些

代码

话不多说,上代码:

package test1;

public class Test {
    /** * 交换数组中两个数 * @param nums * @param i * @param j */
    public static void swap(int nums[],int i,int j){
        int temp;
        temp = nums[i];
        nums[i] = nums[j];
        nums[j]=temp;
    }
    /** * 冒泡排序 * @param nums */
    public static void bubbleSort(int nums[]){
        for(int i = 0;i<nums.length;i++){
            for(int j = 0;j<nums.length-i-1;j++){
                if(nums[j]>nums[j+1]){
                    swap(nums,j,j+1);
                }
            }
        }
    }
    /** * 改进的冒泡排序 * @param nums */
    public static void improvedBubbleSort(int nums[]){
        for(int i = 0;i<nums.length;i++){
            boolean isChanged = false;//标记一轮中,是否发生交换
            for(int j = 0;j<nums.length-i-1;j++){
                if(nums[j]>nums[j+1]){
                    swap(nums,j,j+1);
                    isChanged = true;
                }
            }
            if(!isChanged){//如果没有发生交换,则已经有序,直接结束排序
                break;
            }
        }
    }
    /** * 插入排序 * @param nums */
    public static void insertSort(int nums[]){
        int len = nums.length;
        for(int i = 1;i<len;i++){
            if(nums[i]<nums[i-1]){//如果有逆序,则向前找到一个不大于该值的位置,插到它后面
                int temp = nums[i];//保存当前值
                int j;
                //向前寻找
                for(j = i-1;j>=0&&nums[j]>temp;j--){
                    nums[j+1] = nums[j];
                }
                nums[j+1] = temp;//赋值
            }
        }
    }
    /** * 引入二分查找的插入排序 * @param nums */
    public static void insertBinarySort(int nums[]){
        int len = nums.length;
        for(int i = 1;i<len;i++){
            if(nums[i]<nums[i-1]){//如果有逆序,则向前找到一个不大于该值的位置,插到它后面
                int temp = nums[i];//记录当前值
                int low = 0,h = i-1,mid=0;//二分查找的low,high,mid位置标记
                while(low<=h){
                    mid = (low+h)/2;
                    if(temp>nums[mid]){//如果当前值大于中间位置的值,则区间改为[mid+1,h]
                        low = mid+1;
                    }else{//如果当前值小于中间位置的值,则区间改为[low,mid-1]
                        h = mid-1;
                    }
                }
                //找到位置(就是low)后,从后向前移动数组,以腾出位置
                for(int j = i;j>low;j--){
                    nums[j] = nums[j-1];
                }
                nums[low] = temp;//赋值
            }
        }
    }
    /** * 希尔排序 * @param nums */
    public static void shellSort(int nums[]){
        int len = nums.length;
        for(int gap = len/2;gap>0;gap/=2){//gap每次减半
            for(int i = 0;i<len;i+=gap){//比较的时候,每隔gap个数比较一次
                int temp = nums[i];//记录当前值
                int j;
                //如果当前值小于前面对应的gap位置的数,则把前面的数赋值到当前位置
                //j-=gap,将索引又移到前面gap处,以便于交换
                for(j = i;j>=gap&&temp<nums[j-gap];j-=gap){
                    nums[j] = nums[j-gap];
                }
                //再把当前值赋值到前面的位置,本质就是交换了当前值和前面对应gap位置的数
                nums[j] = temp;
            }
        }
    }
    /** * 选择排序 * @param nums */
    public static void selectSort(int nums[]){
        int len = nums.length;
        for(int i = 0;i<len-1;i++){
            int min = i;//记录最小值的位置
            for(int j = i+1;j<len;j++){
                if(nums[j]<nums[min]){//如果有更小的,则更新最小值的位置
                    min = j;
                }
            }
            //如果最小值位置改变了(不是i了),则交换,就是将最小值从后面换到前面
            if(min!=i){
                swap(nums,i,min);
            }
        }
    }
    /** * 分而治之,找到划分基准,并交换 * @param nums * @param left * @param r * @return */
    public static int partation(int nums[],int left,int r){
        int pv = nums[left];//以最左边的数为基准
        int p = left;//记录基准位置
        //遍历数组,如果小于基准,则与基准交换
        for(int i = p+1;i<=r;i++){
            if(nums[i]<pv){
                p++;//基准前移
                if(p!=i){//如果是基准位置的下一个位置,则不用交换
                    swap(nums,p,i);
                }
            }
        }
        //最后,将新的基准的值赋值给最左边
        nums[left] = nums[p];
        nums[p] = pv;//将基准赋值到中间,划分位置
        return p;//返回划分位置
    }
    /** * 快速排序 * @param nums * @param left * @param r */
    public static void quickSort(int nums[],int left,int r){
        if(left<r){//切记,一定要左边小于右边时才分部处理
            int p = partation(nums,left,r);//找到一个基准位置p
            quickSort(nums,0,p-1);//以p为中心,将数组分成两个子数组,分而治之
            quickSort(nums,p+1,r);
        }
    }
    /** * 快速排序 * @param nums */
    public static void quickSort(int nums[]){
        int len = nums.length;
        quickSort(nums,0,len-1);
    }

    /** * 合并 * @param nums * @param temp * @param left * @param mid * @param r */
    public static void merge(int nums[],int temp[],int left,int mid,int r){
        //拷贝一份数组
        for(int i = left;i<=r;i++){
            temp[i] = nums[i];
        }
        //记录左右部分的索引
        int pa = left,pb = mid+1;
        int index = left;//合并后数组的索引

        while(pa<=mid&&pb<=r){
            if(temp[pa]<=temp[pb]){//哪一部分小,就拷贝哪一部分到新数组
                nums[index++] = temp[pa++];
            }else{
                nums[index++] = temp[pb++];
            }
        }
        //处理没有拷贝完的部分,直接赋值到新数组
        while(pa<=mid){
            nums[index++] = temp[pa++];
        }
        while(pb<=r){
            nums[index++] = temp[pb++];
        }


    }
    /** * 归并排序 * @param nums * @param temp * @param left * @param r */
    public static void mergeSort(int nums[],int temp[],int left,int r){
        if(left<r){//左边小于右边时,分而治之
            int mid = (left+r)/2;
            mergeSort(nums,temp,0,mid);
            mergeSort(nums,temp,mid+1,r);
            //合并
            merge(nums,temp,left,mid,r);
        }
    }
    /** * 归并排序 * @param nums */
    public static void mergeSort(int nums[]){
        int len = nums.length;
        int temp[] = new int[len];
        mergeSort(nums,temp,0,len-1);
    }
    /** * 调整堆 * @param nums * @param i * @param len */
    public static void adjustHeap(int nums[],int i,int len){
        int temp = nums[i];//记录当前节点值(父节点)
        //向下遍历子节点
        for(int k = 2*i+1;k<len;k = k*2+1){
            //如果存在右子节点,且右子节点大于左子节点
            if(k+1<len&&nums[k]<nums[k+1]){
                k++;//指向右子节点
            }
            //如果子节点比当前节点(父节点)大,则将子节点值赋给父节点
            if(nums[k]>temp){
                nums[i] = nums[k];
                i = k;//记录父节点的位置,最终要落到的位置
            }else{//如果子节点小于父节点,则调整结束
                break;
            }
        }
        //将当前父节点的值,最终落位入座
        nums[i] = temp;
    }
    /** * 堆排序,升序,最大堆 * @param nums */
    public static void heapSort(int nums[]){
        int len = nums.length;
        // build heap
        for(int i = len/2+1;i>=0;i--){
            //从最后一个非叶节点开始调整,使之满足最大堆
            adjustHeap(nums,i,len);
        }
        // exchange and adjust
        for(int j = len-1;j>=0;j--){
            //交换根节点与最后一个节点
            swap(nums,0,j);
            //调整剩下的节点,使它们满足最大堆
            adjustHeap(nums,0,j);//j是当做长度传过去的,所以,去掉了最后一个节点
        }

    }

    public static void main(String[] args) {

        int nums[] = {2,4,5,7,2,3,4};
        System.out.println("-----排序前-----");
        for(int n:nums){
            System.out.println(n);
        }
// bubbleSort(nums);//冒泡排序
// improvedBubbleSort(nums);//改进的冒泡排序
// insertSort(nums);//插入排序
// insertBinarySort(nums);//二分插入排序
// shellSort(nums);//希尔排序
// selectSort(nums);//选择排序
// quickSort(nums);//快速排序
// mergeSort(nums);//归并排序
        heapSort(nums);//堆排序

        System.out.println("------排序后-----");
        for(int n:nums){
            System.out.println(n);
        }

 }

}

补充:

快速排序的思想:分治法
– 1.先从数组中取出一个数作为基准数。
– 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
– 3.再对左右区间重复第二步,直到各区间只有一个数。

参考文章
堆排序讲解 https://www.cnblogs.com/chengxiao/p/6129630.html
各算法的性能及复杂度讲解 https://segmentfault.com/a/1190000004994003

点赞