数据结构 之 十大排序算法

一:何为排序:

假设含 n 个记录的序列为{ R1, R2, …, Rn },其相应的关键字序列为 { K1, K2, …, Kn }
这些关键字相互之间可以进行比较
即在它们之间存在着这样一个关系 : Kp1≤Kp2≤…≤Kpn
按此固有关系将上式记录序列重新排列为 { Rp1, Rp2, …, Rpn } 的操作称作排序。
将一系列数据 从小到大 或 从大到小 这样有规律的排序

二:算法稳定性

设 Ki = Kj (1≤i≤n, 1≤j≤n, i≠j ),且在排序前的序列中 Ri 领先于 Rj(即 i < j )。
若在排序后的序列中 Ri 仍领先于 Rj,则称所用的排序方法是稳定的;
反之,则称所用的排序方法是不稳定的。

常见排序算法如下

在这里插入图片描述

😊简单排序

一:选择排序

基本思想:

首先通过 n –1 次关键字比较,从 n 个记录中找出关键字最小的记录,将它与第一个记录交换。
再通过 n –2 次比较,从剩余的 n –1 个记录中找出关键字次小的记录,将它与第二个记录交换。

重复上述操作,共进行 n –1 趟排序后,排序结束
最简单 且 最没用的排序算法( 时间复杂度为 O(N²) 额外空间复杂度为O(1) 不稳定)

public class SelectionSort {
	public static void selectSort(int[] arrs) {
		long startTime = System.nanoTime();//获取开始时间
		
		//判断数组是否为空 或者只有一个元素
		if(arrs.length <2 | arrs == null) {
			return ;
		}
		//循环数组 比较大小 这里i< length-1 下面的j才不会发生数组越界 而且外层循环只需遍历length-1次即可
		for(int i = 0; i <arrs.length-1; i ++) {
			int minIndex = i;
			for(int j = i+1; j < arrs.length; j++) {
				minIndex = arrs[j] < arrs[minIndex] ? j : minIndex;
			}
			if(i != minIndex) {
				swap(arrs,i,minIndex);
			}
		}
		long endTime = System.nanoTime();//获取结束时间
		System.out.println("程序共运行了 "+(endTime-startTime) +"纳秒");
	}
	public static void swap(int[] arrs,int i ,int minIndex) {
		int temp = arrs[i];
		arrs[i] = arrs[minIndex];
		arrs[minIndex] = temp;
	}
	//测试demo
	public static void main(String[] args) {
		int[] arrs = {5,2,3,1};
		selectSort(arrs);
		for(int x:arrs) {
			System.out.print(x+"\t");
		}
	}
}

二:冒泡排序

基本思想:

重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小)错误就把他们交换过来
实质:把小(大)的元素往前(后)调

时间复杂度为O(N²) 额外空间复杂度为O(1) 稳定

public class BubbleSort {
	public static void bubbleSort(int[] nums) {
		//如果数组长度为0 或者小于2 则直接返回
		if(nums.length <2 | nums == null) {
			return;
		}
		//外层循环每次 得到一个最大值
		for(int i = nums.length-1; i>0; i--) {
			for(int y = 0; y<i; y++) {
				if(nums[y] > nums[y+1]) {
					//使用如下位运算的前提是 俩变量不是同一块内存
					nums[y] = nums[y] ^ nums[y+1];
					nums[y+1] = nums[y] ^ nums[y+1];
					nums[y] = nums[y] ^ nums[y+1];
				}
			}
		}
		
	}
	//测试冒泡排序
	public static void main(String[] args) {
		int[] arr = {2,4,1,6,7,5};
		bubbleSort(arr);
		for(int x : arr) {
			System.out.print(x+"\t");
		}
	}
}

三:插入排序

基本思想:

先将序列中第 1 个记录看成是一个有序子序列,
然后从第 2 个记录开始,逐个进行插入,直至整个序列有序

对于基本有序的数组最好用,稳定。时间复杂度为O(N²) 额外空间复杂度为O(1)

