排序也称排序算法(Sort Algorithm),是将一组数据,依指定的顺序进行排列的过程。
术语说明:
内部排序:
指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
外部排序法:
数据量过大无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
比较和非比较的区别:
常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。
比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。
非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
算法思想
冒泡排序(Bubble Sorting)的基本思想:是通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
算法优化
在排序的过程中各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换,从而减少不必要的比较。
算法描述
算法实现
public class BubbleSortTest {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(8);
System.err.println(Arrays.toString(array));
bubbleSort(array);
}
private static void bubbleSort(int[] array) {
int length = array.length - 1;
boolean flag = false;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
flag = true;
// 交换相邻的值
array[j] = array[j] + array[j + 1];
array[j + 1] = array[j] - array[j + 1];
array[j] = array[j] - array[j + 1];
}
}
// 优化
if (!flag) {
break;
} else {
// 重置标志位
flag = false;
}
}
System.err.println(Arrays.toString(array));
}
}
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。
算法思想
选择排序(select sorting)的基本思想是:第一次从 arr[0]~arr[n-1] 中选取最小值与 arr[0] 交换,第二次从 arr[1]~arr[n-1] 中选取最小值与 arr[1]交换,第三次从 arr[2]~arr[n-1] 中选取最小值与 arr[2] 交换…第 i 次从 arr[i-1]~arr[n-1] 中选取最小值与 arr[i-1]交换…第 n-1 次从 arr[n-2]~arr[n-1] 中选取最小值与 arr[n-2]交换,总共通过 n-1 次得到一个按排序码从小到大排列的有序序列。
算法实现:
public class SelectSortTest {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(10);
System.err.println(Arrays.toString(array));
selectSort(array);
}
private static void selectSort(int[] array) {
int length = array.length;
for (int i = 0; i < length - 1; i++) {
int minIndex = i;
int min = array[i];
for (int j = i + 1; j < length; j++) {
if (min > array[j]) {
minIndex = j;
min = array[j];
}
}
// 交换值
if (minIndex != i) {
array[minIndex] = array[i];
array[i] = min;
}
}
System.err.println(Arrays.toString(array));
}
}
插入排序属于内部排序法,对于欲排序的元素以插入的方式寻找该元素的适当位置,以达到排序的目的。
算法思想
插入排序(Insertion Sorting)的基本思想:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
算法描述
算法实现
public class InsertSortTest {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(8);
System.err.println(Arrays.toString(array));
insetSort(array);
}
private static void insetSort(int[] array) {
for (int i = 1; i < array.length; i++) {
// 定义插入的值
int insert = array[i];
// 定义插入的下标
int insertIndex = i - 1;
// insertIndex >= 0 保证插入的位置不会数组越界
// insert < array[insertIndex] 当要插入的值小于插入下标位置的值时
while (insertIndex >= 0 && insert < array[insertIndex]) {
// 将插入下标位置的值复制到下标的下一个空间 min = 1 {2, 3, 1} ---> {2, 3, 3} ---> {2, 2, 3} ---> {1, 2, 3}
array[insertIndex + 1] = array[insertIndex];
// 将插入下标后移
insertIndex--;
}
// 找到要插入的位置
if (insertIndex + 1 != i) {
array[insertIndex + 1] = insert;
}
}
System.err.println(Arrays.toString(array));
}
}
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
算法思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
算法描述
希尔排序的基本步骤:选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
算法实现
希尔排序时,,对有序序列在插入时可以采用交换法或移动法。
public class ShellSortTest {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(10);
System.err.println(Arrays.toString(array));
shellSort2(array);
}
/**
* 交换法
*
* @param array
*/
private static void shellSort1(int[] array) {
int length = array.length;
// 按下标的一定增量分组,增量 gap,并逐步的缩小增量
for (int gap = length / 2; gap > 0; gap /= 2) {
// 遍历所有的分组
for (int i = gap; i < length; i++) {
// 遍历各组中所有的元素(共 gap 组,每组有 length/gap 个元素),步长 gap
for (int j = i - gap; j >= 0; j -= gap) {
// 交换法:如果当前元素大于加上步长后的那个元素则交换
if (array[j] > array[j + gap]) {
array[j] = array[j] + array[j + gap];
array[j + gap] = array[j] - array[j + gap];
array[j] = array[j] - array[j + gap];
}
}
}
}
System.err.println(Arrays.toString(array));
}
/**
* 位移法
*
* @param array
*/
private static void shellSort2(int[] array) {
int length = array.length;
// 按下标的一定增量分组,增量 gap,并逐步的缩小增量
for (int gap = length / 2; gap > 0; gap /= 2) {
// 遍历所有的分组
for (int i = gap; i < length; i++) {
// 从第 gap 个元素,逐个对其所在的组进行直接插入排序
int insert = array[i];
int insertIndex = i;
// insertIndex - gap >= 0 插入位置不能越界
while (insertIndex - gap >= 0 && insert < array[insertIndex - gap]) {
array[insertIndex] = array[insertIndex - gap];
insertIndex -= gap;
}
// 当退出 while 后,就给 insert 找到插入的位置
array[insertIndex] = insert;
}
}
System.err.println(Arrays.toString(array));
}
}
快速排序(Quicksort)是对冒泡排序的一种改进。
算法思想
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的数据均比另一部分的数据小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
算法实现
public class QuickSortTest {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(10);
System.err.println(Arrays.toString(array));
quickSort2(array, 0, array.length - 1);
System.err.println(Arrays.toString(array));
}
private static void quickSort2(int[] array, int paramLeft, int paramRight) {
if (paramLeft > paramRight) {
return;
}
// 左下标
int left = paramLeft;
// 右下标
int right = paramRight;
// 中间值
int pivot = array[left];
while (left < right) {
// 从右到左查找小于中间值的元素
while (left < right && array[right] >= pivot) {
right--;
}
// 从左到右查找大于中间值的元素
while (left < right && array[left] <= pivot) {
left++;
}
// 交换
if (left != right) {
array[left] = array[left] + array[right];
array[right] = array[left] - array[right];
array[left] = array[left] - array[right];
}
}
// 交换中间值
array[paramLeft] = array[left];
array[left] = pivot;
// 向左递归
quickSort2(array, paramLeft, left - 1);
// 向右递归
quickSort2(array, left + 1, paramRight);
}
}
算法思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治策略(divide-and-conquer)。分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起即分而治之。
算法描述
算法实现
public class MergetSort {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(10);
System.err.println(Arrays.toString(array));
int[] ints = mergeSort(array);
System.err.println(Arrays.toString(ints));
}
/**
* 分解数组
*
* @param array
* @return
*/
public static int[] mergeSort(int[] array) {
if (array.length < 2) {
return array;
}
int mid = array.length / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(mergeSort(left), mergeSort(right));
}
/**
* 归并排序——将两段排序好的数组结合成一个排序数组
*
* @param left
* @param right
* @return
*/
public static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= left.length) {
result[index] = right[j++];
} else if (j >= right.length) {
result[index] = left[i++];
} else if (left[i] > right[j]) {
result[index] = right[j++];
} else {
result[index] = left[i++];
}
}
return result;
}
}
class MergetSort1 {
public static void main(String[] args) {
int[] array = CreateArrayUtils.createArray(10);
int[] temp = new int[array.length];
System.err.println(Arrays.toString(array));
mergeSort(array, 0, array.length - 1, temp);
}
/**
* 分 + 合方法
*
* @param array
* @param left
* @param right
* @param temp
*/
private static void mergeSort(int[] array, int left, int right, int[] temp) {
if (left < right) {
// 中间索引
int midIndex = (left + right) / 2;
// 向左递归进行分解
mergeSort(array, left, midIndex, temp);
// 向右递归进行分解
mergeSort(array, midIndex + 1, right, temp);
// 合并
merge(array, left, midIndex, right, temp);
}
}
/**
* 合并的方法
*
* @param array 排序的原始数组
* @param left 左边有序序列的初始索引
* @param midIndex 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
private static void merge(int[] array, int left, int midIndex, int right, int[] temp) {
// 初始化 i,左边有序序列的初始索引
int i = left;
// 初始化 j,右边有序序列的初始索引
int j = midIndex + 1;
// 指向 temp 数组的当前索引
int t = 0;
// 先把左右两边(有序)的数据按照规则填充到 temp 数组,直到左右两边的有序序列,有一边处理完毕为止
while (i <= midIndex && j <= right) {//继续
// 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素,将左边的当前元素,填充到 temp 数组,然后 t++, i++
if (array[i] <= array[j]) {
temp[t] = array[i];
t += 1;
i += 1;
} else {
// 反之将右边有序序列的当前元素填充到 temp 数组
temp[t] = array[j];
t += 1;
j += 1;
}
}
// 把有剩余数据的一边的数据依次全部填充到 temp
while (i <= midIndex) {
// 左边的有序序列还有剩余的元素,就全部填充到 temp
temp[t] = array[i];
t += 1;
i += 1;
}
while (j <= right) {
// 右边的有序序列还有剩余的元素,就全部填充到 temp
temp[t] = array[j];
t += 1;
j += 1;
}
// 将 temp 数组的元素拷贝到 arr,注意并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
// 第一次合并 tempLeft = 0 right = 1,tempLeft = 2 right = 3,tL=0 ri=3
// 最后一次 tempLeft = 0 right = 7
while (tempLeft <= right) {
array[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
System.err.println(Arrays.toString(array));
}
}
算法思想
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中,作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法,计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数,然后根据数组C来将A中的元素排到正确的位置,只能对整数进行排序。
算法描述
算法实现
public class CountingSortTest {
public static void main(String[] args) {
int[] array = new int[]{7, 39, 14, 29};//CreateArrayUtils.createArray(4);
System.err.println(Arrays.toString(array));
countingSort(array);
}
private static void countingSort(int[] array) {
// 最大最小值初始化
int min = array[0], max = array[0];
// 寻找最大最小值
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
// 定义一个额外的数组,相当于是一个空桶,可以存放的下标长度是从 min 到 max 的长度大小
int[] bucket = new int[max - min + 1];
// 初始化桶填充,这里全部为0
Arrays.fill(bucket, 0);
// 保证原数组的元素在bucket中都能占据一个位置
for (int i = 0; i < array.length; i++) {
// 计数,例如一个key为i的元素出现了3次,那么bucket[i-min]=3。
bucket[array[i] - min]++;
}
// 数组内容回填
int index = 0, i = 0;
while (index < array.length) {
if (bucket[i] != 0) {
array[index] = i + min;
bucket[i]--;
index++;
} else {
i++;
}
}
System.err.println(Arrays.toString(array));
}
}
桶排序是计数排序的升级版,它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
算法思想
桶排序 (Bucket sort)的基本思想:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
算法描述
算法实现
public class BucketSortTest {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(5);
list.add(9);
list.add(15);
list.add(2);
list.add(7);
System.err.println(list.toString());
List sort = bucketSort(list, 5);
System.err.println(sort.toString());
}
private static List bucketSort(List list, int bucketSize) {
if (null == list || list.size() < 2) {
return list;
}
// 数组的容量
int size = list.size();
// 数组最大最小值初始化,用来计算映射函数
int min = list.get(0), max = list.get(0);
// 寻找最大最小值
for (int i = 1; i < size; i++) {
if (list.get(i) > max) {
max = list.get(i);
}
if (list.get(i) < min) {
min = list.get(i);
}
}
List result = new ArrayList<>(size);
// 桶数量的映射函数如下(事先设定好的,也可以试着随便用其他的映射关系看看结果),计算出桶的数量
int bucketCount = (max - min) / bucketSize + 1;
// 将整个桶数组用ArrayList表示,每个桶用存放Integer的ArrayList表示
List> buckets = new ArrayList<>(bucketCount);
// 初始化桶,把每一个桶都初始化为一个ArrayList
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
// 根据映射函数实现的对应关系,将属于同一个桶的元素放入对应的桶
for (int i = 0; i < size; i++) {
// 映射函数实现元素与桶之间的对应
int item = (list.get(i) - min) / bucketSize;
// 将属于同一个桶的元素放入对应的桶
buckets.get(item).add(list.get(i));
}
// 对每个桶进行排序
for (int i = 0; i < bucketCount; i++) {
// 如果带排序数组中有重复数字时
if (bucketSize == 1) {
List arrayList = buckets.get(i);
for (int j = 0; j < arrayList.size(); j++) {
result.add(arrayList.get(j));
}
} else {
if (bucketCount == 1) {
bucketSize--;
}
// 递归对每个桶进行排序,直到每个桶中只有一个元素
List temp = bucketSort(buckets.get(i), bucketSize);
for (int j = 0; j < temp.size(); j++) {
result.add(temp.get(j));
}
}
}
return result;
}
}
基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),n 为数组长度,k 为数组中的数的最大的位数。
算法思想
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
算法描述
算法实现