七大排序知识点

目录

1. 🔍稳定性

2.🔍冒泡排序

3. 🔍插入排序

3.1  🚀折半插入排序

4. 🔍希尔排序

5. 🔍选择排序

6. 🔍堆排序

7. 🔍快速排序(挖坑法)

8. 🔍归并排序

 9. 🔍海量数据的排序问题


1. 🔍稳定性

两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
44 2 a1 4 63 56 a2 34  排序后 2 a1 4 34 44 63 56 a2 
相对位置没有发生变化

2.🔍冒泡排序

    public void bubbleSort(int[] arr){
        //最外层表示要比较的趟数,arr.length - 1,- 1 是因为当待排序数组只剩一个元素时,此时数组已经有序
        for (int i = 0; i < arr.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < arr.length - i - 1; j++) {
                if(arr[j] > arr[j + 1]){
                    swap(arr,j,j + 1);
                    flag = true;
                }
            }
            if(flag == false){
                //内层循环没有元素交换,整个数组有序
                break;
            }
        }
    }

时间复杂度:O(N^2)    无论是好是坏都是 O(N^2) 

空间复杂度:O(1) 

稳定

3. 🔍插入排序

整个区间被分为 有序区间 和 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
public void insertSort(int[] array) {
    for(int i = 1; i < array.length; i++) {
        int tmp = array[i];
        int j = i - 1;
        for(; j >= 0; j--) {
            if(array[j] > tmp) {
                array[j + 1] = array[i];  //将前一个数与后一个数交换位置
            } else {
                break;
            }
        }
        array[j + 1] = tmp;
    }
}

 时间复杂度:O(N^2) 

空间复杂度:O(1) 

稳定

直接插入排序,有序情况下是O(N),因为,当 array[j] > tmp时,直接break,j不会再次--。

3.1  🚀折半插入排序

在已经有序的数组中插入一个数(常用在数据量不多,区域有序的数据中)

假设现在有10000个数,如果对这组数据进行排序,使用插入排序。

10000个数据的时间复杂度:10000 * 10000 = 10000 0000

10000个数据 = 100组 * 100个 ,分100组,每一组的时间复杂度为100 * 100 = 10000

由此可以发现,分组使得时间复杂度有很大的改变。

4. 🔍希尔排序

又称缩小增量法

先选定一个整数,把待排序文件中所有记录分成 n 个组,所有距离为n 的记录分在同一组内,并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当分组等于1时,所有记录在统一组内排好序。
1. 希尔排序是对直接插入排序的优化
2. gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。

 

 

 

 

public static void shellSort(int[] array) {
    int gap = array.length;
    while(gap > 1) {
        shell(array, gap);
        gap /= 2;
    }
    shell(array, 1); //保证最后是1组,对整个数组进行排序
}
public static void shell(int[] array, int gap) {
    for(int i = 1; i < array.length; i++) {
        int tmp = array[i];
        int j = i - gap;
        for(; j >= 0; j -= gap) {
            if(array[j] > tmp) {
                array[j + 1] = array[j];
            } else {
                break;
            }
        }
        array[j + gap] = tmp;
    }
}

 时间复杂度:O(N^1.3 ~ N^1.5)

空间复杂度:O(1) 

不稳定

在比较的过程中,看是否发生跳跃式的交换,如果是跳跃式的,则就是不稳定的排序。 

5. 🔍选择排序

 每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。

public static void selectSort(int[] array) {
    for(int i = 0; i < array.length; i++) {
        for(int j = i + 1; j < array.length; j++) {
            if(array[j] < array[i]) {
                swap(array,j,i);
            }
        }
    }
}