public class InsertionSort {
	public static int[] insertionSort(int[] arrays) {
		//如果数组为空 或 数组长度小于2 则直接返回
		if(arrays.length <2 | arrays == null) {
			return arrays;
		}
		//思路 从第一个数开始 做到0~0有序、0~1有序、0~2有序...
		for(int i = 1; i<arrays.length; i++) {//这一步是保证 0~i做到有序
			//这步 判断是否需要交换 因为要判断i是否需要交换 而i-1又是肯定有序的
			for(int y = i; y>0 ;y--) {
				if(arrays[y]<arrays[y-1]) {
					//两者交换
					arrays[y] = arrays[y] ^ arrays[y-1];
					arrays[y-1] = arrays[y] ^ arrays[y-1];
					arrays[y] = arrays[y] ^ arrays[y-1];
				}
				
			}
		}
		return arrays;
		
	}
	public static void main(String[] args) {
		int[] arrays = {1,5,4,2,6,4,2};
		arrays = insertionSort(arrays);
		for(int x: arrays) {
			System.out.print(x+"\t");
		}
	}

}

😗简单排序算法总结

相同点:时间复杂度为O(N²) 额外空间复杂度为O(1)

冒泡排序:基本不用,太慢
选择排序:基本不用,不稳
插入排序:样本小且基本有序的时候效率比较高

😉高级排序

四:堆排序

基本思想:

堆排序(英语:HeapSort)是指利用堆这种数据结构所设计的一种排序算法。

  • 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:
  • 即子结点的键值或索引总是小于(或者大于)它的父结点
  • 时间复杂度:O(N*logN)
  • 稳定性:不稳定
    1、堆结构就是用数组实现的完全二叉树结构
    2、完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
    3、完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
    4、堆结构的heapInsert 和 heapify 操作
    5、堆结构的增大和减少
    6、优先级队列结构,就是堆结构
堆排序

1、先让整个数组都变成大根堆结构,建立堆的过程:

  1. 从上到下的方法,时间复杂度为 O( N * logN )
  2. 从下到上的方法,时间复杂度为 O( N )

2、把堆的最大值和堆末尾的值交换,然后减少堆的大小之后(抛弃尾部,即抛弃最大值),再去调整堆,一直周而复始,时间复杂度为 O( N * logN )
3、堆的大小减小成0之后,排序完成

