基于比较的七种排序算法的总结

1.冒泡排序

最好时间复杂度:O(n) ——有序
最坏时间复杂度:O(n^2) ——逆序
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

思想:遍历数组,从数组第一个数开始,依次比较相邻两个数的大小,将大的数交换到后面,经过一轮冒泡后最大的数就在数组的最后,再从头开始,对最大数(已排好序的数)之前的数进行冒泡,重复。
代码:

    public static void bubbleSort(long[] arr) {
        for(int i = 0; i < arr.length; i++) {
            //无序:[0,arr.length - i)
            //有序:[arr.length - i,arr.length)
            for(int j = 0; j < arr.length - i - 1; j++) {
                if(arr[j] > arr[j + 1]) {
                    long t = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = t;
                }
            }
        }
    }

优化:

    public static void bubbleSort(long[] arr) {
        for(int i = 0; i < arr.length; i++) {
        //用isSort来记录是否发生了交换
            boolean isSort = true;//假设数组有序
            
            //无序:[0,arr.length - i)
            //有序:[arr.length - i,arr.length)
            for(int j = 0; j < arr.length - i - 1; j++) {
                if(arr[j] > arr[j + 1]) {
                    long t = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = t;
                    isSort = false;//发生了交换
                }
            }
            if(isSort) {
                break;
            }
        }
    }

2.插入排序(对区间较小的数排序)

最好时间复杂度:O(n) (数据有序)
最坏时间复杂度:O(n^2) (数据逆序)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

思想:
分为[有序区间,无序区间],每次从无序区间中的数中选出一个,依次与有序区间的数进行比较,选择合适位置插入
代码:

    public static void insertSort(long[] arr) {
        //数据一共有arr.length个
        for(int i = 0; i < arr.length - 1; i++) {
            //分为两个区间
            //有序:[0,i]
            //无序:[i + 1,arr.length)
            //抓出来[i + 1]
            long key = arr[i + 1];//无序区间中的数
            int j ;
            for(j = i; j >= 0; j--) {
                if(key < arr[j]) {
                    arr[j + 1] = arr[j];
                } else {
                    break;
                }
            }
            arr[j + 1] = key;
        }
    }

3.选择排序

最好时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
思想:遍历数组,每次从无序区间选择最大的一个数与有序区间最前面的数进行交换
代码:

    public static void selectSort(long[] arr) {
        for(int i = 0; i < arr.length; i++) {
            //有序:[arr.length - i,arr.length)
            //无序:[0,arr.length - i)
            int maxIndex = 0;//最大数的下标
            for(int j = 0; j < arr.length - i;j++) {
                if(arr[maxIndex] < arr[j]) {
                    maxIndex = j;
                }
            }
            long t = arr[maxIndex];
            arr[maxIndex] = arr[arr.length - i - 1];
            arr[arr.length - i - 1] = t;
        }
    }

4.希尔排序

最好时间复杂度:O(n)
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定

思想:定一个整数gap,把数组分为若干相等的组,再对每个组内的数据排序,再将gap = gap/2,对其进行排序,直到gap = 1
代码:

    public static void shallSort(long[] arr) {
        int gap = arr.length / 2;
        while(true) {
            inserSortGap(arr,gap);
            if(gap == 1) {
                break;
            }
            gap = gap / 2;
        }
    }

    public static void inserSortGap(long[] arr,int gap) {
        for(int i = gap; i < arr.length; i++) {
            long key = arr[i];
            int j = 0;
            for(j = i - gap; j >= 0; j = j - gap) {
                if(key < arr[j]) {
                    arr[j + gap] = arr[j];
                } else {
                    break;
                }
            }
            arr[j + gap] = key;
        }
    }

5.堆排序

堆排序:基于选择的排序
注意:升序建大堆,降序建小堆

无最好与最坏时间复杂度之分
平均时间复杂度:O(n*log(n))
空间复杂度:O(1)
稳定性:不稳定