public static void swap(int[] array, int i, int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

时间复杂度:O(N^2)

空间复杂度:O(n) 

不稳定

 直接插入排序,有序情况下是O(N),因为,当 array[j] > tmp时,直接break,j不会再次--。

选择排序来说,有序无序都是O(N^2) ,因为时间复杂度 !=运行时间。在有序的情况下,选择排序的时间更快,j++不会因此减少。

6. 🔍堆排序

 排升序要建大堆;排降序要建小堆

 调整为大根堆,最大值一定在堆顶,将其与末尾元素进行交换,此时末尾元素就位最大值,然后将剩余的n - 1 个元素重新构造称为一个堆,依次执行该步骤。

    public void heapSort(int[] arr){

        //1.将任意将数组进行原地排序
        //从最后一个非叶子结点开始,即就是最后一个叶子节点的父节点
        //最后一个叶子结点arr.length - 1 , 父节点(arr.length - 1 - 1)/2
        for (int i = (arr.length - 1 - 1) / 2; i >= 0 ; i--) {
            siftDown(arr,i,arr.length);
        }
        //依次取出堆顶元素和最后位置元素进行交换
        //最开始,待排序的数组[0......arr.length - 1],已排序的数组[]
        //第二次,待排序的数组[0......arr.length - 2],已排序的数组[arr.length - 1]
        //第三次,待排序的数组[0......arr.length - 3],已排序的数组[arr.length - 2,arr.length - 1]
        //......

        //此处i > 0,不用写i = 0,因为当数组中只剩一个元素时,数组已经有序
        for (int i = arr.length - 1; i > 0 ; i--) {
            //第0个元素,与最后一个元素进行交换
            swap(arr,0,i);
            //最后一个元素不需要考虑下沉
            siftDown(arr,0,i);
        }
    }

    /**
     * 元素下沉操作
     * @param arr
     * @param i 父节点
     * @param n 当前arr中有效元素个数
     */
    private void siftDown(int[] arr, int i, int n) {
        //终止条件:仍然存在子树(子树小于有效元素的个数)
        while((2 * i) + 1 < n){
            int j = 2 * i + 1;
            //右孩子存在且大于左子树
            if(j + 1 < n && arr[j + 1] > arr[j]){
                j = j + 1;
            }
            if(arr[i] >= arr[j]){
                break;
            }else {
                swap(arr,i,j);
                i = j;
            }
        }
    }

时间复杂度:O(N * logN)

空间复杂度:O(1) 

不稳定

7. 🔍快速排序(挖坑法)

1. 从待排序区间选择一个数,作为基准值(pivot)
2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。

(优化算法)基准的找法:

✨ 三数取中法

✨ 随机法

✨ 把和基准相同的数据,从两边移到一起

利用直接插入排序

    public static void quickSort(int[] array) {
        quick(array,0,array.length - 1);
    }
    public static void quick(int[] array, int left, int right) {
        if(left >= right) {
            return;
        }
        int pivot = partition(array, left, right);
        quick(array,left,pivot - 1);
        quick(array,pivot + 1, right);
    }

    private static int partition(int[] array, int start, int end) {
        int tmp = array[start];
        while(start < end) {
            while(start < end && array[end] >= tmp) {  
                //start < end 加入有 12345,end--会使得end < start ,所以仍要加这个条件
                end--;
            }
            array[start] = array[end];
            while(start > end && array[start] <= tmp) {
                start++;
            }
            array[end] = array[start];
        }
        array[start] = tmp;//此时start和end相遇
        return start;
    }
    

 时间复杂度:O(N * logN) (相当于二叉树,二叉树的每一层都是n)

空间复杂度:O(logN)   (递归,要保留之前的状态) 

不稳定

 时间复杂度:

        最好情况:每次可以均匀的分割待排序序列 O(N * logN) 

        最坏情况:数据有序,或者逆序的情况O(N^2)

空间复杂度:

        最好情况: O(logN) 

        最坏情况:单分支的一棵树O(N) 

8. 🔍归并排序

 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

    public static void mergeSort(int[] array) {
        mergeSortInternal(array, 0, array.length - 1);
    }

    private static void mergeSortInternal(int[] array, int low, int high) {
        int mid = low + ((high - low) >>> 1);
        mergeSortInternal(array, low, mid);
        mergeSortInternal(array, mid + 1, high);
        merge(array, low, mid, high);
    }

    private static void merge(int[] array, int low, int mid, int high) {
        int[] tmp = new int[high - low + 1];
        int k = 0;
        int s1 = low;
        int e1 = mid;
        int s2 = mid + 1;
        int e2 = high;
        while(s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmp[k++] = array[s1++];
            } else {
                tmp[k++] = array[s2++];
            }
        }
        while(s1 <= e1) { // 此时s2已经遍历完
            tmp[k++] = array[s1++];
        }
        while(s2 <= e2) {
            tmp[k++] = array[s2++];
        }
        for (int i = 0; i < k; i++) { //将tmp中的数组元素拷贝到原来的array中
            array[i + low] = tmp[i];
        }
    }

 

 时间复杂度:O(N * logN) 

空间复杂度:O(N) 

稳定

 9. 🔍海量数据的排序问题

内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序
1. 先把文件切分成 200 份,每个 512 M
2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
3. 进行 200 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值