原地排序算法,不使用额外空间
- 冒泡排序
每次循环将最大通过相邻交换换到最后,做N-1次循环,第i次循环的要交换的数下标范围是[0,N-1-i],j的范围是[0,N-2-i]。O(n2)
void bubbleSort(int[] arr){
// 循环次数N-1
for(int i=0;i<arr.length-1;i++){
// 交换的靠前元素下标 j
for(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-1
for(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];//先取出当前元素i
for(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++];
}
}
}