八大排序算法的实现及对比

本贴最后更新于 1141 天前,其中的信息可能已经时过境迁

选取了最常见的八种排序算法:冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序、基数排序、桶排序,做一个整理和对比。

时空复杂度及算法特性

每个算法的细节不一一展开,直接上表,对比不同算法之间的时空复杂度及其特性。

冒泡排序

public class BubbleSort {
	void bubbleSort(int[] nums) {
		int n = nums.length;
		int tmp;
		boolean flag;
		for (int i = 0; i < n - 1; i++) {
			flag = false;
			for (int j = 0; j < n - i - 1; j++) {
				if (nums[j] > nums[j + 1]) {
					tmp = nums[j];
					nums[j] = nums[j + 1];
					nums[j + 1] = tmp;
					flag = true;
				}
			}
			if (!flag) {
				break;
			}
		}
	}
}

插入排序

public class InsertionSort {
	void insertionSort(int[] nums) {
		int n = nums.length;
		for (int i = 1; i < n; i++) {
			int j = i;
			while (j > 0) {
				if (nums[j] < nums[j - 1]) {
					int tmp = nums[j];
					nums[j] = nums[j - 1];
					nums[j - 1] = tmp;
					j--;
				} else {
					break;
				}
			}
		}
	}
}

选择排序

public class SelectionSort {
	void selectionSort(int[] nums) {
		int n = nums.length;
		for (int i = 0; i < n - 1; i++) {
			int minIndex = i;
			for (int j = i + 1; j < n; j++) {
				if (nums[j] < nums[minIndex]) {
					minIndex = j;
				}
			}
			if (minIndex != i) {
				int tmp = nums[minIndex];
				nums[minIndex] = nums[i];
				nums[i] = tmp;
			}
		}
	}
}

快速排序

public class QuickSort {
	void quickSort(int[] nums, int l, int r) {
		while (l < r) {
			int i = partition(nums, l, r);
			// Tail Call:仅递归至较短子数组,控制递归深度
			if (i - l < r - i) {
				quickSort(nums, l, i - 1);
				l = i + 1;
			} else {
				quickSort(nums, i + 1, r);
				r = i - 1;
			}
		}
	}

	int partition(int[] nums, int l, int r) {
		// 随机基准数:闭区间[l, r]随机选取任意索引,并与nums[l]交换,防止最差时间复杂度
		int ra = (int) (l + Math.random() * (r - l + 1));
		swap(nums, l, ra);

		// 基准数:nums[l]
		int i = l, j = r;
		while (i < j) {
			while (i < j && nums[j] >= nums[l]) {
				j--;
			}
			while (i < j && nums[i] <= nums[l]) {
				i++;
			}
			swap(nums, i, j);
		}
		swap(nums, i, l);
		return i;
	}

	void swap(int[] nums, int i, int j) {
		int tmp = nums[i];
		nums[i] = nums[j];
		nums[j] = tmp;
	}
}

归并排序

public class MergeSort {
	void mergeSort(int[] nums, int l, int r) {
		if (l >= r) {
			return;
		}
		int m = (l + r) / 2;
		mergeSort(nums, l, m);
		mergeSort(nums, m + 1, r);

		// 暂存需要合并区间的元素
		int[] tmp = new int[r - l + 1];
		for (int k = l; k <= r; k++) {
			tmp[k - l] = nums[k];
		}

		// 左右指针首元素
		int i = 0, j = m - l + 1;
		// 遍历合并左右数组
		for (int k = l; k <= r; k++) {
			if (i == m - l + 1) {
				nums[k] = tmp[j++];
			} else if (j == r - l + 1 || tmp[i] <= tmp[j]) {
				nums[k] = tmp[i++];
			} else {
				nums[k] = tmp[j++];
			}
		}
	}
}

堆排序

public class HeapSort {
	void heapSort(int[] nums) {
		int n = nums.length;
		// 构建大顶堆
		buildMaxHeap(nums, n);

		for (int i = n - 1; i > 0; i--) {
			// 将大顶堆堆首元素与堆尾元素交换(堆选择排序将最大值放大数组尾部)
			swap(nums, 0, i);
			n--;
			// 维护交换后的树满足大顶堆性质
			heapify(nums, 0, n);
		}
	}

	/**
	 * 构建大顶堆
	 * 从最后一个非叶子节点(len / 2 - 1)开始,若父节点小于子节点则交换位置
	 * 依次从右至左,从下至上
	 */
	void buildMaxHeap(int[] nums, int len) {
		for (int i = len / 2 - 1; i >= 0; i--) {
			heapify(nums, i, len);
		}
	}

	/**
	 * 维护大顶堆性质
	 * 左右叶子节点小于父节点
	 */
	void heapify(int[] nums, int i, int len) {
		int left = 2 * i + 1;
		int right = 2 * i + 2;
		int largest = i;

		if (left < len && nums[left] > nums[largest]) {
			largest = left;
		}
		if (right < len && nums[right] > nums[largest]) {
			largest = right;
		}
		if (largest != i) {
			swap(nums, i, largest);
			// 以交换节点作为父节点递归维护子树的大顶堆性质
			heapify(nums, largest, len);
		}
	}

	void swap(int[] nums, int i, int j) {
		int tmp = nums[i];
		nums[i] = nums[j];
		nums[j] = tmp;
	}
}

基数排序

