排序

排序

算法 稳定性 时间复杂度 空间复杂度
选择排序 N (N^2) 1
冒泡排序 Y (N^2) 1
插入排序 Y (N~N^2) 1
希尔排序 N (N^{(1.3-2)}) 1
归并排序 Y (NlogN) N
快速排序 N (NlogN) logN
堆排序 N (NlogN) 1
public abstract class Sort<T extends Comparable<T>> {

	public abstract void sort(T[] nums);

	protected boolean less(T v, T w) {
		return v.compareTo(w) < 0;
	}

	protected void swap(T[] a, int i, int j) {
		T t = a[i];
		a[i] = a[j];
		a[j] = t;
	}
}

选择排序

从数组中 选择最小元素,将它与数组的第一个元素 交换 位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。

  • (N^2 /2) 次比较
  • (N) 次交换
  • 不稳定
  • 运行时间与输入无关,即对一个已经排序的数组也需要这么多的比较和交换操作
public class Selection<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		int len = nums.length;
		for (int i = 0; i < len - 1; i++) {
			int min = i;
			for (int j = i + 1; j < len; j++) {
				if (less(nums[j], nums[min])) {
					min = j;
				}
			}
			swap(nums, i, min);
		}
	}
}

冒泡排序

从左到右不断 交换相邻逆序 的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
优化:在一轮循环中,如果没有发生交换,那么说明数组已经是有序的。

  • 最好 (N-1) 次比较 (0) 次交换
  • 最差 (N(N-1)/2) 次比较 (N(N-1)/2) 次交换
  • 稳定
public class Bubble<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		int n = nums.length;
		boolean isSorted = false;
		for (int i = n - 1; !isSorted && i > 0; i--) {
			isSorted = true;
			for (int j = 0; j < i; j++) {
				if (less(nums[j + 1], nums[j])) {
					swap(nums, j, j + 1);
					isSorted = false;
				}
			}
		}
	}
}

插入排序

每次都将当前元素 插入到左侧已经排序 的数组中,使得左侧数组依然有序。

插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

  • 平均 (N^2/4) 次比较 (N^2/4) 次交换
  • 最坏 (N^2/2) 次比较 (N^2/2) 次交换
  • 最好 (N-1) 次比较 (0) 次交换
  • 稳定
  • 运行时间取决于数组的初始顺序
public class Insertion<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		int n = nums.length;
		for (int i = 1; i < n; i++) {
			for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) {
				swap(nums, j, j - 1);
			}
		}
	}

	public static void main(String[] args) {
		Sort.test(new Insertion<Integer>());
	}
}

希尔排序

希尔排序使用 插入排序对间隔 h 的序列 进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序。
希尔排序通过交换不相邻的元素,每次可以将逆序数量减少大于 1。

插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

  • 不稳定
public class Shell<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		int n = nums.length;

		int h = 1;
		while (h < n / 3) {
			h = 3 * h + 1;
		}

		while (h >= 1) {
			for (int i = h; i < n; i++) {
				for (int j = i; j >= h && less(nums[j], nums[j - h]); j = j - h) {
					swap(nums, j, j - h);
				}
			}
			h /= 3;
		}
	}
}

归并排序

分而治之 ,将问题分解成几个规模较小的问题,再合并子问题获得答案。

  • 稳定
  • 时间复杂度:(Ο(nlogn))
public class MergeSort<T extends Comparable<T>> extends Sort<T> {

	private T[] tmp;

	@Override
	public void sort(T[] nums) {
		int n = nums.length;
		tmp = Arrays.copyOf(nums, n);
		sortTopDown(nums, 0, n - 1);
//		sortDownTop(nums, n);
	}

	private void sortTopDown(T[] nums, int l, int r) {
		if (l >= r)
			return;
		int m = ((r - l) >> 1) + l;
		sortTopDown(nums, l, m);
		sortTopDown(nums, m + 1, r);
		merge(nums, l, m, r);
	}

