一个快速排序,一个堆排序,一个归并排序,全部被张三防出去了啊

Posted 码外狂徒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个快速排序,一个堆排序,一个归并排序,全部被张三防出去了啊相关的知识,希望对你有一定的参考价值。

在经典的八大排序算法中,由于快速排序、堆排序和归并排序的时间复杂度可以达到O(nlogn),且复杂性较高,所以是面试中比较常问的点,今天我们一起来复习一下这三种排序算法。

说明:本文中所有的排序都是按升序进行排序

归并排序

基本思路:借助额外空间,将两个较短数组合并为一个更长的有序数组,整个过程递归执行,直到得到数组的长度等于原数组长度

算法思想:分治思想

算法过程:

  • 首先对数组进行等长划分,整个过程递归执行,直到子数组长度全部为1

  • 对两个子数组进行归并,由于子数组自身有序,因此两个子数组指针从头开始往后移动,将两个子数组指针指向值的更小值放入排序数组,同时该指针向后移动,直到两个指针都达到数组尾部,将得到的排序数组作为返回值返回,作为上一层递归的子数组。

  • 递归执行第二步,直到整个数组有序


归并排序的思想还是比较直观的,归并排序本身还分为自顶向下和自底向上,我们刚刚说的这种是自顶向下的思想。而自底向上的思路也是比较直观的,会在后面给出的代码中给出详细的注释,这里就不再赘述啦,思想上都是差不多的

public class Solution {
   // 自顶向下的归并排序
   public int[] sortArray(int[] nums) {
       int len = nums.length;

       //temp是辅助数组,用于存放两个子数组中的值
       int[] temp = new int[len];
       mergeSort(nums, 0, len - 1, temp);
       return nums;
  }


   private void mergeSort(int[] nums, int left, int right, int[] temp) {
       //此时子数组长度为一,划分子数组过程结束,
       //返回进入后续归并过程
       if(right==left)
           return ;

       //将原数组划分为两个等长子数组
       int mid = left + (right - left) / 2;

       //对左侧进行归并排序
       mergeSort(nums, left, mid, temp);
       //对右侧进行归并排序
       mergeSort(nums, mid + 1, right, temp);

       // 左侧区间最后一个元素下标为mid
       // 右侧区间第一个元素下标为mid+1
       // 左右数组都有序
       // 因此nums[mid] <= nums[mid + 1]
       // 说明left到right区间内完全有序
       // 直接返回无需再次排序
       if (nums[mid] <= nums[mid + 1]) {
           return;
      }

       //对两个子数组进行排序
       mergeOfTwoSortedArray(nums, left, mid, right, temp);
  }


   private void mergeOfTwoSortedArray(int[] nums, int left, int mid, int right, int[] temp) {

       //将元素拷贝到temp中,
       System.arraycopy(nums, left, temp, left, right + 1 - left);

       // i为左侧区间指针
       int i = left;
       // j为右侧区间指针
       int j = mid + 1;

       for (int k = left; k <= right; k++) {

           
           if (i == mid + 1) {
               //左侧区间到头了
               // 直接取右侧区间元素
               nums[k] = temp[j];
               j++;
          } else if (j == right + 1) {
               // 右侧区间到头了
               // 直接去左侧区间元素
               nums[k] = temp[i];
               i++;
          } else if (temp[i] <= temp[j]) {
               // 注意写成 < 就丢失了稳定性(相同元素原来靠前的排序以后依然靠前)
               nums[k] = temp[i];
               i++;
          } else {
               // 这部分的条件等价于temp[i] > temp[j]
               nums[k] = temp[j];
               j++;
          }
      }
  }
}

时间复杂度O(nlogn)

空间复杂度O(n)

稳定的排序算法


自底向上的归并排序代码

class Solution {
   //自底向上的归并排序
   public int[] sortArray(int[] nums) {
       //temp为辅助数组
       int[] temp = new int[nums.length];
       mergeSort(nums,0,nums.length-1,temp);
       return nums;
  }

   //进行自底向上的子数组划分
   public void mergeSort(int[] nums,int left ,int right , int[] temp){
       int length = nums.length;

       // i为当前一步的子数组长度
       // 初始为1,每次翻倍
       for( int i = 1 ; i < length ;i = i*2 ){
           // j为划分子数组的左指针,
           // 每次对两个数组进行归并
           // 每次自增2*i
           for( int j = 0 ; j< length - i ; j = j + i + i ){

               //对子数组进行归并
               //j为当前子数组左侧下标
               //j+i-1是中间指针
               //为了确保右指针不越界
               //Math.min(length-1, j+i+i-1)表示右指针
               merge(nums,j,j+i-1 , Math.min(length-1, j+i+i-1) , temp);
          }
      }
  }

   /**
    *
    * @param nums 排序后的数组
    * @param left 最左侧下标
    * @param mid 中间指针
    * @param right最右侧下标
    * @param temp 辅助数组
    */
   public void merge(int[] nums ,int left ,int mid ,int right , int[] temp){
       for( int i = left ; i <=right ; i ++){
           temp[i] = nums[i];
      }
       int i = left;
       int j = mid+1;
       for( int k = left ; k<=right ; k ++){
           if( i == mid+1){
               nums[k] = temp[j++];
          }else if(j==right+1)
               nums[k] = temp[i++];
           else if ( temp[i]<= temp[j] )
               nums[k] = temp[i++];
           else
               nums[k] = temp[j++];
      }

  }
   
}

