O(N*logN)的排序算法

时间复杂度为:O(N*logN)的排序算法

注:图来自于网络

归并排序(Merge Sort)

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and
Conquer)的一个非常典型的应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);自下而上的迭代;

算法步骤

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; 重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。

归并

     /**
     * 归并排序是建立在归并操作上的一种有效的排序算法。
     * 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
     * 将已有序的子序列合并,得到完全有序的序列;
     * 即先使每个子序列有序,再使子序列段间有序。
     * 若将两个有序表合并成一个有序表,称为2-路归并
     * @param nums
     * @return
     */
    public  static int[] mergeSort(int [] nums){
        if(nums.length<2|| nums==null)return nums;
         process(nums,0,nums.length-1);
         return nums;
      }

    /**
     * 递归过程
     * @param arr
     * @param L
     * @param R
     */
    private  static void  process(int[] arr ,int L,int R){
        if(L==R){
            return;
        }
        int mid=L+((R-L)>>1); //求他们的中间值
        process(arr,L,mid);
        process(arr,mid+1,R);
        merge(arr,L,mid,R);
    }

    /**
     * merge过程
     * @param arr
     * @param l
     * @param m
     * @param r
     */
    private static void merge(int[] arr, int l, int m, int r) {
        int[] help=new int[r-l+1];
        int i=0;
        int p1=l;
        int p2=m+1;
        while (p1<=m&&p2<=r){
            help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
        }
        while (p1<=m){
            help[i++]=arr[p1++];
        }
        while (p2<=r){
            help[i++]=arr[p2++];
        }
        for (int j = 0; j < help.length; j++) {
           arr[l+j]=help[j];
        }
    }

递归的应用

问题描述:求在数组arr[L…R]上的最大值(核心算法)

 /**
     * 递归应用
     * 求在数组arr[L...R]上的最大值
     * 1>将[L ... R]分成左右两半。左[L ... Mid] 右[Mid+1 ... R]
     * 2>左部分求最大值,有部分求最大值
     * 3>[L ... R]范围上的最大值,是max(左部分求最大值,有部分求最大值)
     * 注意:2>是个递归过程,当范围上只有一个数,就可以不用再递归了
     * @param arr 该数组
     * @param L 起始位置
     * @param R  结束位置
     * @return
     */
    public  static int processMax(int[] arr ,int L,int R){
        if(L==R){
            return arr[L];
        }
        int mid=L+((R-L)>>1); //求他们的中间值
        int leftMax=processMax(arr,L,mid);
        int rightMax=processMax(arr,mid+1,R);
        return Math.max(leftMax,rightMax);
    }

快速排序(Quick Sort)

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。
在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn)
算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)
策略来把一个串行(list)分为两个子串行(sub-lists)
快速排序又是一种分而治之思想在排序算法上的典型应用。
本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

算法步骤

从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

快排

 /**
         * 快速排序
         * 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。
         * 在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn)
         * 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
         * 快速排序使用分治法(Divide and conquer)
         * 策略来把一个串行(list)分为两个子串行(sub-lists)
         * 快速排序又是一种分而治之思想在排序算法上的典型应用。
         * 本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法
         * @param nums
         * @return
         */
    public  static int[] quickSort(int [] nums){
        return  quickSort(nums, 0, nums.length - 1);
    }

    /**
     *
     * @param arr
     * @param left 起始位置
     * @param right 结束位置
     * @return
     */
    private static int[] quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int partitionIndex = partition(arr, left, right);
            quickSort(arr, left, partitionIndex - 1);
            quickSort(arr, partitionIndex + 1, right);
        }
        return arr;
    }

    private static int partition(int[] arr, int left, int right) {
        // 设定基准值(pivot)
        int pivot = left;
        int index = pivot + 1;
        for (int i = index; i <= right; i++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index++;
            }
        }
        swap(arr, pivot, index - 1);
        return index - 1;
    }

堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列; 堆排序的平均时间复杂度为 Ο(nlogn)。

算法步骤

创建一个堆 H[0……n-1];

把堆首(最大值)和堆尾互换;

把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;

重复步骤 2,直到堆的尺寸为 1。

堆

/**
     * 堆排序
     * 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
     * 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:
     * 即子结点的键值或索引总是小于(或者大于)它的父节点。
     * 堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
     * 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
     * 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
     * 堆排序的平均时间复杂度为 Ο(nlogn)。
     * @param nums
     * @return
     */
    public  static int[]  heapSort(int [] nums){
         if(nums==null||nums.length<2)return nums;
        for (int i = 0; i < nums.length; i++) { //o(n)
            heapInsert(nums,i); //o(log(n)))
        }
        int heapSize=nums.length;
        swap(nums,0,--heapSize);
        while (heapSize>0){
            heapIfy(nums,0,heapSize);
            swap(nums,0,--heapSize);
        }
        return nums;
    }
    /**
     * 把一个处于index位置上的数,往上继续移动
     * 典型:大根堆
     * @param arr
     * @param index
     */
    private static void heapInsert(int[] arr, int index) {
        /**
         * (index-1)/2:它父位置下标
         */
       while (arr[index]>arr[(index-1)/2]){
           swap(arr,index,(index-1)/2);
           index=(index-1)/2;
       }
    }
    /**
     *
     * @param arr
     * @param i 从那个位置开始往下
     * @param len 通过这个来约束左右孩子,防止越界
     */
    private static void heapIfy(int[] arr, int i, int len) {
        int left = 2 * i + 1; //左孩子下标
        int right = 2 * i + 2;//右孩子下标
        //写法一
//        int largest = i;
//        if (left < len && arr[left] > arr[largest]) {
//            largest = left;
//        }
//        if (right < len && arr[right] > arr[largest]) {
//            largest = right;
//        }
//        if (largest != i) {
//            swap(arr, i, largest);
//            heapIfy(arr, largest, len);
//        }
        //写法二
        while (left<len){  //下方还有孩子的时候
            //两孩子谁的值大,把下标给largest
            int largest=right<len&&arr[right]>arr[left]?right:left;
            //父和孩子谁的值大,把下标给largest
            largest=arr[largest]>arr[i]?largest:i;
            if(largest==i){
                break;
            }
            swap(arr,largest,i);
            i=largest;
            left=i*2+1;
        }
    }

swap

补充:上篇文章中,swap方法这块是存在一个陷阱的。
当i==j时,就行相当与一样的数在疑惑,异或的结果为0

 public  static void swap(int[] arr,int i,int j){
        /**
         * 陷阱:当i==j时,就行相当与一样的数在疑惑,异或的结果为0
         * eg:当i=2;j=2;
         *     i=i^j=2^2=0
         */
        if(i==j)return; //用于判断i和j的判断
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[i]^arr[j];
        arr[i]=arr[i]^arr[j];
    }

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值