一、排序方法
java中的常用排序方法有:直接插入排序,希尔排序,冒泡排序,递归排序,堆排序,快速排序,选择排序。
二、分类
稳定性:如果在一个待排序的序列中,有多个相同的元素,经过各种排序方法排序后,该相同元素的相对位置没有发生改变,则称该排序算法是稳定的,反正,则为不稳定。
三.排序方法介绍
1.冒泡排序
时间复杂度:o(n^2)
空间复杂度:o(1)
稳定性:稳定
基本思想:
通过重复比较相邻元素,并在顺序错误时交换它们,将最大(或最小)的元素逐步“冒泡”到序列的一端。重复多轮后,整个序列即变得有序。
基本步骤:
1.从第一个元素开始,依次比较相邻的两个元素。
2.如果它们的顺序错误(比如前大后小),就交换它们。
3.这样一轮下来,最大(或最小)元素会“浮”到最后。
4.对剩余未排序的部分重复上述步骤,直到整个序列排序完成。
特点:
1.实现简单,但效率较低,不适合大规模数据的排序。
2.可以通过加标志优化,提前结束排序。
代码实现:
public static void bubblesort(int[] array) { for (int i = 0; i < array.length-1; i++) { boolean flg = false; for (int j = 0; j < array.length-1-i; j++) { if(array[j] > array[j+1]) { swap(array,j,j+1); flg = true; } } if(!flg) { return; } } }
2.选择排序
时间复杂度:o(n^2)
空间复杂度:o(1)
稳定性:不稳定
基本思想:
每一轮从待排序的元素中找到最小(或最大)元素,然后将其放到已排序部分的末尾。
基本步骤:
1.在未排序部分中找到最小(最大)元素;
2.将其与未排序部分的第一个元素交换;
3.将已排序部分扩大一个元素,重复上述过程直到全部排序完成。
特点:
1.算法思路清晰,容易实现,适合学习排序基础;
2.无论输入数据的初始状态如何,时间复杂度总是 o(n2)o(n2),在大数据集上效率较低;
3.每轮只进行一次交换,最坏情况下虽然比较多,但移动次数较少,适合数据交换成本较高的场景;
4.效率低,不适合大规模数据排序。
代码实现:
private static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } public static void selectsort(int[] array) { for (int i = 0; i < array.length; i++) { int minindex = i; for (int j = i+1; j < array.length; j++) { if(array[j] < array[minindex]) { minindex = j; } } swap(array,i,minindex); } }
3.堆排序
时间复杂度:o(n*logn)
空间复杂度:o(1)
稳定性:不稳定
基本思想:
堆排序利用堆(特别是最大堆或最小堆)这种数据结构,将待排序数组变成一个堆。利用堆的性质,堆顶元素(最大值或最小值)就是整个数组中的最大值或最小值。通过不断将堆顶元素移到数组末尾,然后调整堆,使剩余元素仍满足堆的性质,从而实现排序。
基本步骤:
1.建堆:将无序数组构建成一个大根堆(或小根堆);
2.交换堆顶与末尾元素:将堆顶元素(最大或最小)与数组的最后一个元素交换位置。此时,堆的元素缩小一个容量(排好序的元素在最后);
3.调整堆:对剩余的元素重新调整为堆,保持堆性质;
4.重复步骤2和3:直到所有元素都已经排好序。
特点:
1.原地排序,无需额外空间,时间复杂度稳定在 o(nlogn)o(nlogn),适合大数据集。
2.不稳定排序(相等元素可能改变原始相对位置),实际常数因堆调整略大于快速排序
3.不要求稳定性,对空间效率要求较高时。
代码实现:
public static void heapsort(int[] array) { createheap(array); int end = array.length-1; while (end > 0) { swap(array,0,end); siftdown(array,0, end); end--; } } private static void createheap(int[] array) { for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) { siftdown(array,parent,array.length); } } private static void siftdown(int[] array,int parent,int len) { int child = 2 * parent + 1; while (child < len) { if(child+1 < len && array[child] < array[child+1]) { child++; } if(array[child] > array[parent]) { swap(array,child,parent); parent = child; child = 2 * parent + 1; }else { break; } } } private static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; }
4.直接插入排序
时间复杂度:最好情况o(n),最坏情况o(n^2)
稳定性:稳定
基本思想:
将待排序序列划分为已排序和未排序两部分,逐步将未排序的元素插入到已排序部分的适当位置,直到整个序列有序。
基本步骤:
1.从第一个元素开始,认为已排序;
2.取下一个元素,将其与已排序部分的元素从后向前比较,找到合适的位置插入;
3.将该元素插入到正确位置后,已排序部分扩大一位;
4.重复以上步骤,直到所有元素都插入完毕。
特点:
1.简单直观,易于实现。
2.在数据基本有序时表现较好。
3.适合单链表排序,不太适合大量数据的排序。
代码实现:
public static void insertsort(int[] array) { for (int i = 1; i < array.length; i++) { int tmp = array[i]; int j = i-1; for (; j > 0; j--) { if(array[j] > tmp) { array[j+1] = array[j]; }else { //array[j+1] = tmp; break; } } array[j+1] = tmp; } }
5.希尔排序
时间复杂度:o(n^1.3) - o(n^1.5)
空间复杂度:o(1)
稳定性:不稳定
基本思想:
希尔排序通过将待排序数组的元素按一定的间隔(称为“步长”或“增量”)进行分组,对每组内的元素进行插入排序。随着排序逐步进行,步长逐渐缩小,直到最后步长为1,即对整个数组进行一次插入排序,从而实现整体排序。
基本步骤:
1.选择一个初始增量(如数组长度的一半);
2.将数组元素按增量分组,对每组进行插入排序;
3.缩小增量;
4.重复上述步骤,直到增量为1;
5.最后一次插入排序完成,数组即为有序。
特点:
1.改善了简单插入排序在大规模数组中的效率,实现简单,性能优于简单插入排序,对部分预排序的数组效果良好。
2.在最坏情况下性能仍不如归并排序或快速排序,关键在于选择合适的增量序列。
代码实现:
public static void shellsort(int[] array) { int gap = array.length; while (gap > 1) { gap = gap / 2; shell(array,gap); } } public static void shell(int[] array,int gap) { for (int i = gap; i < array.length; i++) { int tmp = array[i]; int j = i-gap; for (; j >= 0; j -= gap) { if(array[j] > tmp) { array[j+gap] = array[j]; }else { break; } } array[j+gap] = tmp; } }
6.归并排序
时间复杂度:o(n*logn)
空间复杂度:o(1)
稳定性:不稳定
基本思想:
归并排序是一种经典的分治算法,其基本思想是将待排序的数组分成两个子数组,分别对这两个子数组递归排序,然后再将排序好的子数组合并成一个有序数组。
基本步骤:
1.分解:将数组不断二分,直到每个子数组只有一个元素(它本身是有序的);
2.解决:递归排序左边和右边两个子数组;
3.合并:将两个有序的子数组合并成一个有序数组。
特点:
适合处理大数据,尤其是链表或者外部存储时表现优越。
代码实现:
private static void mergesortchild(int[] array,int left,int right) { if(left >= right) { return; } int mid = (left+right)/2; mergesortchild(array,left,mid); mergesortchild(array,mid+1,right); merge(array,left,mid,right); } private static void merge(int[] array, int left, int mid, int right) { int[] tmp = new int[right-left+1]; int k = 0; while (left <= mid && mid+1 <= right) { if(array[left] <= array[mid+1]) { tmp[k++] = array[left++]; }else { tmp[k++] = array[mid++]; } } while (left <= mid) { tmp[k++] = array[left++]; } while (mid+1 <= right) { tmp[k++] = array[mid++]; } for (int i = 0; i < tmp.length; i++) { array[i+left] = tmp[i]; } }
7.快速排序
时间复杂度:最好情况:o(n*logn),最坏情况:o(n^2)
空间复杂度:最好情况:o(logn),最坏情况:o(n)
稳定性:不稳定
基本思想:
一种非常高效且常用的排序算法,由英国计算机科学家托尼·霍尔于1960年提出。它的核心思想是分治策略,通过递归不断将数组划分为更小的部分,最终实现排序。
基本步骤:
1.选择一个元素作为基准(常用第一个、最后一个或随机选取);
2.通过划分实现:将数组调整为左边的元素都较小,右边的较大;
3.递归地对左右子数组排序,直到子数组长度为1或0。
特点:
1.实现简单,排序快,不占用额外空间;
2.最佳情况下表现不佳。
代码实现:
有两种方法实现快速排序:
①将区间按照基准值划分成左右两个部分,右边的数据与左边比较,如果右边数据小于左边,则两个数据互换位置。
private static void quick(int[] array,int start,int end) { if(start >= end) { return; } int par = parttion(array,start,end); quick(array,start,par-1); quick(array,par+1,end); } private static int parttion(int[] array, int low, int high) { int p = array[low]; int i = low; while (low < high) { while (low < high && array[high] >= p) { high--; } while (low < high && array[low] <= p) { low++; } swap(array, low, high); } swap(array,i,low); return low; } private static void swap(int[] array,int i,int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; }
②将第一个数据存放在临时变量tmp 中,形成一个坑位,然后进行右边与tmp进行比较小于tmp就与tmp位置交换,同理左边也是,与tmp进行比较,最好将空出来的坑位给tmp。
private static void quick(int[] array,int start,int end) { if(start >= end) { return; } int par = parttion(array,start,end); quick(array,start,par-1); quick(array,par+1,end); } private static int parttion(int[] array, int low, int high) { int tmp = array[low]; while (low < high) { while (low < high && array[high] >= tmp) { high--; } array[low] = array[high]; while (low < high && array[low] <= tmp) { low++; } array[high] = array[low]; } array[low] = tmp; return low; }
快速排序的优化:
三数取中:在每次划分时,从待排序数据的第一个元素、最后一个元素和中间元素中,选择中间值(即三者的中位数)作为枢轴(基准值)。
基本步骤:
1.取待排序数组的左端、右端和中间元素;
2.求这三个元素的中位数(中值);
3.将该中值作为枢轴放置到合适位置(通常是数组的最后或特定位置),然后进行快排的划分操作。
特点:
1.避免在已排序或接近有序的数组中表现差的情况(如全局排序或逆序数组),降低退化为o(n²)的可能性。
2.提高划分的平衡性,从而缩短递归深度,加快排序速度。
private static void quick(int[] array,int start,int end) { if(start >= end) { return; } int index = threemid(array,start,end); swap(array,start,index); int par = parttion(array,start,end); quick(array,start,par-1); quick(array,par+1,end); } private static int threemid(int[] array,int low, int high) { int mid = (low+high)/2; if(array[low] < array[high]) { if(array[mid] < array[low]) { return low; }else if(array[mid] > array[high]) { return high; }else { return mid; } }else{ if(array[mid] < array[high]) { return high; }else if(array[mid] > array[low]) { return low; }else { return mid; } } } private static int parttion(int[] array, int low, int high) { int tmp = array[low]; while (low < high) { while (low < high && array[high] >= tmp) { high--; } array[low] = array[high]; while (low < high && array[low] <= tmp) { low++; } array[high] = array[low]; } array[low] = tmp; return low; }
总结
到此这篇关于java中常用排序方法有哪些的文章就介绍到这了,更多相关java常用排序方法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论