經常使用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位數就相差一個數量級)比較敏感,其餘排序算法基本上沒有影響。

相關文章
相關標籤/搜索