经常使用8中排序算法Java实现与比较

1、经常使用的排序算法有以下几种:html

    一、冒泡排序java

    二、选择排序git

    三、插入排序算法

    四、快速排序数组

    五、堆排序数据结构

    六、归并排序dom

    七、计数排序测试

    八、基数排序动画

    接下来从原理、代码实现和测试三步对它们进行分析。ui

2、算法分析、实现、测试

    一、基类BaseSort.java

        该类包含各个排序类的共用部分和生成测试数据功能,其余全部排序算法类都继承该类。具体代码以下:

package test.algorithm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Random;

public class BaseSort {
	public static int length = 500000; // 测试数据量
	public static int[] data = new int[length]; // 测试数据集
	public static int lengthOfDigit = 8; // 测试数据的最大位数
	public static int maxValue = (int) Math.pow(10, lengthOfDigit); // 测试数据的最大值

	/**
	 * 输出数据到控制台
	 * 
	 * @param data
	 */
	protected static void printArr(int[] data) {
		for (int i = 0; i < data.length; i++) {
			System.out.print(data[i] + " ");
		}
		System.out.println();
	}

	/**
	 * 输出数据到文件
	 * 
	 * @param data
	 * @param path
	 */
	protected static void printToFile(int[] data, String path) {
		try {
			File f = new File(path);
			if (!f.exists() && !f.createNewFile()) {
				System.out.println("文件建立失败!");
				return;
			}

			BufferedWriter output = new BufferedWriter(new FileWriter(f));
			for (int i = 0; i < data.length; i++) {
				output.write(data[i] + "\r\n");
			}

			output.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 交换两个数据
	 * 
	 * @param data
	 * @param i
	 * @param j
	 */
	protected static void swap(int[] data, int i, int j) {
		data[i] = data[i] + data[j];
		data[j] = data[i] - data[j];
		data[i] = data[i] - data[j];
	}

	/**
	 * 生成测试数据集
	 */
	protected static void genData() {
		Random r = new Random();
		for (int i = 0; i < length; i++) {
			data[i] = r.nextInt(maxValue);
		}
	}
}

  二、冒泡排序

    (1)算法原理:冒泡排序简单的说就是重复比较相邻的两个元素,若是顺序错误就交换使之顺序正确。时间复杂度Ο(n2) ,空间复杂度为Ο(1)

    (2)动画演示

        

    (3)代码实现

package test.algorithm;

public class BubbleSort extends BaseSort {
	public static void bubblesort(int[] data, int start, int end)
			throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		for (int i = start; i < end; i++) {
			for (int j = end; j > i; j--) {
				if (data[j] < data[j - 1]) {
					swap(data, j, j - 1);
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		bubblesort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("冒泡排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

        50万数据,时间太长。

    3、选择排序

    (1)算法原理:选择排序每次从未排序的数据中选择最小的,而后将该元素放到已经排好序的数据末尾,循环直到结束。时间复杂度Ο(n2) ,空间复杂度为Ο(1)

    (2)动画演示

       

    (3)代码实现

package test.algorithm;

public class SelectSort extends BaseSort {
	public static void selectsort(int[] data, int start, int end)
			throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		for (int i = start; i < end; i++) {
			int minIndex = i;
			for (int j = i + 1; j <= end; j++) {
				if (data[j] < data[minIndex]) {
					minIndex = j;
				}
			}

			if (i != minIndex) {
				swap(data, i, minIndex);
			}
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		selectsort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("选择排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果


(最大8位)

    四、插入排序

    (1)算法原理:插入排序每次从未排序的数据中选取第一个,而后在已经排好序的数据中找到该元素的正确位置,将该元素放到该正确位置,循环直到结束。时间复杂度Ο(n2) ,空间复杂度为Ο(1)

    (2)动画演示

        

    (3)代码实现

package test.algorithm;

public class InsertSort extends BaseSort {
	public static void insertsort(int[] data, int start, int end)
			throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		for (int i = start; i < end; i++) {
			int curr = data[i + 1];
			int j = i + 1;
			while (j > start && data[j - 1] > curr) {
				data[j] = data[j - 1];
				j--;
			}

			if (j != i + 1) {
				data[j] = curr;
			}
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		insertsort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("插入排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

(最大8位)

    五、快速排序

    (1)算法原理:快速排序每次使用一个元素做为基准,将比基准小的放到前面,比基准大的放到后面,再分别对先后两部分使用快速排序。基准的选择能够任意(下面的代码使用第一个元素)。平均状况下时间复杂度为Ο(log n), 最坏状况下时间复杂度为Ο(n2) ;空间复杂度为Ο(n)

    (2)动画演示

        

    (3)代码实现

package test.algorithm;

public class QuickSort extends BaseSort {
	public static int partition(int[] data, int start, int end)
			throws Exception {
		int lastOne = data[start]; // 使用第一个元素做为基准
		int i = start, j = end;

		while (i < j) {
			while (i < j && data[j] >= lastOne) {
				j--;
			}
			data[i] = data[j];

			while (i < j && data[i] <= lastOne) {
				i++;
			}
			data[j] = data[i];
		}

		data[i] = lastOne;
		return i;
	}

	public static void quicksort(int[] data, int start, int end)
			throws Exception {
		if (data == null || data.length <= 0 || data.length < end
				|| data.length < start) {
			throw new Exception("数据为空或者索引超出范围");
		}

		int index = partition(data, start, end);

		if (index > start) {
			quicksort(data, start, index - 1);
		}

		if (index < end) {
			quicksort(data, index + 1, end);
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		quicksort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("快速排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

(最大8位)

    六、堆排序

    (1)算法原理:堆排序使用堆这种数据结构进行排序。堆是一个彻底二叉树,知足:大根堆的任意节点的左右子节点(若是有)的值都比自己节点值小,小根堆反之。首先将全部数据生成堆,而后每次将第一个元素和未排好序的数据的最后一个交换,再将除最后一个元素外的数据调整成一个堆。时间复杂度为Ο(log n), 且空间复杂度为Ο(1)。

    (2)动画演示

        

    (3)代码实现

package test.algorithm;

public class HeapSort extends BaseSort {
	private static void adjust(int[] data, int start, int end) {
		int remain = data[start];
		int i = start * 2;
		int j = start;

		while (i <= end) {
			if (i + 1 <= end && data[i] < data[i + 1]) {
				i++;
			}

			if (data[i] < remain) {
				break;
			}

			data[j] = data[i];
			j = i;
			i *= 2;
		}

		data[j] = remain;
	}

	// 使用大根堆排序
	public static void heapsort(int[] data, int start, int end)
			throws Exception {
		if (data == null || data.length <= 0 || data.length < end
				|| data.length < start) {
			throw new Exception("数据为空或者索引超出范围");
		}

		for (int i = end / 2; i >= start; i--) {
			adjust(data, i, end);
		}

		for (int i = end; i > start; i--) {
			swap(data, start, i);
			adjust(data, start, i - 1);
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		heapsort(data, 1, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("堆排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

(最大8位)

    七、归并排序

    (1)算法原理:归并排序是分治法的一个典型应用。将待排序数据分红两部分,先将这两部分排好序,而后将两部分组合成一个有序的总体。时间复杂度为Ο(log n),空间复杂度为Ο(n)

    (2)动画演示

        

    (3)代码实现

package test.algorithm;

public class MergeSort extends BaseSort {
	public static void mergesort(int[] data, int start, int end)
			throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		if (start < end) {
			int mid = (start + end) / 2;
			mergesort(data, start, mid);
			mergesort(data, mid + 1, end);

			int[] tmp = new int[end - start + 1];
			int i = start, j = mid + 1, k = 0;
			while (i <= mid && j <= end) {
				if (data[i] < data[j]) {
					tmp[k++] = data[i++];
				} else {
					tmp[k++] = data[j++];
				}
			}

			while (i <= mid) {
				tmp[k++] = data[i++];
			}

			while (j <= end) {
				tmp[k++] = data[j++];
			}

			i = start;
			j = 0;
			while (i <= end) {
				data[i++] = tmp[j++];
			}
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		mergesort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("归并排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

(最大8位)

    八、计数排序

    (1)算法原理:计数排序是惟一一个不经过比较的排序算法。时间复杂度为Ο(n),空间复杂度为Ο(m)。(m为排序的数据范围)

            主要思想是待排序的数据大小必须在必定范围内(最大值为m),开一个m的数组,记录每一个元素出现的次数。基于每一个元素出现的次数,而后将每一个元素以前的元素出现的次数累加(包括自己的值)做为新值,获得的是不比本身小的元素个数,该数值就是该元素应该在的位置。具体下方的动画演示连接。

    (2)动画演示:请参考http://www.cs.usfca.edu/~galles/visualization/flash.html

    (3)代码实现

package test.algorithm;

public class CountSort extends BaseSort {
	public static void countsort(int[] data, int start, int end)
			throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		int[] countArray = new int[maxValue];

		// 清零
		for (int i = 0; i < maxValue; i++) {
			countArray[i] = 0;
		}

		// 计数
		for (int i = start; i <= end; i++) {
			countArray[data[i]]++;
		}

		// 累加
		for (int i = 1; i < maxValue; i++) {
			countArray[i] += countArray[i - 1];
		}

		int[] sortedArray = new int[length];

		// 排序
		for (int i = start; i <= end; i++) {
			countArray[data[i]]--;
			sortedArray[countArray[data[i]]] = data[i];
		}

		// 复制
		for (int i = start; i <= end; i++) {
			data[i] = sortedArray[i];
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		countsort(data, 0, length - 1);
		long endTime = System.currentTimeMillis();
		System.out.println("计数排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

    (4)测试结果

(最大8位)

    九、基数排序

    (1)算法原理:基数排序是从右到左一位一位的比较排序的算法。待排序元素须要有已知最大位数,先按照个位排序(个位相同的以出现顺序为准),而后按照十位进行排序......时间复杂度为Ο(nm),空间复杂度为Ο(n)。(n为元素个数,m为元素的位数)

    (2)动画演示:请参考http://www.cs.usfca.edu/~galles/visualization/flash.html

    (3)代码实现

package test.algorithm;

public class RadixSort extends BaseSort {
	private static int digitOfIndex(int data, int index) throws Exception {
		if (index < 1) {
			throw new Exception("位数不能小于1");
		}

		data = data / ((int) Math.pow(10, index - 1));
		int digit = data % 10;

		return digit;
	}

	public static void radixsort(int[] data, int start, int end,
			int lengthOfDigit) throws Exception {
		if (data == null || start < 0 || end < 0) {
			throw new Exception("参数错误");
		}

		for (int i = 1; i <= lengthOfDigit; i++) {
			int[][] list = new int[10][length]; // list[x][0]保留该组数据个数

			for (int j = start; j <= end; j++) {
				int digit = digitOfIndex(data[j], i);
				list[digit][++list[digit][0]] = data[j];
			}

			int k = start;
			for (int j = 0; j <= 9; j++) {
				for (int l = 1; l <= list[j][0]; l++) {
					data[k++] = list[j][l];
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {
		genData();
		long startTime = System.currentTimeMillis();
		radixsort(data, 0, length - 1, lengthOfDigit);
		long endTime = System.currentTimeMillis();
		System.out.println("基数排序,测试" + length + "个随机数据,耗时:"
				+ (endTime - startTime) + "毫秒");
	}
}

     (4)测试结果

(最大8位)

3、各算法对比

以下表格对上述各算法进行了简要对比,表格中n为元素数量。


时间复杂度 空间复杂度 测试500 测试5千 测试5万 测试50万,6位 测试50万,8位 备注
冒泡排序 Ο(n2) Ο(1) 5ms 32ms 3722ms 时间太长 时间太长
选择排序 Ο(n2) Ο(1) 2ms 14ms 932ms 92339ms 92540ms
插入排序 Ο(n2) Ο(1) 1ms 11ms 614ms 60390ms 59402ms
快速排序 Ο(n log n) Ο(n) 0ms 4ms 12ms 58ms 59ms
堆排序 Ο(n log n) Ο(1) 0ms 4ms 7ms 59ms 59ms
归并排序 Ο(n log n) Ο(n) 0ms 6ms 31ms 98ms 98ms
计数排序 Ο(n) Ο(m) 8ms 8ms 9ms 25ms 256ms m为排序的数据范围
基数排序 Ο(nm) Ο(n) 1ms 4ms 21ms 53ms 65ms m为元素的最大位数

从对比结果能够看出:     

   一、最简单的冒泡排序毫无疑问效率是最差的,并且与其余算法差异很明显;

   二、选择排序和插入排序算法实现难度差很少,效率也相差不大;

   三、快速排序、堆排序、归并排序、计数排序、基数排序从实际测试来看,时间消耗上差异都在同一个数量级;

   四、除了计数排序是基于数据的位数,对数据位数(如8位数比6位数就相差一个数量级)比较敏感,其余排序算法基本上没有影响。

相关文章
相关标签/搜索