工具类
快速测试,不用自己造数据,就可以验证算法是否正确
/**
* 对数器,自动测试排序算法是否正确
*/
public class SortUtils {
/**
* 生成随机大小+随机数字的数组
* @param maxSize 数组最大值
* @param maxValue 数字最大值
* @return
*/
public static int[] generateRandomArray(int maxSize, int maxValue){
//Math.random() -> [0,1) 所有小数,等概率返回一个
//Math.random() * N -> [0,N) 所有小数,等概率返回一个
//(int)(Math.random() * N) -> [0,N-1] 所有整数,等概率返回一个
int arrLength = 0;
//避免生成空数组
while(arrLength == 0){
arrLength = (int)((maxSize + 1) * Math.random());
}
int[] arr = new int[arrLength];
for (int i = 0; i < arr.length; i++) {
//无所谓正数还是负数
arr[i] = (int)((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random());
}
return arr;
}
/**
* 复制一份数组
* @param arr
* @return
*/
public static int[] copyArray(int arr[]){
if(arr == null){
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < res.length; i++) {
res[i] = arr[i];
}
return res;
}
/**
* 判断两个数组是否相等
* @param arr
* @param res
* @return
*/
public static boolean isEquals(int[] arr, int[] res){
boolean equals = true;
for (int i = 0; i < arr.length; i++) {
if(arr[i] != res[i]){
equals = false;
break;
}
}
return equals;
}
/**
* 使用异或运算,交换数字位置
* @param arr
* @param i
* @param j
*/
public static void swap(int[] arr, int i, int j){
if(i == j){
return;
}
arr[i] = arr[i]^arr[j];
arr[j] = arr[i]^arr[j];
arr[i] = arr[i]^arr[j];
}
/**
* 打印数组
* @param arr
*/
public static void printArray(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
}
}
选择排序
遍历0~N项,选取第i大的数,放在i的位置上
时间复杂度:最差
import java.util.Arrays;
/**
* 选择排序
* 遍历0~N项,选取第i大的数,放在i的位置上
*/
public class SelectSort{
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用选择排序
sort(arr);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
/**
* 选择排序
*/
public static void sort(int[] arr){
for(int i = 0; i < arr.length; i++){
//当前最大值的位置
int maxIndex = i;
for(int j = i + 1; j < arr.length; j++){
//大于小于号用来控制是正序还是倒序
maxIndex = arr[maxIndex] < arr[j] ? maxIndex : j;
}
//交换
SortUtils.swap(arr, maxIndex, i);
}
}
}
冒泡排序
时间复杂度:最差
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用选择排序
sort(arr);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
/**
* 冒泡排序
* @param arr
*/
public static void sort(int[] arr){
for(int i = 0; i < arr.length; i++){
for (int j = i+1; j < arr.length; j++) {
if(arr[i] > arr[j]){
SortUtils.swap(arr, i, j);
}
}
}
}
}
插入排序
从后往前比较,到后面的数比前面的数大/小时结束这一轮比较。前面一定是有序的
时间复杂度:最差,最好的时候是
估计时间复杂度时,是去估计这个算法最差的情况
import java.util.Arrays;
/**
* 插入排序
* 从后往前比较,到后面的数比前面的数大/小时结束这一轮比较。前面一定是有序的
* 时间复杂度:最差O(n^2),最好的时候是O(n)
* 估计时间复杂度时,是去估计这个算法最差的情况
*/
public class insertSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用选择排序
sort(arr);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
/**
* 插入排序
* @param arr
*/
public static void sort(int[] arr){
for(int i = 1; i < arr.length ; i++){
//j > 0为不越界
//arr[j-1] < arr[j]结束条件,再前面的数就已经符合这个条件
for(int j = i; j > 0 && arr[j-1] > arr[j]; j--){
SortUtils.swap(arr, j, j-1);
}
}
}
}
归并排序
使用递归算法,保证L-mid和mid-R有序,然后使用使用两个指针,将两个有序数组合并
import java.util.Arrays;
public class MergeSort{
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用归并排序
process(arr, 0 , arr.length -1);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
/**
* 递归调用
* @param arr
* @param L
* @param R
*/
public static void process(int[] arr, int L, int R){
if(L == R){
return;
}
//计算中点
int mid = L + ((R - L) >>1);
//调用两次
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr, L , mid, R);
}
/**
* 合并两个数组
* @param arr
* @param L
* @param M
* @param R
*/
public static void merge(int[] arr, int L, int M, int R){
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M+1;
while(p1 <= M && p2 <= R){
help[i++] = arr[p1] <= arr[p2]? arr[p1++] : arr[p2++];
}
while(p1 <= M){
help[i++] = arr[p1++];
}
while(p2 <= R){
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[L+j] = help[j];
}
}
}
时间复杂度,符合master公式:
a = 2, b= 2 //调用两次递归,将数据等分为两份
d = 1 //除了调用以外的时间复杂度为 O(N)
符合 时间复杂度为
快排
1、荷兰国旗问题1
给定一个数组,把小于等于num的数放在左边,把大于num的数放在右边
分析:
1.如果当前指针指向的值小于等于5,与minPart的后一位交换,minPart++,指针向前
2.如果当前指针指向的值大于5,指针向前i++
/**
* 荷兰国旗问题1
* @param arr 给定数组
* @param num 划分值
*/
public static void heLanGuoQi1(int[] arr, int num) {
//小于等于num的区域
int minPart = -1;
//指针
int i = 0;
//1.如果当前指针指向的值小于等于5,与minPart的后一位交换,minPart++,指针向前
//2.如果当前指针指向的值大于5,指针向前i++
while(i < arr.length){
if(arr[i] <= num){
SortUtils.swap(arr, minPart+1, i);
minPart++;
}
i++;
}
SortUtils.printArray(arr);
}
2、快排1.0
根据上面的荷兰国旗问题,取数组最后一个值作为num,进行递归处理,得到快排1.0版本
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用快速排序
quickSort(arr, 0, arr.length-1);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
public static void quickSort(int[] arr, int L, int R) {
if(L < R){
int minPart = partition(arr, L, R);
quickSort(arr, L, minPart - 1);
quickSort(arr, minPart + 1, R);
}
}
//按荷兰国旗问题1处理数据
public static int partition(int[] arr, int L, int R) {
int num = arr[R];
int minPart= L - 1;
for (int i = L; i <= R ; i++) {
if(arr[i] <= num){
SortUtils.swap(arr, minPart + 1, i);
minPart++;
}
}
//最终停留为是:小于num的最后一个值
return minPart;
}
}
3、荷兰国旗问题2
给定一个数组,把小于num的数放在左边,把等于num的数放在中间,把大于num的数放在右边
1.如果当前指针指向的值小于5,与minPart的后一位交换,minPart++,i++
2.如果当前指针指向的值等于5,i++
3.如果当前指针指向的值大于5,与maxPart的前一位交换,maxPart--,i不变
public static void heLanGuoQi2(int[] arr, int num) {
//小于等于num的区域
int minPart = -1;
int maxPart = arr.length;
//指针
int i = 0;
//1.如果当前指针指向的值小于5,与minPart的后一位交换,minPart++,i++
//2.如果当前指针指向的值等于5,i++
//3.如果当前指针指向的值大于5,与maxPart的前一位交换,maxPart--,i不变
while(i < maxPart){
if(arr[i] < num){
SortUtils.swap(arr, minPart+1, i);
minPart++;
i++;
}else if(arr[i] == num){
i++;
}else{
SortUtils.swap(arr, maxPart-1, i);
maxPart--;
}
}
SortUtils.printArray(arr);
}
4、快排2.0
根据上面的荷兰国旗问题,取数组最后一个值作为num,进行递归处理,得到快排2.0版本。会比1.0效率高一些,因为一次会排好一批数据
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用快速排序
quickSort(arr, 0, arr.length-1);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
public static void quickSort(int[] arr, int L, int R) {
if(L < R){
int[] part = partition(arr, L, R);
quickSort(arr, L, part[0]);
quickSort(arr, part[1], R);
}
}
//按荷兰国旗问题2处理数据
public static int[] partition(int[] arr, int L, int R) {
int num = arr[R];
int minPart= L - 1;
int maxPart= R + 1;
int i = L;
while (i < maxPart) {
if(arr[i] < num){
SortUtils.swap(arr, ++minPart, i++);
}else if(arr[i] == num){
i++;
}else{
SortUtils.swap(arr, --maxPart, i);
}
}
return new int[]{minPart, maxPart};
}
}
5、快排3.0
不管是1.0还是2.0版本,在遇到最差的情况的时候,时间复杂度都是。
例如对{1,2,3,4,5,6,7,8,9}进行倒序排序。
快排3.0的优化方式为,随机选取一个值,放到最后,作为num值,然后进行排序
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用快速排序
quickSort(arr, 0, arr.length-1);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
public static void quickSort(int[] arr, int L, int R) {
if(L < R){
int[] part = partition(arr, L, R);
quickSort(arr, L, part[0]);
quickSort(arr, part[1], R);
}
}
//按荷兰国旗问题2处理数据
public static int[] partition(int[] arr, int L, int R) {
//随机选取一个值,作为num值,使时间复杂度为O(nlogn)
int point = L + (int)((R-L+1) * Math.random());
SortUtils.swap(arr, point, R);
int num = arr[R];
int minPart= L - 1;
int maxPart= R + 1;
int i = L;
while (i < maxPart) {
if(arr[i] < num){
SortUtils.swap(arr, ++minPart, i++);
}else if(arr[i] == num){
i++;
}else{
SortUtils.swap(arr, --maxPart, i);
}
}
return new int[]{minPart, maxPart};
}
}
注:因为多了下面两行代码,所以时间复杂度是
int point = L + (int)((R-L+1) * Math.random());
SortUtils.swap(arr, point, R);
堆排序
1、完全二叉树
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
2、大根堆
任意一颗子树中,头结点的值,比子节点的值大
- 确定i位置的左孩子:
2 * i + 1- 确定i位置的右孩子:
2 * i + 1- 确定i位置的父:
( i - 1 ) / 2
heapInsert:往大根堆末尾插入一个值,或者把index位置的值替换成一个比原值大的数,并重建的过程。
比较index上的值,是否比父大。如果比父小,结束。否则交换并重复执行
/**
* 向大根堆插入值后,构建新的大根堆
* @param arr
* @param index
*/
public static void heapInsert(int[] arr, int index){
//如果当前节点大于父节点
while(arr[index] > arr[(index - 1) / 2]){
//交换
SortUtils.swap(arr, index, (index - 1) / 2);
//当前节点等于父节点
index = (index - 1) / 2;
}
}
heapify:把index位置的值替换成一个比原值小的数,并重建的过程 左右孩子比较,获得最大值; index的值与最大值比较,如果index的值比较大,结束。否则交换并重复执行
/**
*
* @param arr
* @param index 任意节点
* @param heapSize 堆大小
*/
public static void heapify(int[] arr, int index, int heapSize){
//左孩子
int left = 2 * index + 1;
while(left < heapSize){
//找出左右两个孩子的最大值
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//最大的孩子和当前节点比较
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index){
//找到了index应该在的位置,结束循环
break;
}
//交换
SortUtils.swap(arr, index, largest);
index = largest;
left = 2 * index + 1;
}
}
堆排序:
- 使用heapInsert构建一个大根堆
- 把最大值(根节点),与最后一位交换。执行heapify
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
//测试比较次数
int compareTimes = 1000;
boolean isSuccess = true;
for (int i = 0; i < compareTimes; i++) {
//生成随机数组
int[] arr = SortUtils.generateRandomArray(100, 100);
//复制一份
int[] arrCopy = SortUtils.copyArray(arr);
SortUtils.printArray(arr);
//使用快速排序
heapSort(arr);
SortUtils.printArray(arr);
//系统自带排序
Arrays.sort(arrCopy);
SortUtils.printArray(arrCopy);
if(!SortUtils.isEquals(arr, arrCopy)){
isSuccess = false;
break;
}
}
System.out.println(isSuccess ? "success" : "fail");
}
public static void heapSort(int[] arr){
int heapSize = 1;
while(heapSize < arr.length){
heapInsert(arr, heapSize++);
}
while(heapSize > 0){
SortUtils.swap(arr, 0, heapSize - 1);
heapify(arr, 0, --heapSize);
}
}
/**
* 向大根堆插入值后,构建新的大根堆
* @param arr
* @param index
*/
public static void heapInsert(int[] arr, int index){
//如果当前节点大于父节点
while(arr[index] > arr[(index - 1) / 2]){
//交换
SortUtils.swap(arr, index, (index - 1) / 2);
//当前节点等于父节点
index = (index - 1) / 2;
}
}
/**
*
* @param arr
* @param index 任意节点
* @param heapSize 堆大小
*/
public static void heapify(int[] arr, int index, int heapSize){
//左孩子
int left = 2 * index + 1;
while(left < heapSize){
//找出左右两个孩子的最大值
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//最大的孩子和当前节点比较
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index){
//找到了index应该在的位置,结束循环
break;
}
//交换
SortUtils.swap(arr, index, largest);
index = largest;
left = 2 * index + 1;
}
}
}