排序算法

排序的相关介绍:
所谓的排序就是使一串记录,按照其中的某个或者某些关键字的大小,递增或递减的排列起来的操作。平时的上下文中如果提到排序,通常指的是升序(非降序)。通常意义上的排序都是指的原地排序
稳定性:两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
1.直接插入排序:
将区间分为有序区间和无序区间。前半部分为有序区间,后半部分为无序区间。
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入。
实现代码:

 //插入排序
    public static void insertSort(int[] array) {
        //bound 变量来把整个数组分成两个区间
        //[0, bound) 已排序区间
        //[bound, size) 待排序区间
        for (int bound = 1; bound < array.length; 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;
        }
    }

性能分析:
在这里插入图片描述
稳定性:稳定
插入排序的核心操作:拿到当前元素,把当前元素插入到前方的有序顺序表的合适位置
插入排序的两个重要特点:
(1)如果当前这个序列很短,那么插入排序很高效
(2)如果当前这个序列基本有序,那么插入排序效率也很高

2.希尔排序:基本思想是先选定一个整数n,把待排序文件中所有元素分成n个组,所有距离为n的元素分在同一个组,并对每一组内的记录进行排序,并按照一定规律减小 n。然后,取,重复上述分组和排序的工作。当n等于1时。所有元素再同一组内排好序。
希尔排序:gap是两个元素之间下标差值,也是分成的组的数量。
在这里插入图片描述
代码实现:

//希尔排序
    public static void shellSort(int[] array) {
        int gap = array.length;
        while (gap > 1) {
            insertSortGap(array, gap);
            gap = gap / 2;
        }
        insertSortGap(array, 1);
    }

    private static void insertSortGap(int[] array, int gap) {
        for (int bound = 1; bound < array.length; bound++) {
            int tmp = array[bound];
            int cur = bound - gap;
            //同组之内的相邻元素之间下标差了 gap, 同组内元素进行比较。
            for (; cur >= 0; cur -= gap) {
                if (array[cur] > tmp) {
                    array[cur + gap] = array[cur];
                } else {
                    break;
                }
            }
            array[cur + gap] = tmp;
        }
    }

性能分析:
在这里插入图片描述
稳定性:不稳定
实际的希尔排序的实现序列选择很讲究,选择的好效率会很高。

