package algorithm;

import java.util.Arrays;

public class EightAlgorithm {
	/**
	 * 
	 * 常用的八种排序算法Java代码实现
	 * 时间:2018-9-5-下午9:14:30 
	 * 邮件:hasp_Jason@163.com
	 * @exception 
	 * 辅助记忆
	 * 时间复杂度记忆- 
	 * 冒泡、选择、直接 排序需要两个for循环,每次只关注一个元素,平均时间复杂度为O(n2)(一遍找元素O(n),一遍找位置O(n))
	 * 快速、归并、希尔、堆基于二分思想,log以2为底,平均时间复杂度为O(nlogn)(一遍找元素O(n),一遍找位置O(logn))
	 * 稳定性记忆-“快选希堆”(非稳定:快牺牲稳定性) 
	 * 排序算法的稳定性:排序前后相同元素的相对位置不变,则称排序算法是稳定的;否则排序算法是不稳定的。
	 */
	public static void main(String[] args) {
		
		int []array={23,56,8,45,2,95,11,12,17,10,11};
		System.out.println("length: "+array.length);
		insertSort(array);	//1.直接插入
		//sheelSort(array);		//2.希尔排序
		//selectSort(array);	//3.简单选择排序
		//heapSort(array);		//4.堆排序
		//bubbleSort(array);	//5.冒泡排序
		//quickSort(array, 0, array.length-1);//6.快速排序  
		//mergeSort(array);		//7.归并排序
		//sort(array,10);		//8.基数排序
		System.out.println(Arrays.toString(array));
	}
	
	/**
	 * 1.直接插入  经常碰到这样一类排序问题:把新的数据插入到已经排好的数据列中。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度		是否稳定
	 * 	O(n^2)			O(n^2)		O(1)		是
	 * 	将第一个数和第二个数排序,然后构成一个有序序列
	 * 	将第三个数插入进去,构成一个新的有序序列。
  	 * 	对第四个数、第五个数……直到最后一个数,重复第二步。
  	 * 
  	 ***如何写成代码:
  	 *	1.首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
  	 *	2.设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。
  	 *	3.从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
  	 *	4.将当前数放置到空着的位置,即j+1。
	 */
	public static void insertSort(int[] a) {
		int insertNum; // 要插入的数
		for (int i = 1; i < a.length; i++) {
			insertNum = a[i];
			int j = i - 1;
			while (j >= 0 && a[j] > insertNum) {// 将大于insertNum的数向后移动一格
				a[j + 1] = a[j];
				j--;
			}
			a[j + 1] = insertNum;
		}
	}
	
	/**
	 * 2.希尔排序   对于直接插入排序问题,数据量巨大时。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
	 * O(nlogn)		O(n^s)		O(1)	不是
	 * 	将数的个数设为n,取奇数k=n/2,将下标差值为k的数分为一组,构成有序序列。
	 * 	再取k=k/2 ,将下标差值为k的数分为一组,构成有序序列。
	 * 	重复第二步,直到k=1执行简单插入排序。
	 * 
	 ***如何写成代码:
	 *	1.首先确定分的组数。
	 *	2.然后对组中元素进行插入排序。
	 *	3.然后将length/2,重复1,2步,直到length=0为止。
	 */
	public static void sheelSort(int[] a) {
		int length = a.length;
		while (length != 0) {
			length = length / 2;
			System.out.println("length变化: " + length);
			for (int x = 0; x < length; x++) { 	// 分组个数
				for (int i = x + length; i < a.length; i += length) {
					int j = i - length; 	// j为有序序列最后一位的位数
					int insertNum = a[i]; 	// 要插入的元素
					for (; j >= 0 && insertNum < a[j]; j -= length) {
						a[j + length] = a[j];	// 向后移动length位
					}
					a[j + length] = insertNum;
				}
			}
		}
	}
	
	/**
	 * 3.简单选择排序    常用于取序列中最大最小的几个数时。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
	 * O(n^2) 		O(n^2)		O(1)	不是
	 * 	(如果每次比较都交换,那么就是交换排序;如果每次比较完一个循环再交换,就是简单选择排序。)
	 * 	遍历整个序列,将最小的数放在最前面。
	 * 	遍历剩下的序列,将最小的数放在最前面。
	 * 	重复第二步,直到只剩下一个数。
	 * 
	 ***如何写成代码:
	 *	1.首先确定循环次数,并且记住当前数字和当前位置。
	 *	2.将当前位置后面所有的数与当前数字进行对比,小数赋值给key,并记住小数的位置。
	 *	3.比对完成后,将最小的值与第一个数的值交换。
	 *	4.重复2、3步。
	 */
	public static void selectSort(int[] a) {
		int length = a.length;
		for (int i = 0; i < length; i++) {			// 循环次数
			int key = a[i];
			int position = i;
			for (int j = i + 1; j < length; j++) {	// 选出最小值和位置
				if (a[j] < key) {
					key = a[j];
					position = j;
				}
			}
			a[position] = a[i];			// 交换位置
			a[i] = key;
		}
	}
	
	/**
	 * 4.堆排序   对简单选择排序的优化。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
	 * O(nlogn)		O(nlogn)	O(1)	不是
	 *	将序列构建成大顶堆。
	 *	将根节点与最后一个节点交换,然后断开最后一个节点。
	 *	重复第一、二步,直到所有节点断开。
	 */
	public static void heapSort(int[] a) {
		System.out.println("开始排序");
		int arrayLength = a.length;
		// 循环建堆
		for (int i = 0; i < arrayLength - 1; i++) {
			// 建堆
			buildMaxHeap(a, arrayLength - 1 - i);
			// 交换堆顶和最后一个元素
			swap(a, 0, arrayLength - 1 - i);
			System.out.println(Arrays.toString(a));
		}
	}

