常见排序算法总结

插入排序

在这里插入图片描述

代码

import java.util.Arrays;

/**
 * @ClassName InsertSort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/26/11:07
 */

// 时间复杂度O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序(如果排序数组中有两个元素的值是相等的,如果排序后两个相等的元素的相对顺序是一样的,这样的排序称为稳定排序。否则就是不稳定的。)

public class InsertSort {
    // 升序排序
    public static void insertSort(int[] array) {
        // 通过 bound 来划分两个区间
        // 有序区间 :[0,bound)
        // 无序区间 :[bound,size)
        for (int bound = 1; bound < array.length; bound++) {
            // 处理 bound 位置元素,将bound 元素插入前边的有序区间
            int tmp = array[bound];
            // 从后往前遍历,插入到合适位置
            int cur = bound - 1;
            for (; cur >= 0; cur--) {
              // 如果这里写作大于等于就会变成不稳定排序
                if (array[cur] > tmp) {
                    array[cur + 1] = array[cur];
                } else {
                    // 说明已经找到合适位置
                    break;
                }
            }
            array[cur + 1] = tmp;
        }

    }

    public static void main(String[] args) {
        int[] array = {12, 34, 45, 56, 5, 7, 686, 34};
        insertSort(array);
        System.out.println(Arrays.toString(array));

    }
}

性能分析

// 时间复杂度:O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序(如果排序数组中有两个元素的值是相等的,如果排序后两个相等的元素的相对顺序是一样的,这样的排序称为稳定排序。否则就是不稳定的。)

特点

  1. 当待排序区间元素比较少的时候,排序效率很高。
  2. 当整个数组比较接近有序时,排序效率也非常高。

优化(优化查找)

希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

  • 希尔排序是对直接插入排序的优化。
  • 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap ==1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
    在这里插入图片描述

gap = 3 同组相邻元素下标之差也就是3
针对每组元素分别插排后,虽然未能彻底完成最终排序,但是有序性整体提高了。
gap = 2
重复进行插排,此时仍然未能完成排序,但是有序性得到了进一步提高。
gap = 1
最后一次插排,完成排序。

代码

import java.util.Arrays;

/**
 * @ClassName ShellSort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/26/12:04
 */
// 时间复杂度:极限形况;O(N^1.3),按照size,size/2  ... 设定 gap  时间复杂度为:O(N^2)
// 空间复杂度:O(1)
// 稳定性;  不稳定(分组会打乱相对顺序)

public class ShellSort {
    public static void main(String[] args) {
        int[] array = {12, 23, 45, 576, 23, 12356, 34};
        shellSort(array);
        System.out.println(Arrays.toString(array));
    }
    public static void shellSort(int[] array) {
        int gap = array.length / 2;
        while (gap > 1) {
            // 循环进入分组插入排序
            insertSortGap(array, gap);
            gap = gap / 2;
        }
        insertSortGap(array, 1);
    }

    private static void insertSortGap(int[] array, int gap) {
        // 通过 bound 来划分两个区间
        // 有序区间 :[0,bound)
        // 无序区间 :[bound,size)

        for (int bound = gap; bound < array.length; bound++) {
            // 处理 bound 位置元素,将bound 元素插入前边的有序区间
            int tmp = array[bound];
            // 从后往前遍历,插入到合适位置
            int cur = bound - gap;// 找到同组中的上一个元素
            for (; cur >= 0; cur -= gap) {
                // 如果这里写作大于等于就会变成不稳定排序
                if (array[cur] > tmp) {
                    array[cur + gap] = array[cur];
                } else {
                    // 说明已经找到合适位置
                    break;
                }
            }
            array[cur + gap] = tmp;
        }

    }
}

性能

// 时间复杂度:极限形况;O(N^1.3),按照size,size/2 … 设定 gap 时间复杂度为:O(N^2)
// 空间复杂度:O(1)
// 稳定性; 不稳定(分组会打乱相对顺序)

选择排序

基于打擂台的思想,每次从数组中找出最小值,然后把最小值放到合适的位置上。

代码

import java.util.Arrays;

/**
 * @ClassName SelectSort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/26/16:55
 */

// 时间复杂度:O(N^2)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
    
