一般很少自己写算法去进行排序,实现相应的接口,指定排序规则,丰富的函数就帮助完成了排序需求,但是,掌握几门排序算法还是大有必要的。
冒泡
交换类排序,对相邻元素进行比较,如果第一个比第二个大(或者 小),进行交换。
从第一对起,对每一对元素做相同比较操作,依次进行,这样末尾应该是最 大 (或者 小)的数字。
重复上述步骤,除了最后一个。
两个for循环,时间复杂度T(n^2),稳定,适合少量数据
/**
* 冒泡排序
*/
public class Bubbling {
public static void bubbling(int[] arr){
for (int k = 0 ; k < arr.length - 1 ; k ++) { // 如果有5个数字,只需要进行4轮
for (int i = 0; i < arr.length - k - 1; i++) { //每一轮的比较,要排除上一轮最大的数据位置所以 arr.length - k ,为什么还要-1? 如果3个数据进行,只需要比较2对即可,所以3个数据一轮只进行2次比较
if (arr[i] > arr[i+1]) {
int tmp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = tmp;
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void main(String[] args) {
int [] arr = {4,2,8,11,1,4,3};
bubbling(arr);
}
}
选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
初始状态:无序区为R[1..n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;n-1趟结束,数组有序。
两个for循环,时间复杂度T(n^2),不稳定,适合少量数据
public static void selectSort(int[] arr){
int minIndex = 0;
for (int srcIndex = 0 ; srcIndex < arr.length - 1 ; srcIndex ++){ //比较目的地index
minIndex = srcIndex;
for (int nextCompar = srcIndex + 1;nextCompar < arr.length;nextCompar++){ //比较目的地下一个元素
if(arr[minIndex] > arr[nextCompar]){
minIndex = nextCompar;
}
}
int tmp = arr[srcIndex] ;
arr[srcIndex] = arr[minIndex];
arr[minIndex] = tmp;
}
}
public static void main(String[] args) {
int[] a = new int[] { 4, 1, 9, 3, 12, 40, 32, 2 };
selectSort(a);
System.out.println(Arrays.toString(a));
}
快排
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
时间复杂度T(nlogn), 不稳定,大多数情况下适合
/**
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
*/
public class QuickSort {
public static void quickSort(int arr[],int low,int high){
//关键字
int start = low;//前端位置
int end = high; //尾端位置
int key = arr[low];
while (start < end){
//后端
while (start < end && key <= arr[end])
end--;
if(key > arr[end]){
int temp = arr[end];
arr[end] = arr[start];
arr[start] = temp;
}
//前端比较
while (start < end && key >= arr[start])
start++;
//如果发现key小于前端的,进行交换
if(key < arr[start]){
int temp = arr[end];
arr[end] = arr[start];
arr[start] = temp;
}
if(low < start)
quickSort(arr,low,start - 1);
if(high > end)
quickSort(arr,end + 1,high);
}
}
public static void main(String[] args) {
int[] a = new int[] { 4, 1, 9, 3, 12, 40, 32, 2 };
quickSort(a,0,a.length-1);
System.out.println(Arrays.toString(a));
}
}
插入排
插入排序是一种较为简单的排序算法,它的基本思想是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度为O(n^2),稳定,数据量小时使用。并且大部分已经被排序。
public class InsertSort {
public static void insertSort(int arr [] ){
int i ,j = 0;
for (i = 1 ; i < arr.length ; i ++){
int key = arr[i];
for (j = i - 1 ; j >= 0 ;j-- ){
if(arr[j] > key){
arr[j+1] = arr[j] ;
}else{
break;
}
}
arr[j+1] = key;
}
}
public static void main(String[] args) {
int[] a = new int[] { 4, 1, 9, 3, 12, 40, 32, 2 };
insertSort(a);
System.out.println(Arrays.toString(a));
}
}
归并排序
“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表,归并排序和快排一样也采用的是分治的思想,它的基本原理是通过对若干个有序结点序列的合并为一个有序序列来实现排序的。
它的效率是比较高的的排序方法,他通常应用于数据量比较大的场合
时间复杂度T(nlogn),稳定,大多数情况下适合
上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
/**
* 归并排序 分 治,二路归并
*/
public class MergerSort {
public static void mergerSort(int[] arr, int left,int right,int tmp[]){
if(left < right){
int mid = (left+right)/2;
mergerSort(arr,left,mid,tmp);
mergerSort(arr,mid+1,right,tmp);
merger(arr,left,mid,right,tmp);
}
}
public static void merger(int[]arr ,int left,int mid,int right,int[]tmp){
//临时变量
int i = left;
int j = mid + 1;
int tp = 0;
//排序
while(i<=mid && j<=right){
if(arr[i] > arr[j]){
tmp[tp++] = arr[j++];
}else{
tmp[tp++] = arr[i++];
}
}
//填充左侧
while(i <=mid){
tmp[tp++] = arr[i++];
}
//填充右侧
while(j <= right){
tmp[tp++] = arr[j++];
}
tp = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = tmp[tp++];
}
}
public static void main(String[] args) {
int []arr = { 4, 1, 9, 3, 12, 40, 32, 2 };
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
mergerSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
}
应用场景
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
若要求排序稳定,则可选用归并排序。
但前面介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子序列,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。