1017587 数组排序算法

数组排序算法

原文链接:十大经典排序算法总结(JavaScript描述)

  • 冒泡排序
  • 直接选择排序
  • 简单插入排序
  • 希尔排序
  • 归并排序
  • 快速排序
  • 堆排序
  • 计数排序
  • 桶排序
  • 基数排序

排序说明及名词解释

(1):排序的定义

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。

(2):名词解释

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;

内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大小。

(3):排序算法图片总结

1017587 数组排序算法_第1张图片

n:数据规模
k:“桶”的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存

(4):排序分类
  • 内部排序(使用内存)
    • 插入排序
      • 简单插入排序
      • 希尔排序
    • 选择排序
      • 直接选择排序
      • 堆排序
    • 交换排序
      • 冒泡排序
      • 快速排序
  • 外部排序(内存和外存结合使用)
    • 归并排序
    • 计数排序
    • 桶排序
    • 基数排序

冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。

(1):描述

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

(2):算法的具体描述如下
  1. 比较相邻的两个元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始的第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序动图演示:

1017587 数组排序算法_第2张图片

(3):Java代码实现
/**
 * 从小到大
 * asc
 * 升序
 * @param arr
 */
private static void bubbleSortAsc(int[] arr) {
	int len = arr.length;
	for (int i = 0; i < len-1; i++) {
		for (int j = 0; j < len-1-i; j++) {
			if(arr[j] > arr[j+1]){
				//交换
				int temp = arr[j+1];
				arr[j+1] = arr[j];
				arr[j] = temp;
			}
		}
	}
}

改进

设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