public class SelectSort {
    public static void main(String[] args) {
        int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
        selectSort(array);
        System.out.println(Arrays.toString(array));
    }

    public static void selectSort(int[] array) {
        for (int bound = 0; bound < array.length; bound++) {
            // bound 位置作为擂台,将后边区间进行比较
            // 如果打擂成功,进行交换
            for (int i = bound + 1; i < array.length; i++) {
                if (array[i] < array[bound]) {
                    // 打擂成功
                    int temp = array[bound];
                    array[bound] = array[i];
                    array[i] = temp;
                }
            }
        }
    }
}

性能

// 时间复杂度:O(N^2)
// 空间复杂度:O(1)
// 稳定性:不稳定排序

堆排序

升序排序:

  1. 把数组建立一个小堆,取出最小值放到另一个数组中,循环取堆顶元素尾插到新数组即可(缺陷是需要额外的O(N)的空间)
  2. 把数组建立一个大堆,把对堆顶元素和堆的最后一个元素互换,把最后一个元素删除,再从堆顶来向下调整。

代码

import java.util.Arrays;

/**
 * @ClassName HeapSort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/26/17:24
 */

// 时间复杂度:O(N) + O(N*logN)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
    


public class HeapSort {
    public static void main(String[] args) {
        int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }

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

    public static void heapSort(int[] array) {
        //  先建立堆
        createHeap(array);
        // 循环交换堆顶元素,并进行调整
        // 当只剩下一个元素就一定是有序的了
        for (int i = 0; i < array.length - 1; i++) {
            // 交换堆顶元素和堆的最后一个元素
            //  循环一次堆的元素就会减少一个
            // 最后一个元素的下标就是  array.length - 1 - i
            swap(array, 0, array.length - 1 - i);
            // 交换完之后,堆的长度就会变短
            // [0,array.length - i -1) 为待排序区间
            shiftDown(array, array.length - i - 1, 0);
        }
    }

    //O(N*logN)
    private static void shiftDown(int[] array, int heapLength, int index) {
        // 升序排序,建立的是大堆,大堆就需要找出左右子树的最大值,在和根节点比较
        int parent = index;
        int child = 2 * parent + 1;
        while (child < heapLength) {
            if (child + 1 < heapLength && array[child + 1] > array[child]) {
                child = child + 1;
            }
            // 条件结束意味着, child  就已经是左右子树比较大的值的下标
            if (array[child] > array[parent]) {
                swap(array, child, parent);
            } else {
                break;
            }
            parent = child;
            child = parent * 2 + 1;
        }
    }

    private static void createHeap(int[] array) {
        // 从最后一个非叶子节点出发向前循环,依次进行向下调整
        // (array.length - 1 - 1) / 2 得到最后一个节点的父节点
        for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(array, array.length, i);
        }
    }
}

性能

// 时间复杂度:O(N) + O(N*logN)
// 空间复杂度:O(1)
// 稳定性:不稳定排序

冒泡排序

代码

import java.util.Arrays;

/**
 * @ClassName Bubblesort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/26/18:15
 */
// 时间复杂度: O(N^2)
// 空间复杂度;O(1)
//  稳定性:稳定排序



public class Bubblesort {
    public static void main(String[] args) {
        int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }

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

性能

// 时间复杂度: O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序

快速排序

  1. 待排序区间中找到一个基准值(常见的可以取区间的第一个元素或者最后一个元素)
  2. 以基准值为中心,把整个区间整理成三个部分:左侧部分的元素都小于等于基准值,右侧区间都大于等于基准值。
  3. 在针对左侧整理好的区间和右侧整理好的区间,进一步的进行递归,重复之前的整理过程。

一次整理过程:

  1. 取最右侧元素为基准值
  2. 从左往右找到一个大于基准值的元素(left)
  3. 从右往左找到一个小于基准值的元素(right)
  4. 交换 left 和 right 位置的元素
  5. 循环之前的操作,直到 left 和 right 重合

若以左侧元素作为基准值,则需要先从右往左找,再从左往右找;否则会破坏快排要求

