「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战 介绍几种常用的排序方法,主要包括选择排序,冒泡排序,插入排序,希尔排序,归并排序,快速排序和基数排序。
1.选择排序
每遍历一次数组就找出未排好序那部分数组的最小值(最大值)的索引,然后将该索引位置的数与对应的位置的数交换,例如数组{5,1,2,3,6,4},从小到大排列,第一次排序则是找到最小值1,索引是1,则将数字1跟5交换即索引为1(第一次遍历最小值的索引)的数字与对应位置的索引为0的数进行交换得到{1,5,2,3,6,4},此时1被放在了正确的位置,以此类推,代码如下:
public class BubbleSort {
static int[] sort(int[] arr){
for (int i = arr.length-1; i >0 ; i--) {
for (int j = 0; j < i; j++) {
if (arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = {2,3,5,8,4,7,6,9,1};
System.out.println(Arrays.toString(sort(arr)));
}
}
2.冒泡排序
相邻的两个数进行比较,把较大的数字进行后移,通过交换顺序来实现交换
public class BubbleSort {
static int[] sort(int[] arr){
for (int i = arr.length-1; i >0 ; i--) {
for (int j = 0; j < i; j++) {
if (arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j+1] = temp;
}
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = {2,3,5,8,4,7,6,9,1};
System.out.println(Arrays.toString(sort(arr)));
}
}
3.插入排序
从索引为1开始,把索引为1的数插入到前面排好数字应的位置
public class InsertionSort {
static int[] sort(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j]<arr[j-1]){
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
return arr;
}
以上三种为简单排序,冒泡排序和选择排序基本不用换,插入排序在样本较小且基本有序的情况下效率较高,时间复杂度都是O(n2),空间复杂度都是O(1)
4.希尔排序
希尔排序是改进的插入排序,每次确定一个间隔即,将数组用间隔划分成新的数组,分别进行插入排序,每次间隔排序完之后缩小间隔,然后再划分,再进行插入排序,最后缩小到间隔为1,进行最后一次排序。间隔是由Knuth数列来决定的,首项h=1,公式为h=3*h+1,此时h必须小于等于数组的三分之一;时间复杂度为O(n1.3),空间复杂度为O(1);
public class ShellSort {
public static void main(String[] args) {
int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
System.out.println(Arrays.toString(sort(arr)));
}
static int[] sort(int[] arr){
int h = 1;
while(h<=arr.length){
h = h * 3 + 1;
}
for (int gap = h; gap>0 ; gap = (gap-1)/3) { //Knuth序列确定间隔
for (int i = gap; i < arr.length; i++) {
for (int j = i; j >gap - 1 ; j = j - gap) {
if (arr[j]<arr[j-gap]){
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
}
return arr;
}
}
5.归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。就是将原数组分为若干个子数组,分别对这些子数组进行排序,将一个数组分为左右两个,新建一个数组,挨个比较左右两个数组元素的大小,然后复制到一个新的数组(temp)里,最后再将数组temp排好的元素更新到原数组里,如下图:
代码如下:
public class MergeSort {
/**
*
* @param arr 待排序的数组
* @param leftPtr 左指针
* @param rightPtr 右指针
* @param rightBound 右边界
*/
static void merge(int[] arr,int leftPtr, int rightPtr, int rightBound){
int mid = rightPtr - 1;
int[] temp = new int[rightBound-leftPtr+1];
int i = leftPtr;
int j = rightPtr;
int k = 0;
while(i <= mid && j <= rightBound){
temp[k++] = arr[i] <= arr[j]?arr[i++]:arr[j++];
}
while(i <= mid){
temp[k++] = arr[i++];
}
while(j <= rightBound){
temp[k++] = arr[j++];
}
for (int l = 0; l < temp.length; l++) {
arr[leftPtr + l] = temp[l];
}
}
/**
*
* @param arr 待排序的数组
* @param left 左边界
* @param right 右边界
*/
static void sort(int[] arr,int left, int right){
if(left == right) return;
int mid = left + (right-left)/2;
//左边排序
sort(arr,left,mid);
//右边排序
sort(arr,mid+1,right);
merge(arr,left,mid+1,right);
}
public static void main(String[] args) {
int[] arr = {1,3,5,7,9,2,4,6,8};
sort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
6.快速排序
快速排序就是选取一个数作为轴,把比轴大的元素放到轴后面,把比轴小的元素放到轴前面,这时轴的位置固定,但轴前后的元素顺序不对,这时以轴为分界线把数组分为左右两个在进行如上操作,递归进行,代码如下:
public class QuickSort {
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int partition(int[] arr, int leftBound, int rightBound){
int left = leftBound;
int right = rightBound-1;
int provid = arr[rightBound];
while(left <= right){
while(left <= right && arr[left] <= provid) left++;
while(left <= right && arr[right] > provid) right--;
if (left < right)swap(arr, left, right);
}
swap(arr,left,rightBound);
//System.out.println(Arrays.toString(arr));
return left;
}
public static int[] sort(int[] arr, int leftBound, int rightBound){
if (leftBound >= rightBound) return arr;
int mid = partition(arr,leftBound,rightBound);
sort(arr,leftBound,mid-1);
sort(arr,mid+1,rightBound);
return arr;
}
public static void main(String[] args) {
int[] arr = {3,5,8,4,2,6,10,1};
sort(arr,0,arr.length-1);
}
}
7.计数排序
非比较排序,对数据源有要求(数据量大,且数据分布在一定的区域内)
新建一个数组(统计数组,数组的大小与分布的区域的大小一致),以数组的索引(索引只能从0开始,而数据源最小的可能不是0,索引值可以用数据源的数字减去该数据源最小的数)表示数据源里出现的数字,索引对应位置存储数字出现的次数,再新建一个累加数组,该数组负责记录数据数字出现在结果数组里最后的位置,再有累加数组得到结果数组;
import java.util.Arrays;
public class CountSort {
public static void main(String[] args) {
int[] arr = {3,3,1,4,2,5};
sort(arr,6);
}
public static void sort(int[] arr, int nums) {
int[] count =new int[nums];
int[] result = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
//如果原数据不是从0开始,则arr[i]需要用arr[i]减去最小值来代替
count[arr[i]]++; //得到统计数组
}
System.out.println(Arrays.toString(count));
for (int i = 1; i < count.length; i++) {
count[i] = count[i]+count[i-1]; //得到累加数组
}
System.out.println(Arrays.toString(count));
for (int i = arr.length-1; i >= 0; i--) {
//得到结果数组,arr[i]既是源数据,也是count的下标,count数组里的元素减一又是结果数组的下标
//如果原数据不是从0开始,则arr[i]需要用arr[i]减去最小值来代替
result[--count[arr[i]]] = arr[i];
}
System.out.println(Arrays.toString(result));
}
}