浅谈内部排序


内部排序(升序为例)


1. 冒泡排序
每次可以确定出一个元素的最终位置。

public static void bubbleSort(int[] arr){
    int len = arr.length;
    for(int i = 0; i < len; i++){
        for(int j = 0; j < len - i - 1; j++){
            if(arr[j] > arr[j + 1]){
                swap(arr, j, j + 1);
            }
        }
    }
}
//交换函数
private static void swap(int[] arr, int j, int i) {
    int tmp = arr[j];
    arr[j] = arr[i];
    arr[i] = tmp;
}

时间复杂度O(n^2),空间复杂度O(1),是稳定的排序算法。但是当数组已经有序的时候,这样显然是没有比较的必要,修改如下:

public static void bubbleSort(int[] arr){
    int len = arr.length;
    for(int i = 0; i < len; i++){
        boolean flag = true;
        for(int j = 0; j < len - i - 1; j++){
            if(arr[j] > arr[j + 1]){
                swap(arr, j, j + 1);
                flag = false;
            }
        }
        if(flag == true){
            break;
        }
    }
}

增加一个标志flag,如果一次都没有交换过,flag == true,直接跳出循环,此时时间复杂度为O(n),空间复杂度为O(1)。
2. 快速排序
快速排序的基本思路是:(1)确定要排序的数组区间[left, right];(2)找到一个基准值pivotIndex(三种方法);(3)遍历整个区间,并将区间分为三部分,比基准值小的区间[left, povitIndex - 1],比基准值大的区间[povitIndex + 1, right];(4)分治。
选择基准值的三种方法:(1)最边上的数,即数组最左边arr[left]或者数组最右边arr[right]的数,但是当数组已经有序或者逆序时,这样选择基准值,都是最坏的情况;(2)随机法:在数组中随机选择一个数;(3)三数取中法:如9,8,7,6,5,就在9,7,5中选择7作为基准值,然后将基准值交换到边上就行。
关于第三步中,如何实现比基准值小的数在区间[left, povitIndex - 1]中,比基准值大的数在区间[povitIndex + 1, right]中,有三种方法,分别是:(1)Hover法;(2)挖坑法;(3)前后下标法。具体实现看代码:此处基准值均选择最右边的值arr[right],所以应该先走左边begin,反之先走右边right,即基准值与开始走的位置相反,否则会出现数组中有些数不会被比较的情况。

public static void quickSort(int[] arr){
    int len = arr.length;
    quickSortInner(arr, 0, len - 1);
}
private static void quickSortInner(int[] arr, int left, int right) {
    //区间中没有数或者区间不存在
    if(left >= right){
        return ;
    }
    //如何确保小数在右,大数在左,三种方法
    int pivotIndex = parition(arr, left, right);
    //分治,两个小区间,一个存放大于基准值的数,一个存放小于基准值的数
    quickSortInner(arr, left, pivotIndex - 1);
    quickSortInner(arr, pivotIndex + 1, right);
}
//Hover法
private static int parition(int[] arr, int left, int right) {
    int begin = left;
    int end = right;
    //选择最右边的数arr[right]作为基准值
    int pivot = arr[right];
    while(begin < end){
        while(begin < end && arr[begin] <= pivot){
            begin++;
        }
        while(begin < end && arr[end] >= pivot){
            end--;
        }
        //不满足条件,交换begin和end的值
        swap(arr, begin, end);
    }
    //最后将right与begin指向的值交换
    swap(arr,begin,right);
    //begin所指向的数就是基准值
    return begin;
}
private static void swap(int[] arr, int begin, int end) {
    int tmp = arr[begin];
    arr[begin] = arr[end];
    arr[end] = tmp;
}
//挖坑法
private static int parition1(int[] arr, int left, int right) {
    int begin = left;
    int end = right;
    //选择右边的数作为基准值,即最右边有一个坑
    int pivot = arr[right];
    while(begin < end){
        while(begin < end && arr[begin] <= pivot){
            begin++;
        }
        //当不满足条件时,把begin的值赋给end填坑,给begin处挖了一个坑
        arr[end] = arr[begin];
        while(begin < end && arr[end] >= pivot){
            end--;
        }
        arr[begin] = arr[end];
    }
    //最终基准值赋给begin处
    arr[begin] = pivot;
    return begin;
}
//前后指针法
private static int parition2(int[] arr, int left, int right) {
    //tmp后指针
    int tmp = left;
    //i前指针
    for(int i = left; i < right; i++){
        //相当于把arr[right]作为基准值
        if(arr[i] < arr[right]){
            //交换使得小于基准值的数在前,大于基准值的数在后
            swap(arr, i, tmp);
            tmp++;
        }
    }
    //最后交换,得到基准值tmp
    swap(arr, tmp, right);
    return tmp;
}

