目录
序言
个人笔记, 适合快速复习,不适合初学者。
语言描述不清晰,推荐使用数据结构可视化 辅助理解。
一、冒泡排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 冒泡排序,时间复杂度 O(n^2)
* 原理:将大的数字往数组末尾移动,或将小的数字往前移。
*/
public class BubbleSort {
/**
* 简单冒泡排序
*
* @param arr
*/
public static void bubbleSort1(int[] arr) {
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]) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
}
System.out.println(Arrays.toString(arr));
}
/**
* 优化一:添加一个标志,当数组循环遍历时某次循环不再交换元素位置表示排序完成,避免了固定循环次数导致的数组排序完成仍然循环遍历的问题
* 适用于少量数据乱序的数组.
*
* @param arr
*/
public static void bubbleSort2(int[] arr) {
for (int i = 0; i < arr.length - 1; ++i) {
boolean exchange = false; //用于判断数据是否进行了交换,当某次循环没有数据交换时就是排序完成。
for (int j = 0; j < arr.length - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
exchange = true;
}
}
if (!exchange) break;
}
System.out.println(Arrays.toString(arr));
}
/**
* 优化二:每次记录交换位置的元素下标,下次只需遍历到此即可,因为后面的已经是有序了。
* 适用于前面大部分无序,后面大部分有序的数组.
*
* @param arr
*/
public static void bubbleSort3(int[] arr) {
int index = 0; //保存最后一次交换位置的元素的下标
int len = arr.length - 1; //内部循环,循环到每次保存的下标即可
for (int i = 0; i < arr.length - 1; ++i) {
boolean exchange = false; //用于判断数据是否进行了交换,当没有数据交换时就是排序完成。
for (int j = 0; j < len; ++j) {
if (arr[j] > arr[j + 1]) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
index = j;
exchange = true;
}
}
len = index;
if (!exchange) break;
}
System.out.println(Arrays.toString(arr));
}
/**
* 优化三:正向扫描找到最大值交换到最后,反向扫描找到最小值交换到最前面。
*
* @param arr
*/
public static void bubbleSort4(int[] arr) {
int maxindex = 0; //保存正向遍历每一次交换位置的元素的下标,下次循环以此为边界
int minindex = 0; //保存反向遍历每一次交换位置的元素的下标,下次循环以此为边界
for (int i = 0; i < arr.length - 1; ++i) {
boolean exchange = false; //用于判断数据是否进行了交换,当没有数据交换时就是排序完成。
for (int j = 0; j < arr.length - 1; ++j) {
if (arr[j] > arr[j + 1]) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
maxindex = j;
exchange = true;
}
}
//反向遍历
for (int m = maxindex; m > minindex; --m) { //由于maxindex后面的数据已经是有序的,因此反向遍历的位置就可以从maxindex开始。
if (arr[m - 1] > arr[m]) {
arr[m - 1] = arr[m - 1] ^ arr[m];
arr[m] = arr[m - 1] ^ arr[m];
arr[m - 1] = arr[m - 1] ^ arr[m];
exchange = true;
minindex = m;
}
}
if (!exchange) break;
}
System.out.println(Arrays.toString(arr));
}
public static void main(String[] args) {
int[] arr = {3, 2, 1, 5, 4, 7, 6, 8, 9, 10, 11, 0};
bubbleSort1(arr);
int[] arr2 = {3, 2, 1, 5, 4, 7, 6, 8, 9, 10, 11, 0};
bubbleSort2(arr2);
int[] arr3 = {3, 2, 1, 5, 4, 7, 6, 8, 9, 10, 11, 0};
bubbleSort3(arr3);
int[] arr4 = {3, 2, 6, 8, 23, 55, 88, 21, 99, 5, 8, 55, 66, 79, 10, 11, 0};
bubbleSort4(arr4);
}
}
二、插入排序
package Algorithms.sort;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
/**
* 插入排序,时间复杂度O(n^2)
* 把当前待插入的元素取出,让当前元素与之前的所有元素进行一一比较换位,直到遇到小于他的数;
* 由于是从前往后进行插入排序,前面的数必然已经有序,因此遇到小于当前元素的数即可跳出本次循环。
*/
public class InsertSort {
/**
* 简单插入排序
* 1,4,5,7,3,9,10 第0次
* ...
* 1,4,5,7,3,9,10 3
* 1,4,5,3,7,9,10 4
* 1,4,3,5,7,9,10 5
* 1,3,4,5,7,9,10 6
*
* @param arr
*/
public static void insertSort1(int[] arr) {
for (int i = 1; i < arr.length; ++i) {
for (int j = i; j > 0; --j) {
if (arr[j - 1] > arr[j]) {
arr[j - 1] = arr[j - 1] ^ arr[j];
arr[j] = arr[j - 1] ^ arr[j];
arr[j - 1] = arr[j - 1] ^ arr[j];
} else {
break;
}
}
}
// System.out.println(Arrays.toString(arr));
}
/**
* 优化:而是把当前待插入的元素(从后往前插,第一个小于前面元素的数为待插入元素)取出,让当前元素与之前的所有元素进行一一比较,若被比较的元素大于当前元素则将被比较元素的值复制到当前位置,
* 若被比较的元素的值小于或等于当前元素则将当前元素的值赋给当前比较位置(解释不好理解,配合下面的打印结果理解)
* [1, 2, 3, 6, 8, 9, 7, 4] 数组初始状态 当前待插入的元素为4
* [1, 2, 3, 6, 8, 9, 9, 4]
* [1, 2, 3, 6, 8, 8, 9, 4]
* [1, 2, 3, 6, 7, 8, 9, 9]
* [1, 2, 3, 6, 7, 8, 8, 9]
* [1, 2, 3, 6, 7, 7, 8, 9]
* [1, 2, 3, 6, 6, 7, 8, 9]
* [1, 2, 3, 4, 6, 7, 8, 9]
*
* 虽然看起来没有很大改变,只是减少了三个异或运算和两个赋值运算,但是在我电脑上测试,两个相同的1万个数据的数组,第一种需要8800-9900ms,第二种一直稳定在1800ms
*/
public static void insertSort2(int[] arr) {
System.out.println(Arrays.toString(arr));
for (int i = 1; i < arr.length; ++i) {
int temp = arr[i];
for (int j = i; j > 0; --j) {
if (arr[j - 1] > temp) {
arr[j] = arr[j - 1];
if ((j - 1) == 0) {
arr[0] = temp;
}
} else {
arr[j] = temp;
break;
}
System.out.println(Arrays.toString(arr));
}
}
}
public static void main(String[] args) {
int [] arr2 = {1,2,3,6,8,9,7,4};
insertSort2(arr2);
System.out.println(Arrays.toString(arr2));
// int [] arr = new int[100000];
// int [] arr2 = new int[100000];
// for(int i = 0 ;i<100000;i++){
// int random = ThreadLocalRandom.current().nextInt(99999);
// arr[i] = random;
// arr2[i] = random;
// }
//
// long st1 = new Date().getTime();
// insertSort1(arr);
// long et1 = new Date().getTime();
// System.out.println(et1);
// System.out.println(st1);
// System.out.println(et1-st1);
//
// System.out.println("====================");
//
// long st2 = new Date().getTime();
// insertSort2(arr2);
// long et2 = new Date().getTime();
// System.out.println(et2);
// System.out.println(st2);
// System.out.println(et2-st2);
}
}
三、快速排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 快速排序,时间复杂度O(n^2)
* 原理:将数组以某个元素作为中间值将比他大的数据和比他小的数据分成两堆(分好后数据是无序的),再把左右两边的数据作为两个数组再重复
* 之前的操作
*/
public class QuickSort {
/**
* 示例输出 由于每次循环第一次被覆盖的值都是index的值,所以遍历结束时(l=h)将index的值
* [2, 5, 4, 3, 6, 1, 8, 9, 7, 0] index=2;l=>1;h=>10 数组初始状态
* [0, 5, 4, 3, 6, 1, 8, 9, 7, 0] index=2;l=>1;h=>9
* [0, 5, 4, 3, 6, 1, 8, 9, 7, 5] index=2;l=>1;h=>8
* [0, 1, 4, 3, 6, 1, 8, 9, 7, 5] index=2;l=>2;h=>5
* [0, 1, 4, 3, 6, 4, 8, 9, 7, 5] index=2;l=>2;h=>4
* [0, 1, 4, 3, 6, 4, 8, 9, 7, 5] index=2;l=>2;h=>2
* [0, 1, 4, 3, 6, 4, 8, 9, 7, 5] index=2;l=>2;h=>2
* [0, 1, 2, 3, 6, 4, 8, 9, 7, 5] index=0;l=>0;h=>0
* [0, 1, 2, 3, 6, 4, 8, 9, 7, 5] index=0;l=>0;h=>0
* [0, 1, 2, 3, 6, 4, 8, 9, 7, 5] index=3;l=>3;h=>3
* [0, 1, 2, 3, 6, 4, 8, 9, 7, 5] index=3;l=>3;h=>3
* [0, 1, 2, 3, 5, 4, 8, 9, 7, 5] index=6;l=>5;h=>9
* [0, 1, 2, 3, 5, 4, 8, 9, 7, 8] index=6;l=>6;h=>8
* [0, 1, 2, 3, 5, 4, 8, 9, 7, 8] index=6;l=>6;h=>6
* [0, 1, 2, 3, 5, 4, 8, 9, 7, 8] index=6;l=>6;h=>6
* [0, 1, 2, 3, 4, 4, 6, 9, 7, 8] index=5;l=>5;h=>5
* [0, 1, 2, 3, 4, 4, 6, 9, 7, 8] index=5;l=>5;h=>5
* [0, 1, 2, 3, 4, 5, 6, 8, 7, 8] index=9;l=>8;h=>9
* [0, 1, 2, 3, 4, 5, 6, 8, 7, 8] index=9;l=>9;h=>9
* [0, 1, 2, 3, 4, 5, 6, 7, 7, 9] index=8;l=>8;h=>8
* [0, 1, 2, 3, 4, 5, 6, 7, 7, 9] index=8;l=>8;h=>8
* @param arr 需要排序的数组
* @param low 排序开始位置
* @param height 排序结束位置
*/
public static void quickSort(int[] arr, int low, int height) {
if (low >= height) return;
int l = low; //循环头部位置
int h = height; //循环尾部位置
int index = arr[low]; //将第一个元素提取出来作为分割这个数组的中间值
while (l < h) {
//从右往左遍历
while (l < h && arr[h] >= index) {
h--;
}
if (l < h) {
arr[l++] = arr[h];
}
System.out.println(Arrays.toString(arr)+" index="+index+";l=>"+l+";h=>"+h);
//从左往右遍历
while (l < h && arr[l] < index) {
l++;
}
if (l < h) {
arr[h--] = arr[l];
}
System.out.println(Arrays.toString(arr)+" index="+index+";l=>"+l+";h=>"+h);
}
//这里用l和h都是一样的,因为l和h最后退出条件的时候一定是相等的,也就意味着他们的左边都是小于index的数据,
// 右边都是大于index的数据,l和h就是中间位置,也就是index应该存放的位置,下一次循环也就是用这个中间位置的两边作为边界
arr[l] = index;
quickSort(arr, low, l - 1);
quickSort(arr, l + 1, height);
}
public static void main(String[] args) {
int[] arr = {2, 5, 4, 3, 6, 1, 8, 9, 7, 0};
quickSort(arr, 0, arr.length - 1);
}
}
四、选择排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 选择排序,时间复杂度O(n^2)
* 原理:遍历数组找出最小值,然后将他和第一个元素互换位置,然后遍历剩下的数组,重复之前的操作(找最小,换位)。。。
*/
public class SelectSort {
/**
* 示例输出
* [33, 65, 11, 38, 65, 9, 97, 76, 13, 27, 49] 数组初始状态
* [9, 65, 11, 38, 65, 33, 97, 76, 13, 27, 49]
* [9, 11, 65, 38, 65, 33, 97, 76, 13, 27, 49]
* [9, 11, 13, 38, 65, 33, 97, 76, 65, 27, 49]
* [9, 11, 13, 27, 65, 33, 97, 76, 65, 38, 49]
* [9, 11, 13, 27, 33, 65, 97, 76, 65, 38, 49]
* [9, 11, 13, 27, 33, 38, 97, 76, 65, 65, 49]
* [9, 11, 13, 27, 33, 38, 49, 76, 65, 65, 97]
* [9, 11, 13, 27, 33, 38, 49, 65, 76, 65, 97]
* [9, 11, 13, 27, 33, 38, 49, 65, 65, 76, 97]
* [9, 11, 13, 27, 33, 38, 49, 65, 65, 76, 97]
* @param arr
*/
public static void selectSort(int[] arr) {
for (int j = 0; j < arr.length - 1; j++) {
int min = arr[j];
int index = j;
for (int i = j; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
index = i;
}
}
if (index != j) {
//将最小值与数组最前面那个元素交换位置
arr[index] = arr[j];
arr[j] = min;
}
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = {33, 65, 11, 38, 65, 9, 97, 76, 13, 27, 49};
selectSort(arr);
}
}
五、归并排序
原理图:
代码:
package Algorithms.sort;
import java.util.Arrays;
/**
* 归并排序,时间复杂度 O(nlog2n)
* 由于归并排序一般是以二分的方式,即先将一个数组平均分为2块,每一块再继续平均分为2块,如此循环m次,分了m层,即2^m=n => m=log2n。
* 由于每层循环比较的次数都大概是n次,又由于默认是二分归并因此可以省略2,所以时间复杂度为O(nlogn).
* 由于归并排序是稳定排序,所以对象排序是用归并排序(或者timSort)
* 原理:将数组逐层二分分成小块后,创建一个长度等于两个数组长度之和的临时数组遍历分成小块的两个数组,并比较他们的值,将较小的数值放入临时数组,一个数组遍历结束后
* 将另一个数组剩余的数据直接拼接到临时数组后面。
*/
public class MergeSort {
/**
* @param arr 原始数组
* @param l 起始下标
* @param h 末尾下标
* @return 归并后的数组
*/
public static int[] mergeSort(int[] arr, int l, int h) {
if (l == h) {
return new int[]{arr[l]}; //当分到只剩一个元素时返回包含这一个元素的数组。
}
int mid = l + (h - l) / 2; //注意,这是下标注意要加上l
System.out.println(mid);
int leftArr[] = mergeSort(arr, l, mid);
int rightArr[] = mergeSort(arr, mid + 1, h);
int newArr[] = new int[leftArr.length + rightArr.length];
int i = 0, j = 0, m = 0;
//两个数组合并 i:左边数组的计数 j:右边数组的计数 m:合并数组的计数
while (i < leftArr.length && j < rightArr.length) {
newArr[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
}
//当剩余左边数组时,将左边数组直接接在新数组后面
//由于左右两个数组都是有序的,当以上合并完成时左边数组的计数小于该数组的长度,则这个数组从i下标开始的数都比右边数组的最大数要大,因此可以直接拼接在新数组后面
while (i < leftArr.length) {
newArr[m++] = leftArr[i++];
}
//当剩余右边数组时,将右边数组直接接在新数组后面
//由于左右两个数组都是有序的,当以上合并完成时右边数组的计数小于该数组的长度,则这个数组从i下标开始的数都比左边数组的最大数要大,因此可以直接拼接在新数组后面
while (j < rightArr.length) {
newArr[m++] = rightArr[j++];
}
System.out.println(Arrays.toString(newArr));
return newArr;
}
public static void main(String[] args) {
int[] nums = new int[]{9, 8, 7, 6, 5, 4, 3, 2, 10};
int[] newNums = mergeSort(nums, 0, nums.length - 1);
SortUtils.printArr(newNums);
}
}
六、堆排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 堆排序,时间复杂度 O(nlog2n)
* 原理:先把数组按堆的规则(父节点大于(或小于)子节点)排好,这样根节点就是数组中最大的那个数了,然后再把根节点的数和数组末尾的数互换位置
* 然后再把除已经“沉底”的数(放在数组末尾的最大的数)之外的其他数再进行按堆的规则排序,再进行根节点和末尾的数互换位置的操作。。。反复多次,即形成了从小到大排列的数组。
* 关键:最后一个非叶子节点:length/2-1 父节点的左子树:2*i+1 ,父节点的右子树:2*i+2
*/
public class HeapSort {
//===================方法一=========================
/**
* 选择排序-堆排序
*
* @param array 待排序数组
* @return 已排序数组
*/
public static int[] heapSort2(int[] array) {
//先从最后一个非叶子节点开始,从下往上排序,这里元素的索引是从0开始的,所以最后一个非叶子结点为array.length/2 - 1
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap2(array, i, array.length); //调整堆
}
// 上述逻辑,建堆结束
// 下面,开始排序逻辑
for (int j = array.length - 1; j > 0; j--) {
// 元素交换,作用是去掉大顶堆
// 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置
swap(array, 0, j);
// 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。
// 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因
// 而这里,实质上是自上而下,自左向右进行调整的
adjustHeap2(array, 0, j);
}
return array;
}
/**
* 整个堆排序最关键的地方
*
* @param array 待组堆
* @param i 起始结点
* @param length 堆的长度
*/
public static void adjustHeap2(int[] array, int i, int length) {
// 先把当前元素取出来,因为当前元素可能要一直移动
int temp = array[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
// 让k先指向子节点中最大的节点
if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树
k++;
}
//如果发现结点(左右子结点)大于根结点,则进行值的交换
if (array[k] > temp) {
swap(array, i, k);
// 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
i = k;
} else { //不用交换,直接终止循环
break;
}
}
}
/**
* 交换元素
*
* @param arr
* @param a 元素的下标
* @param b 元素的下标
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//=====================方法二==============================
/**
* 调整数组使其符合堆的定义:父节点都大于(或小于)子节点
*
* @param arr 原始数组
* @param start 起始节点
* @param length 堆的长度
*/
public static void adjustHeap1(int[] arr, int start, int length) {
System.out.println("start: " + start + " length:" + length);
if (start >= length) return;
int c1 = 2 * start + 1; //根start的左子树
int c2 = 2 * start + 2; //根start的右子树
int max = start;
if (c1 < length && arr[c1] > arr[max]) {
max = c1;
}
if (c2 < length && arr[c2] > arr[max]) {
max = c2;
}
if (max != start) {
swap(arr, max, start);
System.out.println(Arrays.toString(arr));
adjustHeap1(arr, max, length); //每进行一次根节点的调换,子节点也会受到影响,需要再次调整位置。
}
}
public static void mybuildHeap(int[] arr) {
int n = arr.length - 1; //最后一个节点
int parent = (n - 1) / 2; //最后一个节点的父节点,因为调整堆的方法是将当前节点与子节点进行位置调换的,而叶子节点没有子节点,所以可以跳过叶子节点。
// parent = (arr.length-1-1)/2 = (arr.length-2)/2 = arr.length/2 -1;
for (; parent >= 0; parent--) {
adjustHeap1(arr, parent, arr.length);
}
}
public static void heapSort1(int[] arr) {
mybuildHeap(arr);
int i = arr.length;
while (i > 0) {
swap(arr, 0, i - 1); // i-1是最后一个元素的下标
--i; //数组长度减一。也就是每次都将最大值放到末尾。
// 为什么这里start从0开始,因为经过一次堆变换将数组排序成堆的形式后,大顶堆已经形成,父节点一定大于子节点,即第二大的数一定在第二层根节点中产生
adjustHeap1(arr, 0, i); // i是数组的长度
}
}
public static void main(String[] args) {
int[] arr = {12, 98, 74, 34, 94, 8,77};
heapSort1(arr);
// mybuildHeap(arr);
// int[] arr={9,8,7,6,5,4,3,2,1};
// myadjustHeap(arr,arr.length/2-1,arr.length);
System.out.println(Arrays.toString(arr));
}
}
七、桶(基数)排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 基数排序(桶排序)
* 原理:将所有元素逐位进行统计、根据位上的数据进行排序。
* 如:326,431,67,320当按个位进行排序后为 320,431,326,67,然后对其按十位排序为 320,326,431,67 再按百位排序后为 067,320,326,431
* 每次同位比较虽然顺序依然是乱的,但是对于相同位数的数其实已经有了大致的区分(如320和326,进行按个位排序后,无论整个数组如何,320始终排在326前面),通过多次重排序就排好序了。
* 下面提供了两种实现方法,其实原理一致,方法一可能有助于理解,方法二推荐使用
*/
public class RadixSort {
public static void radixSort(int arr[]) {
int maxLength = Integer.toString(getMax(arr)).length(); //最大的数的长度
//数据存储桶,第一维下标等于数据该位(指个位、十位、...)的实际数据,如231他的个位一维下标为1,第二维下标存储的是改实际数据如231
//注意:使用二维数组仅用于帮助理解,浪费空间,不推荐使用,推荐使用radixSort2()
int bucket[][] = new int[10][arr.length];
//统计每个桶中存放数据的个数
int[] bucketElementCount = new int[10];
//将元素按对应的位数进行排序,个位、十位、百位、...
// i:数组下标 n:用于控制数据的位数,每次大循环结束数据的位数降低一位。
//按位数逐位统计排序
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//逐位统计数字出现次数,并将数据暂存入对应桶内
for (int j = 0; j < arr.length; j++) {
int bit = arr[j] / n % 10;
System.out.println(bit + ":" + bucketElementCount[bit] + ":" + j);
bucket[bit][bucketElementCount[bit]] = arr[j];
bucketElementCount[bit]++;
}
//根据统计结果将暂存桶内的数据按顺序放回原数组(即是按位排序后的数组)
int index = 0;
for (int k = 0; k < bucketElementCount.length; k++) {
if (bucketElementCount[k] > 0) {
for (int b = 0; b < bucketElementCount[k]; b++) {
arr[index] = bucket[k][b];
index++;
}
bucketElementCount[k] = 0;
}
}
SortUtils.printArr(arr);
}
}
/**
* bucketElementCount: 逐位统计数字出现次数
* [1, 0, 1, 2, 2, 2, 1, 0, 1, 1]
* new bucketElementCount: 由于出现次数就是该位(个位、十位、...)数字等于下标的数据的个数,因此可以基于此得到该位(个位、十位、...)排序后的起始位置(或最后一个的位置,具体取决于下次排序是顺序还是倒序)
* [1, 1, 2, 4, 6, 8, 9, 9, 10, 11]
* bucket0: 排序后的数组
* [0, 542, 53, 3, 14, 214, 55, 78955, 996, 748, 99]
* bucketElementCount:
* [2, 2, 0, 0, 2, 3, 0, 0, 0, 2]
* new bucketElementCount:
* [2, 4, 4, 4, 6, 9, 9, 9, 9, 11]
* bucket1:
* [0, 3, 14, 214, 542, 748, 53, 55, 78955, 996, 99]
* bucketElementCount:
* [6, 0, 1, 0, 0, 1, 0, 1, 0, 2]
* new bucketElementCount:
* [6, 6, 7, 7, 7, 8, 8, 9, 9, 11]
* bucket2:
* [0, 3, 14, 53, 55, 99, 214, 542, 748, 78955, 996]
* bucketElementCount:
* [10, 0, 0, 0, 0, 0, 0, 0, 1, 0]
* new bucketElementCount:
* [10, 10, 10, 10, 10, 10, 10, 10, 11, 11]
* bucket3:
* [0, 3, 14, 53, 55, 99, 214, 542, 748, 996, 78955]
* bucketElementCount:
* [10, 0, 0, 0, 0, 0, 0, 1, 0, 0]
* new bucketElementCount:
* [10, 10, 10, 10, 10, 10, 10, 11, 11, 11]
* bucket4:
* [0, 3, 14, 53, 55, 99, 214, 542, 748, 996, 78955]
*
* @param arr
*/
public static void radixSort2(int arr[]) {
int maxLength = Integer.toString(getMax(arr)).length(); //最大的数的长度
//临时存放数据的桶
int bucket[];
//统计该位(指个位、十位、...)上的数的个数
int[] bucketElementCount;
//将元素按对应的位数进行逐位排序,个位、十位、百位、...
// i:数组下标 n:用于控制数据的位数,每次大循环结束数据的位数升高一位。
//逐位进行排序
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//每次逐位比较前重置桶和计数器
bucket = new int[arr.length];
bucketElementCount = new int[10];
//统计数字出现次数
for (int j = 0; j < arr.length; j++) {
int bit = arr[j] / n % 10;
bucketElementCount[bit]++;
System.out.println(bit + ":" + bucketElementCount[bit] + ":" + j);
}
System.out.println("bucketElementCount:");
System.out.println(Arrays.toString(bucketElementCount));
//将统计的该位上的数的个数转化成下标起始位置
//统计的次数即数字出现的次数
for (int m = 0; m < bucketElementCount.length - 1; m++) {
bucketElementCount[m + 1] = bucketElementCount[m] + bucketElementCount[m + 1];
}
System.out.println("new bucketElementCount:");
System.out.println(Arrays.toString(bucketElementCount));
//将排序后得到的数据放到临时数组中
for (int len = arr.length - 1; len >= 0; len--) {
int bit = arr[len] / n % 10;
//取之前先减一,因为取的是个数,要的是下标,下标=个数-1
--bucketElementCount[bit];
bucket[bucketElementCount[bit]] = arr[len];
}
arr = bucket;
System.out.println("bucket" + i + ":");
System.out.println(Arrays.toString(bucket));
}
}
//查找出数组中最大的那个数
public static int getMax(int arr[]) {
int max = arr[0];
for (int a : arr) {
if (a > max) {
max = a;
}
}
return max;
}
public static void main(String[] args) {
int arr[] = {53, 3, 542, 748, 55, 996, 14, 214, 78955, 0, 99};
radixSort2(arr);
}
}
八、希尔排序
package Algorithms.sort;
import java.util.Arrays;
/**
* 希尔排序,时间复杂度 O(n^2)
* 希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),
* 是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
* 通过希尔排序避免了每次插入都要移动多个元素,可以进行“跳跃插入”,加快了排序速度。
*/
public class ShellSort {
/**
* [9, 1, 2, 5, 7, 4, 8, 6, 3, 5, 0]
* [4, 1, 2, 5, 7, 9, 8, 6, 3, 5, 0]
* [0, 1, 2, 5, 7, 4, 8, 6, 3, 5, 9]
* [0, 1, 2, 5, 7, 4, 8, 6, 3, 5, 9]
* [0, 1, 2, 5, 7, 4, 8, 6, 3, 5, 9]
* [0, 1, 2, 3, 7, 4, 8, 6, 5, 5, 9]
* [0, 1, 2, 3, 5, 4, 8, 6, 5, 7, 9]
* 0 1 2 3 5 4 8 6 5 7 9
* [0, 1, 2, 3, 5, 4, 8, 6, 5, 7, 9]
* [0, 1, 2, 3, 5, 4, 8, 6, 5, 7, 9]
* [0, 1, 2, 3, 5, 4, 8, 6, 5, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* 0 1 2 3 5 4 5 6 8 7 9
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 5, 4, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
* [0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
* 0 1 2 3 4 5 5 6 7 8 9
* 0 1 2 3 4 5 5 6 7 8 9
* @param arr
*/
public static void shellSort(int[] arr) {
System.out.println(Arrays.toString(arr));
int h = arr.length / 2; //步长,即每次跨越的元素个数,也就是每次比较的两个元素之间的距离
for (; h >= 1; h /= 2) { //循环至步长为1时,排序完成,排序结束。
for (int i = 0; i < h; i++) {
for (int j = i + h; j < arr.length; j += h) {
int temp = arr[j]; //保存arr[j]的值
int k = j - h; //从j-h的位置开始往前循环,进行插入排序
while (k >= 0 && arr[k] > temp) {
arr[k + h] = arr[k]; //注意:k+h!=j 因为k+h会随k的变化而改变,而j不变。
k -= h; //k往前循环
// System.out.println(Arrays.toString(arr)); //开启这个打印方便理解
}
arr[k + h] = temp; //开启上面的打印语句,查看打印结果,理解当经过循环后执行此语句的意义)
System.out.println(Arrays.toString(arr));
}
}
SortUtils.printArr(arr);
}
}
public static void main(String[] args) {
int[] arr = {9, 1, 2, 5, 7, 4, 8, 6, 3, 5, 0};
shellSort(arr);
SortUtils.printArr(arr);
}
}