常见排序算法刷题总结

253 阅读13分钟

引言

排序算法.001.jpeg

排序算法在实际应用中非常广泛,根据不同的数据规模及业务场景,选择合适的排序算法。

稳定性:排序前和排序后,两个相同的数的相对位置未发生改变则是稳定的。

冒泡排序

思想:从前往后,每次把较大的数交换到数组末尾,使得末尾部分是整体有序的。

14979287-280a09dab967c74b.gif

/**
 * 方法:冒泡排序
 * 复杂度; O(n^2)
 * 稳定性:稳定
 * 说明:遍历过程中把较大的值往后移动。
*/
public static void bubbleSort(int[] arr) {
    if (arr == null || arr.length < 2) return;
    //0 ~ N-1 -> 0 ~ N - 2
    for (int i = arr.length - 1; i > 0; i--) {
	for (int j = 0; j < i; j++) {
	//左边元素大于右边,将大的交换到右边去
            if (arr[j] > arr[j + 1]) {
                  swap(arr, j, j + 1);
            }
        }
    }
}

选择排序

思想:从左往右遍历,每次查找 [i+1,n1][i+1,n-1] 中的最小值与 ii 位置交换,该算法是不稳定的:(7)25934[7]1(7) 2 5 9 3 4 [7] 1 其中 (7)(7)排到[7][7]之后了。

14979287-57867b59332cf182.gif

/**                                                            
 * 方法:选择排序                                                     
 * 算法复杂度:O(N^2)                                                
 * 稳定性:不稳定                                                     
 * 不稳定例子:  (7) 2 5 9 3 4 [7] 1.  (7)排到[7]后了                    
 * 每次查找最小的值放入到数组中                                              
 */                                                            
public static void selectSort(int[] arr) {                     
    if (arr == null || arr.length < 2) return;                 
    //每次选择最小的元素放在对应的位置上                                        
    for (int i = 0; i < arr.length - 1; i++) {                 
        //在 i ~ n - 1位置查找最小值                                   
        int minIndex = i;                                      
        for (int j = i + 1; j < arr.length; j++) {             
            //找到一个更小的元素,更新 minIndex                            
            minIndex = arr[j] < arr[minIndex] ? j : minIndex;  
        }                                                      
        if (minIndex != i) swap(arr, minIndex, i);             
    }                                                          
}                                                              

插入排序

思想:保证[0,i][0,i] 是已经排好顺序的,从后往前遍历数组,将当前元素插入合适的位置,该算法是稳定的。

14979287-78142ffcc4722c0d.gif

/**                                                               
 * 方法: 插入排序                                                       
 * 算法复杂度: O(N^2)                                                  
 * 稳定性:稳定                                                         
 * 1.每次保证 0 ~ i是已经排序好的                                            
 * 2.每次从后往前查询插入的位置                                                
 */                                                               
public static void insertSort(int[] arr) {                        
    if (arr == null || arr.length < 2) return;                    
    //0~0 是有序的,想要 0~i是有序的                                         
    for (int i = 1; i < arr.length; i++) {                        
        // j 是从 i - 1 开始的,就是已经排好序的最后一个位置开始的,然后和即将插入的元素相比,如果最后一个元素比待插入的元素 arr[j + 1] 大,那需要将该元素往数组前移动                       
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { 
            swap(arr, j, j + 1);                                  
        }                                                         
    }                                                             
}                                                                 

归并排序

思想:归并排序的思想非常重要,采用经典的分治思想,将大数组分成小数组,让小数组有序后在向上合并的过程,非常经典。

14979287-98b50b0755cf5028.gif

/**                                                                                                      
 * 方法:归并排序                                                                                               
 */                                                                                                      
public static void mergeSort(int[] arr) {                                                                
    if (arr == null || arr.length < 2) return;                                                           
    process(arr, 0, arr.length - 1);                                                                     
}                                                                                                        
                                                                                                         
