Java排序(七大排序合集)

Posted 木木林Violet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java排序(七大排序合集)相关的知识,希望对你有一定的参考价值。

七大排序

排序是算法中有着很重要的地位。它会涉及到很多方面的知识,不仅仅是算法相关的知识,还有很多很数据结构相关的知识,所以了解排序,对于我们学习算法和数据结构都是很有帮助的。
其中常见的排序有其中排序方法,分别为冒泡排序、选择排序、插入排序、希尔排序、堆排序、归并排序、快速排序,本文则是围绕着着其中排序方法进行讲解。

1、冒泡排序

冒泡排序(Bubble Sort)因其易于理解,作为不少初学者学习到的第一个的排序算法。
冒泡排序之所以叫为冒泡,是因为其想泡泡一样,一步步地将大的泡泡向上冒出,在数据中表现则为将较大的数字一步步交换到无序空间的后面。

1.1、排序过程图

1.2、排序思想

1.依次比较每两个相邻的数的大小,将较大的数交换到后面。
2.当第一次整个数据比较和交换完成后,整个数组中最大的数字则被排在数组的最后。
3.此时,已经将一个数排到了其相应的位置,不需要再移动了。
4.再次遍历整个数组,将次大的数通过比较和交换,排在其相应的位置。
5.重复上述流程,知道整个数组都被排序。

1.3、排序代码

public static void bubbleSort(int[] arr)
	//因为排好数组中第二个数时,第一个数也自然排好了,所以遍历的此数为length - 1
    for (int i = 0; i < arr.length - 1; i++) 
    	//第i + 1次遍历整个数组
        for (int j = 0; j < arr.length - i - 1; j++) 
            if (arr[j] > arr[j + 1])
                swap(arr, j, j + 1);
            
        
        //此时倒数第i + 1个数则排序好了
    

private static void swap(int[] arr, int i, int j)
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

1.4、代码改进

上述的代码任然有一定改进的空间:若输入的数组已经完整排序,此时则不需要再排序,但上述的代码并不能实现这一点,于是可以加上一个对数组排序的判断。

public static void bubbleSort(int[] arr)
	//isSorted 用于判断数组是否已经排序完成
    boolean isSorted = true;
    for (int i = 0; i < arr.length - 1; i++) 
        for (int j = 0; j < arr.length - i - 1; j++) 
            if (arr[j] > arr[j + 1])
                swap(arr, j, j + 1);
                //此时数组仍未排序完成
                isSorted = false;
            
        
        //若排序完成,则 isSorted 应该为true,则提前跳出循环
        if (isSorted)
            break;
        
    

private static void swap(int[] arr, int i, int j)
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

2、选择排序

选择排序(Selection Sort),顾名思义,就是每次遍历的时候,都选择一个最小(大)的数进行排序,直到此数排到相应的位置,结束此数的排序,再选除此数外最小(大)的数进行排序。

2.1、排序过程图

2.2、排序思想

1.初次遍历数组时,根据依次比较,筛选出当前数组中最小的数。
2.将此数通过交换到数组最前的位置,此数则排序完成。
3.再将除排序好的数以外的数组,再次筛选出最小值。
4.再次将当前最小值通过交换,排到数组的最前。
5.重复上述流程,直到整个数组排序完成。

2.3、排序代码

public static void selectSort(int[] arr)
	//依次筛选并排序最小值
    for (int i = 0; i < arr.length - 1; i++) 
    	//用min记录当前数组最小值的索引
        int min = i;
        //遍历数组,筛选出最小值得索引
        for (int j = i + 1; j < arr.length; j++) 
            if (arr[j] < arr[min])
                min = j;
            
        
        //交换最小值和当前数组最前的位置的值
        swap(arr, min, i);
        //此时当前数组最小值得排序完成
    

private static void swap(int[] arr, int i, int j)
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

2.4、代码改进——双向选择排序

在上述的代码,即单向选择排序中,一次遍历数组,只能筛选出一个数进行排序,效率略低,则可以考虑一次遍历筛选出两个数,即可以筛选出最小值和最大值。

2.4.1、改进排序思想

1.在每次遍历数组时,筛选出当前数组中的最小值和最大值。
2.将最小值和最大值分别交换到当前数组的最前和最后。
3.再将除已排序完的数的数组,再次按照上述流程进行排序。
注:
可能会出现max和low重合的情况,即max的值并未变化的情况,则此时min才是max应该指向的位置。

2.4.2、改进排序代码

public static void selectSortOP(int[] arr)
    //记录最小值得索引
    int low = 0;
    //记录最大值的索引
    int high = arr.length - 1;
    while (low <= high)
    	//从左边开始遍历
        int min = low;
        int max= low;
        for (int i = low + 1; i <= high; i++) 
        	//筛选出更小的数
            if (arr[i] < arr[min])
                min = i;
            
            //筛选出更大的值
            if (arr[i] > arr[max])
                max = i;
            
        
        //交换数组最左边的值和最小值
        swap(arr, min, low);
        //max和low重合的情况
        if (max == low)
            max = min;
        
        //交换数组最右边的值和最大值
        swap(arr, max, high);
        //移动左右指针
        ++low;
        --high;
    

