排序
数据的学习就已经结束了,现在我们来学习对数据排序的算法
正式开始!
冒泡排序
现在我们给定一个无序的int
的数组,如下
int arr[]={23,45,2,66,87,44};
我们要求将上面数组的元素从小到大的升序排列,要求结果如下
{2,23,44,45,66,87}
那么我们要怎么实现呢?我们就来学习第一个算法-----冒泡排序法
实现思路
冒泡排序法的实现思路就是将两个元素进行比较,大的往后移一位,这样通过几轮比较,就可以实现对数组的排序了
画图分析
我们来简单画个图
第一轮比较我们就可以将最大的元素排在数组中的最后一位
可以看到,我们将两个相邻的元素进行比较,若前者比后者要大,就两个交换下标
代码实现
我们来通过代码来观察一下
第一轮比较
import java.util.Arrays;
public class ArraySort1 {
public static void main(String[] args){
int arr[]={23,45,2,66,87,44};
for (int i = 0; i < arr.length-1; i++) {
if(arr[i]>arr[i+1]){
int t=arr[i];
arr[i]=arr[i+1];
arr[i+1]=t;
}
}
//Arrays java提供的一个数组帮助类
System.out.println("第一轮比较:"+Arrays.toString(arr));
for (int i = 0; i < arr.length-1-1; i++) {
if(arr[i]>arr[i+1]){
int t=arr[i];
arr[i]=arr[i+1];
arr[i+1]=t;
}
}
System.out.println("第二轮比较:"+Arrays.toString(arr));
for (int i = 0; i < arr.length-1-1-1; i++) {
if(arr[i]>arr[i+1]){
int t=arr[i];
arr[i]=arr[i+1];
arr[i+1]=t;
}
}
System.out.println("第三轮比较:"+Arrays.toString(arr));
}
}
打印如下
可以看到,经过三轮比较后,原数组的元素已经排序完成了,但是代码有冗余,我们优化一下
如下:
import java.util.Arrays;
public class ArraySort1 {
public static void main(String[] args) {
int arr[]={23,45,2,66,87,44};
for (int j =0; j < arr.length-1; j++) {
for (int i = 0; i < arr.length - 1-j; i++) {
if (arr[i] > arr[i + 1]) {
int t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
System.out.println("第"+(j+1)+"轮比较:" + Arrays.toString(arr));
}
}
}
打印结果:
我们再将数组扩宽一下
int arr[] = {23, 45, 2, 66, 87, 44,1,99,76,46,89,0};
再次运行
选择排序
学完冒泡排序,是不是觉得它的过程就好像在打擂台一样,比你厉害(大)的就一直在,直到遇到比他跟厉害的,那么他就是擂主
接下来我们来学习选择排序
他的实现思路也类似于打擂台但是规则相反,比他小的是新擂主,且默认的擂主是下标为零的元素,我们让他与后面的元素一一比较,比擂主小,则推选它为新的擂主
我们来上代码实现一下
public class ChooseSort {
public static void main(String[] args){
int []num={2,4,1,23,65,0,-34,-2,34};
int index=0;
for (int i = 1; i < num.length; i++) {//从下标为一开始是为了不和自己做比较
if(num[index]>num[i]){
int center=num[i];
num[i]=num[index];
num[index]=center;
}
}
System.out.println(Arrays.toString(num));
}
}
打印结果如下
第一轮循环,我们就将最小的元素排在了数组的第一位,那么我们下次比较就可以从下标为1的元素开始重新选举擂主
tips:冗余代码就不展示了
public class ChooseSort {
public static void main(String[] args){
int []num={2,4,1,23,65,0,-34,-2,34};
for (int index = 0; index < num.length; index++) {
for (int i = 1+index; i < num.length; i++) {
if(num[index]>num[i]){
int center=num[i];
num[i]=num[index];
num[index]=center;
}
}
}
System.out.println(Arrays.toString(num));
}
}
打印结果如下
我们多插入几个元素
int []num={2,4,1,23,65,0,-34,-2,34,2,0,23,55};
打印结果如下
直接插入排序
接下来学习直接插入排序
他的实现思想很简单
我们从数组下标为一的地方开始循环遍历,然后与它前一位的元素比较,遇到比它大的,两个元素直接互换位置就可以了
画图理解
第一轮比较
可以看到索引为1的元素比索引为0的元素要小,我们将他们互换位置,接着下轮比较
索引为2的元素比索引为1的下标要小,两者继续互换
接下来具体实现
public class DirectlyInsertedSort {
/**
* 直接插入算法排序思想如下
* 我们从下标为1的元素开始,将其与前一位元素进行比较
* 如果它前面一位的元素比他小,将两个元素互换位置即可
* */
public static void main(String[] args){
int []array={2,5,4,65,32,4,3,1,0};
System.out.println("未排序前的结果为:"+Arrays.toString(array));
for (int i = 1; i < array.length; i++) {
while (i>0&&array[i]<array[i-1]){//防止下标出现负数
int center=array[i-1];
array[i-1]=array[i];
array[i]=center;
i--; //通过减一来继续排序
}
}
System.out.println("通过直接插入排序后结果为:"+ Arrays.toString(array));
}
}
打印结果如下
当然 如果需要降序排列,我们只需要将while循环中的条件改成i>0&&array[i-1]<array[i]
既可
打印结果如下
直接插入排序对于数据量小和数据基本有序的数组效率很高
希尔排序
希尔排序是直接排序优化后的一个算法,他的思路就是,
我们将数组数组拆解成若干个小组,然后对这些小组进行插入排序,由于数据量小,所以他的效率要高很多
或者说
我们定义一个步长(合理的),使某个元素与步他的步长元素比较,然后如果大于,即互换位置
如:我们定义步长为foot=4
第一轮比较就拿array[0]
与array[0+foot],``array[1]
与array[1+foot]
…以此类推进行比较,我们只需要控制好步长即可
画图理解
关于希尔排序,我们只需要控制好步长的增量即可
我们来实际演示一下
public class ShellSort {
public static void main(String[] args){
int []array={99,3,6,88,34,77,66,22};
int foot=4;
System.out.println("排序前:"+ Arrays.toString(array));
for (int i = foot; i < array.length; i++) {
if(array[i-foot]>array[i]){ //此时array[i-foot]所对应的元素为99 array[i]所对应的元素为34
int center=array[i-foot];
array[i-foot]=array[i];
array[i]=center;
}
}
System.out.println("步长增量为"+foot+"时,排序后:"+ Arrays.toString(array));
foot=2;
for (int i = foot; i < array.length; i++) {
if(array[i-foot]>array[i]){
int center=array[i-foot];
array[i-foot]=array[i];
array[i]=center;
}
}
System.out.println("步长增量为"+foot+"时,排序后:"+ Arrays.toString(array));
foot=1;
for (int i = foot; i < array.length; i++) {
if(array[i-foot]>array[i]){
int center=array[i-foot];
array[i-foot]=array[i];
array[i]=center;
}
}
System.out.println("步长增量为"+foot+"时,排序后:"+ Arrays.toString(array));
}
}
打印结果如下
但是这样写,代码冗余严重,我们需要优化一下
优化效果如下
public class ShellSort {
public static void main(String[] args){
int []array={99,3,6,88,34,77,66,22};
// int foot=4;
System.out.println("排序前:"+ Arrays.toString(array));
for (int foot = 4; foot >0 ; foot/=2) {
for (int i = foot; i < array.length; i++) {
for (int j = i; j > foot-1; j-=foot) {
if(array[j-foot]>array[j]){
int center=array[j-foot];
array[j-foot]=array[j];
array[j]=center;
}
}
}
System.out.println("步长增量为"+foot+"希尔排序后的结果"+Arrays.toString(array));
}
}
}
这里我们使用了三层for循环,最外层控制步长增量,二层循环控制循环次数,最内层循环控制比较次数
但是我们这样将步长默认值写成固定其实并不合理,而且并不满足希尔排序要求
那么我们再次改进一下
for (int foot = array.length/2; foot > 0; foot /= 2) {//控制步长
for (int i = foot; i < array.length; i++) {
for (int j = i; j > foot - 1; j -= foot) {
if (array[j - foot] > array[j]) {
int center = array[j - foot];
array[j - foot] = array[j];
array[j] = center;
}
}
}
System.out.println("步长增量为" + foot + "希尔排序后的结果" + Arrays.toString(array));
}
这样就可以根据数据长度动态的获取步长了
为什么希尔排序比直接插入排序效率要高呢?
希尔排序比插入排序快很多,它是基于什么原因呢?
当h值大的时候,数据项每一趟排序需要移动元素的个数很少,但数据项移动的距离很长。这是非常有效率的。当m减小时,每—趟排序需要移动的元素的个数增多,但是此时数据项已经接近于它们排序后最终的位置,这对于插入排序可以更有效率。正是这两种情况的结合才使希尔排序效率那么高。
knuth序列
思考一个问题,如果我的数据假设有 1000个长度,如果希尔排序的话,那么我们的步长就需要取1000/2
,也就是500
从步长500一直到步长为1,一共有9种(500,250,125,62,31,15,7,3,1)那么随着数据量变大,步进的种类越多,那么既有可能就会浪费程序性能
那么?有什么更好的办法取步长嘛?
我们来学习一下knuth序列
knuth序列即是公式3h+1序列(1,4,13,40,。。。)保证3h+1<maxItems(数组中数据项个数)得到最大的h值,然后按照h=(h-1)/3递减,直到h=1为止。
我们假设数组长度为1000,我们来看看Knuth序列
public class ShellSort {
public static void main(String[] args) {
int arrayNum=1000;
int knuthNum=1;
int normalNum=1;
System.out.print("knuth序列 ");
while(knuthNum<=arrayNum/3){
knuthNum=knuthNum*3+1;
System.out.print(knuthNum+" ");
}
System.out.println();
System.out.print("普通序列 ");
while (normalNum>0){
normalNum=arrayNum/2;
arrayNum=arrayNum/2;
System.out.print(normalNum+" ");
}
}
}
打印如下:
可以看到,再同样数据的情况下,knuth序列比普通的方法种类要少一点
那么我们就将knuth序列来作为步进增量来再次优化
public class ShellSort {
public static void main(String[] args) {
int []array={99,3,6,88,34,77,66,22,-2,0,43,22,32,54,231,3,54,11,-4,-32,-88};
System.out.println("排序前:" + Arrays.toString(array));
for (int foot =1; foot <= array.length/3; foot=foot*3+1) {//控制步长
for (int i = foot; i < array.length; i++) {
for (int j = i; j > foot - 1; j -= foot) {
if (array[j - foot] > array[j]) {
int center = array[j - foot];
array[j - foot] = array[j];
array[j] = center;
}
}
}
System.out.println("步长增量为" + foot + "希尔排序后的结果" + Arrays.toString(array));
}
}
}
他也等同于下面代码,但是比较次数会少一次
public class ShellSort {
public static void main(String[] args) {
int []array={99,3,6,88,34,77,66,22,-2,0,43,22,32,54,231,3,54,11,-4,-32,-88};
int knuth=0;
while (knuth<=array.length/3){
knuth=knuth*3+1;
}
System.out.println("排序前:" + Arrays.toString(array));
for (int foot =knuth; foot >0; foot=(foot-1)/3) {//控制步长
for (int i = foot; i < array.length; i++) {
for (int j = i; j > foot - 1; j -= foot) {
if (array[j - foot] > array[j]) {
int center = array[j - foot];
array[j - foot] = array[j];
array[j] = center;
}
}
}
System.out.println("步长增量为" + foot + "希尔排序后的结果" + Arrays.toString(array));
}
}
}
他们两者的区别就在于,一个从knuth序列从1开始,一个从13开始
打印结果分别如下
我们再运行普通序列
快速排序
快速排序是面试经常问的题目
快速排序使用的是一种分治法
第一步:我们从数组中选取一个基准数(一般是数组中的第一个元素),
第二步:然后进行一个分区,将比他大的分为一区,比他小的分为一区,
然后重复二三步骤,直到分区元素只有一个时,这时候数组就排序完成了
当然还有另外一种方法,挖数填坑法
第一步:我们选取一个基准数(一般是数组中的第一个元素),挖出此数
第二步:我们先从后往前找,找到比他小的数,然后挖出次数填到前一个坑中
第三步:从前往后找,找比基准数大或等于的数,然后挖出填到前一个坑中
第四步:重复递归调用二三步
现在我们来实现挖数填坑法
元素 | 5 | 3 | 9 | 1 | 6 | 7 | 2 | 4 | 0 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
坑位 | 坑位1 | 坑位 | 坑位3 | 坑位 | 坑位5 | 坑位7 | 坑位6 | 坑位4 | 坑位2 | 坑位 |
第一轮 | 0 | 3 | 4 | 1 | 2 | 5 | 7 | 6 | 9 | 8 |
第一轮比较完后,我们就得到了以5为分界线的两个分区,我们将元素往前移一位,继续挖坑填数
可能很抽象,我们一步一步的通过代码来演示
第一步:我们先编写算法实现挖数填坑法的第二步
public static void main(String[] args){
int []array={5,3,9,1,6,7,2,4,0,8};
int start=0;
int end=array.length-1;
int center=array[start];
System.out.println("从后往前找比"+center+"小的数有");
//实现第一步 从后往前找,比基准数小的数
while(start<end){
while(start<end&¢er<array[end]){
end--;//如果end下标元素大于基准数 再次缩小
}
if(start<end){
array[start]=array[end];//找到了比他小的,将其挖出填入前一个坑
System.out.print(array[start]+" ");
start++;
end-=1;//继续减一,缩小对比范围
}
}
}
打印结果如下
当然这只是单次的,我们稍后编写控制算法
接着我们编写算法实现挖数填坑法的第三步
public static void main(String[] args){
int []array={5,3,9,1,6,7,2,4,0,8};
int start=0;
int end=array.length-1;
int center=array[start];
System.out.println("从前往后找比"+center+"大的数有");
while (start<end){
start+=1;//先自加1不和自己相比教
while(start<end&¢er>array[start]){
start++;
}
if(start<end){
array[end]=array[start];//填充到后面去
System.out.print(array[start]+" ");
end--;
}
}
}
这里需要注意,从前往后找找到只有9,6,7 三个数 从后往前找有0,4,2,那么 1,3,8呢?
由于我们使用挖数填坑的方式,从前往后,从后往前必然会找到重复的数,还有不满足条件的数,且基准数还没有坑位可放,所以我们将基准数放在重复的数字7 的坑位上,其他不满足条件的数保持原有坑位即可
也就是这样
元素 | 5 | 3 | 9 | 1 | 6 | 7 | 2 | 4 | 0 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
坑位 | 坑位1 | 坑位 | 坑位3 | 坑位 | 坑位5 | 坑位7 | 坑位6 | 坑位4 | 坑位2 | 坑位 |
第一轮 | 0 | 3 | 4 | 1 | 2 | 5 | 7 | 6 | 9 | 8 |
通过代码演示,加上案例操作,这样你就会发现,基准数5放在了坑位7 的位置,且基准数左边都是比他小的数,右边都是比他大的数,如上文所说,我们就得到了以基准数5为分界线的两个分区
接着我们就可以开始第二轮挖坑填数了,这里就不再展开了,我们来编写算法求这个基准数的位置
如下
package com.vision.sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args){
int []array={5,3,9,1,6,7,2,4,0,8};
System.out.println(getIndex(0,array.length-1,array));
}
public static int getIndex(int start,int end,int []array){
int center=array[start];//定义基准数
while(start<end){//持续比较,直到坑位重复
while(start<end&¢er<array[end]){
end--;
}
if(start<end){
array[start]=array[end];
start++;
}
while(start<end&¢er>array[start]){
start++;
}
if(start<end){
array[end]=array[start];
end--;
}
array[start] =center;//基准数填入坑中
}
return start;//返回中位数所在的坑位下标
}
}
打印结果如下
我们再来看看原未排序的数据,下标为五的的元素就是7了
现在我们再来编写算法,来实现多轮比较
我们使用递归调用即可
public static void doQuickSort(int start,int end,int []array){
System.out.println(Arrays.toString(array));
if(start<end){//条件不满足,即快排完毕,结束递归
int index=getIndex(start,end,array);//先获取基准数坑位
doQuickSort(start,index-1,array);//递归调用 比基准数小的
doQuickSort(index+1,end,array);//递归调用 比基准数大的
}
}
这里注意 intdex-1
和 index+1
就是缩小范围,不在于基准数比较
完整代码如下
public class QuickSort {
public static void main(String[] args){
int []array={5,3,9,1,6,7,2,4,0,8};
System.out.println("未排序前:"+Arrays.toString(array));
doQuickSort(0,array.length-1,array);
System.out.println("快排后:"+Arrays.toString(array));
}
public static void doQuickSort(int start,int end,int []array){
if(start<end){
int index=getIndex(start,end,array);
doQuickSort(start,index-1,array);
doQuickSort(index+1,end,array);
}
}
public static int getIndex(int start,int end,int []array){
int center=array[start];
while(start<end){
while(start<end&¢er<array[end]){
end--;
}
if(start<end){
array[start]=array[end];
start++;
}
while(start<end&¢er>array[start]){
start++;
}
if(start<end){
array[end]=array[start];
end--;
}
array[start] =center;
}
return start;
}
}
打印结果如下
我们再来添加些数据
int []array={5,3,9,1,6,7,2,4,0,8,342,-2,-54,234,76,-1};
运行结果如下
归并排序
归并排序又成为二路归并法
他的实现思路如下
第一步:将一个无序的数据,根据中间的索引将其分为两个数组
第二步:重复第一步,直至分出来的数组长度为1即可,这样就可以保证每个数组里的元素都是有序的
第三步:将拆分出的数组一一比较,就会得到两个大致有序的数组,我们再次比较,将这两个数组合并可得到一个有序数组了
这里重点强调数组如何两两拆分和如何两两合并
两两合并
我们先来简单的学习一下如何合并
定义一个数组
int []array={4,5,7,8,1,2,3,6}
我们把它看作两个有序子序列
也就是
4,5,7,8 1,2,3,6
我们来实现合并
public class MergeSort {
public static void main(String[] args){
int []array={4,5,7,8,1,2,3,6};
doMerge(0,(0+array.length-1)/2,array.length-1,array);
}
private static void doMerge(int startIndex,int centerIndex,int endIndex,int [] array){
//4 5 7 8 1 2 3 6
int tempArray[]=new int[endIndex-startIndex+1];//定义临时数组长度
int leftStartIndex=startIndex;//定义左边数组起始下标
int rightStartIndex=centerIndex+1;//定义右边数组起始下标
int tempStartIndex=0;//临时数组默认索引
while(leftStartIndex<=centerIndex&&rightStartIndex<=endIndex){
if(array[leftStartIndex]>array[rightStartIndex]){//如果左边元素大于右边元素
tempArray[tempStartIndex]=array[rightStartIndex];//将右边元素添加进临时数组
rightStartIndex++;//右边需要加1
}else {
tempArray[tempStartIndex]=array[leftStartIndex];//将右边元素添加进临时数组
leftStartIndex++;//左边+1
}
tempStartIndex++;//每次循环临时数组下标也需要+1
}
System.out.println("tempArray "+ Arrays.toString(tempArray));
}
}
我们来看看打印结果
可以看到少了两个元素,临时数组默认补零了
我们打印来看一下
private static void doMerge(int startIndex,int centerIndex,int endIndex,int [] array){
//4 5 7 8 1 2 3 6
int tempArray[]=new int[endIndex-startIndex+1];//定义临时数组长度
int leftStartIndex=startIndex;//定义左边数组起始下标
int rightStartIndex=centerIndex+1;//定义右边数组起始下标
int tempStartIndex=0;
while(leftStartIndex<=centerIndex&&rightStartIndex<=endIndex){
if(array[leftStartIndex]>array[rightStartIndex]){
System.out.println(array[leftStartIndex]+">"+array[rightStartIndex]+" 存入临时数组的下标为 "+tempStartIndex);
tempArray[tempStartIndex]=array[rightStartIndex];
rightStartIndex++;
}else {
System.out.println(array[leftStartIndex]+"<"+array[rightStartIndex]+" 存入临时数组的下标为 "+tempStartIndex);
tempArray[tempStartIndex]=array[leftStartIndex];
leftStartIndex++;
}
tempStartIndex++;
System.out.println("tempArray "+ Arrays.toString(tempArray));
}
问题就在于while循环完毕后,存在7与8元素没有元素与之比较
也就是还存在leftStartIndex<=centerIndex
或者rightStartIndex<=endIndex
两种情况
所以我们需要编写额外的循环来排除出现以上情况
优化如下
public class MergeSort {
public static void main(String[] args){
System.out.println("未排序前 "+Arrays.toString(array));
doMerge(0,(0+array.length-1)/2,array.length-1,array);
System.out.println("两两合并后 "+Arrays.toString(array));
}
private static void doMerge(int startIndex,int centerIndex,int endIndex,int [] array){
//4 5 7 8 1 2 3 6
int tempArray[]=new int[endIndex-startIndex+1];//定义临时数组长度
int leftStartIndex=startIndex;//定义左边数组起始下标
int rightStartIndex=centerIndex+1;//定义右边数组起始下标
int tempStartIndex=0;
while(leftStartIndex<=centerIndex&&rightStartIndex<=endIndex){
if(array[leftStartIndex]>array[rightStartIndex]){
tempArray[tempStartIndex]=array[rightStartIndex];
rightStartIndex++;
}else {
tempArray[tempStartIndex]=array[leftStartIndex];
leftStartIndex++;
}
tempStartIndex++;
}
while(leftStartIndex<=centerIndex){//防止第一次while循环还存在leftStartIndex<=centerIndex的情况
tempArray[tempStartIndex]=array[leftStartIndex];
leftStartIndex++;
tempStartIndex++;
}
while(rightStartIndex<=endIndex){//防止第一次while循环还存在rightStartIndex<=endIndex的情况
tempArray[tempStartIndex]=array[rightStartIndex];
rightStartIndex++;
tempStartIndex++;
}
for (int i = 0; i < tempArray.length; i++) {
//将临时数组中的值重新写入原数组 endIndex与centerIndex会引发数组越界的异常
array[i+startIndex]=tempArray[i];
}
}
}
这个时候我们再次运行
排序完成了
当然这只是针对俩组有序的子序列的元素合并,如果两个序列中有一个是无序序列,如下
int []array={4,5,7,8,1,2,3,6,-1,23,45,-33};
如上,很明显我们没有保证右侧子序列的有序性
这时再次运行,打印结果如下
可以看到排序失败了
所以我们来学习如何将数组两两拆分
两两拆分
两两拆分其实很简单,我们只需要递归即可
private static void doSplit(int []arr,int startIndex,int endIndex){
int centerIndex=(startIndex+endIndex)/2;//每次递归获取中间下标
if (startIndex<endIndex){
doSplit(arr,startIndex,centerIndex);//拆左边序列
doSplit(arr,centerIndex+1,endIndex);//拆右边序列
doMerge(startIndex,centerIndex,endIndex,arr);//两两拆分后做两两合并
}
}
然后我们合并一下,完整代码如下
public class MergeSort {
public static void main(String[] args){
int []array={4,5,7,8,1,2,3,6,-1,23,45,-33};
System.out.println("未排序前 "+Arrays.toString(array));
doSplit(array,0,array.length-1);
System.out.println("两两合并后 "+Arrays.toString(array));
}
private static void doSplit(int []arr,int startIndex,int endIndex){
int centerIndex=(startIndex+endIndex)/2;
if (startIndex<endIndex){
doSplit(arr,startIndex,centerIndex);//拆左边序列
doSplit(arr,centerIndex+1,endIndex);//拆右边序列
doMerge(startIndex,centerIndex,endIndex,arr);//两两拆分后做两两合并
}
}
private static void doMerge(int startIndex,int centerIndex,int endIndex,int [] array){
int tempArray[]=new int[endIndex-startIndex+1];//定义临时数组长度
int leftStartIndex=startIndex;//定义左边数组起始下标
int rightStartIndex=centerIndex+1;//定义右边数组起始下标
int tempStartIndex=0;
while(leftStartIndex<=centerIndex&&rightStartIndex<=endIndex){
if(array[leftStartIndex]>array[rightStartIndex]){
tempArray[tempStartIndex]=array[rightStartIndex];
rightStartIndex++;
}else {
tempArray[tempStartIndex]=array[leftStartIndex];
leftStartIndex++;
}
tempStartIndex++;
}
while(leftStartIndex<=centerIndex){
tempArray[tempStartIndex]=array[leftStartIndex];
leftStartIndex++;
tempStartIndex++;
}
while(rightStartIndex<=endIndex){
tempArray[tempStartIndex]=array[rightStartIndex];
rightStartIndex++;
tempStartIndex++;
}
for (int i = 0; i < tempArray.length; i++) {
array[i+startIndex]=tempArray[i];
}
}
}
运行结果如下
我们再次添加数据
int []array={4,5,7,8,1,2,3,6,-1,23,45,-33,3,534,23,-4,5};
再次运行
基数排序
基数排序不同于之前所介绍的各类排序
我们在前边介绍到的排序方法或多或少的是通过使用比较和移动记录来实现排序.而基数排序的实现不需要进行对关键字的比较,
而基数排序只需要对关键字进行“分配"与“收集"两种操作即可完成。
基数排序法也可以成为桶子法,我们创建桶子来分配元素,然后再拿出
我们画个图来模拟一下
我们先定义一个无序数组,并且创建0-9十个桶子
第一轮我们对数组中的元素进行个位数进行分配
我们再依次取出,重新填入原数组,同时将0-9号桶清空,这就是收集
这时我们就会得到一个新的数组序列
第二轮我们来对元素的十位数进行分配
依次取出,重新填入原数组,清空桶,得到新数组
第三轮比较,我们比较元素的百位数
依次取出,重新填入原数组,清空桶,我们的排序就完成了
这里我们需要注意两点
- 如何知道排序需要多少伦次?
- 桶子内数据如何存储,以及取出?
其实认真思考一下,你就会发现
比较的轮次取决的数组中大元素的位数,也就是说假如最大元素是4位数,那我们比较四轮即可
桶子数据的存储我们可以使用二维数组来存储
那么这又涉及到两个问题了
- 如何获取数组最大元素的最高位?
- 二维数组如何定义?
有了上面问题,我们一步一步来解决
如何获取数组最大元素的最高位?
我们先来获取数组中的最大元素
我们在数组的学习中,通过遍历逐一比较即可获得最大元素
如下:
public class RadixSort {
public static void main(String[] args){
int []array={23,56,3,1,7,443,87,23,12,775,34};
int max=getMaxEle(array);
System.out.println("该数组中最大的元素是:"+max);
}
private static int getMaxEle(int []array){
int max=array[0];
for (int i = 1; i < array.length; i++) {
if(array[i]>max){
max=array[i];
}
}
return max;
}
}
打印结果如下:
那么最大元素的位数呢?
很简单,我们只需要强制转换成String类型,在获取字符串的长度即可
如下:
public static void main(String[] args){
int []array={23,56,3,1,7,443,87,23,12,775,34};
int max=getMaxEle(array);
System.out.println("该数组中最大的元素是:"+max);
int digits=String.valueOf(max).length();
System.out.println(max+"的最高位为"+digits);
}
打印结果如下
这个最高位就是我们需要分配的轮数
也就是我们最外层循环的条件
for (int i = 0; i < digits; i++) {
}
那么,我们的二维数组要怎么定义呢?
首先,我们先找到已知条件定义0-9号桶
那么我们二维数组的长度就是10了
int [][]tempArray=new int [10][?]
那么,还有一个参数要怎么写呢?
这里我们就要想到一种最坏情况
那就是,如果第一轮比较的时候,各元素的个位数都是一样的
我们就这么定义
int [][]tempArray=new int [10][array.length]
我们现在开始编写一步一步的开始编写
private static void doRadixSort(int[] array){
//获取数组中最大的元素,获取其最高位 作为最外层循环
int maxLength=String.valueOf(getMaxEle(array)).length();
//创建二维数组 每个二维数组代表一个桶 0~9
int [][]bucket=new int[10][array.length];
//创建一维数组给二维数组计数 统计对应的二维数组中有多少元素
int []bucketEleCount=new int[10];
//div 我们用来取位
for (int i = 0,div=1; i < maxLength; i++,div*=10) {
for (int ele = 0; ele < array.length; ele++) {
int digit=array[ele]/div%10;
//更具位来存储到对应的二维数组中
// 同时为了避免后面元素覆盖前面的元素 我们拿一维数组来指定其存储位置 同时计数
//注意 一维数组此时所有值都是0
bucket[digit][?]=array[ele];
}
}
}
这里又要想一个问题了,如果有两个相同的元素存入了同一个桶中,我们要如何给他进行排序呢?
我们可以定义一个和桶数相同的一维数组来监控这些桶,每当存入时,我们对里面的值+1即可,且这个数组只做计数功能
物理模型应该如下
同时我们的代码也要做出相应的改变
private static void doRadixSort(int[] array){
//获取数组中最大的元素,获取其最高位 作为最外层循环
int maxLength=String.valueOf(getMaxEle(array)).length();
//创建二维数组 每个二维数组代表一个桶 0~9
int [][]bucket=new int[10][array.length];
//创建一维数组给二维数组计数 统计对应的二维数组中有多少元素
int []bucketEleCount=new int[10];
//div 我们用来取位
for (int i = 0,div=1; i < maxLength; i++,div*=10) {
for (int ele = 0; ele < array.length; ele++) {
int digit=array[ele]/div%10;
//更具位来存储到对应的二维数组中
// 同时为了避免后面元素覆盖前面的元素 我们拿一维数组来指定其存储位置 同时计数
//注意 一维数组此时所有值都是0
bucket[digit][bucketEleCount[digit]++]=array[ele];
}
}
}
这里只是完成了存的操作 现在我们还要从里面取值 然后重新还给待排序数组
private static void doRadixSort(int[] array){
//获取数组中最大的元素,获取其最高位 作为最外层循环
int maxLength=String.valueOf(getMaxEle(array)).length();
//创建二维数组 每个二维数组代表一个桶 0~9
int [][]bucket=new int[10][array.length];
//创建一维数组给二维数组计数 统计对应的二维数组中有多少元素
int []bucketEleCount=new int[10];
//div 我们用来取位
for (int i = 0,div=1; i < maxLength; i++,div*=10) {
for (int ele = 0; ele < array.length; ele++) {
int digit=array[ele]/div%10;
//更具位来存储到对应的二维数组中
// 同时为了避免后面元素覆盖前面的元素 我们拿一维数组来指定其存储位置 同时计数
//注意 一维数组此时所有值都是0
bucket[digit][bucketEleCount[digit]++]=array[ele];
}
//从二维数组取值索引
int index=0;
//遍历一维数组
for (int count = 0; count < bucketEleCount.length; count++) {
//由于一维数组在前面的循环中已经有值了 我们只需要找元素不为0 的即可
if(bucketEleCount[count]!=0){
//遍历 比如当前一维数组有个元素为1且下标为0 那么它所对应的二维数组中就有一个值
for (int countEle = 0; countEle < bucketEleCount[count]; countEle++) {
//重新赋值给排序数组 count:那个桶 countEle取那个值
array[index++]=bucket[count][countEle];
}
//我们此时已经取完值了,一维数组将重新计数
bucketEleCount[count]=0;
}
}
}
}
这个时候 我们的排序也就算完成了
运行 打印结果如下
我们再添加些数据
int []array={23,56,3,1,7,443,87,23,12,775,34,2343,133,5,32,568,553};
注意事项
其实到这里,你会发现基数排序不同于其他的排序,比如冒泡,直接插入法等等,基数排序可以看作桶排序的拓展,当然,我们还没学习,但是掌握基数排序,学习桶排序还是会比较轻松的
还有就是,不知道你们发现没有 我这里没有在数组里面添加负数,是因为 如果 待排序数组中有个元素为 -1 那么他的个位就是1,且我们要带符号,存入数组时 就会引发数组越界的异常,但是 解决办法也很简单,我们对负数取绝对值的的操作存入桶中,取出时,再对其取反即可
排序算法平均时间复杂度和最坏时间复杂度
在这里补充一下排序算法的时间复杂度吧
写在后面
咳咳,本章节的学习就要结束了,当然关于Java数组的排序还有许多方法,本博客只是列举了一些些,如果有兴趣可以查看Arrays
类中的sort
方法,看看Java官方是怎么进行数组排序的,提示一下(二分,快速)这里就不展开了,最后,希望本博客对您有帮助,我们一起进步!!