/**                                                                                                      
 * 归并排序的递归过程                                                                                             
 * arr[L...R]范围上,变成有序的                                                                                   
 * 时间复杂度计算公式:master T(N) = a(N/b) + O(N^d)                                                               
 * 复杂度计算过程:子问题规模 N / 2,调用 2 次,除了递归之外的时间复杂度为 merge 的时间复杂度,为O(N)。a = 2,b = 2,d = 1满足master第一条 logb^a == d规则
 * T(N) = 2T(N/2) + O(N) => O(N*logN)                                                                    
 * 时间复杂度为: O(nlogn)                                                                                      
 * 空间复杂度:O(n)                                                                                            
 * 稳定性:稳定                                                                                                
 */                                                                                                      
public 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);                                                                               
}                                                                                                        
                                                                                                         
/**                                                                                                      
 * 合并两个有序数组                                                                                              
 */                                                                                                      
public static void merge(int[] arr, int l, int m, int r) {                                               
    int[] temp = new int[r - l + 1];                                                                     
    //定义左边数组指针 p1,右边数组指针p2,temp 数组指针p                                                                    
    int p = 0, p1 = l, p2 = m + 1;                                                                       
    while (p1 <= m && p2 <= r) {                                                                         
        temp[p++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];                                           
    }                                                                                                    
    while (p1 <= m) {                                                                                    
        temp[p++] = arr[p1++];                                                                           
    }                                                                                                    
    while (p2 <= r) {                                                                                    
        temp[p++] = arr[p2++];                                                                           
    }                                                                                                    
    //将temp数组赋值到arr数组中                                                                                   
    if (temp.length >= 0) System.arraycopy(temp, 0, arr, l, temp.length);                                
}                                                                                                        

拓展

1.求小和问题

题目:在一个数组中,一个数左边比它小的数的总和,叫做小和,所有数的小和累加起来,叫做数组的小和。求数组的小和。例如 arr=1,3,4,2,5 arr = {1, 3, 4, 2, 5}

  • 1左边比1小的数:没有

  • 3左边比3小的数:1

  • 4左边比4小的数:1、3

  • 2左边比2小的数为:1

  • 5左边比5小的数为:1、3、4、2

  • 所以该数组的小和为:1+1+3+1+1+3+4+2 = 16

图解举例

image.png

了解归并流程后,在 merge 过程中,产生小和。规则是左组比右组小,则产生小和。

image-20211210105631775.png

注意:==当左组和右组数相等的时候,拷贝右组的数,不产生小和(区别于归并排序,我们是拷贝的左数组)(因为此时如果拷贝左数组,就导致左组当前元素没有和右组的后面元素进行比较,会丢失情况,请特别注意)。==当左组的数大于右组的时候,拷贝右组的数,不产生小和。

实质是把找左边比本身小的数的问题,转化为找这个数右侧有多少个数比自己大,在每次 merge 的过程中,一个数如果处在左组中,那么只会去找右组中有多少个数比自己大。

image-20211210112144435.png

/**                                                                                    
 * 左神:小和问题                                                                             
 * 题目:在一个数组中,一个数左边比它小的数的总和,叫做小和,所有数的小和累加起来,叫做数组的小和。求数组的小和。例如[1, 3, 4, 2, 5]            
 * 1左边比1小的数:没有                                                                         
 * 3左边比3小的数:1                                                                          
 * 4左边比4小的数:1、3                                                                        
 * 2左边比2小的数为:1                                                                         
 * 5左边比5小的数为:1、3、4、2                                                                   
 * 所以该数组的小和为:1+1+3+1+1+3+4+2 = 16                                                      
 */                                                                                    
public static int smallSum(int[] arr) {                                                
    if (arr == null || arr.length < 2) return 0;                                       
    return process1(arr, 0, arr.length - 1);                                           
}                                                                                      
                                                                                       
