数据结构笔记_18 基数排序

一、简介

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

该算法具有稳定性。所谓稳定性,即在待排序序列中,存在多个相同的数,a = b 。
经过排序,这俩的相对次序保持不变,即a 仍然在b 的前面,这样的排序算法就具有稳定性。

二、原理

逐层从低位到高位(个位、十位…),把每个位数上的数据按升序排列。

三、例子:

将53,3,542,748,14,214按升序排序。

一共有10个桶(一维数组),下标0 ~ 9.

1、思路

将上面的数据放入arr数组中,并设置十个桶,下标0 ~ 9。第一轮排序时,依次取出这些数的个位上的数字(比如53的个位3,就放入下标为3的桶中),待所有数据的个位都提取并放入对应的桶之后,执行下一步:依次遍历所有桶,并取出桶中数据放入原来数组,完成后便开始第二轮排序;从更新后的arr数组中,提取十位数字,重复上列操作;第三轮亦同理。

第一轮排序:先取出个位上的数字,放入对应的桶中。
在这里插入图片描述

第二轮排序:同理,取出十位数,若缺位例如3,没有十位,则补零为03,再放入对应的桶中。

第三轮排序:同理,取出百位数,因为待排序数组最高位数是百位(748),所以只需要三轮排序。

在这里插入图片描述
这个最大位数怎么确定的呢?有个小技巧:

		int max = arr[0];// 假设第一个数就是最大数
		for (int i = 1; i < arr.length; i++) {
    
    
			if (arr[i] > max) {
    
    
				max = arr[i];
			}
		}
		
		int maxlength = (max + "").length();

2、分布推导:

package com.huey.sort;

import java.util.Arrays;

public class RadixSort {
    
    

	public static void main(String[] args) {
    
    
		int[] arr = {
    
     53, 3, 542, 748, 14, 214 };
		radixSort(arr);
	}

	// 基数排序方法
	public static void radixSort(int[] arr) {
    
    

		// 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
		// 1、二维数组包含10个一维数组
		// 2、为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定义为arr.length
		// 3、基数排序是使用空间换时间的经典算法

		int[][] bucket = new int[10][arr.length];

		// 为了记录每个桶中,实际存放了多少个数据,定义一个一维数组来记录各个桶的每次放入的数据个数
		// 理解:
		// 例如:bucketElementCounts[0],记录的就是 bucket[0] 桶的放入数据个数
		int[] bucketElementCounts = new int[10];

		// ===============================================================

		// 第1轮(针对每个元素的个位进行排序处理)
		for (int j = 0; j < arr.length; j++) {
    
    
			// 取出每个元素的个位的值
			int digitOfElement = arr[j] % 10;
			// 放入到对应的桶中
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
			// 例:53放在下标为3的桶,digitOfElement等于3,表示放在下标为3的桶中。
			//bucketElementCounts[digitOfElement]表示第3个桶放入的数据个数。所以,整句话意思是:
			//将arr[j](53)放到下标为3的桶中,这个下标为3的桶的第一个位置存放53.
			//当结束了再把这个下标++,用来存放下个数据。
			bucketElementCounts[digitOfElement]++;// 用来存放下一个
		}

		// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
		int index = 0;
		// 遍历每一个桶,并将桶中的数据,放入原来数组
		for (int k = 0; k < bucketElementCounts.length; k++) {
    
    
			// 如果桶中有数据,我们才放入到原数组
			if (bucketElementCounts[k] != 0) {
    
    
				// 循环该桶即第k个桶(即第k个一维数组),放入
				for (int l = 0; l < bucketElementCounts[k]; l++) {
    
    
					// 取出元素放入arr
					arr[index++] = bucket[k][l];
				}
			}
			// 第1轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
			bucketElementCounts[k] = 0;
		}
		System.out.println("第1轮:" + Arrays.toString(arr));

		// ===============================================================

		// 第2轮(针对每个元素的十位进行排序处理)
		for (int j = 0; j < arr.length; j++) {
    
    
			// 取出每个元素的十位的值
			int digitOfElement = arr[j] / 10 % 10;// 748/10 => 74%10 => 4
			// 放入到对应的桶中
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];// 例:53放在下标为3的桶
			bucketElementCounts[digitOfElement]++;// 用来存放下一个
		}

		// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
		index = 0;
		// 遍历每一个桶,并将桶中的数据,放入原来数组
		for (int k = 0; k < bucketElementCounts.length; k++) {
    
    
			// 如果桶中有数据,我们才放入到原数组
			if (bucketElementCounts[k] != 0) {
    
    
				// 循环该桶即第k个桶(即第k个一维数组),放入
				for (int l = 0; l < bucketElementCounts[k]; l++) {
    
    
					// 取出元素放入arr
					arr[index++] = bucket[k][l];
				}
			}
			// 第2轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
			bucketElementCounts[k] = 0;
		}
		System.out.println("第2轮:" + Arrays.toString(arr));

		// ===============================================================

		// 第3轮(针对每个元素的百位进行排序处理)
		for (int j = 0; j < arr.length; j++) {
    
    
			// 取出每个元素的百位的值
			int digitOfElement = arr[j] / 100 % 10;// 748/100 => 7%10 => 7
			// 放入到对应的桶中
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];// 例:53放在下标为3的桶
			bucketElementCounts[digitOfElement]++;// 用来存放下一个
		}

		// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
		index = 0;
		// 遍历每一个桶,并将桶中的数据,放入原来数组
		for (int k = 0; k < bucketElementCounts.length; k++) {
    
    
			// 如果桶中有数据,我们才放入到原数组
			if (bucketElementCounts[k] != 0) {
    
    
				// 循环该桶即第k个桶(即第k个一维数组),放入
				for (int l = 0; l < bucketElementCounts[k]; l++) {
    
    
					// 取出元素放入arr
					arr[index++] = bucket[k][l];
				}
			}
		}
		System.out.println("第3轮:" + Arrays.toString(arr));

	}

}

3、经推导,得到最终代码:

package com.huey.sort;

import java.util.Arrays;

public class RadixSort {
    
    

	public static void main(String[] args) {
    
    
		int[] arr = {
    
     53, 3, 542, 748, 14, 214 };
		radixSort(arr);
	}

	// 基数排序方法
	public static void radixSort(int[] arr) {
    
    

		// 根据前面的推导过程,可以得到最终代码

		// 1.得到数组中最大的数的位数
		int max = arr[0];// 假设第一个数就是最大数
		for (int i = 1; i < arr.length; i++) {
    
    
			if (arr[i] > max) {
    
    
				max = arr[i];
			}
		}
		// 得到最大数是几位数【妙啊】
		int maxlength = (max + "").length();

		// 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
		// 1、二维数组包含10个一维数组
		// 2、为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定义为arr.length
		// 3、基数排序是使用空间换时间的经典算法

		int[][] bucket = new int[10][arr.length];

		// 为了记录每个桶中,实际存放了多少个数据,定义一个一维数组来记录各个桶的每次放入的数据个数
		// 理解:
		// 例如:bucketElementCounts[0],记录的就是 bucket[0] 桶的放入数据个数
		int[] bucketElementCounts = new int[10];

		// 这里使用循环将代码处理

		for (int i = 0, n = 1; i < maxlength; i++, n *= 10) {
    
    // 【妙啊】
			// 针对每个元素对应位进行排序处理,第一次个位,后面十位、百位
			for (int j = 0; j < arr.length; j++) {
    
    
				// 取出每个元素对应位进行排序处理
				int digitOfElement = arr[j] / n % 10;
				// 放入到对应的桶中
				bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];// 例:53放在下标为3的桶
				bucketElementCounts[digitOfElement]++;// 用来存放下一个
			}

			// 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
			int index = 0;
			// 遍历每一个桶,并将桶中的数据,放入原来数组
			for (int k = 0; k < bucketElementCounts.length; k++) {
    
    
				// 如果桶中有数据,我们才放入到原数组
				if (bucketElementCounts[k] != 0) {
    
    
					// 循环该桶即第k个桶(即第k个一维数组),放入
					for (int l = 0; l < bucketElementCounts[k]; l++) {
    
    
						// 取出元素放入arr
						arr[index++] = bucket[k][l];
					}
				}
				// 第i+1轮处理后,需要将每个bucketElementCounts[k] = 0!!!!!!!!
				bucketElementCounts[k] = 0;
			}
			System.out.println("第" + (i + 1) + "轮:" + Arrays.toString(arr));
		}

	}
}

输出结果:
在这里插入图片描述
速度测试:

8百万:0.5s左右

5千万:2 ~ 3s.

8千万,报错:堆溢出,不是电脑内存,得看你给 jvm 的堆分配了多少内存空间。
在这里插入图片描述
大约需要3GB。

80000000 * 10 * 4 / 1024 / 1024 / 1024 = 3GB

猜你喜欢

转载自blog.csdn.net/qq_45909299/article/details/113877734
今日推荐