public class RadixSort {
	void radixSort(int[] nums) {
		int maxDigit = getMaxDigit(nums);
		int div = 1;
		int[] count = new int[10];
		int[] result = new int[nums.length];

		for (int i = 0; i < maxDigit; i++) {
			for (int num : nums) {
				int pos = num / div % 10;
				count[pos]++;
			}

			for (int j = 1; j < count.length; j++) {
				count[j] = count[j] + count[j - 1];
			}

			for (int j = nums.length - 1; j >= 0; j--) {
				int pos = nums[j] / div % 10;
				result[--count[pos]] = nums[j];
			}

			System.arraycopy(result, 0, nums, 0, nums.length);
			Arrays.fill(count, 0);
			div *= 10;
		}
	}

	/**
	 * 获取最高位位数
	 */
	int getMaxDigit(int[] nums) {
		int maxValue = nums[0];
		for (int value : nums) {
			if (value > maxValue) {
				maxValue = value;
			}
		}
		if (maxValue == 0) {
			return 1;
		}
		int length = 0;
		for (long tmp = maxValue; tmp != 0; tmp /= 10) {
			length++;
		}
		return length;
	}
}

桶排序

public class BucketSort {
	int BUCKET_SIZE = 10;

	void bucketSort(int[] nums) {
		int minValue = nums[0];
		int maxValue = nums[0];
		for (int value : nums) {
			if (value < minValue) {
				minValue = value;
			} else if (value > maxValue) {
				maxValue = value;
			}
		}

		int bucketCount = (maxValue - minValue) / BUCKET_SIZE + 1;
		int[][] buckets = new int[bucketCount][0];

		// 利用映射函数分配数据到桶中
		for (int num : nums) {
			int index = (num - minValue) / BUCKET_SIZE;
			buckets[index] = arrayAppend(buckets[index], num);
		}

		int arrIndex = 0;
		for (int[] bucket : buckets) {
			if (bucket.length <= 0) {
				continue;
			}
			// 对每个桶进行排序,桶内自主排序使用快速排序算法
			QuickSort s = new QuickSort();
			s.quickSort(bucket, 0, bucket.length - 1);
			for (int value : bucket) {
				nums[arrIndex++] = value;
			}
		}
	}

	/**
	 * 数组自动扩容
	 */
	int[] arrayAppend(int[] nums, int value) {
		nums = Arrays.copyOf(nums, nums.length + 1);
		nums[nums.length - 1] = value;
		return nums;
	}
}

性能对比

对数组长度分别为 10000、100000、300000 的数组进行测试,不同的排序算法的耗时情况如下,本来想测到 100W 的数据量,但无耐冒泡实在太慢,不过 30W 的数据量已经很有区分度了。

测试结果仅具有参考价值,因为其会算测试环境性能及其他条件所影响,且桶排序本身不具备可比性,其性能特点是根据其内部的排序算法所决定,并且还受分配的桶的个数所影响。

public class Testing {
	static int NUMS_LENGTH = 300000;    // 数组长度
	static int MIN_VALUE = 1;           // 随机数最小值
	static int MAX_VALUE = 99999999;    // 随机数最大值

	public static int[] getRandomNums() {
		int[] nums = new int[NUMS_LENGTH];
		Random random = new Random();
		for (int i = 0; i < NUMS_LENGTH; i++) {
			nums[i] = random.nextInt(MAX_VALUE) + MIN_VALUE;
		}
		return nums;
	}

	public static void main(String[] args) {
		BubbleSort bubbleSort = new BubbleSort();
		BucketSort bucketSort = new BucketSort();
		HeapSort heapSort = new HeapSort();
		InsertionSort insertionSort = new InsertionSort();
		MergeSort mergeSort = new MergeSort();
		QuickSort quickSort = new QuickSort();
		RadixSort radixSort = new RadixSort();
		SelectionSort selectionSort = new SelectionSort();

		int[] nums = getRandomNums();
		int[] tmp = new int[NUMS_LENGTH];
		System.arraycopy(nums, 0, tmp, 0, NUMS_LENGTH);
//		System.out.println("待排序数组:" + Arrays.toString(nums));

		long startTime = System.currentTimeMillis();
		bubbleSort.bubbleSort(nums);
		System.out.println("冒泡排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		insertionSort.insertionSort(nums);
		System.out.println("插入排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		selectionSort.selectionSort(nums);
		System.out.println("选择排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		quickSort.quickSort(nums, 0, NUMS_LENGTH - 1);
		System.out.println("快速排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		mergeSort.mergeSort(nums, 0, NUMS_LENGTH - 1);
		System.out.println("归并排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		heapSort.heapSort(nums);
		System.out.println("堆排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		radixSort.radixSort(nums);
		System.out.println("基数排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
		nums = tmp;

		startTime = System.currentTimeMillis();
		bucketSort.bucketSort(nums);
		System.out.println("桶排序 " + NUMS_LENGTH + " 个元素用时:" + (System.currentTimeMillis() - startTime) + "毫秒");
//		System.out.println("已排序数组:" + Arrays.toString(nums));
	}
}

排列 10000 个元素的耗时对比
image.png

排列 100000 个元素的耗时对比
image.png

排列 300000 个元素的耗时对比
image.png

最终,快速排序实至名归,拿下所有测试的 MVP!

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注
  • 算法
    428 引用 • 254 回帖 • 24 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...