public static int process1(int[] arr, int l, int r) {                                  
    //只有一个数,不存在右组,小和为0                                                                 
    if (l == r) return 0;                                                              
    int mid = l + ((r - l) >> 1);                                                      
    // 左侧merge的小和+右侧merge的小和+整体左右两侧的小和                                                 
    return process1(arr, l, mid) + process1(arr, mid + 1, r) + merge1(arr, l, mid, r); 
}                                                                                      
                                                                                       
/**                                                                                    
 * 归并排序的merge过程,在这个过程中记录小和                                                             
 */                                                                                    
public static int merge1(int[] arr, int l, int m, int r) {                             
    int[] help = new int[r - l + 1];                                                   
    int p = 0, p1 = l, p2 = m + 1;                                                     
    int ans = 0;                                                                       
    while (p1 <= m && p2 <= r) {                                                       
        //当前的数是比右组小的,产生右组当前位置到右组右边界数量个小和,累加到 ans。否则 ans 加 0                            
        ans += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;                         
        //归并数组,注意这里的区别,左边小拷贝左边,大于等于的选择拷贝右边                                             
        help[p++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];                         
    }                                                                                  
    while (p1 <= m) {                                                                  
        help[p++] = arr[p1++];                                                         
    }                                                                                  
    while (p2 <= r) {                                                                  
        help[p++] = arr[p2++];                                                         
    }                                                                                  
    //将temp数组赋值到arr数组中                                                                 
    if (help.length >= 0) System.arraycopy(help, 0, arr, l, help.length);              
    return ans;                                                                        
}                                                                                      

2. 数组中的逆序对

image-20220423170131669.png

public int reversePairs(int[] nums) {                                                   
    if (nums == null || nums.length < 2) return 0;                                      
    return process(nums,0,nums.length - 1);                                             
}                                                                                       
                                                                                        
public int process(int[] nums, int l, int r) {                                          
    if (l == r) return 0;                                                               
    int mid = l + ((r - l) >> 1);                                                       
    return process(nums, l, mid) + process(nums, mid + 1, r) + merge(nums, l, mid, r);  
}                                                                                       
                                                                                        
public int merge(int[] nums, int l, int m, int r) {                                     
    int[] help = new int[r - l + 1];                                                    
    int p = 0, p1 = l, p2 = m + 1;                                                      
    int ans = 0;                                                                        
    while (p1 <= m && p2 <= r) {                                                        
        //当前左边数组的比右边大,产生逆序对                                                             
        ans += nums[p1] > nums[p2] ? (m - p1 + 1) : 0;                                  
        //归并数组,注意这里的区别,左边小拷贝左边,大于等于的选择拷贝右边                                              
        help[p++] = nums[p1] > nums[p2] ? nums[p2++] : nums[p1++];                      
    }                                                                                   
    while (p1 <= m) {                                                                   
        help[p++] = nums[p1++];                                                         
    }                                                                                   
    while (p2 <= r) {                                                                   
        help[p++] = nums[p2++];                                                         
    }                                                                                   
    if (help.length >= 0) System.arraycopy(help, 0, nums, l, help.length);              
    return ans;                                                                         
}                                                                                       

快排

要了解快排的思想,首先需要了解荷兰国旗问题,特别经典的一道题目。

1.荷兰国旗问题

现在有若干个红、白、蓝三种颜色的球随机排列成一条直线。现在我们的任务是把这些球按照红、白、蓝排序。

  • 1.需要维护三个区域,左边的小于区域,中间的等于区域,右边的大于区域。用图中的 0,1,20,1,2来表示。

image-20211210135724525.png

  • 2.初始化的时候红-白-蓝 三色是乱序的,所以此时的两条线我们是不是可以看成在最两侧

image-20211210140040578.png

  • 3.我们发现此时 C 等于0。是不是意味着,我们应把这个元素放到 A 的左侧,所以我们移动 A线。当然,我们也需要移动一下 C 的位置

