• 左神算法第二节课:排序(快排、堆排、桶排、计数与基数排序简单介绍)、荷兰国旗问题、大根堆小根堆,排序稳定性,比较器,相邻两数的最大差值问题


    第二节课

    • 排序(快排、堆排、桶排、计数与基数排序简单介绍)
    • 荷兰国旗问题
    • 大根堆小根堆
    • 排序稳定性
    • 比较器
    • 相邻两数的最大差值问题

    1. 题目一:

    给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。

    要求:时间复杂度O(N)、额外空间复杂度O(1)

    该问题同荷兰国旗问题,见下:

     

    2. 题目二:

    荷兰国旗问题

    给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。

    要求时间复杂度为O(N)、额外空间复杂度为O(1)。

    分析:三个指针法:一个指向前头less,一个指向尾部more,一个是当前下标cur。当前下标由指向前面的指针推着前进。

    代码如下:

        public static int[] partition(int[] arr, int L, int R, int num) {
            int less = L-1;   //小于
            int more = R+1;   //大于
            int cur = L;      //等于
            while (cur<more) {
                if (arr[cur]<num) {
                    swap(arr,++less,cur++);
                }else if (arr[cur]>num) {
                    swap(arr,--more,cur);
                }else {
                    cur++;
                }
            }
            
            return arr;
        }
        
        private static void swap(int[] arr, int i, int j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }

    3. 快排

    根据荷兰国旗问题,变形,让num=arr[arr.length-1],相当于R= arr.length-2;

      

    经典快排缺点:如[1,2,3,4,5,6,7…N],快排就成了O(N2);

    改进:随机快排:即是选择num = arr[]中随机的元素。时间复杂度O(N*logN);额外空间复杂度O(logN);【最常用的】

    非随机代码如下:

        public static void quickSort(int[] arr, int L, int R) {
            if (L<R) {
                int[] p = partition(arr, L, R);
                quickSort(arr, L, p[0]-1);
                quickSort(arr, p[1], R);
            }
        }
    
        public static int[] partition(int[] arr, int L, int R) {
            int less = L-1;
            int more = R;
            int cur = L;
            while (cur<more) {
                if (arr[cur]<arr[R]) {
                    swap(arr,++less,cur++);
                }else if (arr[cur]>arr[R]) {
                    swap(arr,--more,cur);
                }else {
                    cur++;
                }
            }
            swap(arr, more, R);
            return new int[] {less+1, more};
        }
        
        private static void swap(int[] arr, int i, int j) {
            // TODO Auto-generated method stub
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;        
        }

    随机快排代码如下:

        public static void quickSort(int[] arr) {
            if (arr == null || arr.length < 2) {
                return;
            }
            quickSort(arr, 0, arr.length - 1);
        }
    
        public static void quickSort(int[] arr, int l, int r) {
            if (l < r) {
                swap(arr, l + (int) (Math.random() * (r - l + 1)), r);   //随机选择一个数作为比较对象
                int[] p = partition(arr, l, r);
                quickSort(arr, l, p[0] - 1);
                quickSort(arr, p[1] + 1, r);
            }
        }
    
        public static int[] partition(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, more, r);
            return new int[] { less + 1, more };
        }
    
        public static void swap(int[] arr, int i, int j) {
            int tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }

    4. 堆排序

    时间复杂度O(N*logN),额外空间复杂度O(1)

    【堆结构非常重要】

    1. 堆结构的heapInsert和heapify;
    2. 堆结构的增大和减少;
    3. 如果只是建立堆的过程,时间复杂度为O(N);
    4. 优先级队列结构就是堆结构。

    堆: 完全二叉树。每一层从左到右依次补齐,满二叉树属于完全二叉树。

    当一个数组满足左:2*i+1,右:2*i+2,父:(i-1)/2就是完全二叉树结构。

    大根堆:任何子树的最大值都是子树的根节点,

    小根堆:任何子树的最小值都是子树的根节点。

    用处:比如实时求所输入数据的中位数。

    大根堆的建立:

    有任何一个子树变成大根堆。复杂度=log1+ log2+… +logN=O(N)

    public class HeapSort {
    
        public static void heapSort(int[] arr) {
            if (arr.length<2 || arr==null) {
                return;
            }
            for (int i = 0; i < arr.length; i++) {
                heapInsert(arr,i);//建立大根堆;
            }
            int heapSize = arr.length;
            swap(arr, 0, --heapSize);
            while (heapSize>0) {
                heapify(arr,0,heapSize);//调整大根堆
                swap(arr, 0, --heapSize);//将大根堆的根和最后一个元素交换,然后size缩小一个;
            }
        }
        
        private static void heapify(int[] arr, int index, int size) {
            //找到left
            int left = 2*index+1;
            //进行循环
            while (left<size) {
                //确定left和right中较大的位置
                int largest = left+1<size && arr[left+1]<arr[left] ? left : left+1;
                //确定孩子和父节点中较大的位置
                largest = largest>arr[index] ? largest : index;
                //如果最大位置和父节点位置相同,则跳出循环
                if (largest == index) {
                    break;
                }
                //否则,交换最大值和父节点的值,将变量更新
                swap(arr, index, largest);
                index = largest;
                left = 2*index+1;
            }
        }
    
        private static void heapInsert(int[] arr, int index) {//如果插入节点值要比父节点值大,则交换,并且比较下一轮。
            while (arr[index]>arr[(index-1)/2]) {
                swap(arr,index,(index-1)/2);
                index = (index-1)/2;
            }
        }
    
        private static void swap(int[] arr, int index, int i) {
            int temp = arr[i];
            arr[i] = arr[index];
            arr[index] = temp;
        }
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
        }
    }

    5. 题目三

     

    从一个流中不断生成数,求吐出的数的中位数。

    同牛客网剑指OfferDay63

     

    6. 排序的稳定性:

    稳定性:排序外,相同元素保持出现的先后顺序。

    复杂度是O(N2)

    • l  冒泡排序:当遇到相同数时,该数不交换,将后面的数往下沉。可以稳定;
    • l  插入排序:当遇到相同数时,该数不交换;可以稳定;
    • l  选择排序:做不到稳定性。因为你要从后面的所有数中找到最小的,然后将前面的某一个a与该最值交换,如果有多个a存在,那么,a的先后顺序将无法保证。故做不到。

    复杂度是O(N*logN)

    • l  归并排序:merge时,当相同时先拷贝左边(小区域)的数;可以稳定
    • l  快排:做不到稳定性;
    • l  堆排:做不到稳定性。在建大根堆的时候,就都已经不能保证稳定性了。

    工程中的排序:

    • l  基础类型:快排
    • l  自定义类型:归并排序(稳定性)
    • l  如果数组长度较短:不管什么类型,都用插排(时间复杂度O(N^2)劣势显示不出来,反而额外空间复杂度O(1)较快)。

    7. 比较器:

    • 负数,第一个参数放前面(o1)
    • 正数,第二个参数放前面(o2)
    • 0,一样大

    在笔试时候,如果不是考察排序,就直接调用系统的Arrays.sort(arr)即可。或者加上自定义的比较器Arrays.sort(arr,new myComparator());

    • l  堆中的比较器

        优先级队列实质上就是堆 ,分为大根堆,小根堆。

        必须指定排序依据。

    • TreeMap<T>红黑树结构

    以下为非比较的排序(基于桶)

    1. 非基于比较的排序,与被排序的样本的实际数据状况很有关系,所以实际中并不经常使用;
    2. 时间复杂度O(N),额外空间复杂度O(N);
    3. 稳定的排序。

    8. 桶排序:

    词频,桶就是容器,可以是队列,可以是堆等,一个萝卜一个坑,按照数据状况分到每个桶。

    题目四

    给定一个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。

    思路:借用桶的概念,但没有进行桶排序。
     先找到最大最小值,若max == min ,则数组中数据全相同,差值最大为0;
    若max != min ,N个数,设定N+1个桶,每个桶范围是(max-min)/(N+1)
    利用鸽笼问题,总有一个桶是空的,可以排除同一桶内的两个数的差值不是最大的。
    记录每个桶内的最大,最小,以及是否进来过数。
    给定某值i,以及数组最大最小值max,min和数组长度len,确定该值所在的桶的标号
    bid = (int)(i-min)*len/(max-min),

        public static int maxGap(int[] nums) {
            if (nums == null || nums.length < 2) {
                return 0;
            }
            int len = nums.length;
            int min = Integer.MAX_VALUE;
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < len; i++) {
                min = Math.min(min, nums[i]);
                max = Math.max(max, nums[i]);
            }
            if (min == max) {
                return 0;
            }
            boolean[] hasNum = new boolean[len + 1];
            int[] maxs = new int[len + 1];
            int[] mins = new int[len + 1];
            int bid = 0;
            for (int i = 0; i < len; i++) {
                bid = bucket(nums[i], len, min, max);
                mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i];
                maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i];
                hasNum[bid] = true;
            }
            int res = 0;
            int lastMax = maxs[0];
            int i = 1;
            for (; i <= len; i++) {
                if (hasNum[i]) {
                    res = Math.max(res, mins[i] - lastMax);
                    lastMax = maxs[i];
                }
            }
            return res;
        }
    
        public static int bucket(long num, long len, long min, long max) {
            return (int) ((num - min) * len / (max - min));
        }

     另附:

    1. Java数据结构和算法(九)——高级排序,里面有快排的详细步骤,很好的一篇文章!

     

  • 相关阅读:
    VSTO安装部署(完美解决XP+2007)
    尝试Office 2003 VSTO的开发、部署
    数据容量大小
    Nginx 学习
    Windows环境下使用Nginx搭建负载均衡
    HTML块元素与内联元素嵌套规则
    js中try、catch、finally的执行规则
    总结下var、let 和 const 的区别
    解决window.onload延迟加载问题
    移动端meta设置大全
  • 原文地址:https://www.cnblogs.com/gjmhome/p/11480113.html
Copyright © 2020-2023  润新知