十大排序算法分别为:选择排序、冒泡排序、插入排序、快速排序、归并排序、堆排序、希尔排序、桶排序、计数排序、基数排序。
本篇只是为了方便我在代码中直接复制调用,因此原理和思想解释的并不清楚,想看原理的朋友可以参考这篇文章:Java实现十大排序算法_木梓沐丶的博客-CSDN博客,有原理和图解,写的超好。
十大排序算法的对比
十大排序算法记忆法
《忆排序,面试我最强》 – 马士兵
选泡插, (选择、冒泡、插入)
快归堆希桶计基, (快速、归并、堆、希尔、桶、计数、基数)
恩方恩老恩一三, (选泡插复杂度是n2,快归希是nlogn,希尔是n1.3)
对恩加K恩乘K, (桶计是n+k,基是n*k)
不稳稳稳不稳稳, (选、泡、插、快、归)
不稳不稳稳稳稳! (堆、希、桶、计、基)
思想:每次选择一个当前未排序部分中最小的,放在最前面的位置
代码:
private static void sort (int[] arr){
int minPos;
for (int i = 0; i < arr.length; i++){
minPos = i;
for (int j = i+1; j < arr.length; j++){
if (arr[j] < arr[minPos]){
minPos = j;
}
}
swap(arr,i,minPos);
}
}
private static void swap (int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
思想:依次循环比较相邻两个数的大小,这样每次循环过后最大的那个数就到了数组尾
代码:
private static void sort(int[] arr){
for (int end = arr.length-1; end > 0; end--){
for (int i = 0; i < end; i++) {
if (arr[i] > arr[i+1]){
swap(arr,i,i+1);
}
}
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
思想:将数组中的数据从第二位开始向前找到合适的位置插入,有点类似向前的冒泡排序
代码:
private static void sort(int[] arr){
for (int i = 1; i < arr.length; i++) {
int j = i;
while (j > 0 && arr[j] < arr[j-1]){
swap(arr,j,j-1);
j--;
}
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
思想:改进版的插入排序,首先设置一个间隔,插入排序相同间隔的每组数据,之后缩小间隔,直至间隔缩小为1
代码:
private static void sort(int[] arr){
int h = 1;
while (h <= arr.length/3){
h = h*3 +1;
}
for (int gap = h; gap > 0; gap = (gap-1)/3 ){
for (int i = gap; i < arr.length; i++){
int j = i;
while (j >= gap && arr[j] < arr[j-gap]){
swap(arr,j,j-gap);
j -= gap;
}
}
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
思想:递归合并两个小的有序数组,先将原数组分成两个小数组,对这两个小数组分别排序使其有序,之后合并这两个小数组,整体来说就是一直拆分源数组为两个数组,给左侧数组排序,给右侧数组排序,合并左右数组。适用于对象排序。
代码:
//数组,左边界下标,右边界下标 (arr,0,arr.length-1)
private static void sort(int[] arr,int left,int right){
if (left == right){
return;
}
int mid = left + (right - left) / 2;
sort(arr,left,mid);
sort(arr,mid+1,right);
merge(arr,left,mid+1,right);
}
//数组,第一个数组的起始下标,第二个数组的起始下标,右边界
private static void merge(int[] arr, int left, int right, int rightBound){
int mid = right - 1;
int i = left, j = right, k = 0;
int[] temp = new int[rightBound - left + 1];
while (i <= mid && j <= rightBound){
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i <= mid){
temp[k++] = arr[i++];
}
while (j <= rightBound){
temp[k++] = arr[j++];
}
//排完序的结果在temp数组里,因此要替换到arr数组中
//变量分别为,源数组,源数组的起始位置,目的数组,目的数组的起始位置,要复制的长度
System.arraycopy(temp, 0, arr, left, temp.length);
}
Java中Arrays.sort()用的是更强大的双轴快排!
思想:常见的实现方式就是首先挑选一个基准点,也称为轴,在轴的左侧找比轴大的,在轴的右侧找比轴小的,找到后进行交换,之后在轴的左侧部分和右侧部分继续执行相同操作。
代码:
//数组,左边界下标,右边界下标 sort(arr,0,arr.length-1)
private static void sort(int[] arr,int left,int right){
if (left >= right){
return;
}
int mid = partition(arr, left, right);
sort(arr,left,mid-1);
sort(arr,mid+1,right);
}
//每次以最右侧的数为轴
private static int partition(int[] arr,int leftBound,int rightBound){
int left = leftBound,right = rightBound - 1,pivot = arr[rightBound];
while (left <= right){
while (left <= right && arr[left] <= pivot){
left++;
}
while (left <= right && arr[right] > pivot){
right--;
}
if (left < right){
swap(arr,left,right);
}
}
swap(arr,left,rightBound);
return left;
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
思想:首先设置一个包含所有范围数据的数组count,count[i]代表i这个数据出现了多少次,最后从起始位置遍历时count[i]等于几,就在数组中追加几个i。计数排序是一种特殊的桶排序,适用于量大但是范围小的数据排序,比如高考成绩排名。
代码:
private static void sort(int[] arr){
int k = 0;
if (arr == null || arr.length < 2) {
return ;
}
int[] count = new int[101];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
for (int i = 0; i < count.length; i++) {
while (count[i]-- > 0){
arr[k++] = i;
}
}
}
思想:从个位开始,循环其中最大的数的位次,每次根据位上的数进行排序。基数排序是一种特殊的桶排序,其中第二次更新count的值后count[m]代表余数<=m的数有多少个。
代码:
public static void sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int[] result = new int[arr.length];
int[] count = new int[10];
int max = Arrays.stream(arr).max().getAsInt();
int flag = String.valueOf(max).length();
for (int i = 0; i < flag; i++) {
int division = (int)Math.pow(10,i);
for (int j = 0; j < arr.length; j++) {
count[ arr[j] / division % 10]++;
}
for (int m = 1; m < count.length; m++){
count[m] = count[m] + count[m-1];
}
for (int n = arr.length-1; n >= 0; n--){
result[--count[arr[n] / division % 10]] = arr[n];
}
System.arraycopy(result,0,arr,0,arr.length);
Arrays.fill(count,0);
}
}
思想:找到这个数组中的最小值和最大值,在最小值和最大值间建立多个桶,把数据放在对应桶中,对每个桶中的数据进行排序,排序完成后按照桶的范围大小进行顺序合并。实际生产中并不常用,原因是时间和空间不可兼得。
代码:
public static void sort(int[] arr){
List[] buckets = new ArrayList[10];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<Integer>();
}
for (int i = 0; i < arr.length; i++) {
buckets[arr[i]/10].add(arr[i]);
}
for (int i = 0; i < buckets.length; i++) {
buckets[i].sort(null);
}
int k = 0;
for (int i = 0; i < buckets.length; i++) {
if (buckets[i].size() > 0){
for (int j = 0; j < buckets[i].size(); j++) {
arr[k++] = (int)buckets[i].get(j);
}
}
}
}
思想:堆是一个数据结构,堆里面有两个特殊形式,分别是大顶堆和小顶堆,大顶堆的首元素是数组中最大的,就可以利用这个特性,将数组首先形成一个大顶堆,接着交换起始位置和终止位置的值,这样终止位置就是数组中最大的值了。然后把起始位置到终止位置-1的元素再次形成一个大顶堆,重复执行上述操作,最终排序完成。
代码:
private static void sort(int[] arr){
for(int i = arr.length/2 - 1; i >= 0; i--){
adjustHeap(arr,i,arr.length);
}
for(int j = arr.length - 1; j > 0; j--){
swap(arr,0,j);
adjustHeap(arr,0,j);
}
}
//实际上就是对于每一个节点,比较其与其子节点中最大的那个数,并将最大的那个数交换到节点位置
private static void adjustHeap(int[] arr,int i,int length){
int temp = arr[i];
for(int k = i*2+1; k < length; k = k*2+1){
if( k+1 < length && arr[k] < arr[k+1] ){
k++;
}
if( arr[k] > temp ){
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}