image-20211210140058275.png

  • 新的 C 位置处等于2,那是不是说明这个元素应该位于 B 的右侧。所以我们要把该位置的元素 和 B位置处的元素进行交换,同时移动B。

image-20211210140134707.png

  • C 交换完毕后,C 不能向前移。因为C指向的元素可能是属于前部的,若此时 C 前进则会导致该位置不能被交换到前部。继续向下遍历。

  • 1)若遍历到的位置为0,则说明它一定位于A的左侧。于是就和A处的元素交换,同时向右移动A和C。

  • 2)若遍历到的位置为1,则说明它一定位于AB之间,满足规则,不需要动弹。只需向右移动C。

  • 3)若遍历到的位置为2,则说明它一定位于B的右侧。于是就和B处的元素交换,交换后只把B向左移动,C仍然指向原位置。(因为交换后的C可能是属于A之前的,所以C仍然指向原位置)

题目1

给定一个数组,和一个整数 num。请把小于 num 的数放在数组的左边,等于num的放中间,大于num的放右边。要求额外空间复杂度为 O(1)O(1),时间复杂度为O(N)O(N)[3,5,4,0,4,6,7,2][3,5,4,0,4,6,7,2]num=4num=4。实质是经典荷兰国旗问题。

public static void sortArray(int[] arr, int target) {                        
    if (arr == null || arr.length < 2) return;                               
    int left = 0, right = arr.length - 1, cur = 0;                           
    while (cur <= right) {                                                   
        if (arr[cur] < target) {                                             
            //交换到左区域,并且cur指针和left指针都移动                                       
            swap2(arr, left, cur);                                           
            cur++;                                                           
            left++;                                                          
        } else if (arr[cur] > target) {                                      
            //此时应该将元素移到右边界。右边界指针移动,cur指针也不动,因为我不知道当前移过来的元素是否满足条件,需要继续判断      
            swap2(arr, cur, right);                                          
            right--;                                                         
        } else {                                                             
            //当前元素与目标元素相等,只用移动cur指针                                          
            cur++;                                                           
        }                                                                    
    }                                                                        
}                                                                            

颜色分类

image-20220423174847711.png