private static void swap(int[] arr, int i, int j)
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

3、插入排序

插入排序(Insertion Sort)是排序算法中稳定的排序算法,它不会改变相同数字之间的相对位置,而且这种排序算法, 和选择排序有点类似,若已理解了选择排序,那插入排序的理解会变得相对简单。插入排序的主要思想,是将无序区间里的数依次插入到有序区间里的对应位置,并不能改变有序区间的有序性。

3.1、排序过程图

3.2、排序思想

1.在排序时,有序区间为[ 0 , i ),无序区间为[ i , arr.length ),注意两区间都是左闭右开的区间。
2.选择当前无序区间的第一个数,和前面的数依次比较,若前面的数比当前的数大,则交换,直到遇到遇到一个小于它的数,或已经到达数组的最前位置。
3.此时,排好了一个数,当前的有序区间为[ 0 , i + 1),[ i - 1, arr.length )。
4.并继续按照上述流程比较和交换,直到整个数据排序完成。

3.3、排序代码

public static void insertSort(int[] arr)
    for (int i = 1; i < arr.length; i++) 
        for (int j = i; j >= 1 && arr[j] < arr[j - 1]; j--) 
            swap(arr, j, j - 1);
        
    

private static void swap(int[] arr, int i, int j)
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

3.4、代码改进——折半插入排序

上述的代码在查找其相应的位置时,效率略微低下,于是可选择常用的二分查找,来帮助我们找到其应该移动到的位置,提高效率。

3.4.1、改进代码思想

1.首先仍然是要找到当前无序区间中的第一个数。
2.通过二分查找,找到其应该移动到的位置。
3.将有序数组从其应该移动到的位置开始,到数组末尾的数,全部向后搬移一个位置。
4.将数字直接插入到相应位置。

3.4.2、改进代码

public static void insertSortBS(int[] arr)
	// 有序区间[0..i)
    // 无序区间[i...n]
    for (int i = 1; i < arr.length; i++) 
        int left = 0;
        int right = i;
        int val = arr[i];
        //使用二叉查找,找到应插入到的位置。
        while (left < right)
            int mid = (left + right) >> 1;
            if (val < arr[mid])
                right = mid;
             else 
                left = mid + 1;
            
        
        // 搬移left..i的元素
        for (int j = i; i > left; i--)
            arr[j] = arr[j - 1];
        
        // left就是val插入的位置
        arr[left] = val;
    

4、希尔排序

希尔排序(Shell Sort)又称缩小增量法。它的思想和插入排序的思想有一定的关联。插入排序是将整个数组看成一个整体,而希尔排序是将整个数组划分为几大块,先每块分别进行插入排序,再将几个大块拆分为更多的小块,并继续上述的操作,直到拆分到每块都只有一个元素,此时整个数组则排序完成。

4.1、排序流程图

4.2、排序思想

1.先将整个数组对半拆分,得到两部分数组。
2.对那两部分数组进行插入排序。
3.再将每块进行对半拆分,得到几部分的数组。
4.再次对那几部分的数组进行插入排序。
5.直到每部分都只有一个元素,此时已无法再拆分,则整个数组都排序完成。

4.3、排序代码

public static void shellSort(int[] arr)
	//首次拆分
    int gap = arr.length >> 1;
    while (gap > 1)
    	//进行插入排序
        insertSortByGap(arr, gap);
        //对每部分进行拆分
        gap >>= 1;
    
    //此时整个数组已经近乎有序,再次使用插入排序即可有序
    insertSort(arr);

//此函数为将插入排序中的起始索引1,改为gap
private static void insertSortByGap(int[] arr, int gap) 
    for (int i = gap; i < arr.length; i++) 
    	//对每部分的数组进行插入排序
        for (int j = i; j >= gap && arr[j] > arr[j - gap]; j -= gap) 
            swap(arr, j, j - gap);
        
    

5、堆排序

堆排序(Heap Sort),顾名思义,就是要用到堆的知识。其基本的原理也就是选择排序,知识不再使用遍历的方式查找无序区间的最大数,而是用堆来来选择无序区间中的最大值。但是需要注意的是,排升序需要用到最大堆,排降序需要用到最小堆。

5.1、排序流程图

5.2、排序思想