时间复杂度O(nlogn)

空间复杂度O(n)


快速排序

基本思路:找到数组中的一个值,然后确定数组中比该值更小的值有多少个,然后把比该值更小的值放在该值左侧,比该值更大的值放在右侧,然后对左右两侧重复进行上述过程

算法思想:分治思想,对已确定位置的元素左右侧进行分治

class Solution {
   //快速排序算法
   public int[] sortArray(int[] nums) {
       quickSort(nums,0,nums.length-1);
       return nums;
  }

   public void quickSort(int[] nums ,int left ,int right ){
       
       //确保当前子数组长度至少为2
       if( left < right){
           //进行一步随机划分
           int i = randomizedPartition(nums,left,right);
           //对左侧进行快排
           quickSort(nums ,  left , i-1);
           //对右侧进行快排
           quickSort( nums , i +1 ,right);
      }
  }

   public int randomizedPartition(int[] nums, int left ,int right){
       
       //通过随机函数找到一个随机下标对子数组进行划分
       int i = new Random().nextInt(right - left + 1 ) + left;
       //将这个随机下标放到最右侧
       swap(nums ,right,i );
       return partition(nums,left,right);
  }
   public int partition(int[] nums ,int left ,int right){
       //将最右侧的值设为pivot
       int pivot = nums[right];
       int i = left-1;
       for( int j = left ; j <right ; j ++){
           //比pivot小的值则靠左进行放置
           if(nums[j] <= pivot){
               i++;
               swap(nums,i,j);
          }
      }
       
       //把pivot放到正确的下标i+1上
       swap(nums,i+1,right);
       //返回pivot正确下标
       return i+1;
  }

   public void swap(int[] nums ,int i ,int j ){
       int temp = nums[i];
       nums[i] = nums[j];
       nums[j] = temp;
  }
}

平均时间复杂度O(nlogn) ,最差时间复杂度O(n*n),最差时间复杂的情况会出现在数组原本就是降序排序,我们需要得到一个升序数组的时候,我们每次都要把最右侧的值放回靠左的位置,一共会进行n趟,每次遍历n个元素。为了减少这种情况的发生,我们加入了一个随机函数,可以尽量避免这种最差情况的发生。

空间复杂度O(nlogn)



堆排序

这里简单介绍一下堆排序的思想,首先将数组模拟成一个二叉堆,我们第一次遍历的时候从堆底开始,将更大的元素逐层向上交换,最后将最大元素放在堆顶,然后我们把对顶元素与最下一层最右侧的元素进行交换,并将最大元素从堆中删除。然后由于第一次遍历的时候,相对较大的元素都在相对靠上的位置,我们将堆顶元素向下沉,结束之后,把堆顶元素与最后一个元素进行交换,并把最大元素删除,重复以上过程直到堆为空即排序结束

public class Solution {

public int[] sortArray(int[] nums) {
   int len = nums.length;
   // 将数组整理成堆
   heapify(nums);

   // 循环不变量:区间 [0, i] 堆有序
   for (int i = len - 1; i >= 1; ) {
       // 把堆顶元素(当前最大)交换到数组末尾
       swap(nums, 0, i);
       // 逐步减少堆有序的部分
       i--;
       // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序
       siftDown(nums, 0, i);
  }
   return nums;
}


//将数组整理成堆(堆有序)
private void heapify(int[] nums) {
   int len = nums.length;
   // 只需要从 i = (len - 1) / 2 这个位置开始逐层下移
   for (int i = (len - 1) / 2; i >= 0; i--) {
       siftDown(nums, i, len - 1);
  }
}

/**
* @param nums
* @param k   当前下沉元素的下标
* @param end [0, end] 是 nums 的有效部分
*/
private void siftDown(int[] nums, int k, int end) {
   while (2 * k + 1 <= end) {
       int j = 2 * k + 1;
       if (j + 1 <= end && nums[j + 1] > nums[j]) {
           j++;
      }
       if (nums[j] > nums[k]) {
           swap(nums, j, k);
      } else {
           break;
      }
       k = j;
  }
}

private void swap(int[] nums, int index1, int index2) {
   int temp = nums[index1];
   nums[index1] = nums[index2];
   nums[index2] = temp;
}

时间复杂度O(nlogn)

空间复杂度O(1)


以上是关于一个快速排序,一个堆排序,一个归并排序,全部被张三防出去了啊的主要内容,如果未能解决你的问题,请参考以下文章

Python八大算法的实现,插入排序希尔排序冒泡排序快速排序直接选择排序堆排序归并排序基数排序。

直接插入排序 ,折半插入排序 ,简单选择排序, 希尔排序 ,冒泡排序 ,快速排序 ,堆排序 ,归并排序的图示以及代码,十分清楚

堆排序;快速排序;归并排序

所有排序算法汇总,冒泡,选择,插入,快速,优化快速,归并,希尔,堆排序

快速排序(QuickSort),归并排序(MergeSort),堆排序(HeapSort)典型C++代码实现总结

经典排序算法和python详解:归并排序快速排序堆排序计数排序桶排序和基数排序