class Solution {
    /**
     * 荷兰国旗问题:双指针思想,一趟走完
     */
    public static void sortColors(int[] nums) {
        if (nums == null || nums.length <= 1) return;
        int l = -1, r = nums.length;
        int index = 0;
        while (index < r) {
            if (nums[index] < 1) {//当前元素放在左边
                swap(nums,++l,index++);
            } else if (nums[index] == 1) {
                index++;
            } else {
                //当前元素在右边,把右边元素交换过来,交换过来的元素的继续判断
                swap(nums, index, --r);
            }
        }
    }

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

2.快排

思想

借助荷兰问题,我们从数组中选择一个元素,然后让数组以该元素分为左右两部分。分完之后,该元素在排序数组中的位置一定是确定下来的。继续计算左边数组和右边数组相同的过程,然后就能将找到所有元素的位置。

注意:因为Partion过程会存在元素的交换(将相同的7交换到数组的最后方,所以快排是不稳定的。)

在最差情况下,如果我们每次找的元素都是位于位于排序后数组的末尾,则此时快排的时间复杂度会递增到O(N2)O(N^2) ,最好情况,其他各种情况的出现概率为1/N1/N 。对于这 N N 种情况,数学上算出的时间复杂度最终期望是 O(NlogN)O(NlogN)

/**                                                                   
 * 左神快排:基于荷兰问题上进一步排序                                                  
 * 时间复杂度:O(NlogN)                                                     
 * 空间复杂度:O(NlogN)                                                     
 * 稳定性:不稳定                                                            
 */                                                                   
public static void quickSort(int[] arr) {                             
    quickSortHelper(arr, 0, arr.length - 1);                          
}                                                                     
                                                                      
public static void quickSortHelper(int[] arr, int l, int r) {         
    if (l < r) {                                                      
        //为了避免最坏的情况,我们随机算选择一个数作为划分值,将其放到数组的尾部,可以随机算去                  
          swap2(arr,l + (int)(Math.random() * (r - l + 1)),r);        
        //通常我们选中间元素就行                                                 
        int mid = l + (r - l) / 2;                                    
        swap2(arr, mid, r);                                           
        int[] p = partition(arr, l, r);                               
        quickSortHelper(arr, l, p[0] - 1);//小于区域                      
        quickSortHelper(arr, p[1] + 1, r);//大于区域                      
    }                                                                 
}                                                                     
                                                                      
/**                                                                   
 * partition过程,这是一个处理 arr[l,r]的过程                                     
 * 默认以 p = arr[r]做划分,分成三部分  <p  =p  >p                                
 * 返回的是中间等于区域(左边界,右边界),所以这里反回了一个长度为2的数组res                            
 */                                                                   
public static int[] partition(int[] arr, int l, int r) {              
    int less = l - 1;   // 小于 p 区域的边界                                 
    int more = r;       // 大于 p 区域的边界                                 
    while (l < more) {   // l表示当前操作的元素,而 arr[r] 为划分值                  
        if (arr[l] < arr[r]) {                                        
            //当前元素小于划分值,应该放在左测区域,并且移动指针,因为less是前一个区域的边界               
            swap2(arr, ++less, l++);                                  
        } else if (arr[l] > arr[r]) {                                 
            //当前元素大于划分值,要分到右边界,此时交换元素,并且l指针不能动,因为我不确定当前交换的值是否满足条件     
            swap2(arr, --more, l);                                    
        } else {                                                      
            //当前元素为划分值,则只用移动l指针                                       
            l++;                                                      
        }                                                             
    }                                                                 
    //将划分值放回右边区域的边界位置                                                 
    swap2(arr, more, r);                                              
    //返回中间等于区域的左右边界                                                   
    return new int[]{less + 1, more};                                 
}                                                                     

堆排序

大根堆-小根堆

image-20220423175454269.png

20180801213938728.png 基础性质

  • 其中 ii 表示数组中的索引,将它对应成堆后的性质。

  • 父节点索引:(i1)/2(i -1)/2

  • 左孩子索引:2i+1 2*i + 1

  • 右孩子索引:2i+22*i + 2

  • 大根堆:arr[i]>arr[2i+1]&&arr[i]>arr[2i+2]arr[i] > arr[2*i + 1] \&\& arr[i] > arr[2*i+2]

  • 小根堆:arr[i]<arr[2i+1]&&arr[i]<arr[2i+2]arr[i] < arr[2*i + 1] \&\& arr[i] < arr[2*i+2]

  • 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆

  • 完全二叉树中如果每颗子树的最小值都在顶部就是小根堆

两个重要堆结构操作:

  • heapInsert

要构建一个大根堆,把所有的数依次添加到一个数组(下标从0开始)中去,每次添加一个数的时候,要去用找父亲节点的公式 parent=(i1)/2parent = (i-1) / 2找到父节点去比较,如果比父节点大就和父节点交换向上移动,移动后再用自己当前位置和父亲节点比较…,小于等于父节点不做处理。这样用户每加一个数,我们都能保证该结构是大根堆。

我们的调整代价实际上就是这颗树的高度层数,logNlogN

  • heapify

删除了最大值,也就是arr[0]arr[0]位置,之后我们把堆最末尾的位置调整到arr[0]arr[0]位置,堆大小减一。让现在arr[0]arr[0]位置的数找左右孩子比较…,进行hearify操作,让其沉下去。沉到合适的位置之后,仍然是大根堆。

heapify的下沉操作,仍然是树的高度,logNlogN

整个过程如下:

image-20211210155142111.png

手写大根堆

在很多特殊情况下,系统提供的堆结构只支持弹出对顶元素,不支持更新堆内元素,这个时候就需要手写堆结构,根据业务需求,拓展堆结构,在算法应用中特别广泛,往往有奇效,必须掌握。

/**
 * 创建大根堆
 */
public class MaxHeap {