	private void sortDownTop(T[] nums, int n) {
		for (int d = 1; d < n; d += d) {
			for (int l = 0; l < n - d; l += d + d) {
				merge(nums, l, l + d - 1, Math.min(l + d + d - 1, n - 1));
			}
		}
	}

	private void merge(T[] nums, int l, int m, int r) {
		int i = l;
		int j = m + 1;
		int index = l;
		while (i <= m && j <= r) {
			if (!less(nums[j], nums[i])) {
				tmp[index++] = nums[i++];
			} else {
				tmp[index++] = nums[j++];
			}
		}
		while (i <= m) {
			tmp[index++] = nums[i++];
		}
		while (j <= r) {
			tmp[index++] = nums[j++];
		}
		for (index = l; index <= r; index++) {
			nums[index] = tmp[index];
		}
	}
}

快速排序

快速排序通过一个 切分元素 将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。

public class QuickSort<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		sort(nums, 0, nums.length - 1);
	}

	private void sort(T[] nums, int l, int r) {
		if (l >= r) {
			return;
		}
		int index = partition(nums, l, r);
		sort(nums, l, index - 1);
		sort(nums, index + 1, r);
	}

	private int partition(T[] nums, int l, int r) {
		T tmp = nums[r];
		int i = l;
		int j = r - 1;
		while (true) {
			// 相等时即停止移动指针
			while (i < r && less(nums[i], tmp))
				i++;
			while (j >= l && less(tmp, nums[j]))
				j--;
			if (i >= j)
				break;
			swap(nums, i++, j--);
		}
		swap(nums, i, r);
		return i;
	}
}

:若指针指向的元素与参考值相等仍继续移动,假设极端情况:数组中所有元素都相等。那么i会移动到最后一个位置,导致数组切分失衡,复杂度变为 (O(n^2))。因此相等时就应停止移动,虽然多了许多交换操作,但能够保证切分均衡。

改进 :三向切分。lt左侧小于切分元素,gt右侧大于切分元素。

private void threeWayQuickSort(T[] nums, int l, int r) {
	if (l >= r) {
		return;
	}
	int lt = l, i = l + 1, gt = r;
	T tmp = nums[l];
	while (i <= gt) {
		int cmp = nums[i].compareTo(tmp);
		if (cmp < 0) {
			swap(nums, lt++, i++);
		} else if (cmp > 0) {
			swap(nums, i, gt--);
		} else {
			i++;
		}
	}
	sort(nums, l, lt - 1);
	sort(nums, gt + 1, r);
}

堆排序

堆是 完全二叉树 ,可以用数组来表示。
位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。(不使用数组索引为 0 的位置)

  • 堆的高度为 (logN),因此在堆中插入元素和删除最大元素的复杂度都为 (logN)
  • 对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 (NlogN)
  • 不稳定
  • 正常来说,堆排序是 原地排序 ,这里为了实现简单,下标从1开始,使用了辅助数组。
public class HeapSort<T extends Comparable<T>> extends Sort<T> {

	@Override
	public void sort(T[] nums) {
		int n = nums.length;
		T[] tmp = (T[]) new Comparable[n + 1];
		System.arraycopy(nums, 0, tmp, 1, n);

		for (int i = n / 2; i >= 1; i--) {
			sink(tmp, i, n);
		}
		for (int i = 1; i <= n; i++) {
			swap(tmp, 1, n - i + 1);
			sink(tmp, 1, n - i);
		}

		System.arraycopy(tmp, 1, nums, 0, n);
	}

	private void sink(T[] nums, int k, int n) {
		while (k <= n / 2) {
			int t = 2 * k;
			if (t + 1 <= n && less(nums[t], nums[t + 1])) {
				t++;
			}
			if (!less(nums[k], nums[t]))
				break;
			swap(nums, k, t);
			k = t;
		}
	}
}
原文地址:https://www.cnblogs.com/JL916/p/12540045.html