	private static void swap(int[] data, int i, int j) {
		int tmp = data[i];
		data[i] = data[j];
		data[j] = tmp;
	}

	// 对data数组从0到lastIndex建大顶堆
	private static void buildMaxHeap(int[] data, int lastIndex) {
		// 从lastIndex处节点(最后一个节点)的父节点开始
		for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
			// k保存正在判断的节点
			int k = i;
			// 如果当前k节点的子节点存在
			while (k * 2 + 1 <= lastIndex) {
				// k节点的左子节点的索引
				int biggerIndex = 2 * k + 1;
				// 如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
				if (biggerIndex < lastIndex) {
					// 若果右子节点的值较大
					if (data[biggerIndex] < data[biggerIndex + 1]) {
						// biggerIndex总是记录较大子节点的索引
						biggerIndex++;
					}
				}
				// 如果k节点的值小于其较大的子节点的值
				if (data[k] < data[biggerIndex]) {
					// 交换他们
					swap(data, k, biggerIndex);
					// 将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
					k = biggerIndex;
				} else {
					break;
				}
			}
		}
	}
	
    
    /**
     * 5.冒泡排序    一般不用。
     * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
     * O(n^2) 		O(n^2)		O(1)	是
	 *	将序列中所有元素两两比较,将最大的放在最后面。
	 *	将剩余序列中所有元素两两比较,将最大的放在最后面。
	 *	重复第二步,直到只剩下一个数。
	 *	
	 ***如何写成代码:
	 *	1.设置循环次数。
	 *	2.设置开始比较的位数,和结束的位数。
	 *	3.两两比较,将最小的放到前面去。
	 *	4.重复2、3步,直到循环次数完毕。
	 *
     */
	public static void bubbleSort(int[] a) {
		int length = a.length;
		int temp;
		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length - i - 1; j++) {
				if (a[j] > a[j + 1]) {
					temp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = temp;
				}
			}
		}
	}

	/**
	 * 6.快速排序		要求时间最快时。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度		是否稳定
	 * O(nlogn) 	O(n^2)		O(logn)	不是
	 *	选择第一个数为p,小于p的数放在左边,大于p的数放在右边。
	 *	递归的将p左边和右边的数都按照第一步进行,直到不能递归。
	 */
	public static void quickSort(int[] numbers, int start, int end) {
		if (start < end) {
			int base = numbers[start];// 选定的基准值
			int temp;
			int i = start, j = end;
			do {
				while ((numbers[i] < base) && (i < end))
					i++;
				while ((numbers[j] > base) && (j > start))
					j--;
				if (i <= j) {
					temp = numbers[i];
					numbers[i] = numbers[j];
					numbers[j] = temp;
					i++;
					j--;
				}
			} while (i <= j);
			if (start < j)
				quickSort(numbers, start, j);
			if (end > i)
				quickSort(numbers, i, end);
		}
	}

	/**
	 * 7.归并排序		速度仅次于快排,内存少的时候使用,可以进行并行计算的时候使用。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
	 * O(nlogn) 	O(nlogn)	O(n)	是
	 *	选择相邻两个数组成一个有序序列。
	 *	选择相邻的两个有序序列组成一个有序序列。
	 *	重复第二步,直到全部组成一个有序序列。
	 */
	public static void mergeSort(int[] arr) {
		int[] temp = new int[arr.length];// 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
		mergeSort(arr, 0, arr.length - 1, temp);
	}

	private static void mergeSort(int[] arr, int left, int right, int[] temp) {
		if (left < right) {
			int mid = (left + right) / 2;
			mergeSort(arr, left, mid, temp);// 左边归并排序,使得左子序列有序
			mergeSort(arr, mid + 1, right, temp);// 右边归并排序,使得右子序列有序
			merge(arr, left, mid, right, temp);// 将两个有序子数组合并操作
		}
	}

	private static void merge(int[] arr, int left, int mid, int right,
			int[] temp) {
		int i = left;// 左序列指针
		int j = mid + 1;// 右序列指针
		int t = 0;// 临时数组指针
		while (i <= mid && j <= right) {
			if (arr[i] <= arr[j]) {
				temp[t++] = arr[i++];
			} else {
				temp[t++] = arr[j++];
			}
		}
		while (i <= mid) {// 将左边剩余元素填充进temp中
			temp[t++] = arr[i++];
		}
		while (j <= right) {// 将右序列剩余元素填充进temp中
			temp[t++] = arr[j++];
		}
		t = 0;
		// 将temp中的元素全部拷贝到原数组中
		while (left <= right) {
			arr[left++] = temp[t++];
		}
	}
	
	
	/**
	 * 8.基数排序		用于大量数,很长的数进行排序时。
	 * 平均时间复杂度	最坏时间复杂度	空间复杂度	是否稳定
	 * O(N∗M)		O(N∗M)		O(M)	是
	 * 将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
	 * 将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。
	 */
	public static void sort(int[] number, int d) {
		int k = 0;
		int n = 1;
		int m = 1;
		int[][] temp = new int[number.length][number.length];
		int[] order = new int[number.length];
		while (m <= d) {
			for (int i = 0; i < number.length; i++) {
				int lsd = ((number[i] / n) % 10);
				temp[lsd][order[lsd]] = number[i];
				order[lsd]++;
			}
			for (int i = 0; i < d; i++) {
				if (order[i] != 0)
					for (int j = 0; j < order[i]; j++) {
						number[k] = temp[i][j];
						k++;
					}
				order[i] = 0;
			}
			n *= 10;
			k = 0;
			m++;
		}
	} 
}