原地排序算法,不使用额外空间
- 冒泡排序
每次循环将最大通过相邻交换换到最后,做N-1次循环,第i次循环的要交换的数下标范围是[0,N-1-i],j的范围是[0,N-2-i]。O(n2)
void bubbleSort(int[] arr){// 循环次数N-1for(int i=0;i<arr.length-1;i++){// 交换的靠前元素下标 jfor(int j=0;j<arr.length-1-i;j++){if(arr[j]>arr[j+1]) swap(arr,j,j+1);}}}void swap(int[] nums, int i, int j) {// 交换 nums[i] 和 nums[j]int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
- 插入排序
类似于扑克牌整牌,分为有序部分和无序部分,每次循环的目的是使无序部分的第一个排到有序部分的合适位置,方式是不断向左交换。循环次数为N-1次。O(n2)
void insertSort(int[] arr){// i为无序部分的第一个数的下标for(int i=1;i<arr.length;i++){// 从i开始往前两两比较,j为交换的靠前元素的下标,初值是有序部分的最后一个数的下标i-1for(int j=i-1;j>=0;j--){if(arr[j]>arr[j+1]) swap(arr,j,j+1);}}}void swap(int[] nums, int i, int j) {// 交换 nums[i] 和 nums[j]int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
- 选择排序
分为有序部分和无序部分,每次循环的目的是使第i次循环找到第i个小的数放在下标i位置,循环N-1次。找第i小的手段是,循环找到无序部分,比原本下标 i 位置的数小的数中最小的位置,然后交换;如果没找到,则说明原本 i 下标就放着第 i 小的数,无需操作。O(n2)
void selectSort(int[] arr){// i为找到数后应该放的下标for(int i=0;i<arr.length-1;i++){int flag=arr[i];int pos=i;// j为无序部分的下标for(int j=i+1;j<arr.length;j++){if(arr[j]<flag) {//更新最小值与位置记录pos=j;flag=arr[j];}}// 判断pos是否已更新if(pos!=i) swap(arr,i,pos);}}void swap(int[] nums, int i, int j) {// 交换 nums[i] 和 nums[j]int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
- 堆排序
分为有序部分和无序部分, i 次循环时,末尾的 i 位是有序部分,前面n-i 位通过维护组成一个大顶堆(以升序为例),然后将堆顶与 无序部分的最后一位交换,交换后,末尾的 i+1位形成有序部分,前面位由于交换而失衡,在下一次循环中重新维护大顶堆。O(nlogn)
package sortdemo;import java.util.Arrays;/*** Created by chengxiao on 2016/12/17.* 堆排序demo*/public class HeapSort {public static void main(String []args){int []arr = {9,8,7,6,5,4,3,2,1};sort(arr);System.out.println(Arrays.toString(arr));}public static void sort(int []arr){//1.构建大顶堆for(int i=arr.length/2-1;i>=0;i--){//从第一个非叶子结点从下至上,从右至左调整结构adjustHeap(arr,i,arr.length);}//2.交换堆顶元素与末尾元素,之后调整堆结构for(int j=arr.length-1;j>0;j--){swap(arr,0,j);//将堆顶元素与末尾元素进行交换adjustHeap(arr,0,j);//重新对堆进行调整}}/*** 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)* @param arr* @param i* @param length*/public static void adjustHeap(int []arr,int i,int length){int temp = arr[i];//先取出当前元素ifor(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始// 使k指向左子节点与右子节点中的较大值if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点k++;}if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)arr[i] = arr[k];i = k;}else{break;}}arr[i] = temp;//将temp值放到最终的位置}/*** 交换元素* @param arr* @param a* @param b*/public static void swap(int []arr,int a ,int b){int temp=arr[a];arr[a] = arr[b];arr[b] = temp;}}
非原地算法
- 快速排序
递归。每次递归按照标志位划分为大于标志位和小于标志位的,具体是将两边的大小元素交换实现,最后把标志位与中间i=j的位置交换。O(logN), O(logN)。
注意,标准快排的具体执行与该代码执行顺序有所不同,每次找到比标志位小的 a[j] 或 比标志位大的 a[i],就会将标志位与其交换。
void quickSort(int[] nums, int l, int r) {// 子数组长度为 1 时终止递归if (l >= r) return;// 哨兵划分操作// 以 nums[l] 作为基准数int i = l, j = r;while (i < j) {// 先从右向左找到arr[j]<flag为止while (i < j && nums[j] >= nums[l]) j--;// 再从左向右找到arr[i]>flag为止while (i < j && nums[i] <= nums[l]) i++;swap(nums, i, j);}swap(nums, i, l);// 递归左(右)子数组执行哨兵划分quickSort(nums, l, i - 1);quickSort(nums, i + 1, r);}void swap(int[] nums, int i, int j) {// 交换 nums[i] 和 nums[j]int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}// 调用int[] nums = { 4, 1, 3, 2, 5 };quickSort(nums, 0, nums.length - 1);
- 归并排序
递归,分治。注意合并时需要复制原数据作为判定依据。O(logN),O(N)
void mergeSort(int[] nums, int start, int stop) {// 终止递归if (start >= stop) return;// 对半分int mid=(start+stop)/2;mergeSort(nums, start, mid);mergeSort(nums, mid+1, stop);// 合并// 由于原数组在合并时会改变,因此复制每次递归中的数组,作为合并的判据int[] temp=new int[stop-start+1];for(int i=0;i<temp.length;i++){temp[i]=nums[start+i];}// 合并[start,mid] 与[mid+1,stop]// i,j 为 待合并数组在temp中的下标int i=start-start, j=mid+1-start;// k为合并所在位置在原数组的下标for(int k=start;k<=stop;k++){// 考虑 i 越界if(i==mid-start+1){nums[k]=temp[j++];// j 越界 或 i位置数更小}else if(j==stop-start+1 || temp[i]<temp[j]){nums[k]=temp[i++];// j位置数更小}else{nums[k]=temp[j++];}}}