public class HeapSort {
	// 堆排序方法实现
	public static void heapSort(int[] arr) {
		//如果数组为空 或 长度小于2  则直接return
		if(arr == null || arr.length < 2) {
			return;
		}
		// 第一步 将整个数组(原来就有值) 变成 大根堆(根据情况 对数组中内部的值进行交换)
//		for(int i = 0; i < arr.length; i++) { // O(N)
//			heapInsert(arr , i ); // O(logN)
//		}
		/*
		 * 或使用 heapify方法使数组变成大根堆
		 * 从树的右叶子结点 依次 heapify 从右往左、从下往上 依次成为大根堆
		 * 这种方式 稍快一些 但是 总的时间复杂度还是一样的
		 */
		for( int i = arr.length-1; i>=0;i--) {
			heapify(arr,i,arr.length);
		}
		// 第二步
		int heapSize = arr.length;
		//将最大值 和 大根堆尾部进行交换 并 将尾部抛弃
		swap(arr, 0 , --heapSize);
		
		// 下面是让剩下的 树 依然成为大根堆的步骤
		
		// 当 heapSize==0 时 则 数组已升序排好
		while( heapSize > 0 ) {
			/* 
			 由于将 树的 头结点 与 尾部进行了交换 所以 该数可能已经不是大根堆
				对该树进行调整 重新调整为大根堆
			(while循环 -> 让index跟 它下面的最大值(左孩子或右孩子)进行交换 最后又是大根堆  )
			*/
			heapify(arr , 0 , heapSize); //O(logN)
			//继续将 树的 头结点 和尾部进行交换
			swap(arr, 0 , --heapSize); // O(1)
		}
		// 这一套流程下来 数组的次小值依次被抛弃 最大值在数组尾部 形成升序排列
	}
	
	
	/*
	 * 某个数现在处在 index位置,往上继续移动 形成大根堆
	 */
	public static void heapInsert(int[] arr,int index) {
		/*
		 * 如果当前数大于父结点 则交换
		 * 判断条件:
		 * 1:index 不比 父结点的值大 结束循环
		 * 2:index已经跑到 整棵树的头结点 结束循环
		 */
		while(arr[index] > arr[(index-1)/2]) { 
			swap(arr,index,(index-1)/2);
			//index往上跑
			index = (index - 1)/2;
		}
	}
	/*	重新调整树 为 大根堆的方法
	 * 
	 * 某个数在index位置,能否往下移动
	 * 一般 index 为 0 重新调整树 为 大根堆
	 */
	public static void heapify(int[] arr , int index, int heapSize) {
		int left = index*2 +1; 			//左孩子的下标
		while(left < heapSize) {		// 下方还有孩子 (如果没有左孩子 肯定没有右孩子)
			//比较 两个孩子谁大 ,将下标给 largest
			int largest = left +1 < heapSize && arr[left + 1] >arr[left] ? left +1 : left;
			//将 父结点 和 刚刚比较出来的较大的孩子 进行比较
			largest = arr[largest] > arr[index] ? largest : index;
			//如果父结点的值依然大过子节点 直接break
			if(largest == index) {
				break;
			}
			//否则 将 父结点(值小)  与 子节点(值大) 进行交换
			swap(arr,largest,index);
			//将index的值往下 
			//index始终要  成为 是父结点的下标(以当前的旧子节点的下标,往下继续遍历)
			index = largest;
			//新的左孩子的下标 再重新判断是否存在这个左孩子
			left = index *2 +1;
		}
	}
	// 交换函数
	public static void swap(int[] arr , int num1, int num2) {
		if(num1 == num2) {
			return;
		}
		arr[num1] = arr[num1] ^ arr[num2];
		arr[num2] = arr[num1] ^ arr[num2];
		arr[num1] = arr[num1] ^ arr[num2];
 	}
	// 测试
	public static void main(String[] args) {
		int[] arr = {1,6,2,8,3,4,5,1,7,9};
		heapSort(arr);
		for(int x : arr) {
			System.out.print(x+"\t");
		}
	}
}

五:希尔排序

基本思想:

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

改进的插入排序
特点:间隔大的时候 移动的次数比较少 间隔小的时候 移动的距离比较短
跳着排序 不稳定
时间复杂度为 O(n的1.3次方) 空间复杂度为 O(1)

public class ShellSort {
	public static void shellSort(int[] arrs) {
		// 如果数组长度小于2个 或者数组为空 则直接return
		if(arrs.length <2 | arrs == null) {
			return;
		}
		//间隔 使用Knuth序列求得
		int h = 1;
		while( h <= arrs.length/3) {
			h = h*3+1;
		}
		
		for(int gap = h;gap >0 ;gap = (gap-1)/3) {
			for(int i = gap; i < arrs.length; i++) {
				for(int j = i; j>gap-1; j-=gap) {
					//进行插入排序
					if(arrs[j] < arrs[j-gap]) {	
						arrs[j] = arrs[j] ^ arrs[j-gap];
						arrs[j-gap] = arrs[j] ^ arrs[j-gap];
						arrs[j] = arrs[j] ^ arrs[j-gap];
					}
				}
			}
		}
	}
	public static void main(String[] args) {
		int[] arrays = {1,5,4,2,6,4,2,3,8,7};
		shellSort(arrays);
		for(int x: arrays) {
			System.out.print(x+"\t");
		}
	}
}

六:归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
已有序子序列合并,得到完全有序的序列
先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并
实质:将两个或两个以上的序列组合成一个新的有序表
就是一个简单的递归,左边排好序,右边排好序,让整体有序
时间复杂度 O(N*logN) ,额外空间复杂度 O(N)

public class MergeSort {
	public static void sort(int[] arrs, int left, int right) {
		//System.out.println("进入递归 left:"+left+" right:"+right);
		if(left == right ) {
			return;
		}
		int mid = left + ((right-left)>>1);//防止int太大溢出
		sort(arrs,left,mid); 	//对左子分支进行递归
		sort(arrs,mid + 1,right); 	//对右子分支进行递归
		mergeSort(arrs,left,mid+1,right); //对刚刚递归完的左右子分支进行排序 不会进递归
		}
	
