目录
1. 冒泡排序
2. 选择排序
3. 插入排序
4. 归并排序
5. 快速排序
6. 桶排序
7. 计数排序
8. 基数排序
9. 希尔排序
10. 堆排序
11. 十种排序的时间复杂度,空间复杂度,稳定性
0. 准备构造无需数组的方法,便于生成测试数据:
/**
* 构造int类型的数组数据
*
* @param length 数组长度
* @param max 数组值范围
* @return 数组
*/
public static int[] generatorArray(int length, int max) {
int[] arr = new int[length];
Random random = new Random();
for (int i = 0; i < length; i++) {
// 随机获取max以内的数据
arr[i] = random.nextInt(max);
}
return arr;
}
1. 冒泡排序
每次对相邻的两个元素进行比较,大的数后移像金鱼吐泡泡一样上浮,经过一轮比较可以得出一个最大值放在最后,再比较除最大值之外的数据,得到次级最值;
/**
* 冒泡排序方法 0---1 1---2 2---3 3---4 ; 0---1 1---2 2---3 ; 0---1 1---2 ; 0---1
*
* @param arr 待排序数组
*/
public static void bubbleSort(int[] arr) {
log.info("冒泡排序------开始");
for (int i = 0; i < arr.length; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < arr.length - 1 - i; j++) {
sb.append(j).append("---").append(j + 1).append(" ");
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];
}
}
if (sb.length() > 0) {
log.info("第 {} 轮排序: {}", i, sb);
}
}
log.info("冒泡排序------结束");
}
2. 选择排序
每次比较未排序区间的首值与每个值的大小,取小的放在初始位置,经过一轮比较可以得出最小的放在初始值;然后初始值索引后移,未排序区间就是后面的数据;
/**
* 选择排序 0---1 0---2 0---3 0---4 ; 1---2 1---3 1---4 ; 2---3 2---4 ; 3---4
*
* @param arr 待排序数组
*/
public static void selectSort(int[] arr) {
log.info("简单选择排序------开始");
int temp;
for (int i = 0; i < arr.length; i++) {
StringBuilder sb = new StringBuilder();
for (int j = i + 1; j < arr.length; j++) {
sb.append(i).append("---").append(j).append(" ");
if (arr[i] > arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
if (sb.length() > 0) {
log.info("第 {} 轮排序: {}", i, sb);
}
}
// 优化:减少交换次数
// for (int i = 0; i < arr.length; i++) {
// // 最小值的下标
// int minIndex = i;
// for (int j = i + 1; j < arr.length; j++) {
// if (arr[j] < arr[minIndex]) {
// minIndex = j;
// }
// }
// // 交换此轮找到的最小值到此轮数据的第一个
// if (minIndex != i) {
// temp = arr[i];
// arr[i] = arr[minIndex];
// arr[minIndex] = temp;
// }
// }
log.info("简单选择排序------结束");
}
/**
* 测试: 选择排序
*/
@Test
public void selectSortTest() {
int[] arr = generatorArray(10, 100);
log.info("排序前: {}", Arrays.toString(arr));
selectSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
3. 插入排序
将数组分为已排序区间与未排序区间,初始的已排序区间就是一个首元素,取出未排序区间的每个元素在已排序区间找到合适位置插入,保证已排序区间一致有序,重复,知道未排序区间元素个数为0; 插入排序相对于冒泡排序,移动交换数据时只要一行代码更加简单更快!
/**
* 插入排序
*
* @param arr 待排序数组
*/
public static void insertSort(int[] arr) {
log.info("插入排序------开始");
// 要插入的数
int insertNum;
for (int i = 1; i < arr.length; i++) {
insertNum = arr[i];
int j = i - 1;
log.info("i:{},insertNum:{},j:{}", i, insertNum, j);
while (j >= 0 && arr[j] > insertNum) {
// 元素向后移动
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = insertNum;
}
log.info("插入排序------结束");
}
/**
* 测试: 插入排序
*/
@Test
public void insertSortTest() {
int[] arr = generatorArray(5, 100);
log.info("排序前: {}", Arrays.toString(arr));
insertSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
4. 归并排序
基于分治思想将数组分为两个子序列,递归处理两个子序列,再合并;由下到上,先处理子问题,再合并;
/**
* 归并排序
*
* @param arr 待处理数组
* @param left 起始点
* @param right 尾节点
*/
public static void mergeSort(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = (right + left) >> 1;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
/**
* 归并排序-合并
*
* @param arr 待处理数组
* @param left 开始节点
* @param mid 中间节点
* @param right 尾节点
*/
public static void merge(int[] arr, int left, int mid, int right) {
// 临时数组
int[] temp = new int[right - left + 1];
// 临时数组 下标
int i = 0;
// 检测指针
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
if (arr[p1] <= arr[p2]) {
temp[i++] = arr[p1++];
} else {
temp[i++] = arr[p2++];
}
}
while (p1 <= mid) {
temp[i++] = arr[p1++];
}
while (p2 <= right) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for (i = 0; i < temp.length; i++) {
arr[left + i] = temp[i];
}
}
/**
* 测试: 归并排序
*/
@Test
public void mergeSortTest() {
int[] arr = generatorArray(10, 100);
log.info("排序前: {}", Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
log.info("排序后: {}", Arrays.toString(arr));
}
归并排序还有另外一种写法,只是空间复杂度会上升:
/**
* 归并排序2
*
* @param array 待处理数组
* @return 排好序的数组
*/
public static int[] mergeSort2(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(mergeSort2(left), mergeSort2(right));
}
/**
* 测试: 归并排序2
*/
@Test
public void mergeSortTest2() {
int[] arr = generatorArray(10, 100);
log.info("排序前: {}", Arrays.toString(arr));
arr = mergeSort2(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
5. 快速排序
基于分治思想,先从数组中取出一个数作为基准数,将小于基准数的放左边,大于基准数的放右边,再递归处理子数组,知道子数组元素只有一个;快排是由上到下,先分区再处理子问题;
/**
* 快速排序
*
* @param array 待处理数组
* @param left 起点
* @param right 结点
*/
public static void quickSort(int[] array, int left, int right) {
int start = left;
int end = right;
// 放在k之前,防止下标越界
if (start > end) {
return;
}
log.info("start:{},end:{}", start, end);
// 基准点
int key = array[start];
log.info("基准索引start:{},基准数key:{}", start, key);
while (start < end) {
// 找出小的数
while (start < end && array[end] > key) {
end--;
}
// 找出大的数
while (start < end && array[start] <= key) {
start++;
}
log.info("找到start:{},end:{}", start, end);
// 交换
if (start < end) {
array[start] = array[start] ^ array[end];
array[end] = array[start] ^ array[end];
array[start] = array[start] ^ array[end];
}
}
// 交换K
key = array[start];
array[start] = array[left];
array[left] = key;
// 对左边进行排序,递归算法
quickSort(array, left, start - 1);
// 对右边进行排序
quickSort(array, start + 1, right);
}
/**
* 测试: 快速排序
*/
@Test
public void quickSortTest() {
int[] arr = generatorArray(10, 100);
log.info("排序前: {}", Arrays.toString(arr));
quickSort(arr, 0, arr.length - 1);
log.info("排序后: {}", Arrays.toString(arr));
}
6. 桶排序
把要排序的数据分别放入几个有序的桶内,再把每个桶内的数据排序,最后再把每个桶内的数据按照顺序取出即可组成有序的数组;桶排序适用于外部排序--->10个10G的文件订单,内存只有1G,可以设置n个桶文件,从文件订单中取出订单插入到桶内,最后合并;
/**
* 桶排序
*
* @param arr 待排序数组
* @param bucketCount 桶的总数
*/
public void bucketSort(int[] arr, int bucketCount) {
// 待排序数组的最值
int min = arr[0];
int max = arr[arr.length - 1];
for (int target : arr) {
max = Math.max(max, target);
min = Math.min(max, target);
}
log.info("待排序数组的最大值:{},最小值:{},桶的总个数:{}", max, min, bucketCount);
// 用于排序的桶
Map<Integer, LinkedList<Integer>> bucketMap = new HashMap<>(bucketCount);
// 将数组中的元素分别装入桶中
for (int element : arr) {
// 获取此元素应该放入第几个桶
int bucketIndex = (element - min) / bucketCount;
bucketMap.putIfAbsent(bucketIndex, new LinkedList<>());
bucketMap.get(bucketIndex).add(element);
}
// 对每个桶中的数组分别进行排序,这里选择的排序算法,将直接决定这个桶排序的时间复杂度;若是快排则桶排序为O(n)
for (Integer key : bucketMap.keySet()) {
if (null != bucketMap.get(key)) {
Collections.sort(bucketMap.get(key));
}
}
// 把各个桶的排序结果合并
int count = 0;
for (Integer key : bucketMap.keySet()) {
LinkedList<Integer> linkedList = bucketMap.get(key);
if (null != linkedList) {
// 使用迭代器,不用再建立新的数组,空间复杂度降低
Iterator iter = linkedList.iterator();
while (iter.hasNext()) {
Integer d = (Integer) iter.next();
arr[count] = d;
count++;
}
}
}
}
/**
* 测试: 桶排序
*/
@Test
public void bucketSortTest() {
int[] arr = generatorArray(20, 100);
log.info("排序前: {}", Arrays.toString(arr));
int bucketCount = 9;
bucketSort(arr, bucketCount);
log.info("排序后: {}", Arrays.toString(arr));
}
7. 计数排序
需要占用大量空间,适用于数据比较集中的情况,比如高考考试分数的排序;
/**
* 计数排序
*
* @param arr 待排序数组
*/
public void countingSort(int[] arr) {
int length = arr.length;
if (length <= 1) {
return;
}
// 数组最大值
int max = arr[0];
for (int target : arr) {
max = Math.max(max, target);
}
// 申请一个计数数组countArray,下标为:[0,max]
int[] countArray = new int[max + 1];
// 计算每个元素的个数,放入c中
for (int i = 0; i < length; ++i) {
countArray[arr[i]]++;
}
// 依次累加
for (int i = 1; i <= max; ++i) {
countArray[i] = countArray[i - 1] + countArray[i];
}
// 临时数组tempArray,存储排序之后的结果
int[] tempArray = new int[length];
// 计算排序的关键
for (int i = length - 1; i >= 0; --i) {
int index = countArray[arr[i]] - 1;
tempArray[index] = arr[i];
countArray[arr[i]]--;
}
// 将结果拷贝给a数组
for (int i = 0; i < length; ++i) {
arr[i] = tempArray[i];
}
}
/**
* 测试: 计数排序
*/
@Test
public void countingSortTest() {
int[] arr = generatorArray(20, 100);
log.info("排序前: {}", Arrays.toString(arr));
countingSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
8. 基数排序
根据位数进行排序
/**
* 基数排序
*
* @param arr 待排序数组
*/
public void radixSort(int[] arr) {
// 数组长度
int length = arr.length;
// 数组最大值
int max = arr[0];
for (int element : arr) {
max = Math.max(max, element);
}
// 需要装桶排序的次数: 最大值的位数
int sortCount = String.valueOf(max).length();
// 定义每一轮的除数,1,10,100...
int divisor = 1;
// 定义了10个桶,以防每一位都一样全部放入一个桶中
int[][] bucket = new int[10][length];
// 统计每个桶中实际存放的元素个数
int[] count = new int[10];
// 获取元素中对应位上的数字,即装入哪个桶
int digit;
// 经过sortCount次装桶操作,排序完成
for (int i = 1; i <= sortCount; i++) {
// 计算入桶
for (int temp : arr) {
digit = (temp / divisor) % 10;
bucket[digit][count[digit]++] = temp;
}
// 被排序数组的下标
int k = 0;
// 从0到9号桶按照顺序取出
for (int b = 0; b < 10; b++) {
// 如果这个桶中没有元素放入,那么跳过
if (count[b] == 0) {
continue;
}
for (int w = 0; w < count[b]; w++) {
arr[k++] = bucket[b][w];
}
// 桶中的元素已经全部取出,计数器归零
count[b] = 0;
}
divisor *= 10;
}
}
/**
* 测试: 基数排序
*/
@Test
public void radixSortTest() {
int[] arr = generatorArray(20, 1000);
log.info("排序前: {}", Arrays.toString(arr));
radixSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
9. 希尔排序
实质上市分组插入排序,又称为缩小增量排序,将无序数组分为n个子数组(由间隔某个'增量'的元素组成),分别进行插入排序,再一次缩减增量值进行排序,最后再来一次全体数据的插入排序;
/**
* 希尔排序
*
* @param array 待处理数组
*/
public static void shellSort(int[] array) {
log.info("希尔排序------开始");
int length = array.length;
int gap = length / 2;
while (gap > 0) {
for (int i = gap; i < length; i++) {
int temp = array[i];
int preIndex = i - gap;
while (preIndex >= 0 && array[preIndex] > temp) {
array[preIndex + gap] = array[preIndex];
preIndex -= gap;
}
array[preIndex + gap] = temp;
}
gap /= 2;
}
log.info("希尔排序------结束");
}
/**
* 测试: 希尔排序
*/
@Test
public void shellSortTest() {
int[] arr = generatorArray(20, 100);
log.info("排序前: {}", Arrays.toString(arr));
shellSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
10. 堆排序
利用堆数据结构进行排序,堆积是一个近似于完全二叉树的结构,并且同时满足堆积的性质:子节点的健值或索引总是小于或大于它的父节点;(不太理解啊哈哈)
/**
* 堆排序算法
*
* @param array 待处理数组
*/
public static void heapSort(int[] array) {
//构造初始堆,从第一个非叶子节点开始调整,左右孩子节点中较大的交换到父节点中
for (int i = (array.length) / 2 - 1; i >= 0; i--) {
headAdjust(array, array.length, i);
}
//排序,将最大的节点放在堆尾,然后从根节点重新调整
for (int i = array.length - 1; i >= 1; i--) {
int temp = array[0];
array[0] = array[i];
array[i] = temp;
headAdjust(array, i, 0);
}
}
/**
* 调整使之成为小顶堆
*
* @param array 待处理数组
* @param len 数组长度
* @param i 待处理索引
*/
private static void headAdjust(int[] array, int len, int i) {
int k = i, temp = array[i], index = 2 * k + 1;
while (index < len) {
if (index + 1 < len) {
if (array[index] < array[index + 1]) {
index = index + 1;
}
}
if (array[index] > temp) {
array[k] = array[index];
k = index;
index = 2 * k + 1;
} else {
break;
}
}
array[k] = temp;
}
/**
* 测试: 堆排序
*/
@Test
public void heapSortTest() {
int[] arr = generatorArray(20, 100);
log.info("排序前: {}", Arrays.toString(arr));
headSort(arr);
log.info("排序后: {}", Arrays.toString(arr));
}
11. 十种排序的时间复杂度,空间复杂度,排序方式,稳定性对比
引用下:
做个笔记.....