  • 如果是先从左往右找,在从右往左找,left 和 right 重合位置的元素,一定大于等于基准值
  • 如果是先从右往左找,在从左往右找,left 和 right 重合位置的元素,一定小于等于基准值

递归代码

import java.util.Arrays;

/**
 * @ClassName QuickSort2
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/27/11:29
 */
// 时间复杂度:O(N*logN)
// 空间复杂度:O(logN)
// 稳定性:不稳定
public class QuickSort2 {
    public static void main(String[] args) {
        int[] array = {12, 23, 45, 576, 23, 12356, 34};
        quickSort(array);
        System.out.println(Arrays.toString(array));

    }

    public static void quickSort(int[] array) {
        // 辅助完成递归过程
        // 全闭区间
        quickSortHelper(array, 0, array.length - 1);
    }

    private static void quickSortHelper(int[] array, int left, int right) {
        if (left >= right) {
            // 区间中有一个或者0 个元素时,此时就不需要排序了
            return;
        }
        // 针对  【left ,right】 区间进行整理
        // index 返回值就是整理完毕后,left  和 right  的重合位置,在进行下一步递归
        int index = partition(array, left, right);
        quickSortHelper(array, left, index - 1);
        quickSortHelper(array, index + 1, right);

    }

    private static int partition(int[] array, int left, int right) {
        int i = left;
        int j = right;
        // 取最右侧元素为基准值
        int base = array[right];
        while (i < j) {
            //  从左往右找到比基准值大的元素
            while (i < j && array[i] <= base) {
                i++;
            }
            //  当上面的循环结束时,i 和 j 要莫重合,要莫 i 就指向一个大于 base 的值
            // 从右往左找比基准值小的元素
            while (i < j && array[j] >= base) {
                j--;
            }
            // 当上面的循环结束时,i 和 j 要莫重合,要莫 j 就指向一个小于 base 的值
            // 交换 i 和 j 的值
            swap(array, i, j);

        }
        //  当 i 和 j 重合的时候,最后一步要把重合位置的元素与基准值进行交换
        //
        swap(array, i, right);
        return i;
    }

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

非递归代码

import java.util.Arrays;
import java.util.Stack;

/**
 * @ClassName QuickSort3
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/27/14:45
 */
public class QuickSort3 {

    public static void main(String[] args) {
        int[] array = {12, 23, 45, 576, 23, 12356, 34};
        quickSortByLoop(array);
        System.out.println(Arrays.toString(array));

    }
    public static void quickSortByLoop(int[] array) {
        // 借助栈,模拟实现递归过程
        // stack 用来存放数组下标,通过下标来表示接下来要处理的区间是啥
        Stack<Integer> stack = new Stack<>();
        // 初始情况下,先把右侧边界下标入栈,再把左侧边界下标入栈,左右边界仍然构成前闭区间后闭区间
        stack.push(array.length - 1);
        stack.push(0);

        while (!stack.isEmpty()) {
            // 这个取元素的顺序要和push 的顺序正好相反
            int left = stack.pop();
            int right = stack.pop();
            if (left >= right) {
                // 区间只有一个或者没有元素,就不需要进行整理
                continue;
            }
            // 通过 partition 把区间整理成以基准值为中心,左侧小于等于基准值,右侧大于等于基准值
            //
            int index = partition(array, left, right);
            // 准备处理下一个区间
            // 【index+1,right】 基准值右侧区间
            stack.push(right);
            stack.push(index + 1);


            //  【left, index-1】 基准值左侧区间
            stack.push(index - 1);
            stack.push(left);
            //


        }
    }

    private static int partition(int[] array, int left, int right) {
        int i = left;
        int j = right;
        // 取最右侧元素为基准值
        int base = array[right];
        while (i < j) {
            //  从左往右找到比基准值大的元素
            while (i < j && array[i] <= base) {
                i++;
            }
            //  当上面的循环结束时,i 和 j 要莫重合,要莫 i 就指向一个大于 base 的值
            // 从右往左找比基准值小的元素
            while (i < j && array[j] >= base) {
                j--;
            }
            // 当上面的循环结束时,i 和 j 要莫重合,要莫 j 就指向一个小于 base 的值
            // 交换 i 和 j 的值
            swap(array, i, j);

        }
        //  当 i 和 j 重合的时候,最后一步要把重合位置的元素与基准值进行交换
        //
        swap(array, i, right);
        return i;
    }
    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}

优化