	public static void mergeSort(int[] arrs,int leftPtr , int rightPtr, int rightBound) {
		//System.out.println("进入MergeSort left:"+leftPtr+" mid "+(rightPtr-1) +" right:"+rightBound);
		//判断数组是否为空 或长度小于2
		if(arrs == null || arrs.length < 2 ) {
			return;
		}
		//把数组分成两部分进行排序
		int mid = rightPtr-1; // 中间指针为右指针的前一个
		int i = leftPtr; // 左指针
		int j = rightPtr;// 右指针
		int num = 0;	//新数组的指针,从0开始
		//新建一个长度一样的数组
		int[] arrays = new int[rightBound-leftPtr+1];
		while( i <= mid && j <= rightBound ) {
			arrays[num++] = arrs[i]<=arrs[j] ? arrs[i++] : arrs[j++];
		}
		//考虑到可能有一部分数组全部拼接完成 但还剩下另一半数组(直接拼接)
		while( i <= mid) {
			arrays[num++] = arrs[i++];
		}
		while( j <= rightBound ) {
			arrays[num++] = arrs[j++];
		}
		//排序完 直接重新修改旧数组的元素
		for(int x = 0 ;x<arrays.length; x++) {
			arrs[leftPtr + x] =arrays[x];
		}
	}
	public static void main(String[] args) {
		//数组只能奇数 且左右两边需有序 左4右3
//		int[] arrs = {1,3,5,6,4,6,7};
		int[] arrs = {5,2,4,6,8,1,5,6};
		sort(arrs,0,arrs.length-1);
		for(int x: arrs) {
			System.out.print(x+"\t");
		}
	}
}

在这里插入图片描述

七:快速排序

基本思想:

任选一个记录,以它的关键字作为“基准”,凡关键字小于基准的记录均移至基准之前,凡关键字大于基准的记录均移至基准之后。

首先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行一趟快速排序。
在这里插入图片描述

V1.0 时间复杂度O(N²)

在整个数组中 以最后一个数为基准 将前面的数按照 <=num | >num | num 排好
再将num与> num的最左边一个数做交换 变成 <=num | num | >num
然后让 <=num 和 >num 这两部分重复这个行为

V2.0 时间复杂度O(N²) 特点:适用于重复值对的情况

利用荷兰国旗问题,
在整个数组中 以最后一个数为基准 将前面的数按照 <num | ==num | >num | num 排好
再将 num与> num的最左边一个数做交换 变成 <num | ==num | >num
这样子每次就搞定了一批数据,比1.0版本稍快

V3.0 修改划分值(情况好的话 划分值应该在数组的数值中的中位数)

时间复杂度O(N*LogN)
解决办法 随机取 划分值
在整个数组中 随机取 划分值 交换到 数组的尾部
再将前面的数按照 <num | ==num | >num | num 排好
再将 num与 > num的最左边一个数做交换 变成 <num | ==num | >num
这样子每次就搞定了一批数据,每次num的值都是等概率事件

快速排序V3.0版本
//V3.0
public class QuickSort {
	public static void quickSort(int[] arr) {
		//如果数组长度小于2 或 为空 则直接return
		if(arr == null || arr.length <2 ) {
			return;
		}
		quickSort(arr,0,arr.length-1);
	}
	//arr[ L -> R 排好序]
	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); // 返回的数组长度一定为2
			quickSort(arr,L,p[0]-1); // <区域
			quickSort(arr,p[1]+1,R);//  >区域
		}
	}
	//交换方法
	public static void swap(int[] arr,int num1 , int num2) {
		int temp = 0;
		temp = arr[num1];
		arr[num1] = arr[num2];
		arr[num2] = temp;
	}
	/*
	 * 这是一个处理arr[L....R] 的函数 分层函数
	 * 默认以最后一个数做划分 ,arr[R] 为P 划分成  <P  ==P  <P
	 * 返回等于区域( 左边界 、右边界),所以返回一个长度为2的数组res,res[0]和res[1]
	 */
	public static int[] partition(int[] arr, int L, int R) {
		int less = L -1; // <区域的右边界
		int more = R;	 // >区域的左边界
		while(L < more) { // L表示当前数的位置  arr[R] 是划分值 说明L还没到大于区域还不能停止
			if(arr[L] < arr[R]) { //当前数 < 划分值
				swap(arr,++less,L++);
			}else if(arr[L] > arr[R]) { //当前数 大于划分值
				swap(arr,--more,L);
			}else {
				L++;
			}
		}
		swap(arr,more,R);
		return new int[] {less+1,more};
	}
	
	public static void main(String[] args) {
		int[] arr = {1,6,2,4,8,1,5,3,4,8};
		quickSort(arr);
		for(int x: arr) { 
			System.out.print(x+"\t");
		}
	}
}

八:桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

在额外空间充足的情况下,尽量增大桶的数量
使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要

桶排序的时间复杂度和空间复杂度都是O(n),并且桶排序是一种稳定的排序算法。
但是桶排序的性能并非是绝对稳定的,因为如果元素分布不均衡,比如说创建了5个桶,大多数元素都集中在了第2个桶,这样桶排序的时间复杂度就会退化为O(nlogn),而且还浪费了空间。

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;

public class BucketSort {
	public static double[] bucketSort(double[] array){
	    //得到数列的最大值和最小值,并计算出差值d
	    double max=array[0];
	    double min=array[0];
	    for (int i=1;i<array.length;i++){
	        if (array[i]>max){
	            max=array[i];
	        }
	        if (array[i]<min){
	            min=array[i];
	        }
	    }
	    double d=max-min;

	    //初始化桶
	    int bucketNum=array.length;
	    ArrayList<LinkedList<Double>> bucketList=new ArrayList<LinkedList<Double>>(bucketNum);
	    for (int i=0;i<bucketNum;i++){
	        bucketList.add(new LinkedList<Double>());
	    }

	    //遍历原始数组将每个元素放入桶中
	    for (int i=0;i<array.length;i++){
	        int num=(int)((array[i]-min)*(bucketNum-1)/d);
	        bucketList.get(num).add(array[i]);
	    }

	    //对每个桶内部进行排序
	    for(int i=0;i<bucketList.size();i++){
	        // 使用Collections.sort,其底层实现基于归并排序或归并排序的优化版本
	        Collections.sort(bucketList.get(i));
	    }

	    //输出全部元素
	    double[] sortedArray=new double[array.length];
	    int index=0;
	    for (LinkedList<Double> list:bucketList) {
	        for (double element:list){
	            sortedArray[index]=element;
	            index++;
	        }
	    }
	    return sortedArray;
	}
	
	  public static void main(String[]args){
		    //排序的数组
		      double a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95};
		      double b[]=bucketSort(a);
		      for(double i:b){
		         System.out.print(i+" ");
		      }
		  }
}

九:计数排序

计数排序(Counting Sort)是一个非基于比较的排序算法,它的优势在于一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序),通过计数将时间复杂度降到了O(N)

基本思想:

对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改

排序过程

第一步:找出数组中的最大值max、最小值min。

第二步:创建一个新数组count,其长度是max-min加1,其元素默认值都为0。

第三步:遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值

第四步:对count数组变形,新元素的值是前面元素累加之和的值,即count[i+1] = count[i+1] + count[i];

第五步:创建结果数组result,长度和原始数组一样。

第六步:遍历原始数组中的元素,当前元素A[j]减去最小值min,作为索引,在计数数组中找到对应的元素值count[A[j]-min],再将count[A[j]-min]的值减去1,就是A[j]在结果数组result中的位置,做完上述这些操作,count[A[j]-min]自减1。

public class CountSort{
 