在讲解堆排序思想前,需要先知道一些关于堆排序的知识:
1.堆在逻辑上事一颗完全二叉树。
2.堆在物理上是保存在数组中(按照层序遍历顺序保存)。
3.最大堆:每一个节点的值都大于或等于其两个子节点的值。
4.最大堆的下沉操作:为满足最大堆的性质,需要将指定位置的数在二叉树的逻辑上,向下交换到合适的位置。
5.最大堆的构建:从层序遍历中最后一个非叶子节点开始,每个节点执行下沉操作,则可完成二叉树的构建。
在堆排序中,需要用到以上的有关于堆的知识,此时来讲解堆排序:
1.将无序数组构建为最大堆的形式,此时堆顶的数就是当前无序数组中最大的数。
2.将无序数组的最后一个数和第一个数交换,则最大的数被交换到了无序数组的最后,成为了有序数组的一部分。
3.将交换后的第一个数(也就是刚刚无序数组的最后一个数)进行下沉操作,以继续满足最大堆的性质。
4.重复上述的操作,直到整个数组排序完成。

5.3、排序代码

public static void heapSort(int[] arr)
	//从最后一个非叶子节点开始下沉操作,构建最大堆
    for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--)
        siftDown(arr, i, arr.length);
    
    //对数组进行交换和下沉操作
    for (int i = arr.length - 1; i > 0; i--)
        swap(arr, 0, i);
        siftDown(arr, 0, i);
    

//下沉操作
private static void siftDown(int[] arr, int i, int length) 
	//保证此节点有左节点
    while (2 * i + 1 < length)
    	//左节点的索引
        int j = (i << 1) + 1;
        //判断是否有右节点,并筛选出两节点的最大值所在的索引
        if (j + 1 < length && arr[j + 1] > arr[j])
            ++j;
        
        //若节点的值小于节点的最大值,则结束循环
        if (arr[i] > arr[j])
            break;
        
        //交换两节点的值,并更改当前节点的索引
        swap(arr, i, j);
        i = j;
    

6、归并排序

归并排序(Merge Sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

6.1、排序过程图

6.2、排序思想

归并排序的主要思想就是先分割,再合并。
1.将整个数组折半分割,直到每个子数组都只有一个数。
2.再将每两个相邻的子数组结合。
3.创建一个新数组,来暂时保存数组。
4.一次比较两子数组中的值,将较小的值依次放入新数组中。
5.再将剩下未放入到新数组中的数,依次放入到新数组中。
6.当整个新数组都放满时,再用新数组的值覆盖掉之前数组的值。
3.不停地结合并排序,直到整个数组合并完成,则排序完成。

6.3、排序代码

public static void mergeSort(int[] arr)
	//数组的区间为闭区间
    mergeSortInternal(arr, 0, arr.length - 1);

private static void mergeSortInternal(int[] arr, int l, int r) 
    //求中间索引
    int mid = r + ((l - r) >> 1);
    //将左数组排序
    mergeSortInternal(arr, l, mid);
    //将右数组排序
    mergeSortInternal(arr, mid + 1, r);
    //若数组已排序成功,则返回
    //若数组还为排序成功,则将左右数组合在一起归并排序
    if (arr[mid] > arr[mid + 1])
        merge(arr, l, mid, r);
    

private static void merge(int[] arr, int l, int mid, int r) 
	//创建新数组,来暂时保存数组
    int[] newArr = new int[r - l + 1];
    //左数组的第一个索引
    int i = l;
    //右数组的第一个索引
    int j = mid + 1;
    //辅助数组的第一个索引
    int k = 0;
    //依次比较出较小的数,并放入到新数组中
    while (i <= mid && j <= r)
        if (arr[i] <= arr[j])
            newArr[k++] = arr[i++];
         else 
            newArr[k++] = arr[j++];
        
    
    //前数组有剩余的数
    while (i <= mid)
        newArr[k++] = arr[i++];
    
    //后数组有剩余的数
    while (j <= r)
        newArr[k++] = arr[j++];
    
    //将辅助数组的值赋给原数组
    for (int m = 0; m < newArr.length; m++) 
        arr[l + m] = newArr[m];
    

6.4、代码改进——解决可能的栈溢出问题

6.4.1、代码改进思想

归并排序在执行时,需要多次调用函数,而当数据过多时,可能会出现栈溢出的问题,于是,可以在数组长度较短时,使用插入排序(在近乎有序的情况下,插入排序的效率高),来解决栈溢出的问题。

6.4.2、改进代码

public static void mergeSort(int[] arr)
	//数组的区间为闭区间
    mergeSortInternal(arr, 0, arr.length - 1);

private static void mergeSortInternal(int[] arr, int l, int r) 
    //若当前数组偿付较短时,使用插入排序,否则容易栈溢出
    if (r - l <= 15)
        insertSort(arr, l, r);
        return;
     
    //求中间索引
    int mid Java排序(七大排序合集)

八:快速排序

七大排序算法分析及java实现

Java实现七大排序算法

java七大排序——7_归并排序

Java中的七大排序_1 插入排序