zoukankan      html  css  js  c++  java
  • leetcode 215. 数组中的第K个最大元素(常用排序算法的回顾)

    题目:

      在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

    题解:

      当看到TOP K问题时,脑海中应立马想到堆排序。以这个题目来回顾下排序算法。在回顾排序算法前,我们来回顾下几个术语:时间复杂度、空间复杂度、稳定性。稳定性的定义我们容易忘记,下面来回顾下它的定义:

        前提条件:a=b的情况下,若在未排序时a在b前面,在已排序后a仍在b前面,那么这个算法就是稳定的;否则就是不稳定的。

    1:冒泡排序:两两比较,交换小的数和大的数的位置。

        时间复杂度:最好O(N),最坏O(N²),平均O(N²) ;空间复杂度:O(1);是否稳定:稳定

     public void bubbleSort(int[] nums){
            for(int i=0;i<=nums.length-2;i++){
                for(int j=i+1;j<=nums.length-1;j++){
                    if(nums[j] < nums[i]){
                        int tmp = nums[i];
                        nums[i] = nums[j];
                        nums[j] = tmp;
                    }
                }
            }
        }

      若数组是有序的,可以用flag标志位来判断是否排序过。

     //O(N) 若元素已有序,那么借助flag判断是否有序
        public void bubbleSort2(int[] nums){
            for(int i=0,flag = 0;i<=nums.length-2;i++){
                for(int j=i+1;j<=nums.length-1;j++){
                    if(nums[j] < nums[i]){
                        int tmp = nums[i];
                        nums[i] = nums[j];
                        nums[j] = tmp;
    
                        flag = 1;//表示已排序过,原数组不是有序的
                    }
                }
                if(flag == 0) break;
            }
        }

     2.选择排序:从前往后一次找到未排序过元素的最小值,然后依次填入第0个位置、第1个位置、第2个位置。。。

      时间复杂度:最好O(N²),最坏O(N²),平均O(N²) ;空间复杂度:O(1);是否稳定:不稳定

        public static void selectSort(int[] nums){
            //首先选择最小的数字放在第0个位置
            //再选择剩余数字最小的数字放在第1个位置
            //...
            for(int i=0;i<=nums.length-1;i++){
                int idx = i;//当前最小数的下标
                int min = nums[idx];
                //找到最小的数填充第i个位置
                for(int j=i+1;j<=nums.length-1;j++){
                    if(nums[j] < min){
                        min = nums[j];
                        idx = j;
                    }
                }
                //若找到了更小的数,则交换位置
                if(idx > i){
                    int tmp = nums[i];
                    nums[i] = nums[idx];
                    nums[idx] = tmp;
                }
            }
        }

    3.快速排序:首先选择一个基准数x,它的位置(坑位)是i,然后先从后向前找比x小的数(所在位置是j)填这个基准数所在的坑位i,然后从左向右找比x大的数填这个新的坑位j。交替进行。

      时间复杂度:最坏(数组已排序)O(N²),最好O(nlogN),平均O(nlogN) ; 空间复杂度 O(1);是否稳定:不稳定

      

        //1.快速排序(分治法)
        public static void quickSort(int l,int r,int[] nums){
           if(l < r){
               int x = nums[l];
               int i = l, j = r;
               while(i < j){
                   //从后向前找比x小的数
                   while(i < j && nums[j] >= x) j--;
                   if(i < j){
                       nums[i] = nums[j];
                       i++;
                   }
                   //从前向后找比x大的数
                   while(i < j && nums[i] < x){
                       i++;
                   }
                   if(i < j){
                       nums[j] = nums[i];
                       j--;
                   }
               }
    
               //最后i=j
               nums[i] = x;
               //分治法
               quickSort(l,i-1,nums);
               quickSort(i+1,r,nums);
           }
        }

    4.堆排序:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。它有非常重要的三步:

      1.建堆

      2.调整堆,使得堆符合大(小)顶堆的性质

      3.删除已排序的数,即数组新的最后一个元素已经排好序了,下次调整堆不要调整这个数了。当把堆顶元素与最后一个元素交换后,大的元素都在数组后面。交换后堆顶元素发生了变化,需要重新调整堆。

      时间复杂度:最坏O(NlogN),最好O(NlogN),平均O(NlogN);空间复杂度:O(1)。建堆的时间复杂度是O(N),调整堆的时间复杂度是O(logN);是否稳定:不稳定

    下面上代码(也是本题的解法):

      

    class Solution {
        public int findKthLargest(int[] nums, int k) {
            heapSort(nums);
            //quickSort(nums,0,nums.length - 1);
            //bubble(nums);
            //selectSort(nums);
            
            //for(int p: nums) System.out.println(p);
            return nums[nums.length-k];
        }
        //冒泡算法
        public void bubble(int[] nums){
            for(int i=0;i<=nums.length-2;i++){
                for(int j=i+1;j<=nums.length-1;j++){
                    if(nums[j] < nums[i]){
                        int tmp = nums[i];
                        nums[i] = nums[j];
                        nums[j] = tmp;
                    }
                }
            }
        }
    
        //选择排序(冒泡排序的改版)
        public void selectSort(int[] nums){
            for(int i=0;i<nums.length;i++){
                int min = nums[i];
                int idx = i;
                for(int j=i+1;j<nums.length;j++){
                    if(nums[j] < min){
                        min = nums[j];
                        idx = j;
                    }
                }
                int tmp = nums[i];
                nums[i] = nums[idx];
                nums[idx] = tmp;
            }
        }
    
        //快速排序
        public void quickSort(int[] nums, int l, int r){
           if(l < r){
               int x = nums[l];
               int i= l, j = r;//l r 是分治法的左右边界
               while(i < j){
                   //从右到左找比x小的数
                   while(i < j && nums[j] >= x) j--;
                   if(i < j){
                       nums[i] = nums[j];
                       i++;
                   }
                   //从左到右找比x大的数
                   while(i < j && nums[i] < x) i++;
                   if(i < j){
                       nums[j] = nums[i];
                       j--;
                   }
    
                   nums[i] = x;
                   quickSort(nums,l,i-1);
                   quickSort(nums,i+1,r);
               }
           }
        }
    
        //堆排序
        public void heapSort(int[] nums){
            buildHeap(nums);
            //将大堆顶元素与末尾元素交换,堆顶的元素就放在数组后面了,然后再维护剩下的堆
            for(int i=nums.length-1;i>=0;i--){
                swap(nums,i,0);
                //交换后需要重新维护大顶堆的性质
                adjustHeap(0,nums,i);//最后一个元素已经排好序了,不需要再调整了,即需要调整的元素的个数为i,相当于删除了最后一个元素
            }
        }
    
        //建堆
        public void buildHeap(int[] nums){
            for(int i=nums.length/2 -1;i>=0;i--){
                //维护堆的位置
                adjustHeap(i,nums,nums.length);
            }
        }
    
        //i 根节点 
        public void adjustHeap(int i,int[] nums, int len){
            int l = 2 * i + 1, r = 2 * i +2;
            int largestIdx = i;//默认根节点是最大的节点
            // l < len 防止越界
            if(l < len && nums[largestIdx] < nums[l]){
                largestIdx = l;
            }
            if(r < len && nums[largestIdx] < nums[r]){
                largestIdx = r;
            }
            if(largestIdx != i){
                //交换位置
                swap(nums,i,largestIdx);
                //交换后需要重新维护大顶堆的性质
                adjustHeap(largestIdx,nums,len);
            }
        }
    
        public void swap(int[] nums,int i,int j){
            int tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
    }
  • 相关阅读:
    专题——递归
    今日听郝斌老师鸡汤
    线性结构的两种常见应用之一 队列
    线性结构的两种应用之一——栈
    C++常用库函数
    洛谷 标志重捕法?
    c++复习——临考前的女娲补天 >=.<
    c++复习——类(2)
    c++复习——类(1)
    Python单元测试
  • 原文地址:https://www.cnblogs.com/bobobjh/p/14440339.html
Copyright © 2011-2022 走看看