    /**
     * 存储堆元素数组
     */
    private int[] heap;

    /**
     * 堆的大小限制,当然超过了会进行数组的两倍扩容
     */
    private final int capacity;

    /**
     * 表示目前这个堆收集了多少个数,也表示添加的下一个数应该放在哪个位置
     */
    private int heapSize;

    public MaxHeap(int capacity) {
        this.capacity = capacity;
        heap = new int[capacity];
        heapSize = 0;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }

    public boolean isFull() {
        return heapSize == capacity;
    }

    public void push(int value){
        if (heapSize == capacity){
            throw new RuntimeException("heap is full");
        }
        heap[heapSize] = value;
        //调整堆结构,heapSize作为操作数组的指针
        heapInsert(heap,heapSize++);
    }

    /**
     * 从堆中取出堆顶元素,即返回最大值,并且在大根堆中,
     * 把最大值删掉剩下的数,依然保持大根堆组织
     */
    public int pop(){
        if (heapSize == 0) {
            throw new RuntimeException("heap is empty");
        }
        int ans = heap[0];
        //这里的移除只是改变 heapSize 指针。至于数组的元素还存在对我们堆结构没有任何影响。因为我们在push操作中会覆盖当前值
        //我们将堆中最后一个元素移动到堆顶执行heapify操作,下沉堆顶元素
        swap(heap,0,--heapSize);
        //执行下沉操作
        heapify(heap,0,heapSize);
        return ans;
    }
    
    public int peek(){
        if (heapSize == 0) {
            throw new RuntimeException("heap is empty");
        }
        return heap[0];
    }

    /**
     * 向堆中插入元素
     */
    private void heapInsert(int[] arr,int index){
        //arr[index] 不比 arr[index父]大了,停止heapInsert过程
        //注意当比较到 0 位置即堆顶时候,此时循环也不会进入了
        while (arr[index] > arr[(index - 1) / 2]){
            swap(arr,index,(index - 1) / 2);
            //将指针移动到父节点,继续上升的过程
            index = (index - 1) / 2;
        }
    }

    /**
     * 从index位置,往下看,不断的下沉,停的条件:
     * 我的孩子都不再比我大;已经没孩子了
     */
    private void heapify(int[] arr,int index,int heapSize){
        //找到左孩子索引
        int leftIndex = index * 2 + 1;
        //这里如果左孩子越界,那右孩子更越界,右孩子等于左孩子+1
        while (leftIndex < heapSize){
            //下沉操作,父节点与左右孩子相比, 左右两个孩子中,谁大,谁把自己的下标给largest,因为大的节点得上升
            // 选择右  ->  (1) 有右孩子   && (2)右孩子的值比左孩子大才行
            int largest = leftIndex + 1 < heapSize && arr[leftIndex + 1] > arr[leftIndex] ?  leftIndex + 1 : leftIndex;
            //左右孩子中最大值,和当前值父节点比较,谁大谁把下标给largest(当前,左,右的最大值下标)
            largest = arr[largest] > arr[index] ? largest : index;
            //index 位置上的数比左右孩子的数都大,已经无需下沉
            if(index == largest) break;
            //交换父节点和子节点中的较大孩子。周而复始进行
            swap(arr,index,largest);
            //更新父节点的index,即父节点往下沉
            index = largest;
            //继续看它的左右孩子是否需要调整
            leftIndex = index * 2 + 1;
        }
    }
    