3.直接选择排序:每一次从无序区间选出最大(或者最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。
代码实现:

//直接选择排序
    public static void selectSort(int[] array) {
        //先创建一个 bound 变量,表示边界
        //[0,bound) 已排序区间
        //[bound, size) 待排序区间
        for (int bound = 0; bound < array.length; bound++) {
            //使用打擂台的方式找到待排序区间中的最小值
            //bound 位置的元素就是擂台
            for (int cur = bound + 1; cur < array.length; cur++) {
                if (array[cur] < array[bound]) {
                    //打败擂主
                    swap(array, cur, bound);
                }
            }
        }
    }
    private static void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

性能分析:
在这里插入图片描述
稳定性:不稳定

4.堆排序:基本原理也是基于选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大数。
注意:排升序要建立大堆;排降序要建立小堆
以升序为例:首先建立一个将待排序区间建成大堆,然后将待排序区间的第一个元素与最后一个元素交换,待排序区间减少一个元素,已排序区间增加一个元素,然后重复上述工作,直到排序完成。
代码实现:

 //堆排序
    public static void heapSort(int[] array) {
        //1.创建堆
        createHeap(array);
        //2.循环取出堆顶的最大值,放到最后
        for (int i = 0; i < array.length; i++) {
            //待排序区间:[0, array.length - i)
            //已排序区间:[array.length - i, array.length)
            swap(array, 0, array.length - 1 - i);
            //第一个参数树是数组,第二个参数是数组中有效元素的个数,
            //第三个参数是从哪个位置进行调整
            shiftDown(array, array.length - 1 - i , 0);
        }
    }
    private static void createHeap(int[] array) {
        //从最后一个非叶子结点出发,从后往前向下调整
        for (int i = (array.length - 1 - 1)/ 2; i >= 0; i--) {
            shiftDown(array, array.length, i);
        }
    }
    private static void shiftDown(int[] array, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;
        while (child < size) {
            if (child + 1 < size && array[child + 1] > array[child]) {
                child = child + 1;
            }
            if (array[child] > array[parent]) {
                swap(array,parent, child);
            } else {
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }

性能分析:
在这里插入图片描述
稳定性:不稳定

5.冒泡排序:在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序。
代码实现:

//冒泡排序
    public static void bubbleSort(int[] array) {
        //[0, bound)已排序区间
        //[bound,array.length)待排序区间
        for (int bound = 0; bound < array.length; bound++) {
            for (int cur = array.length - 1; cur > bound; cur--) {
                if (array[cur - 1] > array[cur]) {
                    swap(array,cur - 1, cur);
                }
            }
        }
    }

性能分析:
在这里插入图片描述
稳定性:稳定

6.快速排序:
(1)从待排序区间选择一个数作为基准
(2)Partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边
(3)对左右两个小区间按照同样的方式处理,直到小区间的长度等于 1,代表已经有序,或者小区间的长度等于 0,代表没有数据
代码实现:两种方法
(1)方法一:

 //快速排序
    public static void quickSort(int[] array) {
        //参数的含义表示针对数组中的哪段区间进行快速排序
        //[0, array.lengt - 1]
        quickSortHelp(array, 0, array.length - 1);
    }
    private static void quickSortHelp(int[] array, int left, int right) {
        if (left >= right) {
            //如果只有一个元素或者没有元素,都不需要排序
            return;
        }
        //这个方法就是刚才进行区间整理的方法
        //选取基准值,并且把小于基准值的放到左侧,大于基准值的放到右侧
        //返回值[left, right] 最终整理完毕后,基准值的下标
        int index = partition(array, left, right);
        quickSortHelp(array, left, index - 1);
        quickSortHelp(array, index + 1, right);
    }

    //基准值为最后一元素
    private static int partition(int[] array, int left, int right) {
        //基准值
        int baseValueIndex = right;
        int baseValue = array[right];
        while (left < right) {
            while (left < right && array[left] <= baseValue) {
                left++;
            }
            //循环结束之后,left 指向的位置就是从左往右第一个比基准值大的元素
            while (left < right && array[right] >= baseValue) {
                right--;
            }
            //这个循环结束之后,right 指向的位置就是从右往左第一个比基准值小的元素
            //交换 left 和 right 位置的元素
            swap(array,left, right);
        }
        //为什么证明此处的 left 比基准值大呢?
        //循环结束有两种情况
        //1.left++ 导致的循环结束
        //  上次循环过程中进行了一个 swap 操作,经过这个 swap 操作之后,right 一定指向一个大于基准值的元素,
        //  此时如果 left 和 right 重合,那么 left 也指向在 right 位置处的那个大于基准值的元素。
        //2.right-- 导致的循环结束
        //  此时由于 left 刚刚找到一个比基准值大的元素,此时 left 与 right 重合之后,
        //  对应的元素也就是刚才那个比基准值大的元素。
        swap(array, left, baseValueIndex);
        return left;
    }

(2)方法二:

//非递归版本的快速排序
    public static void quickSortByLoop(int[] array) {
        //1.先创建一个栈,栈里面存的是待处理区间的下标
        Stack<Integer> stack = new Stack<>();
        //2.初始情况下待处理区间就是整个数组
        stack.push(array.length - 1);
        stack.push(0);
        while (!stack.isEmpty()) {
            //3.取栈顶元素,栈顶元素就是我们要处理的区间
            int left = stack.pop();
            int right = stack.pop();
            if (left >= right) {
                continue;
            }
            //4.对当前待处理区间进行整理
            int index = partition(array, left, right);
            //5.接下来要处理的区间要入栈
            //[left, index - 1]
            //[index + 1, right]
            stack.push(index - 1);
            stack.push(left);

            stack.push(right);
            stack.push(index + 1);
        }
    }

性能分析:
在这里插入图片描述
稳定性:不稳定
快速排序的缺点:
(1)如果当前逆序,表现不佳
(2)如果基准值选的不好,也会影响性能
(3)如果元素数量非常多,就会导致递归深度过大,有可能栈就存不下了
优化手段:
(1)优化取基准值的方式(取三个元素中第二大的元素):(第一个元素,最后一个元素,中间位置的元素)
(2)如果递归深度达到一定深度之后,就不再进行递归了,而是对当前的待排序区间直接使用其他排序算法(比如:堆排序).
(3)如果当前待排序区间已经比较小了(left 与 right 之间的差比较小),直接使用插入排序即可。

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

//归并排序
    public static void mergeSort(int[] array) {
        //后两个参数表示要进行归并排序的区间
        //[0,array.length)
        mergeSortHelp(array, 0, array.length);
    }
    private static void mergeSortHelp(int[] array, int left, int right) {
        //[left, right) 构成了要去进行排序的区间
        if (left >= right || right - left == 1) {
            //空区间或者期间只有一个元素,都不需要进行归并排序
            return;
        }
        //使用一种类似于后序遍历的方式
        //先把当前的待排序区间拆分成两半,
        //先递归的对这两个子区间进行归并排序,保证两个区间有序之后,再进行合并
        int mid = (left + right) / 2;
        //[left, mid)
        //[mid, right)
        mergeSortHelp(array, left, mid);
        mergeSortHelp(array, mid, right);
        merge(array, left, mid, right);
    }
    private static void merge(int[] array, int left, int mid, int right) {
        //创建一段临时空间辅助进行归并
        //这个临时空间的长度应该是两个带归并区间的长度之和
        int length = right - left;
        int[] output = new int[length];
        //这个变量保存着当前 output 中的末尾元素的下标
        int outputIndex = 0;
        //i 和 j 是用来遍历两个区间的辅助变量
        //[left, mid)
        //[mid, right)
        int i = left;
        int j = mid;
        while (i < mid && j < right) {
            //此处的 if 条件必须要 <= ,否则无法保证稳定性
            if (array[i] <= array[j]) {
                //i 对用的元素比 j 小
                //就把 i 对用的元素插入到 output 末尾
                output[outputIndex++] = array[i++];
            } else {
                output[outputIndex++] = array[j++];
            }
        }
        //上面的循环结束之后,两个区间至少有一个是遍历完了的,
        //就把剩下的区间的内容直接拷贝到 output 中即可。
        while (i < mid) {
            output[outputIndex++] = array[i++];
        }
        while (j < right) {
            output[outputIndex++] = array[j++];
        }
        //最后一步把 output 中的元素拷贝回原来的区间
        for (int k = 0; k < length; k++) {
            array[left + k] = output[k];
        }
    }

(2)方法二:

 //归并排序(非递归)
    public static void mergeSortByLoop(int[] array) {
        //借助下标和相关的规律来进行分组
        //初始情况下,每个元素单独作为一组
        //[0] [1]  [2] [3] [4] [5]
        //[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) {
                //这个循环负责在 gap 为指定值的情况下把所有区间进行合并
                //针对当前的 i ,也能划分出两个需要进行归并的区间
                //[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);
            }
        }
    }

性能分析:
在这里插入图片描述
稳定性:稳定
归并排序的相关应用领域:
1.数据允许在外存中,外部排序的核心思路就是归并排序
2. 归并排序也是一种高效的给链表进行排序的算法。
3.也是各种标准库中稳定排序算法的主要实现方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值