【Java 算法】排序算法评测模板 (12 种排序算法对比)

代码

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;

public class 算法评测 {

	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		while (scan.hasNext()) {
			int n = scan.nextInt(); //创建 N 个随机数赋值给数组
			if (n < 2) break;
			Random ran = new Random();
			int[] arr = new int[n];
			for (int i = 0; i < arr.length; i++) { //填充随机数给数组
				arr[i] = ran.nextInt(n);
			}
			int[] bak = arr.clone(); //备份
			System.out.println(n + " 个数据排序 排序中... Running...");
			for (int i = 5; i <= 12; i++) { //控制排序算法种类范围
				long startTime = System.currentTimeMillis();
				String mode = "";
				try {
					switch (i) {
					case 1:
						mode = "选择排序";
						selectSort(arr);
						break;
					case 2:
						mode = "冒泡排序";
						bubbleSort(arr);
						break;
					case 3:
						mode = "插入排序";
						insertSort(arr);
						break;
					case 4:
						mode = "希尔排序";
						shellSort(arr);
						break;
					case 5:
						mode = "快速排序";
						quickSort(arr);
						break;
					case 6:
						mode = "迭代快速";
						quickSortStack(arr);
						break;
					case 7:
						mode = "归并排序";
						mergeSort(arr);
						break;
					case 8:
						mode = "迭代归并";
						mergeSortStack(arr);
						break;
					case 9:
						mode = "计数排序";
						countSort(arr);
						break;
					case 10:
						mode = "计数TWO";
						countTwoSort(arr);
						break;
					case 11:
						mode = "基数排序";
//						baseSort(arr);
						break;
					case 12:
						mode = "API并行排序(数量 < N 快速 > N 归并 (???))";
						Arrays.parallelSort(arr);
						break;
					}
				} catch (Error e) {
					System.out.print("狗带了 ————> ");
				}
				double runningTime = System.currentTimeMillis() - startTime;
				System.out.println(mode + " == " + (runningTime / 1000) + "s");
				arr = bak.clone(); //重置数组
			}
			System.out.println("Sort Over!");
		}
		scan.close();
	}
	
	public static void swap(int[] arr, int a, int b) {
		arr[a] = arr[a] ^ arr[b];
		arr[b] = arr[a] ^ arr[b];
		arr[a] = arr[a] ^ arr[b];
	}
	
	public static void selectSort(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			int targetIndex = i; //一直寻找最小值的下标
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[j] < arr[targetIndex]) {
					targetIndex = j;
				}
			}
			if (targetIndex != i) {
				swap(arr, i, targetIndex);
			}
		}
	}
	
	public static void bubbleSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - 1 - i; j++) {
				if (arr[j] > arr[j + 1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}
	
	public static void insertSort(int[] arr) {
		for (int i = 1; i < arr.length; i++) { //默认第一个为最小
			int bak = arr[i]; //备份找到的当前索引
			int insertIndex = i; //记录插入索引位置
			for (int j = i - 1; j >= 0; j--) {
				if (bak < arr[j]) { //不使用交换方法
					arr[insertIndex] = arr[j]; //移动
					insertIndex = j;
				} else { //比前面的大,直接停止
					break;
				}
			}
			if (insertIndex != i) { //自身无需交换
				arr[insertIndex] = bak;
			}
		}
	}
	
	public static void shellSort(int[] arr) { //插入排序升级
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			for (int i = gap; i < arr.length; i++) {
				int bak = arr[i];
				int insertIndex = i; //定义倒退索引
				while (insertIndex - gap >= 0 && bak < arr[insertIndex - gap]) {
					arr[insertIndex] = arr[insertIndex - gap]; //移动
					insertIndex -= gap; //最多等于 0,不怕越界
				}
				if (insertIndex != i) { //不等于本身时,插入
					arr[insertIndex] = bak;
				}
			}
		}
	}
	
	public static void quickSort(int[] arr) {
		quickSort(arr, 0, arr.length - 1);
	}
	
	public static void quickSort(int[] arr, int startIndex, int endIndex) { //优化版
		int i = startIndex;
		int j = endIndex;
		while (i < j) {
			while (j >= arr[startIndex] && j > i) j--; //寻找小于基数值
			while (i <= arr[startIndex] && i < j) i++; //寻找大于基数值
			if (i < j) { //不是交换基数时
				swap(arr, i, j);
			}
		}
		if (i != startIndex) { //不交换自己
			swap(arr, startIndex, i);
		}
		if (startIndex < i - 1) quickSort(arr, startIndex, i - 1);
		if (i + 1 < endIndex) quickSort(arr, i + 1, endIndex);
	}
	
	static class Quick {
		public int startIndex;
		public int endIndex;
		public Quick(int startIndex, int endIndex) {
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}
	}
	
	public static void quickSortStack(int[] arr) { //Stack 非递归优化版,快排特性是每个区域的排序都是互不相干的,所以也可以使用队列
		Stack<Quick> stack = new Stack<>();
		stack.push(new Quick(0, arr.length - 1));
		while (! stack.isEmpty()) {
			Quick pop = stack.pop(); //快速排序直接取出即可,因为只有区间排序
			//二分每次最左边的数作为基数
			int i = pop.startIndex;
			int j = pop.endIndex;
			while (i < j) { //实行检查交换, 基数从最左边开始时,一定要让 j 先走
				while (arr[j] >= arr[pop.startIndex] && j > i) j--; //寻找小于 base 的数
				while (arr[i] <= arr[pop.startIndex] && i < j) i++; //寻找大于 base 的数
				if (i < j) { //交换
					swap(arr, i, j);
				}
			}
			//交换基数
			if (i != pop.startIndex) { //基数自己不需要交换
				swap(arr, pop.startIndex, i);
			}
			if (pop.startIndex < i - 1) { //压栈左区间
				stack.push(new Quick(pop.startIndex, i - 1));
			}
			if (i + 1 < pop.endIndex) { //压栈右区间
				stack.push(new Quick(i + 1, pop.endIndex));
			}
		}
	}
	
	public static int[] countSort(int[] arr) {
		int max = arr[0];
		int min = arr[0];
		for (int i = 1; i < arr.length; i++) { //打擂台
			if (arr[i] > max) max = arr[i];
			if (arr[i] < min) min = arr[i];
		}
		int[] prefix = new int[max - min + 1]; //创建前缀和数组
		for (int i = 0; i < arr.length; i++) prefix[arr[i] - min]++; //记录每个数出现个数
		for (int i = 1; i < prefix.length; i++) prefix[i] = prefix[i] + prefix[i - 1]; //计算前缀和
		int[] result = new int[arr.length]; //创建排序数组
		for (int i = 0; i < arr.length; i++) {
			result[prefix[arr[i] - min] - 1] = arr[i]; //精准打击
			prefix[arr[i] - min]--; //更新数个数
		}
		return result;
	}
	
	public static void countTwoSort(int[] arr) {
		// 打擂台
		int max = arr[0];
		int min = arr[0];
		for (int i = 1; i < arr.length; i++) {
			if (arr[i] > max) max = arr[i];
			if (arr[i] < min) min = arr[i];
		}
		// 创建长度
		int[] count = new int[max - min + 1]; // + 1 是加上 min 本身被减去的数
		// 计算每个数出现个数
		for (int i = 0; i < arr.length; i++) count[arr[i] - min]++;
		// 计算并写入源数组
		for (int i = 0, index = 0; i < count.length; i++) {
			while (count[i] > 0) {
				arr[index++] = i + min;
				count[i]--;
			}
		}
	}
	
	public static void mergeSort(int[] arr) {
		mergeSort(arr, new int[arr.length], 0, arr.length - 1);
	}
	
	public static void mergeSort(int[] arr, int[] temp, int startIndex, int endIndex) { //优化版
		if (startIndex < endIndex) { //直到分成 1 个,递归回归后,从最低 2 个开始合并
			int mid = (endIndex + startIndex) / 2;
			mergeSort(arr, temp, startIndex, mid);
			mergeSort(arr, temp, mid + 1, endIndex);
			merge(arr, temp, startIndex, mid, endIndex);
		}
	}
	
	public static void merge(int[] arr, int[] temp, int startIndex, int mid, int endIndex) {
		int i = startIndex;
		int j = mid + 1;
		int k = 0;
		while (i <= mid && j <= endIndex) { //要么左边区域先存完,要么右边先存完
			if (arr[i] < arr[j]) {
				temp[k++] = arr[i++];
			} else {
				temp[k++] = arr[j++];
			}
		}
		while (i <= mid) temp[k++] = arr[i++]; //把没存完的直接一次性存完
		while (j <= endIndex) temp[k++] = arr[j++];
		for (int x = 0; x < k; x++) { //把排序好的存进主数组,x < k 时则是小于之前的 k++,所以避免越开真实排序数据
			arr[startIndex++] = temp[x]; //从 startIndex 开始存入
		}
	}
	
	static class Area {
		public int left;
		public int right;
		public boolean isBinary; //重要标识,判断是否已经二分过,没有则继续二分,然后标识 true
		public Area (int left, int right) {
			this.left = left;
			this.right = right;
		}
	}
	
	static class Merge extends Area {
		public int mid;
		public Merge(int left, int mid, int right, boolean isBinary) {
			super(left, right);
			this.mid = mid;
			this.isBinary = isBinary;
		}
	}
	
	public static void mergeSortStack(int[] arr) { //非递归版
		Stack<Area> stack = new Stack<>();
		stack.push(new Merge(0, (arr.length - 1) / 2, arr.length - 1, false)); //初始化最底层合并对象
		int[] temp = new int[arr.length];
		while (! stack.isEmpty()) {
			Area peek = stack.peek(); //归并需要拿出来看,而不是直接取,最后需要合并
			if (peek.isBinary) { //判断是否已二分
				if (peek instanceof Merge) { //此时要么是 Merge 对象,要么是 Area 对象
					merge(arr, temp, peek.left, ((Merge) peek).mid, peek.right);
					stack.pop();
				} else { //Area 直接弹栈
					stack.pop();
				}
			} else {
				int left = peek.left;
				int right = peek.right;
				if (left < right) { //直至剩余一个时,则弹栈,然后就是从最小两个开始合并
					peek.isBinary = true; //标记已二分操作
					int mid = (left + right) / 2;
					// 添加对象入栈时,要按常规递归版倒序入栈,把合并对象先压栈,区域对象后入栈,是为了查看区域对象是否已经二分,没有则继续二分
					// 最关键是,存储的压栈关系,Area <—— Area <—— Merge <—— Area <—— Area <—— Merge <—— Area <—— Area <—— Merge(这个是最开始入栈的合并对象)
					stack.push(new Merge(left, mid, right, true)); //之后的操作一直为是已经二分的状态 true
					stack.push(new Area(mid + 1, right)); //右区间
					stack.push(new Area(left, mid)); //左区间
				} else { //在等于 1 个元素时,直接弹出栈
					stack.pop();
				}
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public static void baseSort(int[] arr) { //优化版
		Queue<Integer>[] queue = new LinkedList[10]; //创建10个桶
		for (int i = 0; i < queue.length; i++) queue[i] = new LinkedList<>();
		int maxRadix = 1;
		for (int i = 0; i < arr.length; i++) if (arr[i] > maxRadix) maxRadix = arr[i];
		maxRadix = String.valueOf(maxRadix).length(); //转换成遍历的最大次数与数各位
		for (int i = 0; i < maxRadix; i++) {
			int pow = (int) Math.pow(10, i); //直接利用数学,把后面的数除掉,取最后一个位置,即可达到目的
			for (int j = 0; j < arr.length; j++) { //入桶
				queue[arr[j] / pow % 10].offer(arr[j]);
			}
			for (int j = 0, x = 0; j < arr.length; x++) { //回写源数组
				while (! queue[x].isEmpty()) {
					arr[j++] = queue[x].poll();
				}
			}
		}
	}

}

输出

10000000
10000000 个数据排序 排序中... Running...
狗带了 ————> 快速排序 == 0.588s
迭代快速 == 1.162s
归并排序 == 1.186s
迭代归并 == 1.474s
计数排序 == 0.349s
计数TWO == 0.164s
基数排序 == 0.0s
API并行排序(数量 < N 快速 > N 归并 (???)) == 0.381s
Sort Over!
100000000
100000000 个数据排序 排序中... Running...
狗带了 ————> 快速排序 == 0.612s
迭代快速 == 12.981s
归并排序 == 13.947s
迭代归并 == 16.608s
计数排序 == 4.622s
计数TWO == 1.946s
基数排序 == 0.0s
API并行排序(数量 < N 快速 > N 归并 (???)) == 1.014s
Sort Over!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚妄狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值