二:常见排序算法
-
排序另一种分法
-
外排序:需要在内外存之间多次交换数据才能进行
-
内排序:
-
插入类排序
-
直接插入排序
-
希尔排序
-
-
选择类排序
-
简单选择排序
-
堆排序
-
-
交换类排序
-
冒泡排序
-
快速排序
-
-
归并类排序
-
归并排序
-
-
-
2.1选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
//选择排序 public class SelecetSort { public static void SelecetSort(int[] arr) { if (arr==null || arr.length<2) return; for(int i=0;i<arr.length-1;i++){//i~N-1 int minIndex=i; for(int j=i+1;j<arr.length;j++){//i~N-1上找最小值的下表 minIndex=arr[j]>arr[minIndex] ? j:minIndex; } swap(arr,i,minIndex); } } public static void swap(int arr[] ,int i, int j){ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } }
2.2冒泡排序
冒泡排序算法的运作如下:
-
比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
-
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
//冒泡排序 public class BubbleSort { public static void BubbleSort(int[] arr) { if (arr==null || arr.length<2) return; for(int i=arr.length-1;i>0;i--){ for(int j=0;j<i;j++){//比较前一个树是否比后一个数大 冒泡 最后每一轮都会将当前论最大值冒到最后面 if(arr[j]>arr[j+1]){ swap(arr,j,j+1); } } } } public static void swap(int[] arr,int i,int j){ arr[i]=arr[i]^arr[j]; arr[j]=arr[i]^arr[j]; arr[j]=arr[i]^arr[j]; }
2.3插入排序
public class InsertSort { public static void InsertSort(int [] arr ){ if(arr==null || arr.length<2) return; for (int i=0;i<arr.length;i++){ for (int j=i-1;j>=0 && arr[j]>arr[j+1];j--){ swap(arr,j,j+1); } } } public static void swap(int arr[] ,int i, int j{ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } }
2.4归并排序
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
-
重复步骤3直到某一指针到达序列尾
-
将另一序列剩下的所有元素直接复制到合并序列尾
public class MergeSort { public static void MergeSort(int[] arr) { if (arr==null || arr.length<2) return; process(arr,0,arr.length-1); } public static void process(int [] arr,int L,int R){ if (L==R) return; int mid=L+((R-L)>>1); //此处的>>是移位操作 移动一1位相当于除以2 等同于Mid=L+(R-L/2) process(arr,L,mid); process(arr,mid+1,R); merge(arr,L,mid,R); } public static void merge(int[] arr,int L, int M,int R){ int [] help=new int[R-L+1];//申请一个空间helP 进行存放合并后的数组 int i=0; int p1=L; int p2=M+1; while (p1<=M && p2<=R){ help[i++] =arr[p1]<=arr[p2] ? arr[p1++]:arr[p2++]; } while (p1<=M){ help[i++] =arr[p1++]]; } while (p1<=M && p2<=R){ help[i++] =arr[p2++]; } for (i=0;i<help.length;i++){ arr[L+i]=help[i]; } } }
2.5快速排序
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
-
从序列中挑出一个元素,作为"基准"(pivot).
-
把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
-
对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
空间复杂度最差是O(N),好情况类似于二叉树(选的基准很好)则是O(logN)
public class quickSort { 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);//等概率的随机选一个数字把他和R交换到最后一各位置 int[] p =parttition(arr,L,R);//这个数组一个为2,分别为等于区域的左边界和右边界 quickSort(arr,L,p[0]-1);//<这个数的区域 quickSort(arr,p[1]+1,R);//<这个数的区域 } } //这里默认处理的arr[L-R]之间的函数 //返回值为一个长度为2的数组 public static int [] parttition(int[] arr,int L ,int R){ int less=L-1; int more=R; while (L<more) { if (arr[L]<arr[R]){ //当前值小于划分值 swap(arr,++less,L++); } else if (arr[L]>arr[R]){//当前值 大于划分值 swap(arr,--more,L); } } return new int []{less+1,more}; } public static void swap(int arr[] ,int i, int j){ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } }
备注:
Java系统提供的Arrays.sort函数。对于基础类型,底层使用快速排序。对于非基础类型,底层使用归并排序。请问是为什么?
答:这是考虑到排序算法的稳定性。对于基础类型,相同值是无差别的,排序前后相同值的相对位置并不重要,所以选择更为高效的快速排序,尽管它是不稳定的排序算法;而对于非基础类型,排序前后相等实例的相对位置不宜改变,所以选择稳定的归并排序。
2.6堆排序
堆排序其实就是结合了堆的两个操作
import java.util.Scanner; public class Code04_Heapsort { 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);//0位置的数与堆上最后一个数交换 然后堆大小-- while (heapsize>0){ heapify(arr,0,heapsize); swap(arr,0,--heapsize); } } //某一数的下标index位置上一直与其父亲进行比较,如果比父亲大,则与之交换位置并更新下标 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; } } //某个数的idnex位置,能否向下移动 public static void heapify(int [] arr,int index ,int heapsize){ //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; if (largest==index) { //如果最大的下标就是这个数的下标,及连同这个index下标下的所有孩子节点来说 它就是最大值。跳出循环完成调整 break; } swap(arr,largest,index);//二者交换数据 index=largest;//交换下标,证明Index还未调整成功,继续向下调整 left=index*2+1; } } public static void swap(int arr[] ,int i, int j){ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } public static void printArray(int [] arr){ if (arr==null) return; for(int i=0;i<arr.length;i++){ System.out.println(arr[i]+""); } System.out.println(); } public static void main(String[] args) { Scanner scanner=null; scanner=new Scanner(System.in); System.out.print( "请输入个数: " ); int inputNum = scanner.nextInt(); if( inputNum <= 0 ) { System.out.println("输入错误"); } System.out.println( "请输入数字: " ); int arr[] = new int[inputNum]; int num = 0; int count = 0; while( count < inputNum ) { num = scanner.nextInt(); arr[count] = num; count++; } printArray(arr); heapSort(arr); printArray(arr); } }