/**
* 冒泡排序改进
* @param arr
*/
public static void bubbleSortAscImprove(int[] arr){
	int len = arr.length;
	int i = len-1-0;
	while(i > 0){
		int pos = 0;
		for (int j = 0; j < i; j++) {
			if(arr[j] > arr[j+1]){
				pos = j;//记录最后一次交换的位置。显然,在此位置之后的一定都已经是排好序了的
				//交换
				int temp = arr[j+1];
				arr[j+1] = arr[j];
				arr[j] = temp;
			}
		}
		i = pos;
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n)

      当输入的数据已经是正序时
    
  • 最差情况:T(n) = O(n²)

      当输入的数据是反序时
    
  • 平均情况:T(n) = O(n²)

直接选择排序

选择排序(Selection sort)是一种简单直观的排序算法。

(1):描述

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

(2):算法的具体描述如下
  1. 初始状态:无序区为R[1…n],有序区为空。
  2. 第1趟排序:在无序区R[1…n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1…1]和R[2…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
  3. 第i趟排序开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。

选择排序动图演示:

1017587 数组排序算法_第3张图片

(3):Java代码实现
/**
 * 选择排序
 * asc
 * 升序
 * @param arr
 */
public static void SelectSortAsc(int[] arr){
	int len = arr.length;
	for (int i = 0; i < len-1; i++) {
		int minIndex = i;
		for (int j = i+1; j < len; j++) {
			if (arr[j] < arr[minIndex]) {
				minIndex = j;//标记最小值的下标
			}
		}
		//交换
		int temp = arr[i];
		arr[i] = arr[minIndex];
		arr[minIndex] = temp;
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n²)
  • 最差情况:T(n) = O(n²)
  • 平均情况:T(n) = O(n²)

简单插入排序

(1):描述

将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。

(2):算法的具体描述如下
  1. 从第一个元素开始,该元素可以认为已经被排序。
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
  3. 如果该元素(已排序)大于新元素,将该元素移到下一个位置。
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
  5. 将新元素插入到下一个位置中。
  6. 重复步骤2~5。

插入排序动图显示:
1017587 数组排序算法_第4张图片

(3):Java代码实现
/**
 * 插入排序
 * asc
 * 升序
 * @param arr
 */
public static void insertionSortAsc(int[] arr){
	int len = arr.length;
	for (int i = 0; i < len-1; i++) {
		//已排序序列[0,i]
		int key = arr[i+1];//待插入元素
		int j = i;
		while(j >= 0 && arr[j] > key) {
			arr[j+1] = arr[j];//往后挪
			j--;//从后向前扫描
		}
		arr[j+1] = key;//插入到下一个位置
	}
}

改进

二分法

/**
 * 二分插入排序
 * asc
 * 升序
 * @param arr
 */
public static void binaryInsertionSortAsc(int[] arr){
	int len = arr.length;
	for (int i = 0; i < len-1; i++) {
		//已排序序列[0,i]
		int key = arr[i+1];//待插入元素
		int left = 0;
		int right = i;
		while (left <= right) {
			int middle = (left + right)/2;
			if(key < arr[middle]){//
				right = middle - 1;
			} else {
				left = middle + 1;
			}
		}
		//把left及之后的值往右挪一个位置
		for (int j = i; j >= left ; j--) {
			arr[j+1] = arr[j];
		}
		arr[left] = key;
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n)

      当输入的数据已经是正序时
    
  • 最差情况:T(n) = O(n²)

      当输入的数据是反序时
    
  • 平均情况:T(n) = O(n²)

说明:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

希尔排序

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。

(1):描述

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

(2):算法的具体描述如下
  1. 先取一个小于n的整数d1作为第一个增量。
  2. 将数据分组,所有距离为d1的倍数的记录放在同一组中。
  3. 在各组内进行直接插入排序。
  4. 取第二个增量d2
  5. 重复上面的步骤,直到dt=1。
(3):Java代码实现
/**
 * 希尔排序
 * asc
 * 升序
 * @param arr
 */
public static void shellSortAsc(int[] arr){
	int len = arr.length;
	int d = len/2;//设置第一个增量
	if(d < 1)
		d = 1;
	while (true) {//增量d
		//分组
		for (int i = 0; i < d; i++) {//[0,0+d,0+d+d,...],[1,1+d,1+d+d,...],...[i,i+d,i+d+d,...],...[d-1,d-1+d,d-1+d+d,...]
			for (int j = i; j < len-1; j+=d) {
				//已排好序的是[i,j]
				int key = arr[j+1];//带插入元素
				int k = j;
				while (k >= i && arr[k] > key) {
					arr[k+1] = arr[k];
					k--;
				}
				arr[k+1] = key;
			}
		}
		if(d == 1)
			break;
		d = d/2 - 1;//尽量保证相邻的增量直接互不为倍数关系
		if(d<1)
			d = 1;
	}
}

说明:

  • 最后一个增量必须为1;
  • 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
(4):算法分析
  • 最佳情况:T(n) = O(nlog2n)
  • 最坏情况:T(n) = O(nlog2n)
  • 平均情况:T(n) = O(nlogn)

归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

(1):描述

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

(2):算法的具体描述如下
  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 递归,直到子序列只有一个元素。
  4. 将两个排序好的子序列合并成一个最终的排序序列。

归并操作的工作原理如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置。
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。
  4. 重复步骤3直到某一指针超出序列尾。
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

归并排序动图演示:
1017587 数组排序算法_第5张图片

(3):Java代码实现
/**
 * 归并排序
 * asc
 * 升序
 * @param arr
 */
public static void mergingSortAsc(int[] arr){
	int len = arr.length;
	if (len < 2) {
		return;
	}
	int middle = len/2;//把长度为n的输入序列分成两个长度为n/2的子序列;
	mergeAsc(arr,0,middle,len);
}

/**
 * 将子序列[left,middle)与子序列[middle,right)归并排序
 * @param arr
 * @param left
 * @param middle
 * @param right
 */
private static void mergeAsc(int[] arr, int left, int middle, int right) {
	if ( (middle - left)==1 && (right - middle)==1 ){
		//左右各为1
		if(arr[left] > arr[middle]){
			//交换
			int temp = arr[middle];
			arr[middle] = arr[left];
			arr[left] = temp;
		}
		return;
	}
	/**
	 * 对这两个子序列分别采用归并排序;
	 */
	//先将子序列1归并排序
	if( (middle - left) > 1){
		int m_mid = (middle + left) / 2;
		mergeAsc(arr, left, m_mid, middle);
	}
	//再将子序列2归并排序
	if ( (right - middle) > 1 ){
		int m_mid = (right + middle) / 2;
		mergeAsc(arr, middle, m_mid, right);
	}
	//归并操作
	int[] is = new int[right - left];//申请空间
	int k = 0;
	//设定两个指针
	int m = left;
	int n = middle;
	while (m < middle && n < right) {
		if(arr[m] > arr[n])
			is[k++] = arr[n++];
		else 
			is[k++] = arr[m++];
	}
	while (m < middle)
		is[k++] = arr[m++];
	while (n < right) 
		is[k++] = arr[n++];
	for (int i = 0; i < is.length; i++) {
		arr[left+i] = is[i];
	}
}

改进

归并算法的思路是将序列分成两个子序列,子序列再分成两个子序列。然而序列的长度不一定是2的整数,所以可能会出现两个子序列的长度分别为1和2(这时长度为2的则需要再分成两个子序列),且这种情况极有可能有很多。
如果采用反向思维,第一趟把序列的第一个和第二个元素采用归并排序,第三个和第四个采用归并排序,重复直到将序列拆分成多个长度为2的有序子序列(若最后一个元素为单个,则将最后一个元素与最后一个子序列归并)。第二趟将每两个子序列进行归并排序,得到多个长度为4的有序子序列。重复直到只有两个有序子序列,再将这两个有序子序列归并排序。

代码如下:

/**
 * 归并排序
 * 改进
 * asc
 * 升序
 * @param arr
 */
public static void mergingSortAscImprove(int[] arr){
	mergeSortAscImprove(arr, 1);
}
/**
 * 
 * @param arr
 * @param size 从第一个元素开始,依次将相邻两个将长度为size的子序列合并成长度为2*size的序列
 */
private static void mergeSortAscImprove(int[] arr, int size){
	int len = arr.length;
	int sum = len/(size*2);
	if(sum <= 0){//不能再拆分了
		return ;
	}
	for (int i = 0; i < sum; i++) {
		int left =  i*size*2;
		mergeAscImprove(arr,left , left+size, left+size*2);
	}
	int c = len%(size*2);//余数
	if (c != 0){//余数与最后一个有序子序列归并排序。(如果最后一个也是第一个,那么这趟排序完整个排序就结束了。下次递归得到的sum==0)
		mergeAscImprove(arr, len-c-size*2, len-c, len);
	}
	mergeSortAscImprove(arr,2*size);
}
/**
 * 归并排序(子序列已经是有序的)
 * @param arr
 * @param left
 * @param middle
 * @param right
 */
private static void mergeAscImprove(int[] arr, int left, int middle, int right) {
	//归并操作
	int[] is = new int[right - left];//申请空间
	int k = 0;
	//设定两个指针
	int m = left;
	int n = middle;
	while (m < middle && n < right) {
		if(arr[m] > arr[n])
			is[k++] = arr[n++];
		else 
			is[k++] = arr[m++];
	}
	while (m < middle)
		is[k++] = arr[m++];
	while (n < right) 
		is[k++] = arr[n++];
	for (int i = 0; i < is.length; i++) {
		arr[left+i] = is[i];
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。

(1):描述

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

(2):算法的具体描述如下
  1. 设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据。
  2. 将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
  3. 将关键数据的两部分别做快速排序,递归。

具体算法如下:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j;
  6. 3、4步的前置条件都是i < j;

快速排序动图演示:
1017587 数组排序算法_第6张图片

(3):Java代码实现
/**
 * 快速排序
 * asc
 * 升序
 * @param arr
 */
public static void quickSortAsc(int[] arr){
	int len = arr.length;
	int i = 0;
	int j = len-1;
	quickSortAscRe(arr,i,j);
}

/**
 * 递归
 * 升序
 * @param arr
 * @param low
 * @param high
 */
private static void quickSortAscRe(int[] arr, int low, int high) {
	int i = low;
	int j = high;
	int key = arr[i];
	while (i < j) {
		//从j开始由后往前找到第一个小于key的数
		while(i < j && arr[j] >= key){
			j--;
		}
		if (i < j) {//找到了
			//交换
			int temp = arr[j];
			arr[j] = arr[i];
			arr[i] = temp;
		}
		//从i开始由前往后找到第一个大于key(此时key值对应的下边为j)的值
		while (i < j && arr[i] <= key) {
			i++;
		}
		if (i < j) {//找到了
			//交换
			int temp = arr[j];
			arr[j] = arr[i];
			arr[i] = temp;
			//key又跑到i这里了
		}
	}
	if (i > low) {
		quickSortAscRe(arr, low, i-1);
	}
	if (j < high) {
		quickSortAscRe(arr, i+1, high);
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(n²)
  • 平均情况:T(n) = O(nlogn)

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。

(1):描述

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

(2):算法的具体描述如下
  1. 先将初始文件R[1…n]建成一个大根堆,此堆为初始的无序区。
  2. 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1…n-1]和有序区R[n],且满足R[1…n-1].keys≤R[n].key
  3. 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1…n-1]调整为大根堆。
  4. 然后再次将R[1…n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1…n-2]和有序区R[n-1…n],且仍满足关系R[1…n-2].keys≤R[n-1…n].keys,同样要将R[1…n-2]调整为大根堆。
  5. 重复步骤4,直到无序区只有一个元素为止。

堆排序动图演示:

(3):Java代码实现
/**
 * 堆排序
 * asc
 * 升序
 * @param arr
 */
public static void heapSortAsc(int[] arr){
	int len = arr.length;
	if (len <= 1)
		return;
	//建堆
	int startIndex = getParentIndex(len-1);//从最后一个节点的父节点开始创建堆
	for (int i = startIndex; i >=0 ; i--) {
		buildHeapAsc(arr,len,i);
	}
	//头与尾交换,交换之后再建堆。之后再交换
	for (int i = len-1; i >0 ; i--) {
		int temp = arr[i];
		arr[i] = arr[0];
		arr[0] = temp;
		//交换之后原堆除了跟节点,其他的子节点仍是大根堆。所以只要重新调整index为0的堆。堆的大小刚好为i
		buildHeapAsc(arr, i, 0);
	}
}
/**
 * 
 * @param arr 堆数组
 * @param size 堆的大小
 * @param i 保证父节点i为大根堆
 */
private static void buildHeapAsc(int[] arr, int size, int i) {
	int left = getLeftChildIndex(i);
	int right = getRightChildIndex(i);
	int max = i;
	if(left < size && arr[left] > arr[max]){
		max = left;
	}
	if(right < size && arr[right] > arr[max]){
		max = right;
	}
	if(max != i){
		//交换
		int temp = arr[i];
		arr[i] = arr[max];
		arr[max] = temp;
		//交换之后的子节点可能不是最大堆。需要重新建堆
		buildHeapAsc(arr, size, max);
	}
}

/**
 * 返回左子节点
 */
private static int getLeftChildIndex(int i){
	return 2*i + 1;
}

/**
 * 返回右子节点
 */
private static int getRightChildIndex(int i){
	return 2*i + 2;
}

/**
 * 返回父节点
 */
private static int getParentIndex(int i){
	return (i-1)/2;
}
(4):算法分析
  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)

(1):描述

计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。

(2):算法的具体描述如下
  1. 找出待排序的数组中最大和最小的元素max和min;
  2. 统计数组中每个值为value的元素出现的次数,并将它放在辅助数组C中,下标为value-min;
  3. 根据辅助数组下标确定排序。比如C[0] = 5,则排序后的数组前五个元素为0+min。

基数排序动态演示图:
1017587 数组排序算法_第7张图片

(3):Java代码实现
/**
 * 计数排序非负数
 * asc
 * 升序
 * 比较适合于数量大但数值的范围是在一定限度内。
 * 比如统计100万学生高考成绩的排名,由于高考成绩一定是在0-满分(假如750)之间。
 * 所以一次遍历便可以统计每个分数所对应的人数。(假设分数是整数)
 * 最后根据每个分数对应的人数排序即可。
 * @param arr
 */
public static void countingSortAsc(int[] arr){
	//先确定最大值和最小值
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < arr.length; i++) {
		if (arr[i] > max) {
			max = arr[i];
		}
		if (min > arr[i]) {
			min = arr[i];
		}
	}
	//定义一个有序集
	int[] order = new int[max-min+1];//arr数组中所有的数一定是在区间[min,max]
	//统计区间中的每个数有多少个。为了不浪费空间,我们将数组arr中的数都减去min,得到的值对应order的下边。
	//则order[index] = 数组arr中值为index+min的数量
	for (int i = 0; i < arr.length; i++) {
		order[arr[i]-min] += 1;
	}
	int k = 0;
	for (int i = 0; i < order.length; i++) {
		for (int j = 0; j < order[i]; j++) {
			arr[k++] = i+min;
		}
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n+k)

桶排序

(1):描述

将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。

(2):算法的具体描述如下
  1. 设置一个定量的数组当作空桶;
  • 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  • 对每个不是空的桶进行排序;
  • 从不是空的桶里把排好序的数据拼接起来。

桶排序的具体实现算法可能有很多种,这里提供的例子只使用了桶排序。具体算法说明如下:

  1. 设置10个空桶。
  • 遍历一遍数组,得到最大数的位数。即该数组的最大位数。
  • 遍历数组,比较数值的个位数,个位数为几便将该数放入对应的桶中。遍历完成后,则每个桶是按照数据的个位数大小进行排序的。
  • 将每个桶的数据按桶的顺序放入原数组中。此时数组中左边的数的个位数一定不比右边的数的个位数大。
  • 遍历数组,比较数值的十位数,十位数为几便将该数放入对应的桶中。
  • 将每个桶的数据按桶的顺序放入原数组中。此时数组中左边的数的十位数一定不比右边的数的十位数大。若十位数相同,则由于第二步已经将个位数排好,所以个位数一定不比右边的个位数大。由此可见,左边数的后两位对应的数值一定不比右边数的后两位对应的数值大。
  • 继续遍历数组,比较百位、千位,直到比较到最大位数。
  • 将每个桶的数据按桶的顺序放入原数组中。此时排序完成。

桶排序动图演示:

1017587 数组排序算法_第8张图片

(3):Java代码实现
/**
 * 桶排序
 * asc
 * 升序
 * @param arr
 */
public static void BucketSortAsc(int[] arr){
	int n = arr.length;
	int bucket[][] = new int[10][n];
	int index[] = new int[10];
	int intLen = 0;//数组中位数最多的数
	int min = 0;//主要用于处理负数的情况
	for (int i = 0; i < arr.length; i++) {
		if(arr[i] < min){
			min = arr[i];
		}
	}
	for (int i = 0; i < arr.length; i++) {
		arr[i] -= min;//变为正数处理
		if ((""+arr[i]).length() > intLen) {
			intLen = (""+arr[i]).length();
		}
	}
	for (int i = 0; i < intLen; i++) {//从个位数开始比较。如果该位数为0,则放在第一个桶子里,以此类推。
		for (int j = 0; j < arr.length; j++) {
			int temp = 0;
			String str = Integer.toString(arr[j]);
			if(str.length()>i){
				temp = str.charAt(str.length()-i-1)-'0';
			}
			//temp表示当前位数i对应的数字。
			bucket[temp][index[temp]++] = arr[j];
		}
		//经过一次分类之后。每个桶按照当前位数从小到大排列了。即第1一个桶子的位数i均为0。
		//等到排列下一位的时候,该位相同的数会被放入同一个桶子里,而该位的低位已经在前一次循环中排好序了。
		//所以对每一位桶排序之后,整个数组就有序了。
		int pos = 0;
		for (int j = 0; j < index.length; j++) {
			for (int k = 0; k < index[j]; k++) {
				arr[pos++] = bucket[j][k];
			}
		}
		for (int j = 0; j < index.length; j++) {
			index[j] = 0;
		}
	}
	for (int i = 0; i < arr.length; i++) {
		arr[i] += min;
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n²)
  • 平均情况:T(n) = O(n+k)

基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)。

回顾之前的桶排序和计数排序,其实原理类似。基数排序分为最高位优先(Most Significant Digit first)法,简称MSD法,和最低位优先(Least Significant Digit first)法,简称LSD法。本文所讲的桶排序就是LSD法。

基数排序LSD动图演示:

1017587 数组排序算法_第9张图片

(1):描述

最高位优先(Most Significant Digit first)法,简称MSD法:MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

(2):算法的具体描述如下
  1. 先按k1排序分组,同一组中记录,关键码k1相等;
  • 再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组;
  • 直到按最次位关键码kd对各子组排序后;
  • 再将各组连接起来,便得到一个有序序列。
(3):Java代码实现
/**
 * 基数排序MSD
 * asc
 * 升序
 * @param arr
 */
public static void RadixSortAsc(int[] arr){
	int min = 0;//主要用于处理负数的情况
	for (int i = 0; i < arr.length; i++) {
		if(arr[i] < min){
			min = arr[i];
		}
	}
	int max = 0;
	for (int i = 0; i < arr.length; i++) {//全部转化为非负数,并获得最大位数
		arr[i] -= min;//变为正数处理
		if(arr[i] > max){
			max = arr[i];
		}
	}
	int maxLen = Integer.toString(max).length();//数组中最大数的位数
	/**
	 * 先对最高位进行桶排序
	 */
	RadixSortAsc(arr,arr.length,maxLen-1);//先对最高位排序
	if (min<0) {
		for (int i = 0; i < arr.length; i++) {
			arr[i] += min;
		}
	}
}

/**
 * 递归
 * @param arr
 * @param n 待桶排序的数据
 * @param i 待排序的位数
 */
private static void RadixSortAsc(int[] arr,int n, int i) {
	int bucket[][] = new int[10][n];
	int index[] = new int[10];
	for (int j = 0; j < n; j++) {
		int temp = 0;
		String str = Integer.toString(arr[j]);
		if(str.length()>i){
			temp = str.charAt(str.length()-i-1)-'0';
		}
		//temp表示当前位数i对应的数字。
		bucket[temp][index[temp]++] = arr[j];
	}
	//对每个桶里面的数据再进行桶排序
	for (int j = 0; j < index.length; j++) {
		if(i<=0)
			break;
		if(index[j] == 0)
			continue;
		RadixSortAsc(bucket[j],index[j], i-1);//依次对每个桶的数据进行桶排序
	}
	int pos = 0;
	for (int j = 0; j < index.length; j++) {
		for (int k = 0; k < index[j]; k++) {
			arr[pos++] = bucket[j][k];
		}
	}
}
(4):算法分析
  • 最佳情况:T(n) = O(n * k)
  • 最差情况:T(n) = O(n * k)
  • 平均情况:T(n) = O(n * k)

参考文档

十大经典排序算法总结(JavaScript描述)


时间复杂度和空间复杂度详解




thanks! 顶部 底部 **
--郑泽旺
**
2017-12-12

你可能感兴趣的:(java基础)