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

第二节课

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

 

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数据结构和算法(九)——高级排序,里面有快排的详细步骤,很好的一篇文章!

 

 

 

posted @ 2019-09-07 14:14  额是无名小卒儿  阅读(663)  评论(0编辑  收藏  举报