代码:


    public static void heapSort(long[] arr){
        creatHeap(arr,arr.length);//建大堆
        //选择过程,arr.length - 1
        for(int i = 0; i < arr.length - 1;i++) {
            //无序:[0,arr.length - i)
            swap(arr,0,arr.length - i- 1) ;
            //交换0号下标和无序区间最后一个
            //交换后无序:[0,arr.length - i - 1)
            //在进行向下调整
            adjustDown(arr,arr.length - i - 1,0);
        }
    }
    //建堆(大堆)
    public static void creatHeap(long[] arr,int size) {
        for(int i = (size - 2)/2; i >= 0; i--) {
            adjustDown(arr,size,i);
        }
    }
    //向下调整
    public static void adjustDown(long[] arr,int size,int index) {
        while(2*index + 1 < size) {
            int maxIndex = 2*index + 1;
            if(maxIndex + 1 < size && arr[maxIndex + 1] > arr[maxIndex]) {
                maxIndex++;
            }
            if(arr[index] >= arr[maxIndex]) {
                break;
            }
            swap(arr,index,maxIndex);
            index = maxIndex;
        }
    }
    //交换
    public static void swap(long[] arr,int i,int j) {
        long t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

6.快速排序

最好时间复杂度:O(nlog(n))
最坏时间复杂度:O(n^2)
平均时间复杂度:O(n
log(n))
空间复杂度:O(log(n))[最好和平均]~O(n)
稳定性:不稳定

思想:
从待排区间选择一个数(这里选最左边的),将比该数小的放到前面,大的放后面,不断重复,直到小区间有序(size 等于0或1)

快排的优化:
1.挖坑法
2.快排在数量较小的时候不是最快(当区间内的个数低于某个阈值时(16)使用插排)
3.优化选择特殊的数字
随机选
选几个数,选择最中间的值
把相等的值特殊处理

public static void quickSort(long[] arr) {
        quickSortInternal(arr,0,arr.length - 1);
    }
    private static void quickSortInternal(long[] arr,int lowIndex,int highIndex) {
        //区间:[lowIndex,highIndex]
        int size = highIndex - lowIndex + 1;
        if(size <= 1) {
            return;
        }
        //每次选择最左边的数执行partition操作
        //keyIndex是经过partition后选出来的数最终所在下标
        int keyIndex = partition(arr,lowIndex,highIndex);
        //分别对左右区间进行相同处理(递归)
        quickSortInternal(arr,lowIndex,highIndex - 1);
        quickSortInternal(arr,keyIndex + 1,highIndex);
    }

    private static int partition(long[] arr,int lowIndex,int highIndex){
        //区间:[lowIndex,highIndex]
        //选择arr[lowIndex]作为基准值
        //需要遍历整个区间和选出来的数进行比较,小于等于的在左边,让大于等于的在右边(无顺序要求)
        return partitionHover(arr,lowIndex,highIndex);
        //return partition挖坑法(arr,lowIndex,highIndex);//较快
        //return partition前后遍历法(arr,lowIndex,highIndex);
    }
    //法一
    private static int partitionHover(long[] arr, int lowIndex, int highIndex) {
        int leftIndex = lowIndex;
        int rightIndex = highIndex;
        long key = arr[lowIndex];
        // 选择最左边的数,让右边的先走
        // 停止条件:leftIndex == rightIndex
        // 循环条件:leftIndex < rightIndex
        while (leftIndex < rightIndex) {
            while (leftIndex < rightIndex && arr[rightIndex] >= key) {
                rightIndex--;
            }
            //[rightIndex] 遇到了小的
            while (leftIndex < rightIndex && arr[leftIndex] <= key) {
                leftIndex++;
            }
            //[leftIndex] 遇到了大的
            swap(arr, leftIndex, rightIndex);//交换位置
        }
        swap(arr, lowIndex, leftIndex);
        return leftIndex;
    }

    //法二 (较重要)
    private static int partition挖坑法(long[] arr,int lowIndex,int highIndex) {
        int leftIndex = lowIndex;
        int rightIndex = highIndex;
        long key = arr[lowIndex];
        while(leftIndex < rightIndex) {
            while(leftIndex < rightIndex && arr[rightIndex] >= key) {
                rightIndex--;
            }
            arr[leftIndex] = arr[rightIndex];
            while(leftIndex < rightIndex && arr[leftIndex] <= key) {
                leftIndex++;
            }
            arr[rightIndex] = arr[leftIndex];
        }
        arr[leftIndex] = key;
        return leftIndex;

    }
    //法三
    private static int partition前后遍历法(long[] arr,int lowIndex,int highIndex) {
        int separateIndex = lowIndex + 1; //分割
        //保证
        for(int i = lowIndex + 1;i <= highIndex; i++) {
            if(arr[i] < arr[lowIndex]) {//小于时交换位置
                swap(arr,i,separateIndex);
                separateIndex++;
            }
        }
        swap(arr,lowIndex,separateIndex - 1);
        return separateIndex - 1;
    }

7.归并排序

最好时间复杂度:O(nlog(n))
最坏时间复杂度:O(n
log(n))
平均时间复杂度:O(n*log(n))
空间复杂度:O(n)
稳定性:稳定

思想:对一个数组平均分成两份,分别对两个区间进行相同排序处理,当区间内数组的个数小于等于1是停止,最后合并有序区间

public static void mergeSort(long[] arr) {
        mergeSortInternal(arr, 0, arr.length);
    }
    // 区间范围左闭右开
    // arr[lowIndex, highIndex)
    private static void mergeSortInternal(long[] arr, int lowIndex, int highIndex) {
        int size = highIndex - lowIndex;//数组个数
        if (size <= 1) {
            return;
        }
        int middleIndex = (lowIndex + highIndex) / 2;
        // 左区间:[lowIndex, middleIndex)
        // 右区间:[middleIndex, highIndex)
        mergeSortInternal(arr, lowIndex, middleIndex);//对左边区间进行排序
        mergeSortInternal(arr, middleIndex, highIndex);//对右边区间进行排序

        // 左右两个区间都有序了
        合并两个有序区间(arr, lowIndex, middleIndex, highIndex);//合并有序区间
    }

    private static void 合并两个有序区间(long[] arr, int lowIndex, int middleIndex, int highIndex) {
        int size = highIndex - lowIndex;
        long[] extraArray = new long[size];

        int leftIndex = lowIndex;
        int rightIndex = middleIndex;
        int extraIndex = 0;

        // 两个队伍都有人
        while (leftIndex < middleIndex && rightIndex < highIndex) {
            if (arr[leftIndex] <= arr[rightIndex]) {
                extraArray[extraIndex] = arr[leftIndex];
                extraIndex++;
                leftIndex++;
            } else {
                extraArray[extraIndex] = arr[rightIndex];
                extraIndex++;
                rightIndex++;
            }
        }
        // 有个队伍没有人
        if (leftIndex < middleIndex) {
            while (leftIndex < middleIndex) {
                extraArray[extraIndex++] = arr[leftIndex++];
            }
        } else {
            while (rightIndex < highIndex) {
                extraArray[extraIndex++] = arr[rightIndex++];
            }
        }
        // 最后把数据从新数组统一搬回去
        // 新数组 [0, size)
        // 搬回去的下标范围 [lowIndex, highIndex)
        for (int i = 0; i < size; i++) {
            arr[i + lowIndex] = extraArray[i];
        }

海量数据的排序问题 ——归并排序(多路归并)

海量数据的特点:内存中存不下,需借助硬盘
步骤:

  1. 先将数据平均分为n份(每份的大小较小)
  2. 分别对每份数据进行排序(采用学过的算法)
  3. 即可得到n个分别有序的数据文件
  4. 借助内存,进行n个有序数据文件的合并
    (将每份文件中最小的数放入内存中【实践中不止一个】,再将最小的数选出来,尾插到最后的有序文件中)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值