1.1 选择排序O(n2)
public class SelectSort {
public static void swap2(int[] arrs,int a,int b){
int temp = arrs[a];
arrs[a] = arrs[b];
arrs[b] = temp;
}
public static void selectsort(int[] arrs){
if(arrs == null || arrs.length <= 2){
return;
}
//每次选择最小数放在前面(每次遍历n-i次)
for(int i = 0;i < arrs.length-1;i++){
int minIndex = i;
for(int j = i+1;j < arrs.length;j++){
minIndex = arrs[minIndex] <= arrs[j] ? minIndex : j;
swap2(arrs,i,minIndex);
}
}
}
}
1.2 冒泡排序O(n2)
public class BubbleSort {
//交换两个数 该方式要满足 a != b ,即内存上不是一块的 否则会为0
public static void swap(int[] arrs,int a,int b){
arrs[a] = arrs[a] ^ arrs[b];
arrs[b] = arrs[a] ^ arrs[b];
arrs[a] = arrs[a] ^ arrs[b];
}
public static void bubblesort(int[] arrs){
for(int i = 0;i < arrs.length;i++){
boolean ischange = false;
for(int j = 0;j < arrs.length - i - 1;j++){ //每次比较相邻两个数的值,大的在后
if(arrs[j] > arrs[j+1]){
swap(arrs,j,j+1);
ischange = true;
}
}
if(!ischange){
break;
}
}
}
}
1.3 异或运算补充
异或运算(^):① 0^a = a; a^a = 0
② a^b^c=a^c^b(交换律)
a^(b^c)=(a^b)^c(结合律)
交换两个数的值(不能是同一个内存上的)
//交换两个数 该方式要满足 a != b ,即内存上不是一块的 否则会为0
public static void swap(int[] arrs,int a,int b){
arrs[a] = arrs[a] ^ arrs[b];
arrs[b] = arrs[a] ^ arrs[b];
arrs[a] = arrs[a] ^ arrs[b];
}
异或运算补充
①数列arr中 有一个数的个数为奇数个,其他数为偶数个,找出这个奇数个数的数字。
//数列arr中 有一个数的个数为奇数个,其他数为偶数个,找出这个奇数个数的数字。
public static int findOddTimesNum1(int[] arr){
int eor = 0;
for(int cur : arr){
eor ^= cur; //运用异或运算的交换律以及 a^a=0
}
return eor; //此时为奇数个数的值就是eor
}
②数列arr中 有两个数的个数为奇数个,其他数为偶数个,找出奇数个数的两个数字。
//数列arr中 有两个数的个数为奇数个,其他数为偶数个,找出奇数个数的两个数字。
public static int[] findOddTimesNum2(int[] arr){
int eor = 0;
for(int cur : arr){
eor ^= cur;
}//此时运算结果为 eor = a^b;为两个奇数个数值的异或
//那么我们可以知道这两个值的异或肯定不是0,必然在其二进制数表达上存在 1
int rightOne = eor & (~eor + 1); //提取出两个奇数个数值的异或表达式中 最右边的1
int Onlyone = 0; //用于存储其中的一个值
for(int cur : arr){
if((rightOne & cur) == 1){
Onlyone ^= cur;
}
}//此时OnlyOne = a or b;
int[] num = {Onlyone,eor ^ Onlyone};
return num;
}
1.4 插入排序O(n2)~ O(n)
public class InsertSort {
//交换两个数 该方式要满足 a != b ,即内存上不是一块的 否则会为0
public static void swap(int[] arrs,int a,int b){
arrs[a] = arrs[a] ^ arrs[b];
arrs[b] = arrs[a] ^ arrs[b];
arrs[a] = arrs[a] ^ arrs[b];
}
public static void insertSort(int[] arr){//从小到大
for(int i = 1;i < arr.length;i++){
for(int j = i;j > 0;j--){
if(arr[j] < arr[j-1]){
swap(arr,j,j-1);
}
}
}
}
}
1.5 二分查找(查找的数组需要是一个有序的数组,假设为升序数组)O(log2n)
①在一个有序数组中,找某个数是否存在
//①在一个有序数组中,找某个数是否存在
public static int binarysearch(int[] arr,int target,int size){//size想要进行查找的范围 [0,size-1]
int left = 0;
int right = size - 1; //定义目标寻找的位置 [left,right]
//①循环条件的判断 ②迭代过程中middle和right的关系
while(left <= right){
int middle = (left + right) / 2;
if(target < arr[middle]){
right = middle - 1;
}else if(target > arr[middle]){
left = middle + 1;
}else if(target == arr[middle]){
return middle; //找到返回该元素的下标
}
}
return -1; //找不到则返回-1
}
②在一个有序数组中,找>=某个数最左侧的位置
//②在一个有序数组中,找>=某个数最左侧的位置
public static int findLeftLocation(int[] arr,int target){
int t = binarysearch(arr,target,arr.length); //查看该目标数是否在该数组中,并获取到其下标
if(t == -1){
return -1;//表示没有
}
//该数存在数组中,但不一定是最左的位置,因此需要继续二分
int temp = -1;//用于保存找到的下标
while(t != -1){
temp = t; //t会被下一次迭代掉,因此需要先保存
t = binarysearch(arr,target,t); //搜索范围[0,t],不能t-1,会导致搜索范围出现问题
}
return temp;
}
③局部最小值的问题
//③局部最小值的问题
public static int localMiniMum(int[] arr){//返回的是该元素对应的下标
//先判断i=0和n-1的情况
if(arr.length == 1 || arr[0] < arr[1]){
return 0;
}
if(arr.length == 1 || arr[arr.length-1] < arr[arr.length-2]){
return arr.length-1;
}
//排除上两种情况后,在进行二分查找局部最小
int left = 0;
int right = arr.length - 1;
//此时二分查找的条件变化:arr[i-1]>arr[i] && arr[i]<arr[i+1]
while(left <= right){
int middle = (left + right) / 2; //存在溢出的可能性,需要优化(left +(right – left)>> 1;
if(arr[middle-1] < arr[middle] && arr[middle] < arr[middle+1]){
right = middle - 1;
}else if(arr[middle+1]> arr[middle] && arr[middle] > arr[middle-1]){
left = middle + 1;
}else if(arr[middle-1] >= arr[middle] && arr[middle+1] >= arr[middle]){//没有等于可能在有相同最小值时出现死循环
return middle; //找到返回该元素的下标
}
}
return -1; //找不到则返回-1
}
1.6 对数器
①有一个你想要测的方法a
②实现一个绝对正确但是复杂度不好的方法b
③实验一个随机样品产生器
④实现对比算法a和b的方法
⑤把方法a和方法b比对多次来验证方法a是否正确
⑥如果有一个样品使得对比出错,打印样品分析时那个方法出错
⑦当样品数量很多时比对测试依然正确,可以确定方法a已经正确
public class LogarithmicDetector {
//生成一个随机大小,最大数随机的数组
public static int[] generatorRandomArray(int maxSize,int maxNum){
int[] arr = new int[(int)((maxSize+1)*Math.random())]; //random()->[0,1) 生成[1,maxSize]的随机数组
for(int i = 0;i < arr.length;i++){
arr[i] = (int)(Math.random()*(maxNum+1)) - (int)(Math.random()*maxNum); //范围(-maxNum,maxNum+1)整数
}
return arr;
}
//复制当前数组的一个样本
public static int[] copyArray(int[] arr){
int[] newArr = Arrays.copyOf(arr, arr.length);
return newArr;
}
//判断两个数是否相同
public static boolean isEquals(int[] arr1,int[] arr2){
if(arr1.length != arr2.length){
return false;
}
if(arr1 == null && arr2 != null || arr1 != null && arr2 ==null){
return false;
}
for(int i = 0;i < arr1.length;i++){
if(arr1[i] != arr2[i]){
return false;
}
}
return true; //若两个数组都为空,返回的也是true
}
@Test
public void test(){
int testTimes = 10000;
int maxSize = 50;
int maxNum = 100;
boolean isEqual = true;
for(int i = 0;i < testTimes;i++){
int[] arr1 = generatorRandomArray(maxSize, maxNum);
int[] arr2 = copyArray(arr1);
BubbleSort.bubblesort(arr1); //冒泡排序 (假设是自己写的方法a)
InsertSort.insertSort(arr2); //插入排序 (已知正确的方法b)
if(!isEquals(arr1,arr2)){ //通过对比两种方法运行后的数据结果是否一致,不一致直接跳出用于后续打印分析
isEqual = false;
break;
}
}
System.out.println(isEqual ? "Success":"Failed");//Success
}
}
1.7 剖析递归行为和递归行为时间复杂度的估算
Master公式的使用:T(N)=a*T(N/b)+O(N^d)
① log(b,a)>d --> 复杂度O(N^log(b,a)
② log(b,a)=d --> 复杂度O(N^d*logN)
③ log(b,a)<d --> 复杂度O(N^d)
1.8 归并排序O(nlogn)
①整体就是一个简单递归,左边排好序,右边排好序,让其整体有序
②让其整体有序的过程用了外排序的方法
③利用master公式来求解时间复杂度
④归并排序的实质:时间复杂度O(nlogn),额外空间复杂度O(n)
public class MergeSort {
public static void mergeSort(int[] arr){
if(arr == null || arr.length < 2) {
return;
}
divide(arr,0,arr.length-1);
}
//分治 --> 采用递归的方式
public static void divide(int[] arr,int L,int R){
if(L == R){
return;
}
int mid = L + ((R - L) >> 1);
divide(arr,L,mid); //左侧递归
divide(arr,mid+1,R); //右侧递归
merge(arr,L,mid,R);
}
//将排好序的[L,M]和[M+1,R]合并成一个[L,R]的有序 ---> 归并
public static void merge(int[] arr,int L,int M,int R){
//需要开辟一个新的空间用于暂时存放合并的数组,长度为R-L+1
int[] temp = new int[R - L + 1];
//需要指针分别指向三个数组的索引
int i = 0;
int L1 = L;//[L,M]数组的指针
int R1 = M + 1;//[M+1,R]数组的指针
//合并
while (L1 <= M && R1 <= R){
temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];//相等则先拷贝右侧的数据
}//表明有一侧已经排完,还有一侧没有排完
while (L1 <= M){ //[M+1,R]已经全部排完,只要把[L,M]剩余的依次放入就行
temp[i++] = arr[L1++];
}
while(R1 <= R){ //[L,M]已经全部排完,只要把[M+1,R]剩余的依次放入就行
temp[i++] = arr[R1++];
}
for(i = 0;i < temp.length;i++){//数组原来排序的范围arr[L,R],赋值的时候要注意不能从0开始
arr[L+i] = temp[i];
}
}
}
eg1:小和问题(在一个数组中,每一个数左边比当前数小的数累加起来,叫做数组的小和),求一个数组的小和。 --> 在归并排序的思想上进行添加修改
例子:[1,3,4,2,5]1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1,3;2左边比2小的数,1;5左边比5小的数,1,3,4,2;所以小和为:1+1+3+1+1+3+4+2=16。
public class XiaoHe {
/**
* 小和问题(在一个数组中,每一个数左边比当前数小的数累加起来,叫做数组的小和),求一个数组的小和。
* 例子:[1,3,4,2,5]1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1,3;2左边比2小的数,1;
* 5左边比5小的数,1,3,4,2;所以小和为:1+1+3+1+1+3+4+2=16。
*/
//方式一:暴力解法 时间复杂度O(n^2)
public static int smallSumSoulation1(int[] arr){
if(arr == null || arr.length < 2){
return 0;
}
int sum = 0;
for(int i = 0;i < arr.length;i++){
for(int j = 0;j <= i;j++){
if(arr[i] > arr[j]){
sum += arr[j];
}
}
}
return sum;
}
//方式二:通过归并排序计算,在合并过程中比较计算 --> 时间复杂度O(nlogn)
public static int smallSumSoulation2(int[] arr){
if(arr == null || arr.length < 2) {
return 0;
}
return divide(arr,0,arr.length-1);
}
//排序的过程,也要求小和
public static int divide(int[] arr,int L,int R){
if(L == R){
return 0;
}
int mid = L + ((R - L) >> 1);
return divide(arr,L,mid) + divide(arr,mid+1,R) + merge(arr,L,mid,R);
}
public static int merge(int[] arr,int L,int M,int R){
int[] temp = new int[R - L + 1];
int i = 0;
int L1 = L;//[L,M]数组的指针
int R1 = M + 1;//[M+1,R]数组的指针
int result = 0;//小和
while (L1 <= M && R1 <= R){ //均没有越界
//相等则先拷贝右侧的数据,并且小和不加
result += arr[L1] < arr[R1] ? (R - R1 + 1) * arr[L1] : 0;
temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];
}
while (L1 <= M){
temp[i++] = arr[L1++];
}
while(R1 <= R){
temp[i++] = arr[R1++];
}
for(i = 0;i < temp.length;i++){
arr[L+i] = temp[i];
}
return result;
}
}
eg2:逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。 --> 在归并排序的思想上进行添加修改
public class ReverseDouble {
/**
* 逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。
*/
public static void reverseDouble(int[] arr){
if(arr == null || arr.length < 2) {
return;
}
divide(arr,0,arr.length-1);
}
public static void divide(int[] arr,int L,int R){
if(L == R){
return;
}
int mid = L + ((R - L) >> 1);
divide(arr,L,mid);
divide(arr,mid+1,R);
merge(arr,L,mid,R);
}
public static void merge(int[] arr,int L,int M,int R){
int[] temp = new int[R - L + 1];
int i = 0;
int L1 = L;//[L,M]数组的指针
int R1 = M + 1;//[M+1,R]数组的指针
while (L1 <= M && R1 <= R){ //均没有越界
if(arr[L1] > arr[R1]) System.out.println("["+arr[L1]+","+arr[R1]+"]");
temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];//相等则先拷贝右侧的数据
}
while (L1 <= M){
temp[i++] = arr[L1++];
}
while(R1 <= R){
temp[i++] = arr[R1++];
}
for(i = 0;i < temp.length;i++){
arr[L+i] = temp[i];
}
}
}
问题:荷兰国旗问题
一、给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(n)。
二、(荷兰国旗问题)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(n)。
public class HeLanGuoQiProblems {
/**
* 一、 给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
* 要求额外空间复杂度O(1),时间复杂度O(n)。
*/
public static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void heLanGuoQi(int[] arr,int num){
int L = -1;//左边边界条件,用于记录小于等于num数的边界范围
for(int i = 0;i < arr.length;i++){
//如果arr[i]<=num,当前数和边界L区的下一个数进行交换,并且边界L区进行右扩,再判断下一个数;没有则直接判断下一个数
if(arr[i] <= num){
swap(arr,i,L+1);
L++;
}
}
}
/**
* 二、(荷兰国旗问题)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
* 要求额外空间复杂度O(1),时间复杂度O(n)。
*/
public static void heLanGuoQiAdvance(int[] arr,int num){
int L = -1; //小于num数的左边界
int R = arr.length;//大于num数的右边界
int i = 0;
//当i=R-1时,表明大于num的数以及在右侧了,程序以及完成。若没有这个条件,会导致已经在右侧的数重新进入循环进行交换
while(i < arr.length && i < R){
if(arr[i] < num){//如果arr[i]<num,当前数和边界L区的下一个数进行交换,并且边界L区进行右扩,再判断下一个数
swap(arr,i,L+1);
L++;
i++;
}else if(arr[i] > num){//如果arr[i]>num,当前数和边界R区的前一个数进行交换,并且边界R区进行左扩,此时i不变,继续判断当前数
swap(arr,i,R-1);
R--;
}else {//没有则直接判断下一个数
i++;
}
}
}
}
1.9 快速排序(时间复杂度O(nlogn)~ O(n^2))
不改进的快速排序:
1> 把数组范围内中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值
2>对左侧范围和右侧范围,递归执行
3>时间复杂度为O(n^2)
随机快速排序(改进的快速排序)
1>在数组范围中,等概率随机选一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值
2>对左侧范围和右侧范围,递归执行
3>时间复杂度为O(nlogn)
public class QuickSort {
public static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//方式一
public static void quickSortVersion1(int[] arr,int L,int R){
if(L >= R){
return;
}
int temp = arr[R];//选取最后一个数为基值作为比较
int i = L;
int j = R;
while(i < j){
while(arr[i] <= temp && i < j){//选取的基准数为最右,因此探测顺序应为 先左后右
i++;
}
while(arr[j] >= temp && i < j){
j--;
}
swap(arr,i,j);
}
arr[R] = arr[i];
arr[i] = temp;
quickSortVersion1(arr,L,i-1);
quickSortVersion1(arr,i+1,R);
}
//方式二:推荐(再数组中随机选取基准值,并放在数组最后)
public static void quicksort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
quicksort(arr,0,arr.length-1);
}
public static void quicksort(int[] arr,int L,int R){
if(L < R){
swap(arr,L+(int)(Math.random()*(R-L+1)),R);
int[] p = partition(arr, L, R);//拿到与基准数相同数的下标数组
quicksort(arr,L,p[0]-1);//小于基准数再进行快排
quicksort(arr,p[1]+1,R);//大于基准数的再进行快排
}
}
//荷兰国旗问题,小于的放左边,等于的放中间,大于的放右边,返回中间相同的坐标数组
public static int[] partition(int[] arr,int L,int R){
//放两个指针,分别代表左边界和右边界(当前数为判断数,该数已在右边界内)
int left = L - 1;
int right = R;
while(L < right){ //已最右边的数为基准数
if(arr[L] < arr[R]){//边界left右移,数组指针指向下一个
swap(arr,++left,L++);
}else if(arr[L] > arr[R]){//边界right左移,数组指针不变
swap(arr,--right,L);
}else {//和判断的数相同,数组指针直接指向下一个
L++;
}
}
swap(arr,right,R);//将判断的数和大于他的第一个数进行交换
return new int[]{left+1,right};
}
}
1.10 堆排序(时间复杂度O(nlogn),空间复杂度O(1))
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。
大根堆:每个结点的值都大于其左孩子和右孩子结点的值。
小根堆:每个结点的值都小于其左孩子和右孩子结点的值。
已知某个索引i的数,其父节点索引:(i-1)/2;左孩子索引:2*i+1;右孩子索引:2*i+2。
堆排序基本思想:
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端。
2.将顶端的数与尾端的数交换,此时,末尾的数为最大值,剩余待排序数组的个数为n-1。
3.将剩余n-1个数再构造成大根堆,重复步骤2的操作。
public class HeapSort {
public static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
//构造大根堆
for(int i = 0;i < arr.length;i++){
heapInsert(arr,i);
}
int heapSize = arr.length;
//固定大根堆的最大值,使其不参与下次大根堆的运算
swap(arr,0,--heapSize);
while(heapSize > 0){
heapify(arr,0,heapSize);
swap(arr,0,--heapSize);
}
}
//如果当前插入的数比其父位置上的元素大,则需要交换他们的值 --> 构造大根堆(向上)
public static void heapInsert(int[] arr,int index){
while(arr[index] > arr[index - 1] / 2){
swap(arr,index,(index - 1) / 2);
index = (index - 1) / 2;
}
}
//如果当前插入的数比起孩子结点位置的数小,则需要交换位置 --> 构造大根堆(向下)
public static void heapify(int[] arr,int index,int heapSize){
int left = index * 2 + 1;
while(left < heapSize){
//先找出左孩子和右孩子之间的最大值,返回最大孩子的下标
int largetIndex = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//比较最大孩子和其父的大小,返回数大的值的下标
largetIndex = arr[largetIndex] > arr[index] ? largetIndex : index;
if(largetIndex == index){
break;
}
swap(arr,largetIndex,index);
index = largetIndex;
left = index * 2 + 1;
}
}
}
堆排序扩展问题:已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
解法:对数组k个元素进行构造小根堆(PriorityQueue),保证最大移动不超过k。每次把小根堆的最小值放在这k个元素的首位,然后弹出首位元素,再向下进行k个元素构造小根堆,如此反复。
public static void sort_k(int[] arr,int k){
//PriorityQueue -> 拥有小根堆的特性
PriorityQueue<Integer> heap = new PriorityQueue<>();
//添加k个元素,进行小根堆排序
int index = 0;
for(;index <= Math.min(arr.length,k);index++){
heap.add(arr[index]);
}
int i = 0;
for(;index < arr.length;i++,index++){
heap.add(arr[index]);
arr[i] = heap.poll(); //返回根结点元素,并删除源数据
}
while(!heap.isEmpty()){
arr[i++] = heap.poll();//将最后的数依次弹出加入数组中
}
}
1.11 比较器
1>比较器的实质就是重载比较运算符
2>比较器可以很好的应用在特殊标准的排序上
3>比较器可以很好的应用在根据特殊标准排序的结构上
默认比较器方法compare的性质:返回负数,第一个参数排在前面;返回正数,第二个参数排在前面;返回0,谁放前面都可以。
1.12 桶排序(时间复杂度O(n),空间复杂度O(n))
桶排序之计数排序
public class CountSort {
public static int[] getMaxAndMin(int[] arr){
int[] p = new int[]{arr[0],arr[0]}; //用于存放最小值和最大值。初始为数组的第一个元素值
for(int i = 0;i < arr.length;i++){
p[0] = arr[i] < p[0] ? arr[i] : p[0];//保存最小值
p[1] = arr[i] > p[1] ? arr[i] : p[1];//保存最大值
}
return p;
}
public static int[] countSort(int[] arr){
//得到数组的最大值和最小值,并确定统计数组的长度 max - min + 1
int[] p = getMaxAndMin(arr);
int[] count = new int[p[1] - p[0] + 1]; //统计最小值到最大值之间每个数出现的次数
//遍历数组,记录出现的次数
for(int i = 0;i < arr.length;i++){
count[arr[i] - p[0]]++;
}
//统计数组变形,每个元素的值为该元素和前面所有元素之和
for(int i = 1;i < count.length;i++){
count[i] += count[i - 1];
}
//从右向左遍历原数组,输出;没输出一个,对应计数数组对应位置减一
int[] help = new int[arr.length];//辅助数组,保存输出结果
for(int i = arr.length - 1;i >= 0;i--){
help[count[arr[i] - p[0]] - 1] = arr[i];
count[arr[i] - p[0]]--;//对应元素的个数减一
}
return help;
}
}
桶排序之基数排序
public class RadixSort {
public static void radixSort(int[] arr){
radixSort(arr,0,arr.length-1,getMaxBits(arr));
}
//获得数组中最大元素的位数(1000->3位,100->2位)
public static int getMaxBits(int[] arr){
int max = arr[0];
for(int i = 0;i < arr.length;i++){
max = max < arr[i] ? arr[i] : max;
}
int bits = 0; //记录最大数是几位的
while(max != 0){
bits++;
max /= 10;
}
return bits;
}
//获得数字x对应d为的值。 (117,2) -> x/10%10=1;(12345,3)->x/100%10
public static int getDigit(int x,int d){
return (x / ((int)Math.pow(10,d-1)) % 10);
}
//digit表示元素的最大位数 --> 将根据这个判断需要进行几次的桶排序
public static void radixSort(int[] arr,int left,int right,int digit){
final int radix = 10;//桶的个数,基数排序桶的个数为10,是确定的
int i = 0,j = 0;
int[] help = new int[right - left + 1];//准备和要排序数一样大的数组
for(int d = 1;d <= digit;d++){
int[] count = new int[radix];
//记录相同位数的个数
for(i = left;i <= right;i++){
j = getDigit(arr[i],d);
count[j]++;
}
//将count变形,每个元素为该元素和前面所有元素的和,用于后续的原数组的倒叙遍历
for(i = 1;i < radix;i++){
count[i] += count[i - 1];
}
//将原数组从右向左遍历,通过count对应位上的值-1找到在辅助数组的位置
for(i = right;i >= left;i--){
j = getDigit(arr[i],d);
help[count[j] - 1] = arr[i];
count[j]--;
}
//将排序完成的数组重新赋给原数组
for(i = left,j=0;i <= right;i++,j++){
arr[i] = help[j];
}
}
}
}
1.13 排序算法的稳定性及其汇总
同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有。
不具备稳定性的排序:选择排序、快速排序、堆排序
具备稳定性的排序:冒泡排序、插入排序、归并排序、一切桶排序思想下的排序
目前没有找到时间复杂度O(nlogn),额外空间复杂度O(1),又稳定的排序。