排序算法的分类:
- 内部排序:整个排序过程在内存储器中进行.
- 外部排序:由于待排序元素数量太大,以至于内存储器无法容纳全部数据,排序需要借助外部存储设备才能完成
实现接口:
package com.my.sort;
public interface IArraySort {
int[] sort(int[] sourceArray) throws Exception;
}
1.冒泡排序
1.1 性质
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 稳定排序
- 原地排序
1.2 代码
package com.my.sort.impl;
/**
* 冒泡排序
*/
import java.util.Arrays;
import com.my.sort.IArraySort;
public class BubbleSort implements IArraySort {
/**
* 相邻两个元素进行比较
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for(int i = 0; i < arr.length - 1; i++) {
//一共需要比较N - 1趟
//设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成
boolean flag = true;
for(int j = 0; j < arr.length - (i + 1); j ++) {
//每趟需要比较N - 第几趟,趟数从1开始算起
if(arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
//说明此趟没有进行任何交换,则排序已经完成
if(flag) {
break;
}
}
return arr;
}
}
2.选择排序
2.1 性质
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 非稳定排序
- 原地排序
2.2 代码
package com.my.sort.impl;
import java.util.Arrays;
/**
* 选择排序
*/
import com.my.sort.IArraySort;
public class SelectionSort implements IArraySort {
/**
* 选择排序,将未排序数组分为左右两区,左边为已排序区,右边为未排序区
* 假设未排序区的第一个元素为未排序部分的最小值,将此值与未排序区所
* 有值进行比较
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变原参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//总共需要N-1趟比较
for(int i = 0; i < arr.length - 1; i++) {
int min = i;
for(int j = i + 1; j < arr.length; j++) {
if(arr[min] > arr[j]) {
//说明此时min中记录的值并非最小值,记录新的最小值下标
min = j;
}
}
//此时min所记录的值便是本趟循环的最小值
if(i != min) {
//说明i记录的不是最小值,交换位置
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
return arr;
}
}
3.插入排序
3.1 性质
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
- 稳定排序
- 原地排序
3.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 插入排序
* @author asus
*
*/
public class InsertSort implements IArraySort {
/**
* 插入排序,分为左右两区,左边为已排序区,右边为未排序区,假设
* 左边第一个元素已排好序,从第二个元素开始,将该元素插入到已
* 排序区的适当位置
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//第一个元素默认已经排好序,从第二个元素开始遍历
for(int i = 1; i < arr.length; i++) {
//tmp记录要排序的元素
int tmp = arr[i];
//j记录要排序元素的下标
int j = i;
while(j > 0 && tmp < arr[j - 1]) {
//将要插入元素从后向前与已排序的序列的元素进行比较,
//若已排序元素大于要插入的元素,则向后移动一位,为要插入的
//元素腾出位置,最终找到要插入的位置的下标j
arr[j] = arr[j - 1];
//因为j下标,及其所对应的元素已经被记录,所以不会被覆盖
j--;
}
if(j != i) {
//如果要插入位置的下标不等于要插入元素的原始下标,则插入要插入的元素
arr[j] = tmp;
}
}
return arr;
}
}
4.希尔排序
4.1 性质
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 非稳定排序
- 原地排序
4.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 希尔排序
* @author asus
*
*/
public class ShellSort implements IArraySort {
/**
* 希尔排序:是对直接插入排序的一种升级,直接插入排序时待排序的元素多次与相邻的元素实现交换,效率较低
* 希尔排序对未排序的元素进行等步长分组,对每个分组内部的元素进行排序,则待排序元素多次与不相邻的元素
* 实现交换,提供了效率
* 希尔排序是对分组内部元素进行局部选择排序,且是每个分组轮流排序,并不是等一个分组完成排序后再进行下一
* 分组的排序,当步长为1时,实现整个数组的选择排序,提高了效率
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//设置起始步长step为1
int step = 1;
//确定分组的最大步长
while(step < arr.length) {
step = step *3 + 1;
}
//按步长序列个数k,对序列进行k趟排序
while(step > 0) {
//默认每个分组的第一个元素是有序的,从每个分组的第二个元素开始插入排序,即下标为step的元素
//每个分组交替进行插入排序
for(int i = step; i < arr.length; i++) {
//tmp记录将要排序的元素
int tmp = arr[i];
//j记录每组的最后一个已排序元素的下标
int j = i - step;
while(j >= 0 && arr[j] > tmp) {
//将改组大于要排序元素的元素向后移动
arr[j + step] = arr[j];
//j向前移动,找到该分组的上一个已排序元素
j = j - step;
}
//退出循环时的j是本组要插入的正确位置的上一个位置,j+step为要插入的正确位置,将
//要插入的元素插入到正确位置
arr[j + step] = tmp;
}
//该步长的分组全部完成局部排序后,缩短步长
step = (int)Math.floor(step / 3);
}
return arr;
}
}
5.归并排序
5.1 性质
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 稳定排序
- 非原地排序
5.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 归并排序
* @author asus
*
*/
public class MergeSort implements IArraySort {
/**
* 归并排序采用了分而治之的思想,归并即合并之意
* 主要有三个重要的步骤:分,治,合
* 分:分组
* 治:只需不断地分组,直到每组只有一个元素
* 合:合并两个有序数组
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
if(arr.length < 2) {
return arr;
}
//找到未排序的数组的中间位置
int middle = (int)Math.floor(arr.length / 2);
//左分组的待排序数组,包含头,不包含尾
int[] left = Arrays.copyOfRange(arr, 0, middle);
//右分组的待排序数组,包含头,不包含尾
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
//返回合并两个有序数组的结果
return merge(sort(left),sort(right));
}
/**
* 合并两个有序数组
* @param left 左组的有序数组
* @param right 右组的有序数组
* @return
*/
protected int[] merge(int[] left, int[] right) {
//定义一个数组来存储合并后的有序数组
int[] result = new int[left.length + right.length];
int i = 0;
//左右有序链表都有剩余元素,比较第一个元素,将较小的元素存入结果数组
while(left.length > 0 && right.length > 0) {
if(left[0] <= right[0]) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
//左有序链表有剩余,将左有序链表剩余部分存入结果数组
while(left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
//右有序链表有剩余,将右有序链表剩余部分存入结果数组
while(right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
}
6.快速排序
6.1 性质
- 时间复杂度:O(nlogn)
- 空间复杂度:O(nlogn)
- 非稳定排序
- 原地排序
6.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 快速排序采用了分而治之的思想,用递归来实现
* 主要有三个重要的步骤:分,治
* 分:即分割,不断地找到基准点,将无序数组以基准点分割成左右分区,
* 基准点处于有序状态,左分区的值都小于基准点,右分区的
* 值都大于基准点
* 治:只需不断地分割,每分割一次,就有一个基准点处于有序状态,
* 直到每个分组只有一个元素,则所有的元素都处于有序状态
* @author asus
*
*/
public class QuickSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
//对arr进行拷贝,不改变原参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//返回快速排序的结果
return quickSort(arr, 0, arr.length - 1);
}
//对无序数组进行快速排序
private int[] quickSort(int[]arr, int left, int right) {
if(left < right) {
//如果left < right,说明排序还没有完成,递归的对左右分区进行快速排序
//先找到分区的基准点,该点处于有序状态
int partitionIndex = partition1(arr, left, right);
//对左右分区递归的进行快速排序
quickSort(arr, left, partitionIndex -1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
/**
* 分割操作方法一:单向调整
* 缺点:该方法可能会对一个元素进行多次无效的调整,因为i和index可能对应同一个元素
* 或者对一个元素进行多次重复交换
* 该方法用于找到分区的基准,该基准处于有序状态,基准左侧的值都小与基准值,
* 基准右侧的值都大于基准值
* @param arr
* @param left
* @param right
* @return 返回基准值坐标
*/
private int partition1(int[ ] arr, int left, int right) {
//pivot基准值
int pivot = left;
int index = pivot + 1;
for(int i = index; i <= right; i++) {
if(arr[i] < arr[pivot]) {
//说明i下标对应的值应该在pivot的左侧,而index就是用来做找到基准值pivot的临时变量
swap(arr, i, index);
//交换位置后,index位置的元素一定小与pivot,所以index向后移动
index++;
}
}
//循环结束后index - 1 便是基准值的下标,交换原基准值下标与现基准值下标所对应的值
swap(arr, pivot, index - 1);
return index - 1;
}
/**
* 交换两个下标对应的值得操作
* @param i
* @param j
*/
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 分割操作方法二:双向调整
* 优点:比单向调整效率更高,每个元素只需要进行一次调整
* @param arr
* @return
*/
private int partition2(int[] arr, int left, int right) {
int pivot = left;
int i = left + 1;
int j = right;
while(true) {
while( i <= j && arr[i] <= arr[pivot]) {
i++;
}
while( i <= j && arr[j] >= arr[pivot]) {
j--;
}
if(i >= j) {
break;
}
swap(arr, i, j);
}
//此时j下标对应的应该就是基准值的位置,交换原基准值下标与现基准值下标对应的值
swap(arr, pivot, j);
//返回现在的基准值下标
return j;
}
}
优化之后代码如下:
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 桶排序优化
* @author asus
*
*/
public class CountingSort1 implements IArraySort {
/**
* 桶排序的优化:上面的代码中,我们是根据 max 的大小来创建对应大小的数组,假如原数组只有10个元素,
* 并且最小值为 min = 10000,最大值为 max = 10005,那我们创建 10005 + 1 大小的数组不是很吃亏,最大值
* 与最小值的差值为 5,所以我们创建大小为6的临时数组就可以了。
* 也就是说,我们创建的临时数组大小 (max - min + 1)就可以了,然后在把 min作为偏移量。优化之后的代码如下所示:
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//拷贝arr数组,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//获取数组的最大值
int maxValue = getMaxValue(arr);
//获取数组的最小值
int minValue = getMinValue(arr);
return countingSort(arr, maxValue, minValue);
}
private int[] countingSort(int[] arr, int maxValue, int minValue) {
int bucketLen = maxValue - minValue + 1;
//创建一个长度为最大值与最小值的差值的数组
int[] bucket = new int[bucketLen];
//遍历要排序的数组将要排序的数组中的元素对最小值的偏移量与新数组的下标对应,
//元素出现的次数为新数组对应下标的内容
for(int i = 0; i < arr.length; i++) {
bucket[arr[i] - minValue]++;
}
//根据新数组下标即元素值实现原数组的排序过程
int sortedLen = 0;
for(int j = 0; j < bucketLen; j++) {
while(bucket[j] > 0) {
arr[sortedLen++] = j + minValue;
bucket[j]--;
}
}
return arr;
}
/**
* 获取数组的最大值
* @param arr
* @return
*/
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for(int value : arr) {
if(maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
/**
* 获取数组中的最小值
* @param arr
* @return
*/
private int getMinValue(int[] arr) {
int minValue = arr[0];
for(int value : arr) {
if(minValue > value) {
minValue = value;
}
}
return minValue;
}
}
7.堆排序
7.1 性质
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 非稳定排序
- 原地排序
7.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 堆排序
* @author asus
*
*/
public class HeapSort implements IArraySort {
/**
* 堆排序,最主要的是构建最大堆,将对顶元素与未排序部分最后一个元素交换
* 位置,则此元素处于有序状态,直到最大堆只剩下最后一个元素,则所有元素有序
* 在构建最大堆的时候,一定要从最后一个父节点开始下浮
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//拷贝数组arr,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//二叉堆一般使用数组结构
int len = arr.length;
//将整个数组构建成二叉堆(最大堆)
buildMaxHeap(arr, len);
//因为最大堆的堆顶元素,即下标为0的元素是整个二叉堆的最大值,
//因此每次取出最大值,然后将二叉堆的长度缩小1,将最后一个元素
//补充到堆顶,并将此元素在二叉堆中归位就会形成新的二叉堆,重复
//以上步骤,直到二叉堆为空
for(int i = len - 1; i > 0; i--) {
//将第一个元素与无序部分的最后一个元素交换,则最后一个元素处于有序状态
swap(arr,0, i);
//将二叉堆的长度缩小1,并将新的堆顶元素归位,使其重新成为最大堆
len--;
//heapify堆化
heapify(arr, 0, len);
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for(int i = (int)Math.floor(len / 2); i >= 0; i--) {
//建堆的时候一定是从最后一个父节点开始下浮,而此时的i便是可能存在的最大的父节点下标
//将此父节点堆化
heapify(arr, i, len);
}
}
/**
* 堆化传进来的父节点i
* @param arr
* @param i
* @param len
*/
private void heapify(int[] arr, int i, int len) {
//该父节点的左孩子的下标
int left = 2 * i + 1;
//该节点的右孩子的下标
int right = 2 * i + 2;
//记录最大值的下标
int largest = i;
if(left < len && arr[left] > arr[largest]) {
//说明该父节点存在左孩子,且左孩子大于父节点,则此父节点需要下浮
largest = left;
}
if(right < len && arr[right] > arr[largest]) {
//说明该父节点存在右孩子,且右孩子大于父节点,则此父节点需要下浮
largest = right;
}
//说明此时largest对应的值是最大值,交换位置,并继续将堆顶元素堆化
if(i != largest) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
/**
* 交换两个下标所对应的元素
* @param arr
* @param i
* @param j
*/
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
8.计数排序
8.1 性质
- 时间复杂度:O(n+k)
- 空间复杂度:O(k)
- 稳定排序
- 非原地排序
8.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 计数排序
* @author asus
*
*/
public class CountingSort implements IArraySort {
/**
* 计数排序,是一种适合于最大值和最小值的差值不是很大的排序。
*基本思想:把数组元素作为数组的下标,然后用一个临时数组统计该元素
*出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把
*临时数组统计的数据从小到大汇总起来,此时汇总起来是数据是有序的。
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//拷贝arr数组,不改变原参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//获取数组中的最大值
int maxValue = getMaxValue(arr);
//返回计数排序的结果
return countingSort(arr, maxValue);
}
private int[] countingSort(int[] arr, int maxValue) {
int bucketLen = maxValue + 1;
//创建一个新数组,数组的长度为要排序的数组的最大值,其下标对应要排序的数组中的元素
int[] bucket = new int[bucketLen];
//将要排序的数组中的元素存入新建数组对应的下标,值为该元素出现的次数
for(int value : arr) {
bucket[value]++;
}
//根据新数组的下标即存储的内容,完成排序过程
int sortedIndex = 0;
for(int i = 0; i < bucketLen; i++) {
while(bucket[i] > 0) {
arr[sortedIndex++] = i;
bucket[i]--;
}
}
return arr;
}
/**
* 获取数组中的最大值
* @param arr
* @return
*/
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for(int value : arr) {
if(maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
}
9.桶排序
9.1 性质
- 时间复杂度:O(n+k)
- 空间复杂度:O(n+k)
- 稳定排序
- 非原地排序
9.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 桶排序
* @author asus
*
*/
public class BucketSort implements IArraySort {
//创建插入排序对象
private static final InsertSort insertSort = new InsertSort();
/**
* 桶排序是对计数排序的一种升级,因为不一定所有的元素都是整数,都可以
* 与其下标一一对应,并且元素可能比较分散,就可以用桶排序来代替计数排序.
* 创建等间距的桶,将待排序元素放入对应的桶中,再将每个桶内部的元素进行
* 插入排序,最后将桶中的元素依次放入结果数组
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//拷贝数组arr,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
/**
*
* @param arr
* @param bucketSize 每个桶数据的范围
* @return
* @throws Exception
*/
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if(arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
//找到数组中的最大值和最小值
for(int value : arr) {
if(value < minValue) {
minValue = value;
}else if(value > maxValue) {
maxValue = value;
}
}
//计算需要的桶的数量
int bucketCount = (int)Math.floor((maxValue - minValue) / bucketSize) + 1;
//创建需要的桶
int[][] buckets = new int[bucketCount][0];
//将待排序数组的每一个元素添加到对应的桶中
for(int i = 0; i < arr.length; i++) {
int index = (int)Math.floor((arr[i] - minValue) / bucketSize);
//buckets[index]对应的是该元素所对应的桶,是一个一维数组
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
//将每个桶中的元素排序并添加到结果数组中
for(int[] bucket : buckets) {
//说明该桶是一个空桶
if(bucket.length <= 0) {
continue;
}
//对每个桶内部的元素进行插入排序
bucket = insertSort.sort(bucket);
//遍历排序好的数组,将元素添加到结果数组中
for(int value : bucket) {
arr[arrIndex++] = value;
}
}
return arr;
}
/**
* 每添加一个元素前,先将桶扩容1
* @param arr
* @param value
* @return
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
//将该元素添加到桶的最后一个位置
arr[arr.length - 1] = value;
return arr;
}
}
10.基数排序
10.1 性质
- 时间复杂度:O(kn)
- 空间复杂度:O(n+k)
- 稳定排序
- 非原地排序
10.2 代码
package com.my.sort.impl;
import java.util.Arrays;
import com.my.sort.IArraySort;
/**
* 基数排序
* @author asus
*
*/
public class RadixSort implements IArraySort {
/**
* 基数排序,先计算绝对值最大的数的位数,创建20个桶,从最低位开始进行最大位数次
* 排序,每次排序时将元素放入对应的桶中
*/
@Override
public int[] sort(int[] sourceArray) throws Exception {
//拷贝数组arr,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
//获取待排序数组的最大值有多少位数
int maxDigit = getMaxDigit(arr);
//返回基数排序的结果
return radixSort(arr, maxDigit);
}
/**
* 获取待排序数组中的绝对值最大的数的位数
* @param arr
* @return
*/
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLength(maxValue);
}
/**
* 获取数组中的最大值
* @param arr
* @return
*/
private int getMaxValue(int[] arr) {
int maxValue = Math.abs(arr[0]);
for(int value : arr) {
if(maxValue < Math.abs(value)) {
maxValue = Math.abs(value);
}
}
return maxValue;
}
/**
* 获取元素的位数
* @param num
* @return
*/
private int getNumLength(long num) {
if(num == 0) {
return 1;
}
int length = 0;
for(long tmp = num; tmp != 0; tmp /= 10) {
length++;
}
return length;
}
/**
* 基数排序
* @param arr
* @param maxDigit
* @return
*/
private int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for(int i = 1; i <= maxDigit; i++, mod *= 10, dev *= 10) {
//考虑负数,其中[0-9]对应负数,[10-19]对应正数
int[][] buckets = new int[20][0];
for(int j = 0; j < arr.length; j ++) {
int index = ((arr[j] % mod) / dev) + 10;
buckets[index] = arrAppend(buckets[index], arr[j]);
}
int arrIndex = 0;
for(int[] bucket : buckets) {
//该桶为空
if(bucket.length <= 0) {
continue;
}
//将该桶中的元素放入结果数组中
for(int value : bucket) {
arr[arrIndex++] = value;
}
}
}
return arr;
}
/**
* 自动将桶的大小扩充1后,将元素添加进对应的桶中
* @param arr
* @param valuee
* @return
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}