快排时间复杂度O(n*log(n)),空间复杂度O(log(n)),是一种不稳定的排序算法。
3. 直接插入排序
基本思想是:i之前的区间已经有序,需要找到i对应元素tmp的正确位置,与前面的数作比较并将比较过的元素后移一位,直至找到正确的位置并赋值。

public static void insertSort(int[] arr){
    //有序区间[0, i),无序区间[i, len)
    int len = arr.length;
    for(int i = 0; i < len; i++){
        //tmp为将要移动的元素
        int tmp = arr[i];
        int j = i - 1;
        //i之前都是有序的,若tmp < arr[j],将比较过的元素后移一位
        for(; j >= 0 && tmp < arr[j]; j--){
            arr[j + 1] = arr[j];
        }
        //找到tmp的正确位置并赋值
        arr[j + 1] = tmp;
    }
}

时间复杂度为O(n^2),空间复杂度为O(1),是稳定的排序算法。
4. 希尔排序
希尔排序是对直接插入排序的一种改进(分组插排),先进行一次预排序,很快让数据变得基本有序;然后进行插排。

public static void shellSort(int[] arr){
    int gap = arr.length;
    while(true){
        gap = (gap / 3) + 1;
        insertSortWithGap(arr, gap);
        //当步长 == 1时,说明整个区间已经有序
        if(gap == 1){
            break;
        }
    }
}
private static void insertSortWithGap(int[] arr, int gap) {
    int len = arr.length;
    for(int i = 0; i < len; i++){
        int tmp = arr[i];
        int j = i - gap;
        for(; j >= 0 && tmp < arr[j]; j = j - gap){
            arr[j + gap] = arr[j];
        }
        arr[j + gap] = tmp;
    }
}

空间复杂度为O(1),是不稳定的排序算法。
5. 简单选择排序
基本思想是:在无序区间中找到最大数(或最小的数),放在无序区间的后面,每循环一次,记录最大数的位置,并不直接交换,而是最后进行数据的交换,直至整个区间有序。

public static void selectSort(int[] arr){
    int len = arr.length;
    //每次选择最大的数放在无序区间的最后
    //[0, len - i)是无序区间,[len - i, len)是有序区间
    for(int i = 0; i < len; i++){
        //假设最大数下标为0
        int max = 0;
        //在无序区间中找出最大数
        for(int j = 1; j < len - i; j++){
            if(arr[max] < arr[j]){
                max = j;
            }
        }
        //并将该最大数放到区间末尾
        int tmp = arr[max];
        arr[max] = arr[len - i - 1];
        arr[len - i - 1] = tmp;
    }
}

时间复杂度为O(n^2),空间复杂度为O(1),是稳定的排序算法。
6. 堆排序

public static void heapSort(int[] arr){
    int len = arr.length;
    //建堆过程
    for(int i = (len - 2) / 2; i >= 0; i--){
        bigHeapify(arr, len, i);
    }
    //选择排序
    for(int j = 0; j < len; j++){
        int tmp = arr[0];
        arr[0] = arr[len - j - 1];
        arr[len - j - 1] = tmp;
        bigHeapify(arr, len - j - 1, 0);
    }
}
//堆调整
private static void bigHeapify(int[] arr, int size, int index) {
    int max = 2 * index + 1;
    while(max < size){
        //找最大节点
        if(max + 1 < size && arr[max + 1] > arr[max]){
            max += 1;
        }
        if(arr[index] >= arr[max]){
            break;
        }
        int tmp = arr[index];
        arr[index] = arr[max];
        arr[max] = tmp;
        index = max;
        max = 2 * index + 1;
    }
}

时间复杂度为O(nlogn),空间复杂度为O(1),是不稳定的排序算法。
7. 归并排序

//1. 平均切分
 //2. 分治算法处理两个小区间
public static void mergeSortNoDiGui(int[] array){
    int[] extra = new int[array.length];
    for(int i = 1; i < array.length; i *= 2){
        for(int j = 0; j < array.length; j += 2 * i){
            int low = j;
            int mid = low + i;
            if(mid >= array.length){
                continue;
            }
            int high = mid + i;
            if(high > array.length){
                high = array.length;
            }
            merge(array,low,mid,high,extra);
        }
    }
}
private static void merge(int[] array, int low, int mid, int high, int[] extra) {
    //遍历array:[low, mid)
    int i = low;
    //遍历array:[mid, high)
    int j = mid;
    //遍历:extra
    int x = 0;
    while (i < mid && j < high){
        //优先取左边,保证稳定性
        if(array[i] <= array[j]){
            extra[x++] = array[i++];
        }else{
            extra[x++] = array[j++];
        }
    }
    //当一个区间的数被取完
    while (i < mid){
        extra[x++] = array[i++];
    }
    while (j < high){
        extra[x++] = array[j++];
    }
    //从额外空间搬移出来
    for(int k = low; k < high; k++){
        array[k] = extra[k - low];
    }
}

时间复杂度为O(nlogn),空间复杂度为O(n),是稳定的排序算法。
小结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值