排序
1、定义:就是将一组杂乱无章的数据元素按照一定规则编排成为一个整齐有次序序列的过程。
2、数据表:有限个数据元素编排在一集合中。
3、排序码:在排序列表的节点元素中必须存在某个数据域作为节点元素间比较排序规则的码,这个码就是统称的排序码。
4、稳定性和不稳定性:列表中存在A[i] == a[j],排序前A[i] 节点元素在A[j] 节点元素的前面。如果排序后A[i] 节点元素依然排在A[j] 节点元素前面,则说明该排序方式是稳定性排序。反之,如果排序后A[i] 节点元素排在A[j] 节点元素后面,则说明该排序方式是不稳定性排序。
5、时间复杂度:
a、事后统计
这种方法可行,但是有两个问题:
一、是想要对设计的算法进行性能评测,需要实际运行该程序;
二、是所得时间的统计量依赖于计算机的硬件、软件等环境因素,这种方式,要在同一台计算机的相同状态下运行,才能比较哪个算法速度更快。
b、事前估算
通过分析某个算法的时间复杂度来判断哪个算法更优
c、时间频度
①一个算法花费的时间与算法中语句执行次数成正比例,那个算法中语句执行次数多,它花费时间就多。算法中的语句执行次数称之为语句频度或者时间频度。记为T(n)
②在计算时间频度的时候:都是可以在庞大频度中忽略的
常数项
1)、2n+20 和 2n 随着n 变大,执行曲线无限接近, 20可以忽略
2)、3n+10 和 3n 随着n 变大,执行曲线无限接近, 10可以忽略
低次项
1)、2n^2+3n+10 和 2n^2 随着n 变大, 执行曲线无限接近, 可以忽略 3n+10
2)、n^2+5n+20 和 n^2 随着n 变大,执行曲线无限接近, 可以忽略 5n+20
系数
1)、随着n值变大,5n^2+7n 和 3n^2 + 2n ,执行曲线重合, 说明 这种情况下, 5和3可以忽略。
2)、而n^3+5n 和 6n^3+4n ,执行曲线分离,说明多少次方式关键
d、时间复杂度概念:
①一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋于无穷大时,T(n)/f(n)的极限值为不等于零的常熟,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
②T(n)不同,但时间复杂度可能是相同。如T(n)=n^2+7n+6 与T(n)=3n^2+2n+2 它们的T(n)不同,但时间复杂度相同,都为O(n^2)。
③计算时间复杂度的方法:
1)、用常数1代替运行时间中的所有加法常数 如:T(n)=n^2+7n+6——>T(n)=n^2+7n+1
2)、修改后的运行次数函数中,只保留最高阶项 如:T(n)=n^2+7n+1——>T(n)=1n^2
3)、去除最高阶项的系数 如:T(n)=1n^2——>T(n)=n^2 => O(n)=n^2
e、常见的时间复杂度(由上到下复杂度依次增大)
①常熟阶——O(1)——无论代码执行了多少行,只要没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
②对数阶——O(log2n)——在while循环里面,每次都将i*2,假设while循环了x次之后i>n了,结束了while循环。也就是说(i*2)^x=n ——> 2^x=n ——> x=log2n ——> i^log2n ——> O(log2n)
代码:
int i = 1;
int n = 1024;
int x = 0;
while(i<n){
i = i * 2
x ++;
}
③线性阶——O(n)——for 循环里面的代码会在执行n 遍,因此它消耗的时间是随着n的变化而变化的——>O(n)
代码:
int n = 0;
for(int i=0; i<n; i++){
n += i;
}
④线性对数阶——O(nlog2n)——for 循环n次 ②
代码:
int i = 1;
int n = 1024;
int x = 0;
for(int i=0; i<n; i++){
while(i<n){
i = i * 2
x ++;
}
}
⑤平方阶——O(n^2)——双层for循环n次的遍历嵌套使用
代码:
int n = 0;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
n += j;
}
n += i;
}
⑥立方阶——O(n^3)——三层for循环n次的遍历嵌套使用
代码:
int n = 0;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
for(int z=0; z<n; z++){
n += z;
}
n += j;
}
n += i;
}
⑦k次方阶——O(n^k)——k层for循环n次的遍历嵌套使用
代码:
int n = 0;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
.....
for(int z=0; z<n; z++){
n += z;
}
n += j;
}
n += i;
}
⑧指数阶O(2^n)——n层for循环2次的遍历嵌套使用
代码:
int n = 0;
for(int i=0; i<2; i++){
for(int j=0; j<2; j++){
.....
for(int z=0; z<2; z++){
n += z;
}
n += j;
}
n += i;
}
f、平均时间复杂度和最坏时间复杂度
①平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该运算的运行时间。
②最坏情况下的时间复杂度称最坏时间复杂度,一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
③平均时间复杂度和最坏时间复杂度是否一致,和算法有关。
6、空间复杂度:
a、类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所消耗的存储空间,它也是问题规模 n 的函数。
b、空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元与解决问题的规模 n 有关,它随着 n 的增大而增大,当n 较大时,将占用较多的存储单元,例如快速排序和归并排序算法,基数排序就属于这种情况。
c、在做算法分析时,主要讨论的是时间复杂度。从用户体验上看,更看重的是程序执行速度。一些缓存产品(Redis、memcache)和算法(基数排序)本质就是以空间换时间。
7、排序算法分类:
①内部排序:指的是将需要处理的所有数据都加载到内部存储器(内存)中进行排序
1)、插入排序:直接插入排序、希尔排序(插入排序优化版)
2)、选择排序:简单选择排序、堆排序
3)、交换排序:冒泡排序、快速排序
4)、归并排序
5)、基数排序
②外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储(文件磁盘)进行
8、插入排序
a、思想:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
b、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 注解:插入排序算法把无序部分往有序部分找到适当位置插入 * * 步骤: * 1、定义两个辅助变量:待插元素、插入位 * 2、遍历无序部分列表的元素 * 遍历顺序: * a、无序列表:从左到右 * b、有序列表:从右到左 * 3、初始化待插元素和插入位 * 4、遍历有序部分列表 * 遍历规则: * a、insertIndex >= 0 插入位必须在数组范围 * b、insertVal < arr[insertIndex] 排序规则:从小到大——把后面小的插到前面 * c、insertVal * 5、进行元素移位,移到到插入位 * 6、把待插元素放进插入位 */ public static int[] insertSort(int[] arr){ int insertVal = 0; // 需要插入的元素 int insertIndex = 0; // 插入位 // 刚开始的时候有序列表只有数组的首元素,后面n-1 都是无序列表 for (int i=1; i<arr.length; i++){ // 从左到右(需插入元素下标) /** * 遍历顺序: * 1、无序列表:从左到右 * 2、有序列表:从右到左 */ // 初始化待插元素和插入位 insertVal = arr[i]; insertIndex = i - 1; // insertIndex >= 0 插入位必须在数组范围 // insertVal < arr[insertIndex] 排序规则:从小到大——把后面小的插到前面 // insertVal > arr[insertIndex] 排序规则:从大到小——把后面大的插到前面 while (insertIndex >= 0 && insertVal < arr[insertIndex]){ arr[insertIndex + 1] = arr[insertIndex]; // 空出插入位 insertIndex --; // 从右到左(插入位) } if (insertIndex + 1 != i){ // 说明找到了插入位 arr[insertIndex + 1] = insertVal; // 把待插入元素插入到正确位置 } // 否则当前位置就是插入位 } return arr; }
9、希尔排序
a、概念:希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
b、思想:希尔排序是把待排序列表按一定的增量进行分组(如:间隔为gap的元素),对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键字越来越多,当增量减到1时,整个列表恰好被分成一个整体,算法终止。
c、交换法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 希尔排序:交换法 * * 步骤: * 1、定义一个中间件变量供后面元素间交换 * 2、循环以二倍速缩小分组步长(增量) * 3、按指定步长遍历每一个分组 * 4、遍历分组中的元素 * 5、元素间的比较 * 6、元素交换 */ public static int[] shellSort1(int[] arr) { int temp = 0; // 交换中间件 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // gap 为分组步长(增量) for (int i = gap; i < arr.length; i++) { // 遍历每一个分组 for (int j = i - gap; j >= 0; j -= gap) { // 分组元素遍历(每个分组中只有两个元素) // arr[j] > arr[j + gap] 排序规则:从小到大 // arr[j] < arr[j + gap] 排序规则:从大到小 if (arr[j] < arr[j + gap]) { // 分组元素间比较 temp = arr[j]; arr[j] = arr[j + gap]; arr[j + gap] = temp; } } } } return arr; }
d、移位法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 希尔排序:移位法 * * 步骤: * 1、定义两个辅助变量,待插入元素、插入位 * 2、循环以二倍速缩小分组步长(增量) * 3、按指定步长遍历每一个分组 * 4、初始化辅助变量 * 5、遍历分组元素,并且按指定排序规则进行元素移位 * 6、把待插入元素插入到正确位置 */ public static int[] shellSort2(int[] arr){ int insertVal = 0; // 需要插入的元素 int insertIndex = 0; // 插入位 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // gap 为分组步长(增量) for (int i = gap; i < arr.length; i++) { // 遍历每一个分组 insertIndex = i; // 假设插入位 insertVal = arr[insertIndex]; // 假设待插入元素 if (arr[insertIndex] < arr[insertIndex - gap]){ // insertIndex >= 0 插入位必须在数组范围 // insertVal < arr[insertIndex - gap] 排序规则:从小到大——把后面小的插到前面 // insertVal > arr[insertIndex - gap] 排序规则:从大到小——把后面大的插到前面 while (insertIndex - gap >= 0 && insertVal < arr[insertIndex - gap]){ arr[insertIndex] = arr[insertIndex - gap]; // 元素移位 insertIndex -= gap; // 依次往前面移动 } if (insertIndex - gap != i){ // 说明插入位不在有序列表中 arr[insertIndex] = insertVal; // 把待插入元素插入到正确位置 } } } } return arr; }
10、冒泡排序
a、概念:冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
b、优化描述:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,再进行优化)
c、排序规则:
①一共进行数组长度-1 次大的循环
②每一趟排序的次数都在逐渐减少
③如果发现在某趟排序中,没有发生一次交换,可以提前结束冒泡排序,这个就是优化。
d、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
11、快速排序
a、概念:是对冒泡排序的改进
b、思想:在待排序列表中找一个基准对象,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按照同样的方式对这两部分数据分别进行快速排序,整个排序过程可以递归进行(递归次数取决于基准对象选择的位置,越是靠近中间递归次数越少),以此达到整个数据列表有序排列。
c、优点:在待排序列表的数据量很大的情况下,快速排序可以达到很高的效率。缺点:快速排序是不稳定性排序,而且当待排序列表数据量小的时候效率很低。
d、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 快速排序 * * 步骤: * 1、定义两个辅助变量,代替left、right * 2、根据左、右指针确定数组的中间元素 * 3、利用左、右指针遍历数组 * 4、利用左指针遍历中间元素的左边部分数组 * 利用右指针遍历中间元素的右边部分数组 * 5、(r == l)证明:数组遍历完成,右边的数据全部小于pivot,左边数据全部大于pivot * 6、交换值(左边大值、右边小值) * 7、如果交换完后,发现这个arr[l] == pivot,r --,前移(避免死锁) * 如果交换完后,发现这个arr[r] == pivot,l ++,后移(避免死锁) * 8、(r == l)避免后面递归的时候产生栈溢出 * 9、向左递归 * 向右递归 * @param arr 要排序的数组 * @param left 左指针 * @param right 右指针 * @return */ public static int[] quickSort(int[] arr, int left, int right){ int l = left; // 左下标 int r = right; // 右下标 int pivot = arr[(l + r) / 2]; // 中间元素 int temp = 0; // 辅助变量,用于值交换。 // 3、利用左、右指针遍历数组 while (l < r){ // 4、利用左指针遍历中间元素的左边部分数组 while (arr[l] < pivot){ l ++; } // 5、利用右指针遍历中间元素的右边部分数组 while (arr[r] > pivot){ r --; } // 证明:右边的数据全部小于pivot,左边数据全部大于pivot if (l >= r){ break; } // 交换值 temp = arr[r]; arr[r] = arr[l]; arr[l] = temp; // 如果交换完后,发现这个arr[l] == pivot,r --,前移(避免死锁——交换值) if (arr[l] == pivot){ r --; } // 如果交换完后,发现这个arr[r] == pivot,l ++,后移(避免死锁——交换值) if (arr[r] == pivot){ l ++; } } // 避免后面递归的时候产生栈溢出 if (l == r){ l ++; r --; } // 向左递归 if (left < r){ quickSort(arr, left, r); } // 向右递归 if (right > l){ quickSort(arr, l, right); } return arr; }
12、选择排序
a、概念:选择排序也属于内部排序,是从要排序的数据中,按指定的规则选出某一元素,在依规定交换位置达到排序目的。如,选出最小的元素与放在列表的前面依次从小到大排序。
b、思路:
第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,
第二次从arr[1]~arr[n-1]中选取最小值,与arr[1]交换,
第三次从arr[2]~arr[n-1]中选取最小值,与arr[2]交换,
…,
第i次从arr[i-1]~arr[n-1]中选取最小值,与arr[i-1]交换,
…,
第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,
总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
c、说明:
①选择排序一共有数组.length - 1 轮排序
②每一轮排序,又是一次循环,循环的规则
1)、先假定当前元素为最小数
2)、然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
3)、当遍历到数组的最后时,就得到本轮最小数和下标
4)、交换
d、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * c、说明: * ①选择排序一共有数组.length - 1 轮排序 * ②每一轮排序,又是一次循环,循环的规则 * 1)、先假定当前元素为最小数 * 2)、然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标 * 3)、当遍历到数组的最后时,就得到本轮最小数和下标 * 4)、交换 */ public static int[] selectSort(int[] arr){ // 选择排序时间复杂度是O(n^2) for (int i=0; i<arr.length-1; i++){ int minIndex = i; // 最小值下标 int min = arr[i]; // 最小值 // 在当前假定最小值元素后面找到最小值元素 for (int j=i+1; j<arr.length; j++){ if (min > arr[j]){ // 说明min,并不是最小 min = arr[j]; // 重置min minIndex = j; // 重置minIndex } } // 将最小值放置在arr[0]的位置,即交换(排序规则:从小到大——把小的调到前面) if (minIndex != i){ // 说明存在更小的值 arr[minIndex] = arr[i]; // 把大的放到后面 arr[i] = min; // 把小的放到前面 } } return arr; }
13、堆排序
a、概念:想要了解堆排序,首先要了解堆这种数据结构。平均时间复杂度O(nlogn),不稳定性排序。
b、堆:简单来说是利用完全二叉树的方式来维护一个数组列表的数据结构方式。它与普通树相比节省了节点维护其孩子节点数据域的内存空间。
c、大顶堆:每个结点的值都大于或等于其左右孩子结点的值(arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2])
d、小顶堆:每个结点的值都小于或等于其左右孩子结点的值(arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2])
e、思想:
1、将待排序列表构造成一个堆结构(可根据排序规则进行选择:升序——从小到大——大顶堆、降序——从大到小——小顶堆)列表
2、堆结构数组构造完成后,把数组的首、尾元素互换(这是列表排序的关键步骤)。
3、把数组剩余的n-1个元素重新构造成一个堆结构,再把首元素与数组倒数第二个元素互换。如此反复进行n-1次便可以得到一个有序列表。
f、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 功能需求:把一个无序列表使用堆排序的思想进行排序处理得到一个有序列表 * * 功能分析: * A:首先把无序列表编排成一个堆结构数组 * 排序规则: * 1、降序(从大到小)构造一个小顶堆(父结点 <= 左/右子节点) * 2、升序(从小到大)构造一个大顶堆(父结点 >= 左/右子节点) * B:构造堆结构数组方式: * 1、大顶堆:找到最后一个非叶子结点(arr.length / 2 - 1)比较其左/右子节点大小, * 并按照大顶堆规则做调整(子节点 > 父结点,则交互位置),再找倒数第二个非叶子结点、第三个... * 2、小顶堆:找到最后一个非叶子结点(arr.length / 2 - 1)比较其左/右子节点大小, * 并按照小顶堆规则做调整(子节点 < 父结点,则交互位置),再找倒数第二个非叶子结点、第三个... * C:对构造完成的大顶堆/小顶堆结构数组的首节点与尾结点进行互换。 * D:重复进行B、C这两个步骤(每次B、C过后再操作的arr.length - 1,达到排序效果)。直到arr.length - i >= 0 排序完成 */ public static int[] heapSorting(int[] array){ int temp = 0; for (int i=array.length/2-1; i>=0; i--){ array = createBiggerHeap(array, i, array.length); } for (int j=array.length-1; j>=0; j--){ temp = array[0]; array[0] = array[j]; array[j] = temp; array = createBiggerHeap(array, 0, j); } return array; } /** * 功能需求:构造把一个普通数组构成成一个堆结构数组 * @param array 源数组 * @param index 非叶子节点下标 * @param length 数组长度 * @return */ public static int[] createBiggerHeap(int[] array, int index, int length){ int temp = array[index]; // 辅助换值变量 // 遍历堆(完全二叉树) for (int i=2*index+1; i<length; i=i*2+1){ if (i+1 < length && array[i] < array[i+1]){ // 右子结点 > 左子结点 i++; } // 比较右子结点与父结点大小 if (temp < array[i]){ array[index] = array[i]; // 把大的值放到父结点 index = i; // 后面遍历 } else { break; } } // 把小的值放到子节点 array[index] = temp; return array; }
14、归并排序
a、概念:利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
b、思想:
1)分过程:可以看到这种结构很像一棵完全二叉树,把一个完整的列表进行查分成若干个小列表的过程
①递归方式:可以理解为就是把整个列表进行递归折半拆分成子序列的过程。
②迭代方式:假设初始对象序列有 n 个对象,首先把它看成是 n 个长度为 1 的有序子序列 (归并项),先做两两归并,得到 n / 2 个长度为 2 的归并项 (如果 n 为奇数,则最后一个有序子序列的长度为1)。
2)治过程:再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8]
c、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 分+合的方法 * @param arr * @param left * @param right * @param temp * @return */ public 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); } } /** * 归并排序:合并的方法 * * 步骤: * 1、把数组从中间拆分成(left、right)两个列表 * 2、定义三个辅助变量:l(左部分遍历指针)、r(右部分遍历指针)、t(有序列表指针) * 3、同时遍历左、右列表:依次把左指针指向元素和右指针指向元素做比较: * a、左指针指向元素值 >= 右指针指向元素值:左元素进有序列表 * b、右指针指向元素值 >= 左指针指向元素值:右元素进有序列表 * c、直到左、右列表有任意一边遍历完成跳出循环体 * 4、把有剩余的一边的数据依照顺序全部填充到temp 中 * a、左指针和左列表边界mid比较:l <= mid 表示左列表还有元素 * b、右指针和右列表边界right比较:r <= right 表示右列表还有元素 * 5、把当前递归进入有序列表的数据拷贝到原数组上 * @param arr 排序数组 * @param left 左边有序列表的索引 * @param mid 中间索引 * @param right 右边有序列表的索引 * @param temp 辅助数组 * @return */ public static int[] merge(int[] arr, int left, int mid, int right, int[] temp){ int l = left; // 左边有序列表的索引 int r = mid + 1; // 右边有序列表的索引 int t = 0; // temp 数组的当前索引 /** * 第一步: * 1、先把左右两边(有序)的数据按照排序规则填充到temp 数组 * 2、直到左右两边(有序)列表,有一边的数据处理完毕 */ while (l <= mid && r <= right){ // arr[l] <= arr[r] 排序规则(从小到大) if (arr[l] <= arr[r]){ temp[t] = arr[l]; // 左边小,就把左边的元素填充到temp t ++; l ++; } else { temp[t] = arr[r]; // 右边边小,就把右边的元素填充到temp t ++; r ++; } } /** * 第二步: * 1、把有剩余的一边的数据依照顺序全部填充到temp 中 */ while (l <= mid){ // 左边列表还有元素 temp[t] = arr[l]; // 充填到temp t ++; l ++; } while (r <= right){ // 右边列表还有元素 temp[t] = arr[r]; // 充填到temp t ++; r ++; } /** * 第三步: * 1、将temp数组的元素拷贝到arr * 2、注意:并不是每次都拷贝所有元素(每次只拷贝排完序需要合并那一部分元素) */ t = 0; int tempLeft = left; while (tempLeft <= right){ arr[tempLeft] = temp[t]; t ++; tempLeft ++; } return arr; }
15、基数排序
a、概念:基数排序属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或(bin sort)
顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序作用。是桶排序的扩展
b、特点:基数排序法是属于稳定性的排序,基数排序是效率高的稳定性排序算法
c、思想:
①将整数按位数切割成不同的数字,然后按每个位数大小分别比较并放进相应的桶中
②将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。
这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
d、说明:
①基数排序是对传统桶排序的扩展,速度很快.
②基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。
③基数排序时稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,
这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,
r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]
④有负数的数组,我们不用基数排序来进行排序, 如果要支持负数
e、代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 基数排序算法 * * 步骤: * 1、得到数组中最大数字的位数长度,用于确定要排序几趟 * 2、定义一个二维数组包含十个一维数组来模拟十个桶 * 3、定义一个一维数组来记录每个桶放进的数据量 * 4、定义一个变量记录当前的位数(个、十、百、千。。。。。)处理级别 * 5、进行位数级别处理流程 * * 缺点:处理大数据量的时候可能会出现内存溢出 */ public static void radixSort(int[] arr){ // 1、得到数组中最大数字的位数长度,用于确定要排序几趟 int maxLength = 0; int max = arr[0]; for (int i=1; i<arr.length; i++){ if (max < arr[i]){ max = arr[i]; } } maxLength = (max + "").length(); /** * 2、定义一个二维数组包含十个一维数组来模拟十个桶 * 2.1、为了防止放入数据的时候产生数据溢出,则每一个数组(桶)大小定位arr.length * 2.2、明确:这就是典型的以空间换时间的经典算法 */ int[][] bucket = new int[10][arr.length]; /** * 3、定义一个一维数组来记录每个桶放进的数据量 * 3.1、可以这样理解:bucketElementCounts[0] 记录的就是bucket[0] 桶中数据的个数 */ int[] bucketElementCounts = new int[10]; /** * 4、定义一个变量记录当前的位数(个、十、百、千。。。。。)处理级别 */ int digit = 1; // 1、10、100、1000..... /** * 5、进行位数级别处理流程 * 5.1、n 代表了进行多少轮位数级别处理 * 5.2、第一轮个位数、第二轮十位数、第三轮百位数..... */ for (int n=0; n<maxLength; n++){ // 遍历数组中的元素,把位数进行切割并且放进对应桶内 for (int i=0; i<arr.length; i++){ // 数据位数切割出来作为入桶判别标识 int digitOfElment = arr[i] / digit % 10; // 根据入桶标识把数据放进桶内 bucket[digitOfElment][bucketElementCounts[digitOfElment]] = arr[i]; bucketElementCounts[digitOfElment] ++; } int index = 0; // 当前一维数组(桶)的下标,用于遍历桶中的数据 // 遍历记录桶数据个数的数组 for (int k=0; k<bucketElementCounts.length; k++){ // 对应桶中有数据 if (bucketElementCounts[k] > 0){ // 把数据从桶中取出放进arr for (int j=0; j<bucketElementCounts[k]; j++){ arr[index] = bucket[k][j]; index ++; } } // 每一轮处理之后都要把记录桶数据个数的数组清空(避免数据重复) bucketElementCounts[k] = 0; } // 进入个位数级别处理 542 053 003 014 214 748 // 进入十位数级别处理 003 014 214 542 748 053 // 进入百位数级别处理 003 014 053 214 542 748 digit *= 10; } }
各大排序算法比较分