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排序(七大排序合集)