  1. 优化基准值的取法:三个元素进行取中间值。(最左侧元素、中间位置元素、最右侧元素,取中间值作为基准值,把确认的基准值 swap 到数组末尾或者开头,为后边的整理做铺垫)
  2. 当区间已经比较小的时候,再去进行递归,其效率就会下降,应不再进行递归,而是直接进行插入排序
  3. 如果区间特别的大,递归的深度也会比较深。当递归深度达到一定程度的时候,把区间的排序使用堆排序来进行优化

性能分析

// 时间复杂度:O(N*logN)
// 空间复杂度:O(logN)
// 稳定性:不稳定

归并排序

可以适用于外部排序(数组存在磁盘上),也可以适用于链表排序。

递归代码

import java.util.Arrays;

/**
 * @ClassName MergeSort
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/03/27/17:20
 */
// 时间复杂度;O(N*logN)
// 空间复杂度;O(N) + O(logN)
// 稳定性;稳定
    
public class MergeSort {
    // [low,mid) 有序区间
    // [mid,high) 有序区间
    // 把这两个有序区间合并为一个有序区间
    public static void merge(int[] array, int low, int mid, int high) {
        int[] output = new int[high - low];
        int outputIndex = 0;  //记录当前 output 数组中被放入了多少个元素
        int cur1 = low;
        int cur2 = mid;
        while (cur1 < mid && cur2 < high) {
            // 这里写作 <= 才能保证稳定性
            if (array[cur1] <= array[cur2]) {
                output[outputIndex] = array[cur1];
                outputIndex++;
                cur1++;
            } else {
                output[outputIndex] = array[cur2];
                outputIndex++;
                cur2++;
            }
        }
        // 当循环结束,有一个先到达,一个后到达
        // 后边的直接加入
        while (cur1 < mid) {
            output[outputIndex] = array[cur1];
            outputIndex++;
            cur1++;
        }
        while (cur2 < high) {
            output[outputIndex] = array[cur2];
            outputIndex++;
            cur2++;
        }
        // 把 output 中的元素搬运回去
        for (int i = 0; i < high - low; i++) {
            array[low + i] = output[i];
        }
    }


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

    // [low,high) 两者差值小于等于1 区间就只有一个或者0个元素
    private static void mergeSortHelper(int[] array, int low, int high) {
        if (high - low <= 1) {
            return;
        }
        int mid = (low + high) / 2;
        //这个方法执行完就认为low ,mid  已经排序ok
        mergeSortHelper(array, low, mid);
        //这个方法执行完就认为mid ,high  已经排序ok
        mergeSortHelper(array, mid, high);

        // 当把左右区间已经归并完了,说明在左右区间已经是有序的了
        /// 接下来就可以针对两个区间进行合并了
        merge(array, low, mid, high);
    }

    public static void main(String[] args) {
        int[] array = {12, 23, 45, 576, 23, 12356, 34};
        mergeSort(array);
        System.out.println(Arrays.toString(array));

    }
}

非递归代码

public static void mergeSortByLoop(int[] array) {
        // 引入一个 gap 变量进行分组
        //  当gap 为 1 的时候,[0][1]进行合并,[2][3]进行合并, [4][5]进行合并...
        //  当gap 为 2 的时候,[0,1] 和 [2,3]进行合并,[4,5]和 [6,7]进行合并...
        //  当gap 为 4 的时候,[0,1,2,3]和 [4,5,6,7]进行合并...
        for (int gap = 1; gap < array.length; gap *= 2) {
            // 接下来进行具体的分组合并
            // 下边的循环执行一次,就完成了一次相邻两个组的合并
            for (int i = 0; i < array.length; i += 2 * gap) {
                // 当前相邻组
                // [beg,mid)
                // [mid,end)
                int beg = i;
                int mid = i + gap;
                int end = i + 2 * gap;
                // 防止下标越界
                if (mid > array.length) {
                    mid = array.length;
                }
                if (end > array.length) {
                    end = array.length;
                }
                merge(array, beg, mid, end);
            }
        }

    }

性能分析

// 时间复杂度;O(N*logN)
// 空间复杂度;O(N) + O(logN)
// 稳定性;稳定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值