	public static int[] countSort(int[] A) {
	    // 找出数组A中的最大值、最小值
	    int max = Integer.MIN_VALUE;
	    int min = Integer.MAX_VALUE;
	    for (int num : A) {
	        max = Math.max(max, num);
	        min = Math.min(min, num);
	    }
	    // 初始化计数数组count
	    // 长度为最大值减最小值加1
	    //将数组长度定为max-min+1,即不仅要找出最大值,还要找出最小值,根据两者的差来确定计数数组的长度
	    int[] count = new int[max-min+1];
	    // 对计数数组各元素赋值
	    for (int num : A) {
	        // A中的元素要减去最小值,再作为新索引
	        count[num-min]++;
	    }
	    // 创建结果数组
	    int[] result = new int[A.length];
	    // 创建结果数组的起始索引
	    int index = 0;
	    // 遍历计数数组,将计数数组的索引填充到结果数组中
	    for (int i=0; i<count.length; i++) {
	        while (count[i]>0) {
	            // 再将减去的最小值补上
	            result[index++] = i+min;
	            count[i]--;
	        }
	    }
	    // 返回结果数组
	    return result;
	}
  
  
  public static void main(String[]args){
	    //排序的数组
	      int a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95};
	      int b[]=countSort(a);
	      for(int i:b){
	         System.out.print(i+" ");
	      }
	  }
}

计数排序结果

计数排序算法没有用到元素间的比较,它利用元素的实际值来确定它们在输出数组中的位置。因此,计数排序算法不是一个基于比较的排序算法,计数排序算法是一个稳定的排序算法。
虽然它可以将排序算法的时间复杂度降低到O(N),但是有两个前提需要满足:一是需要排序的元素必须是整数,二是排序元素的取值要在一定范围内,并且比较集中。只有这两个条件都满足,才能最大程度发挥计数排序的优势

十:基数排序

基数排序法是属于稳定性的排序
可参考教程

基本思想:

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

public class RadixSort {
	public static void radixSort(int[] arr) {
		if(arr == null || arr.length <2) {
			return;
		}
		radixSort(arr,0,arr.length-1,maxbits(arr));
	}
	public static int maxbits(int[] arr) {
		//最大值 为 系统最小
		int max = Integer.MIN_VALUE;
		// 遍历 求最大值
		for(int i = 0; i< arr.length;i++) {
			max = Math.max(max,arr[i]);
		}
		// 求 最大值有几个十进制位
		int res = 0;
		while(max != 0 ) {
			res++;
			max /= 10;
		}
		return res;
	}
	//arr[begin...end]排序  digit 表示这一批数据中 最大的值有几个十进制位
	public static void radixSort(int[] arr,int L,int R,int digit) {
		final int radix  = 10;
		int i = 0, j = 0;
		//有多少个数 就准备多少个辅助空间
		int[] bucket = new int[R-L+1];
		for(int d = 1; d<= digit; d++) { //有多少位就进出几次
			// 10 个空间
			int[] count = new int[radix];	//count[0...9]
			for(i = L;i<= R;i++) {
				// 要哪个位上的数字
				j = getDigit(arr[i],d);
				count[j]++;
			}
			// 把count处理成前缀和
			for(i = 1;i<radix;i++) {
				count[i] = count[i]+count[i-1];
			}
			// 数组从右往左遍历 做完即出桶
			for(i  = R; i >=L;i --) {
				j = getDigit(arr[i],d);
				bucket[count[j]-1] = arr[i];
				count[j]--;
			}
			// 将桶里面的数据导出到数组中 维持此次出桶的结果
			for( i = L,j=0;i<=R;i++,j++) {
				arr[i] = bucket[j];
			}
		}
	}
	public static int getDigit(int x ,int d) {
		return ((x/((int)Math.pow(10, d-1)))%10);
	}
	public static void main(String[] args) {
		int[] arr = {5,1,4,6,2,8,4,1,5,3,6,8,7,9};
		radixSort(arr);
		for( int x :arr) {
			System.out.print(x+"\t");
		}
		
	}
}

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;

总结:

一般 用快速排序(3.0版本) ,如果有空间限制的话使用堆排序, 如果有要求稳定性的话一般使用归并排序

在这里插入图片描述

基于比较的排序
目前没有发现时间复杂度低于 O(N * logN) 以下的算法
说明 O(N * logN)这个指标已经是极限

没有发现 时间复杂度为 O(N * logN) 同时 空间复杂度为O(N) 以下 还能保持稳定性的 算法

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值