写下这篇博客作用是在于自己忘记后,通过这篇文章再次回忆起来!
冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。时间复杂度:O(n²),空间复杂度:O(1)。
实现步骤
1,遍历待排序数组,每一轮确定一个最大值或最小值的位置。
2,从第一个元素开始,一次比较相邻的两个元素,如果前者大于后者,交换他们位置。
3,每一轮遍历结束后,最后一个元素已经是本轮最大值或最小值,因此下一轮遍历时只需要 比较前 n - 1 个元素。
4,最终得到的数组就是排好序的数组。
动图
代码
public void bubbleSort(int[] array) {
// 遍历 n 次
for (int i = 0; i < array.length - 1; i++) {
// 比较 n - i 次
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。时间复杂度:O(n²),空间复杂度:O(1)。
实现步骤
1,遍历待排序数组,每一轮确定一个最小值的位置。
2,假设当前未排序部分第一个元素为最小值,然后在未排序部分查找最小值。
3,如果找到最小值,将它与当前未排序部分的第一个元素交换位置。
4,下一轮遍历时,只需要在剩余的未排序部分中查找最小值。
5,最终得到的数组就是排好序的数组。
动图
代码
public void selectSort(int[] array) {
int minIndex = 0;
// 遍历 n - 1 次
for (int i = 0; i < array.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < array.length; j++) {
minIndex = array[j] < array[minIndex] ? j : minIndex;
}
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法
实现步骤
1,认为第一个元素是已经排好序的。
2,从第二个元素开始,将这个元素插入到前面已经排好序的元素中。
3,在已排好序的元素中,从后往前扫描,如果当前元素比前面的元素小,则交换两元素位置。
4,重复步骤三,直到当前元素找到插入位置或者扫描到排序的第一个元素为止。
动图
代码
public void insertSort(int[] array) {
// 从 1 开始排序,因为默认 0 位置是已排序的元素
for (int i = 1; i < array.length; i++) {
// 下元素和该元素比较(已排序),如果该元素大于下一元素,则交换位置
for (int j = i - 1; j >= 0 && array[j] > array[j + 1]; j--) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是插入排序算法的一种更高效的改进版本。时间复杂度:O(n²),空间复杂度:O(1)。
实现步骤
1,选取一个初始步长,通常为数组长度的一半。
2,根据步长分组,对于每组进行插入排序。
3,缩小步长,重复步骤二,直到步长为一。
4,最后对整个数组进行一次插入排序,这时数组已经基本有序。
动图
代码
public void shellSort(int[] array) {
// 初始增量 按照 n / 2 后续每次除 2
for (int inc = array.length / 2; inc > 0; inc /= 2) {
// 遍历分组后的数据
for (int i = inc; i < array.length; i++) {
int temp = array[i];
// 满足条件 当前元素小于前一个元素交换位置
for (int j = i; j >= inc && temp < array[j - inc]; j -= inc) {
array[j] = array[j - inc];
array[j - inc] = temp;
}
}
}
}
归并排序
建立在归并操作上的一种有效的排序算法。它将多个排序列表作为输入并生成单个列表作为输出,包含按排序顺序排列的输入列表的所有元素。 时间复杂度:O(n log n),空间复杂度:O(n)。
实现步骤
1,将待排序数组从中间位置划分为左右两个子数组。
2,递归对左右两个子数组进行归并排序。
3,合并左右两个有序数组,将他们合并成一个有序数组。
4,重复上述步骤,直到所有的子数组都有序。
动图
代码
/**
* 归并排序
*
* @param array 排序数组
* @param left 数组最小索引(最左)
* @param right 数组最大索引(最右)
*/
public void mergeSort(int[] array, int left, int right) {
// 条件成立代表已经递归到底了
if (left >= right) {
return;
}
// 分组序列中间索引位置,
int middle = (left + right) / 2;
// 左分组
mergeSort(array, left, middle);
// 右分组
mergeSort(array, middle + 1, right);
// 合并
merge(array, left, middle, right);
}
/**
* 二路合并
*
* @param array 排序数组
* @param left 数组最小索引(最左)
* @param middle 中间索引
* @param right 数组最大索引(最右)
*/
public void merge(int[] array, int left, int middle, int right) {
// 左序列索引
int leftIndex = left;
// 右序列索引
int rightIndex = middle + 1;
int[] temp = new int[right - left + 1];
// 临时数组下标
int index = 0;
// 循环,条件是左边序列索引不超过中间索引,右边序列索引不超过最大索引
while (leftIndex <= middle && rightIndex <= right) {
// 左边比右边大,取左边放在临时数组,反之亦然
if (array[leftIndex] <= array[rightIndex]) {
temp[index++] = array[leftIndex++];
} else {
temp[index++] = array[rightIndex++];
}
}
// 存在右边序列遍历完,左边序列未遍历完的情况
while (leftIndex <= middle) {
temp[index++] = array[leftIndex++];
}
// 存在左边序列表里万,右边序列未遍历完的情况
while (rightIndex <= right) {
temp[index++] = array[rightIndex++];
}
// 到这里,临时数组就是已排好序的数据,将其替换带原数组对应位置
for (int i = 0; i < temp.length ; i++) {
array[i + left] = temp[i];
}
}
快速排序
快速排序(Quicksort),是对冒泡排序算法的一种改进。时间复杂度:O(n²),空间复杂度:O(n)。
实现步骤
1,选择一个基准值,通常是数组的最后一个元素。
2,通过一趟扫描,将数组分为左右两个区域,左区域都小于等于基准值,右区域都大于基准值,基准值到了正确的位置上。
3,递归对左右两个区域进行快速排序。
动图
代码
/**
* 快速排序
*
* @param array 排序数组
* @param left 最左索引
* @param right 最右索引
*/
public void quickSort(int[] array, int left, int right) {
// 左边界 > 右边界 代表不需要拆分了
if (left > right) {
return;
}
// 中心轴
int pivotIndex = partition(array, left, right);
// 左数组
quickSort(array, left, pivotIndex - 1);
// 右数组
quickSort(array, pivotIndex + 1, right);
}
/**
* 划分数组 得到中心轴索引
*
* @param array 排序数组
* @param left 最左索引
* @param right 最右索引
* @return 中心轴索引
*/
public int partition(int[] array, int left, int right) {
// 选择一个中心轴
int pivot = array[left];
// 左边界索引
int leftIndex = left;
// 右边界索引
int rightIndex = right;
// 左边界小于右边界,还需要排序
while (leftIndex < rightIndex) {
// 左边界 <= 右边界,并且左边界数据 <= 中心轴数据
while (leftIndex <= rightIndex && array[leftIndex] <= pivot) {
// 左边索引右移一位
leftIndex++;
}
// 左边界 <= 右边界,并且右边界数据 > 中心轴数据
while (leftIndex <= rightIndex && array[rightIndex] > pivot) {
// 右边索引左移一位
rightIndex--;
}
// 左边界数据 < 右边界数据
if (leftIndex < rightIndex) {
swap(array, leftIndex, rightIndex);
}
}
// 正常交换 到这里,已经遍历出来最右边需要交换的位置索引
swap(array, left, rightIndex);
return rightIndex;
}
/**
* 数组下标数据交换
*
* @param array 数组
* @param i 交换下标1
* @param j 交换下标2
*/
public void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。时间复杂度:O(n log n),空间复杂度:O(1)。
实现步骤
1,初始化堆:从最后一个非叶子节点开始,对所有非叶子节点进行堆化操作,构建出一个大根堆或小跟堆。
2,一次取出堆顶元素,放入已排序的部分末尾。将堆顶元素和已排序部分的末尾元素进行交换,然后跟堆顶元素进行一次堆化操作。
3,重复步骤二,知道堆为空。
动图
代码
/**
* 堆排序
*
* @param array 排序数组
*/
public void heapSort(int[] array) {
int length = array.length;
// 构建堆(初始为最大堆)
for (int i = length / 2 - 1; i >= 0; i--) {
heapify(array, length, i);
}
// 从堆中提取一个一个元素
for (int i = length - 1; i > 0; i--) {
// 将当前根(最大的元素) 移动到已排序的列表的末尾
swap(array,0,i);
// 再次构造堆(从未排序的元素中)
heapify(array, i, 0);
}
}
/**
* 维护堆的性质
*
* @param array 数组
* @param length 数组长度
* @param i 待维护节点的下标
*/
public void heapify(int[] array, int length, int i) {
// 初始化最大值为根
int largest = i;
// 左孩子
int leftSon = 2 * i + 1;
// 右孩子
int rightSon = 2 * i + 2;
// 左孩子大于根
if (leftSon < length && array[leftSon] > array[largest]) {
largest = leftSon;
}
// 右孩子大于根
if (rightSon < length && array[rightSon] > array[largest]) {
largest = rightSon;
}
// 如果根不是最大值,则代表左右孩子存在比根大的值,进行交换
if (largest != i) {
swap(array,i,largest);
// 递归 堆化受影响的子树
heapify(array, length, largest);
}
}
/**
* 数组下标数据交换
*
* @param array 数组
* @param i 交换下标1
* @param j 交换下标2
*/
public void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
计数排序
计数排序是一个非基于比较的排序算法,它的优势在于在对一定范围内的整数排序时,快于任何比较排序算法。时间复杂度: O(n+k),空间复杂度:O(k)。
实现步骤
1,遍历待排序数组,找到数组中的最大值。
2,初始化一个计数数组,数组长度为最大值加一。
3,遍历待排序数组,统计每个元素出现的次数,将其存入计数数组的对应位置中。
4,再次遍历计数数组,将值进行累加操作,将计数数组变成一个记录小于等于数组中某个元素个数的数组。
5,初始化一个排序后的数组,其长度和待排序数组相同。
6,倒序遍历待排序数组,将每个元素放到排序后数组对应位置上,同时更新计数数组中该元素的位置。
7,返回排序后的数组。
动图
代码
public int[] countSort(int[] array) {
// 结果集
int[] result = new int[array.length];
// 拿到数组最大值
int maxValue = findMaxValue(array) + 1;
// 计数数组
int[] count = new int[maxValue];
for (int i = 0; i < array.length; i++) {
// 进行计数操作
count[array[i]]++;
}
// 进行计数累加操作
for (int i = 1; i < count.length; i++) {
count[i] = count[i] + count[i - 1];
}
// 从原数组的末尾开始遍历,将每个元素放入排序后的数组中正确的位置
for (int i = array.length - 1; i >= 0; i--) {
result[--count[array[i]]] = array[i];
}
return result;
}
/**
* 找到最大值
*
* @param array 素组
* @return maxValue
*/
public int findMaxValue(int[] array) {
int maxValue = array[0];
for (int i = 1; i < array.length; i++) {
maxValue = maxValue > array[i] ? maxValue : array[i];
}
return maxValue;
}
桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。时间复杂度:O(n²),空间复杂度:O(n + k)。
实现步骤
1,初始化桶:确定桶的数量和大小,并创建相应数量的空桶。
2,分配数据到桶中:遍历待排序数据,根据数据值的范围将每个数据分配到其对应的桶中。
3,对每个桶中的数据进行排序,使用其他排序方法(方法不限)对每个桶的数据进行排序。
4,合并桶中的数据:按照桶的顺序依次合并所有非空桶中的数据,得到有序序列。
动图
代码
/**
* 桶排序
*
* @param array 数组
* @param bucketSize 桶长度,自定义
*/
public void bucketSort(int[] array, int bucketSize) {
// 最小值
int minValue = array[0];
// 最大值
int maxValue = array[0];
// 找到数组中的最小值和最大值
for (int value : array) {
if (value < minValue) {
minValue = value;
}
if (value > maxValue) {
maxValue = value;
}
}
// 计算桶的数量 这里+1 是因为 java 除法运算会向下取整造成少一个桶
int bucketCount = (maxValue - minValue) / bucketSize + 1;
// 创建 bucketCount 个桶
List<List<Integer>> buckets = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
// 将数据分配到桶中
for (int value : array) {
// 得到桶索引
int bucketIndex = (value - minValue) / bucketSize;
buckets.get(bucketIndex).add(value);
}
// 对每个桶进行排序,排序方式不限制
for (List<Integer> bucket : buckets) {
// 冒泡排序
bubbleSort(bucket);
}
// 合并每个桶中的数据
int index = 0;
for (List<Integer> bucket : buckets) {
for (Integer value : bucket) {
array[index++] = value;
}
}
}
/**
* 冒泡排序
*
* @param list 排序数组
*/
public void bubbleSort(List<Integer> list) {
// 遍历 n 次
for (int i = 0; i < list.size() - 1; i++) {
// 比较 n - i 次
for (int j = 0; j < list.size() - 1 - i; j++) {
if (list.get(j) > list.get(j + 1)) {
swap(list, j, j + 1);
}
}
}
}
/**
* 集合下标元素交换
*
* @param list 集合
* @param i 交换下标1
* @param j 交换下标2
*/
public void swap(List<Integer> list, int i, int j) {
int temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。时间复杂度:O(n * k),空间复杂度:O(n + k)。
实现步骤
1,确定排序对象的最大位数。从最低位开始,按照个,十,百,千等等顺序对每一位进行排序。
2,对每一位上的数字,将其放入对应的桶中,
3,其中桶的数量等于数字的基数(一般情况为10)。
4,将所有桶中的数据按照桶的顺序依次取出,可得到按当前为排序后的结果。
5,重复上面步骤,直到对所有位都进行了排序,最终得到的结果就是完整的有序序列。
动图
代码
/**
* 基数排序 采用 LSD 基数排序
*
* @param array 数组
*/
public void radixSort(int[] array) {
// 找到最大值
int maxValue = getMaxValue(array);
// 计算位数
int digitNumber = getDigitNumber(maxValue);
// 使用桶排序进行基数排序
// 桶的数量,从 0~9 共 10 个桶
int bucketNum = 10;
// 每个桶可以存放所有元素
int[][] bucket = new int[bucketNum][array.length];
// 记录每个桶中元素的数量
int[] bucketCounts = new int[bucketNum];
// 循环遍历数组长度 n 是位数 * 10 则是多一位
for (int i = 0, n = 1; i < digitNumber; i++, n *= 10) {
// 将元素放入桶中
for (int value : array) {
int digit = (value / n) % 10;
bucket[digit][bucketCounts[digit]] = value;
bucketCounts[digit]++;
}
int index = 0;
// 将桶中的元素放回到原数组中
for (int j = 0; j < bucketCounts.length; j++) {
if (bucketCounts[j] == 0) {
continue;
}
// 赋值
for (int k = 0; k < bucketCounts[j]; k++) {
array[index++] = bucket[j][k];
}
// 清零 便于下次运算
bucketCounts[j] = 0;
}
}
}
/**
* 得到数组中最大值
*
* @param array 数组
* @return 最大值
*/
public int getMaxValue(int[] array) {
int maxValue = 0;
for (int value : array) {
maxValue = Math.max(value, maxValue);
}
return maxValue;
}
/**
* 获得数字的位数
*
* @param maxValue 最大数字
* @return 位数
*/
public int getDigitNumber(int maxValue) {
// 从个位开始
int digitNum = 1;
while (maxValue / 10 > 0) {
digitNum++;
maxValue /= 10;
}
return digitNum;
}