import java.util.*;
import java.util.function.IntPredicate;

public class Sort {

	static Random rnd = new Random(1);

	public static void qSort(int[] a, int low, int high) {
		if (low >= high)
			return;
		int separator = a[low + rnd.nextInt(high - low + 1)];
		int i = low;
		int j = high;
		do {
			while (a[i] < separator)
				++i;
			while (a[j] > separator)
				--j;
			if (i > j)
				break;
			int t = a[i];
			a[i] = a[j];
			a[j] = t;
			++i;
			--j;
		} while (i <= j);
		qSort(a, low, j);
		qSort(a, i, high);
	}

	public static void mergeSort(int[] a, int low, int high) {
		if (high - low < 2)
			return;
		int mid = (low + high) >>> 1;
		mergeSort(a, low, mid);
		mergeSort(a, mid, high);
		int[] b = Arrays.copyOfRange(a, low, mid);
		for (int i = low, j = mid, k = 0; k < b.length; i++) {
			if (j == high || b[k] <= a[j]) {
				a[i] = b[k++];
			} else {
				a[i] = a[j++];
			}
		}
	}

	public static void mergeSort2(int[] a, int low, int high) {
		int size = high - low;
		if (size < 2)
			return;
		int mid = (low + high) >>> 1;
		mergeSort2(a, low, mid);
		mergeSort2(a, mid, high);
		int[] b = new int[size];
		int i = low;
		int j = mid;
		for (int k = 0; k < size; k++) {
			if (i < mid && (j == high || a[i] <= a[j])) {
				b[k] = a[i++];
			} else {
				b[k] = a[j++];
			}
		}
		System.arraycopy(b, 0, a, low, size);
	}

	public static void inPlaceMergeSort(int[] a, int low, int high) {
		if (low < high - 1) {
			int mid = (low + high) >>> 1;
			mergeSort(a, low, mid);
			mergeSort(a, mid, high);
			inPlaceMerge(a, low, mid, high);
		}
	}

	// O(n*log(n)) complexity
	static void inPlaceMerge(int[] a, int from, int mid, int to) {
		if (from >= mid || mid >= to) return;
		if (to - from == 2) {
			if (a[from] > a[mid])
				swap(a, from, mid);
			return;
		}

		final int firstCut;
		final int secondCut;

		if (mid - from > to - mid) {
			firstCut = from + (mid - from) / 2;
			secondCut = binarySearchFirstTrue(i -> a[i] >= a[firstCut], mid, to);
		} else {
			secondCut = mid + (to - mid) / 2;
			firstCut = binarySearchFirstTrue(i -> a[i] > a[secondCut], from, mid);
		}

		if (mid != firstCut && mid != secondCut) {
			rotate(a, firstCut, mid, secondCut);
		}

		mid = firstCut + (secondCut - mid);
		inPlaceMerge(a, from, firstCut, mid);
		inPlaceMerge(a, mid, secondCut, to);
	}

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

	static void rotate(int[] a, int first, int middle, int last) {
		int next = middle;
		while (first != next) {
			swap(a, first++, next++);
			if (next == last)
				next = middle;
			else if (first == middle)
				middle = next;
		}
	}

	static int binarySearchFirstTrue(IntPredicate predicate, int fromInclusive, int toExclusive) {
		int lo = fromInclusive - 1;
		int hi = toExclusive;
		while (lo < hi - 1) {
			int mid = (lo + hi) >>> 1;
			if (!predicate.test(mid)) {
				lo = mid;
			} else {
				hi = mid;
			}
		}
		return hi;
	}

	public static void heapSort(int[] a) {
		int n = a.length;
		for (int i = n / 2 - 1; i >= 0; i--)
			pushDown(a, i, n);
		while (n > 1) {
			swap(a, 0, n - 1);
			pushDown(a, 0, --n);
		}
	}

	static void pushDown(int[] h, int pos, int size) {
		while (true) {
			int child = 2 * pos + 1;
			if (child >= size)
				break;
			if (child + 1 < size && h[child + 1] > h[child])
				child++;
			if (h[pos] >= h[child])
				break;
			swap(h, pos, child);
			pos = child;
		}
	}

	public static void bubbleSort(int[] a) {
		for (int i = 0; i + 1 < a.length; i++) {
			for (int j = 0; j + 1 < a.length; j++) {
				if (a[j] > a[j + 1]) {
					swap(a, j, j + 1);
				}
			}
		}
	}

	public static void selectionSort(int[] a) {
		int n = a.length;
		int[] p = new int[n];
		for (int i = 0; i < n; i++)
			p[i] = i;
		for (int i = 0; i < n - 1; i++) {
			for (int j = i + 1; j < n; j++) {
				if (a[p[i]] > a[p[j]]) {
					swap(p, i, j);
				}
			}
		}
		int[] b = a.clone();
		for (int i = 0; i < n; i++)
			a[i] = b[p[i]];
	}

	public static void insertionSort(int[] a) {
		for (int i = 1; i < a.length; i++) {
			for (int j = i; j > 0; j--) {
				if (a[j - 1] > a[j]) {
					swap(a, j - 1, j);
				}
			}
		}
	}

	public static void countingSort(int[] a) {
		int max = 0;
		for (int x : a) {
			max = Math.max(max, x);
		}
		int[] cnt = new int[max + 1];
		for (int x : a) {
			++cnt[x];
		}
		for (int i = 1; i < cnt.length; i++) {
			cnt[i] += cnt[i - 1];
		}
		int n = a.length;
		int[] b = new int[n];
		for (int i = 0; i < n; i++) {
			b[--cnt[a[i]]] = a[i];
		}
		System.arraycopy(b, 0, a, 0, n);
	}

	public static void radixSort(int[] a) {
		final int d = 8;
		final int w = 32;
		int[] t = new int[a.length];
		for (int p = 0; p < w / d; p++) {
			// counting-sort
			int[] cnt = new int[1 << d];
			for (int i = 0; i < a.length; i++)
				++cnt[((a[i] ^ Integer.MIN_VALUE) >>> (d * p)) & ((1 << d) - 1)];
			for (int i = 1; i < cnt.length; i++)
				cnt[i] += cnt[i - 1];
			for (int i = a.length - 1; i >= 0; i--)
				t[--cnt[((a[i] ^ Integer.MIN_VALUE) >>> (d * p)) & ((1 << d) - 1)]] = a[i];
			System.arraycopy(t, 0, a, 0, a.length);
		}
	}

	// random test
	public static void main(String[] args) {
		Random rnd = new Random(1);
		for (int step = 0; step < 1000; step++) {
			int n = rnd.nextInt(10) + 1;
			int[] a = rnd.ints(n, 0, 1000).toArray();
			int[] s = a.clone();
			Arrays.sort(s);

			int[] b = a.clone();
			bubbleSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			selectionSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			insertionSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			countingSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			qSort(b, 0, b.length - 1);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();
		}

		for (int step = 0; step < 10; step++) {
			int n = rnd.nextInt(50_000) + 100_000;
			int[] a = step == 0 ? new int[n] : rnd.ints(n).toArray();
			int[] s = a.clone();
			Arrays.sort(s);

			int[] b = a.clone();
			qSort(b, 0, b.length - 1);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			mergeSort(b, 0, b.length);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			mergeSort2(b, 0, b.length);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			inPlaceMergeSort(b, 0, b.length);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			heapSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();

			b = a.clone();
			radixSort(b);
			if (!Arrays.equals(s, b))
				throw new RuntimeException();
		}
	}
}
