七大排序
“菜鸟教程”这个站讲的很好可以参考一下(点这里就可以)
本次排序以数组为例,排序数组
一、测试用例
测试后续的排序是否正确排序
1.1 生成待测试的排序数组
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class 生成测试数组 {
public static final ThreadLocalRandom random = ThreadLocalRandom.current();
public static void testSort(String sortName , int[] arr){
Class<七大排序> cls = 七大排序.class;
try {
Method method = cls.getMethod(sortName , int[].class);
Long startTime = System.nanoTime();
method.invoke(null , arr);
Long endTime = System.nanoTime();
if (isSort(arr)){
System.out.println(sortName + "算法共耗时:" + (endTime - startTime) / 1000000 + "ms");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static boolean isSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]){
System.err.println("没排序好");
return false;
}
}
return true;
}
public static int[] disorderArr(int n , int left , int right){
int[] arr = new int[n];
for (int i = 0; i < arr.length - 1; i++) {
arr[i] = random.nextInt(left , right);
}
return arr;
}
public static int[] orderlyArr(int n , int times){
int[] arr = new int[n];
for (int i = 0; i < arr.length - 1; i++) {
arr[i] = i;
}
for (int i = 0; i < times; i++) {
int a = random.nextInt(n);
int b = random.nextInt(n);
swap(arr , a , b);
}
return arr;
}
public static void swap(int[] arr , int i , int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int[] copyOf(int[] arr){
int[] arrCopyOf = Arrays.copyOf(arr , arr.length);
return arrCopyOf;
}
}
二、七大排序本体
开始了开始了,一起踏上这条打怪升级路咯
2.1 冒泡排序
每一趟确定排序区间中一个位置的元素
热个身,先来个简单的
2.1.1 冒泡排序图示
2.1.2 冒泡排序
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean isSwap = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j + 1, j);
isSwap = true;
}
}
if (!isSwap) {
break;
}
}
}
2.1.3 冒泡排序理解
- isSwap方法:如果这一趟没有发生交换说明后面的每一个元素都没有他前面的这个元素大,所以已经完成排序,退出即可
- 冒泡排序属于交换排序;时间复杂度:O(n2);稳定排序算法
2.2 插入排序
来点有操作的
2.2.1 插入排序图示
2.2.2 插入排序
2.2.2.1 直接插入排序
从前往后逐个排查把元素向前寻找到合适位置后插入
public static void insertionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j >= 1 && arr[j] < arr[j - 1]; j--) {
swap(arr, j, j - 1);
}
}
}
2.2.2.2 二分优化的直接插入排序
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int val = arr[i];
int left = 0;
int right = i;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (val < arr[mid]) {
right = mid;
} else {
left = mid + 1;
}
}
for (int j = i; j > left; j--) {
arr[j] = arr[j - 1];
}
arr[left] = val;
}
}
2.2.3 插入排序理解
- 二分取中值时这样写不会有越界问题
int mid =left + ((right - left) >> 1); 尽量不要mid=(left+right)/2 - 直接插入排序时间复杂度:O(n2);稳定排序
2.3 希尔排序
逐渐加难度了哟 把排序区间分组后,组内进行插入排序
2.3.1 希尔排序图示
2.3.2 希尔排序
2.3.2.1 希尔排序一
public static void shellSort(int[] arr) {
int gap = arr.length >> 1;
while (gap > 1) {
insertionSortGap(arr, gap);
gap = gap >> 1;
}
insertionSort(arr);
}
public static void insertionSortGap(int[] arr, int gap) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j >= 0; j = j - gap) {
swap(arr, j, j - gap);
}
}
}
2.3.2.2 希尔排序二
public static void shellSort(int[] arr) {
int length = arr.length;
int temp;
for (int step = length / 2; step >= 1; step /= 2) {
for (int i = step; i < length; i++) {
temp = arr[i];
int j = i - step;
while (j >= 0 && arr[j] > temp) {
arr[j + step] = arr[j];
j -= step;
}
arr[j + step] = temp;
}
}
}
2.3.3 希尔排序理解
- 希尔排序也属于插排的一种;时间复杂度大概O(n1.3~1.5);不稳定排序
2.4 归并排序
对待排序区间进行拆分再合并,合并过程中完成排序
在来加点难度
2.4.1 归并排序图示
2.4.2 归并排序
2.4.2.1 归并排序一
private static void mergeSort(int[] arr, int l, int r) {
if (l >= r) {
return;
}
if (r - l <= 15) {
insertionSort(arr, l, r);
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
if (arr[mid] > arr[mid + 1]) {
merge(arr, l, mid, r);
}
}
private static void insertionSort(int[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
swap(arr, j, j - 1);
}
}
}
private static void merge(int[] arr, int l, int mid, int r) {
int[] aux = new int[r - l + 1];
for (int i = 0; i < aux.length; i++) {
aux[i] = arr[i + l];
}
int i = l;
int j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
} else if (j > r) {
arr[k] = aux[i - l];
i++;
} else if (aux[i - l] <= aux[j - l]) {
arr[k] = aux[i - l];
i++;
} else {
arr[k] = aux[j - l];
j++;
}
}
}
2.4.2.2 归并排序二
public int[] mergeSort(int[] sourceArray) {
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));
}
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;
}
2.4.2.3 归并排序三
这个相对不好理解,用两个for循环替换递归拆分的过程
public static void mergeSort(int[] arr) {
for (int i = 1; i < arr.length; i += i) {
for (int j = 0; j + i < arr.length; j += 2 * i) {
merge(arr, j, j + i - 1, Math.min(j + 2 * i - 1, arr.length - 1));
}
}
}
private static void merge(int[] arr, int l, int mid, int r) {
int[] aux = new int[r - l + 1];
for (int i = 0; i < aux.length; i++) {
aux[i] = arr[i + l];
}
int i = l;
int j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
} else if (j > r) {
arr[k] = aux[i - l];
i++;
} else if (aux[i - l] <= aux[j - l]) {
arr[k] = aux[i - l];
i++;
} else {
arr[k] = aux[j - l];
j++;
}
}
}
2.4.3 归并排序理解
- 15以内插排序效率最高可以用插排优化一部分
- copyOfRange(数组[] , a ,b)是左闭右开区间[a , b);
- Math.floor() 向下取整,即小于这个数的最大的那个整数。
- Math.ceil() 向上取整,即大于这个数的最小的那个整数。
- Math.round() 四舍五入
- 一定理解归并时左右区间在哪
- 归并排序是稳定排序;时间复杂度O(nlogn)
2.5 选择排序
看的很头大了吧,那先再来个简单的缓解一下疲劳 遍历无序区间在其中找到最合适的元素与有序区间的边界进行交换
2.5.1 选择排序图示
2.5.2 选择排序
2.5.2.1 单向选择排序
循环遍历数组,i之前是已经排序的,每次从i开始在未排序区间找到最小值与当前位置交换逐个排序,每次从无序区间找到最小值放在无需区间最前端,每经过一轮,一个元素归为,有序加1,无序-1
public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
swap(arr, min, i);
}
}
2.5.2.2 双向选择排序
一次循环同时确定左右即最小值与最大值
public static void selectionSort(int[] arr) {
int low = 0;
int height = arr.length - 1;
while (low <= height) {
int min = low;
int max = low;
for (int i = low + 1; i <= height; i++) {
if (arr[i] < arr[min]) {
min = i;
}
if (arr[i] > arr[max]) {
max = i;
}
}
swap(arr, min, low);
if (max == low) {
max = min;
}
swap(arr, max, height);
low += 1;
height -= 1;
}
}
2.5.3 选择排序理解
- 选择排序时间复杂度O(n2);不稳定排序
2.6 堆排序
现在我宣布正式进入实力操作环节 从第一个非叶子节点开始进行元素下沉操作来构造最大堆,构造好之后在依次交换首尾值进行首元素下沉
2.6.1 堆排序图示
2.6.2 堆排序
从第一个非叶子节点开始进行元素下沉操作来构造最大堆,构造好之后在依次交换首尾值进行首元素下沉
public static void heapSort(int[] arr) {
for (int i = (arr.length - 1 - 1) >> 1; i >= 0; i--) {
siftDown(arr, i, arr.length - 1);
}
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, i, 0);
siftDown(arr, 0, i);
}
}
public static void siftDown(int[] arr, int i, int length) {
while (2 * i + 1 < length) {
int j = 2 * i + 1;
if (j + 1 < length && arr[j + 1] > arr[j]) {
j += 1;
}
if (arr[i] < arr[j]) {
swap(arr, i, j);
i = j;
} else {
break;
}
}
}
2.6.3 堆排序理解
- 堆排序时间复杂度:O(nlogn),不稳定排序
- 堆的排序会牵扯很多二叉树的知识这些(avl树、红黑树等)后续将添加链接
- 一定注意下沉操作时的下沉范围,每次都是下沉完后会有一个最大元素归位
2.7 快速排序
既然都瞅到这里了那也就不藏了,该放大招了 以一个数为基准,每次把为排序区间分成比这个基准大和比这个基准值小的两个部分,在讲这个基准值放在合适的分界处
2.7.1 快速排序图示
2.7.2 快速排序
准备好兄弟们,纷争开始了 以一个数为基准,每次把为排序区间分成比这个基准大和比这个基准值小的两个部分,再将这个基准值放在合适的分界处
2.7.2.1 单路快排
public static void quickSort(int[] arr) {
partitionHelp(arr, 0, arr.length - 1);
}
private static void partitionHelp(int[] arr, int l, int r) {
if (r - l <= 15) {
insertionSort(arr,l,r);
return;
}
int p = partition(arr, l, r);
partitionHelp(arr, l, p - 1);
partitionHelp(arr, p + 1, r);
}
public static final ThreadLocalRandom random = ThreadLocalRandom.current();
private static int partition(int[] arr, int l, int r) {
int p = random.nextInt(l, r);
swap(arr, l, p);
int val = arr[l];
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < val) {
swap(arr, j + 1, i);
j++;
}
}
swap(arr, l, j);
return j;
}
2.7.2.2 双路快排
来来来已经翻过一座山,继续继续核心理解的话下面这个也就简单了
从两端开始操作,同时排序大于等于和小于等于基准值的数
public static void quickSort(int[] arr){
partitionHelpTwo(arr , 0 , arr.length - 1);
}
private static void partitionHelp(int[] arr, int l, int r) {
if (r - l <= 15){
insertionSort(arr , l , r);
return;
}
int p = partition(arr , l , r);
partitionHelp(arr , l , p - 1);
partitionHelp(arr , p + 1 , r);
}
private static int partition(int[] arr, int l, int r) {
int p = random.nextInt(l , r);
swap(arr , p , l);
int val = arr[l];
int i = l + 1;
int j = r;
while (true){
while (i <= j && arr[i] < val){
i ++ ;
}
while (i <= j && arr[j] > val){
j -- ;
}
if (i >= j){
break;
}
swap(arr , i , j);
i ++ ;
j -- ;
}
swap(arr , l , j );
return j;
}
2.7.2.3 三路快排
嗯~8错,小怪打完了,现在就来刷最后的boss吧,原理核心是一样的哦
核心是一样的,只是把等于基准值的也独立出来
public static void quickSortThree(int[] arr){
partitionHelpThree(arr , 0 , arr.length - 1);
}
private static void partitionHelpThree(int[] arr, int l, int r) {
if (r - l <= 15){
insertionSort(arr , l , r);
return;
}
int p = random.nextInt(l , r);
swap(arr , l , p);
int val = arr[l];
int k = l;
int i = k + 1;
int j = r + 1;
while (i < j){
if (arr[i] < val){
swap(arr , i , k + 1 );
i++;
k++;
}else if (arr[i] > val){
swap(arr , i , j - 1);
j -- ;
}else {
i ++ ;
}
}
swap(arr , l , k );
partitionHelpThree(arr , l ,k - 1);
partitionHelpThree(arr , j , r );
}
2.7.3 快速排序理解
- 快排时间复杂度O(nlogn),不稳定排序
- 上面介绍了递归的分区操作,下面展示另一种方法
嗯,只是把递归的分区换成栈进行操作,排序的过程还是一样的
public static void quickSortParticular(int[] arr){
Deque<Integer> stack = new ArrayDeque<>();
int l = 0 ;
int r = arr.length - 1 ;
stack.push(r);
stack.push(l);
while (!stack.isEmpty()){
int left = stack.pop();
int right = stack.pop();
if (left >= right){
continue;
}
int p = partition(arr , left , right);
stack.push(right);
stack.push(p + 1);
stack.push(p - 1);
stack.push(left);
}
}
- 三路快排只是提出来了等于基准时的情况,注意边界元素代表的位置,尤其三路时大于基准值时的情况
2.8 计数排序
boss刷完了看看剩下的小野怪吧都是很简单的 找到原数组最大值,记录每个值对应出现的次数,在以最大值定义的数组中,找出对应元素位置记录次数,循环这个数组,在元素内不为0时说明索引是待排序元素,值表示原数组中元素出现的个数,按次序对原数组更新。
2.8.1 计数排序图示
2.8.2 计数排序
public class CountingSort {
public int[] sort(int[] sourceArray) {
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 j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
}
还有两个野怪分别是‘桶排序’和‘基数排序’,嗯。。。我就不做搬运工了,因为这个我确实没有自己写,具体的排序方式可以参考《菜鸟教程》,这里是链接,桶排序、基数排序。 骑士的征战旅完成了哦,日后的学习继续加油
|