    private void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

堆排序

14979287-db06cbc007a1c286.gif

/**                                                                                         
 * 左神:堆排序                                                                                   
 * 时间复杂度为:O(NlogN)                                                                          
 * 空间复杂度:O(1)                                                                               
 * 稳定性:不稳定                                                                                  
 * 对于用户给的所有数据,我们先让其构建成为大根堆                                                                  
 * 对于0到N-1位置的数,我们依次让N-1位置的数和0位置的数(全局最大值)交换,此时全局最大值来到了数组最大位置,堆大小减一,                          
 * 再heapify调整成大根堆。再用N-2位置的数和调整后的0位置的数交换,相同操作。直至0位置和0位置交换。每次heapify为logN,交换调整了N次             
 * <p>                                                                                      
 * 堆排序额为空间复杂度为O(1),且不存在递归行为                                                                 
 */                                                                                         
public static void heapSort(int[] arr) {                                                    
    if (arr == null || arr.length < 2) return;                                              
    //1.将元素数组构建成堆事件复杂度为 0(NlogN)                                                            
    for (int i = 0; i < arr.length; i++) {                                                  
        heapInsert(arr, i);                                                                 
    }                                                                                       
    //1.1拓展,如果直接给定一个数组,我们直接执行heapify操作构建大堆,从末尾开始看是否需要heapify                                
      for (int i = arr.length - 1; i >= 0;i--){                                             
          heapify(arr,i,arr.length);                                                        
      }                                                                                     
                                                                                            
    //2.将堆顶元素移到末尾,堆的heapSize--;                                                             
    int heapSize = arr.length;                                                              
    swap2(arr, 0, --heapSize);                                                              
    //开始下沉操作                                                                                
    while (heapSize > 0) {                                                                  
        heapify(arr, 0, heapSize);                                                          
        swap2(arr, 0, --heapSize);                                                          
    }                                                                                       
}                                                                                           
                                                                                            
/**                                                                                         
 * 向堆中插入元素,维持大堆结构                                                                           
 * arr[index]刚来的数,通过比较进行上升                                                                  
 */                                                                                         
public static void heapInsert(int[] arr, int index) {                                       
    //与父节点相比,大于就上身,否则停止                                                                     
    while (arr[index] > arr[(index - 1) / 2]) {                                             
        swap2(arr, index, (index - 1) / 2);                                                 
        index = (index - 1) / 2;                                                            
    }                                                                                       
}                                                                                           
                                                                                            
/**                                                                                         
 * 下沉操作,将当前 index 位置下沉到合适的位置,保持堆结构稳定                                                        
 */                                                                                         
public static void heapify(int[] arr, int index, int heapSize) {                            
    int left = index * 2 + 1;                                                               
    while (left < heapSize) {                                                               
        //1.找左右孩子中较大的一个                                                                     
        int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;   
        //2.将孩子与父节点进行比较,父节点比孩子小,则需要下沉                                                       
        largest = arr[index] < arr[largest] ? largest : index;                              
        //3.如果父节点的索引没变,则说明下层已经满足大堆结构了,直接break                                               
        if (largest == index) break;                                                        
        //4.否则父节点与子节点交换                                                                     
        swap2(arr, index, largest);                                                         
        //5.父节点指针移动,移动到孩子节点,接续判断孩子节点的树是否满足大堆结构,周而复始                                         
        index = largest;                                                                    
        left = index * 2 + 1;                                                               
    }                                                                                       
}                                                                                           
                                                                                            
                                                                                            
public static void swap2(int[] arr, int i, int j) {                                         
    int tem = arr[i];                                                                       
    arr[i] = arr[j];                                                                        
    arr[j] = tem;                                                                           
}                                                                                           

结语

排序算法在算法题中应用特别广泛,通常对数组进行排序,可以调用系统函数完成。但有些题目下,可能需要我们自己动手去实现一些排序算法。例如:利用快排找第k大的数,利用归并排序寻找逆序对,利用手写堆来更新词频等等。都需要我们对这几种排序算法有深刻的理解和运行。