排序算法之快速排序、堆排序

目录

一、快速排序

1、概念

2、算法原理

3、代码实现

二、堆排序

1、简介

2、堆分类

 2.1、大根堆:每个节点的值都大于或者等于他的左右孩子节点的值

2.2、小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值

​编辑 2.3、两种结构映射到数组为:

3、排序思想

4、构造堆

5、代码实现


一、快速排序

1、概念

        快速排序(Quick Sort)是从冒泡排序算法演变而来的,实际上是在冒泡排序基础上的递归分治法。快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。

2、算法原理

这是一个无序数列:4、5、8、1、7、2、6、3,我们要将它按从小到大排序。按照快速排序的思想,我们先选择一个基准元素,进行排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeR19ua8-1592286987672)(./快速1.png)]

我们选取4为我们的基准元素,并设置基准元素的位置为index,设置两个指针left和right,分别指向最左和最右两个元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EFkeo4Z-1592286987676)(./快速2.png)]

接着,从right指针开始,把指针所指向的元素和基准元素做比较,如果比基准元素大,则right指针向左移动,如果比基准元素小,则把right所指向的元素填入index中

3和4比较,3比4小,将3填入index中,原来3的位置成为了新的index,同时left右移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ahJscJM-1592286987677)(./快速3.png)]

然后,我们切换left指针进行比较,如果left指向的元素小于基准元素,则left指针向右移动,如果元素大于基准元素,则把left指向的元素填入index中

5和4比较,5比4大,将5填入index中,原来5的位置成为了新的index,同时right左移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28sZ32EQ-1592286987678)(./快速4.png)]

接下来,我们再切换到right指针进行比较,6和4比较,6比4大,right指针左移一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BmFtPyoS-1592286987680)(./快速5.png)]

2和4比较,2比4小,将2填入index中,原来2的位置成为新的index,left右移一位 

 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-At3QbmBx-1592286987682)(./快速6.png)]

 随着left右移,right左移,最终left和right重合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WfcU8kdr-1592286987684)(./快速7.png)]

此时,我们将基准元素填入index中,这时,基准元素左边的都比基准元素小,右边的都比基准元素大,这一轮交换结束

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjOiHUyg-1592286987685)(./快速8.png)]

第一轮,基准元素4将序列分成了两部分,左边小于4,右边大于4,第二轮则是对拆分后的两部分进行比较

此时,我们有两个序列需要比较,分别是3、2、1和7、8、6、5,重新选择左边序列的基准元素为3,右边序列的基准元素为7

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTPfcLIT-1592286987686)(./快速9.png)]

第二轮排序结束后,结果如下所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YvVyuVCL-1592286987686)(./快速10.png)]

此时,3、4、7为前两轮的基准元素,是有序的,7的右边只有8一个元素也是有序的,因此,第三轮,我们只需要对1、2和5、6这两个序列进行排序 

 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDg3vGlK-1592286987687)(./快速11.png)]

第三轮排序结果如下所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BT70Q2eJ-1592286987690)(./快速12.png)]

至此所有的元素都是有序的 

3、代码实现

package com.kgf.algorithm.sort;

/***
 * 快速排序
 */
public class QuikSort {

    public static void main(String[] args) {

        int[] nums = {9,1,2,5,7,4,8,6,3,5};

        QuikSort qs = new QuikSort();
        qs.quikSort(nums,0,nums.length-1);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    /***
     * 快速排序
     * @param nums
     */
    public void quikSort(int[] nums,int statIndex,int endIndex){
        if (statIndex>=endIndex)return;
        //进行指针移动,寻找新的pivot
        int pivotindex = getPartition(statIndex,endIndex,nums);
        quikSort(nums,statIndex,pivotindex-1);
        quikSort(nums,pivotindex+1,endIndex);
    }

    /***
     * 进行指针移动
     * @param statIndex
     * @param endIndex
     * @return
     */
    private int getPartition(int statIndex, int endIndex,int[] nums) {
        //定义一个基准元素
        int pivot = nums[statIndex];
        //定义左右指针
        int left = statIndex,right = endIndex;
        int index = statIndex;
        while (right>left){
            while (right>left &&nums[right]>=pivot){
                right--;
            }
            nums[left] = nums[right];
            index = right;
            while (right>left && nums[left]<=pivot){
                 left++;
            }
            nums[index] = nums[left];
            index = left;
        }
        nums[index] = pivot;
        return index;
    }
}

二、堆排序

1、简介

        堆排序是指利用堆这种数据结构所设计的一种排序算法。

        堆(Heap)是一个近似完全二叉树的结构,并同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

        一般情况,将堆顶元素为最大值的叫做“大顶堆”(Max Heap),堆顶为最小值的叫做“小顶堆”。

        算法简单来说,就是构建一个大顶堆,取堆顶元素作为当前最大值,然后删掉堆顶元素、将最后一个元素换到堆顶位置,进而不断调整大顶堆、继续寻找下一个最大值。

这个过程有一些类似于选择排序(每次都选取当前最大的元素),而由于用到了二叉树结构进行大顶堆的调整,时间复杂度可以降为O(nlogn)

2、堆分类

 2.1、大根堆:每个节点的值都大于或者等于他的左右孩子节点的值

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

2.2、小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16 2.3、两种结构映射到数组为:

 大根堆:

9e9b5f5965884d02b5e863b58788c5ff.png

小根堆:

b35ef1f4b145466ab7918883eb7a281a.png 

 注意:

        父-->子:

                i--->左孩子:2*i+1

                i--->右孩子:2*i+2

        子-->父:

                i--->(i-1)/2

3、排序思想

  • 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  • 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  • 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
  • 注意:升序用大根堆,降序就用小根堆(默认为升序)

4、构造堆

如何构造堆呢?

46c6a6511f2742df99a359463e97736e.png 

6b543b30211443bfae03a3b9f67ee09a.png 

 首先我们给定一个无序的序列,将其看做一个堆结构,一个没有规则的二叉树,将序列里的值按照从上往下,从左到右依次填充到二叉树中。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

         对于一个完全二叉树,在填满的情况下(非叶子节点都有两个子节点),每一层的元素个数是上一层的二倍,根节点数量是1,所以最后一层的节点数量,一定是之前所有层节点总数+1,所以,我们能找到最后一层的第一个节点的索引,即节点总数/2(根节点索引为0),这也就是第一个叶子节点,所以第一个非叶子节点的索引就是最后一个叶子结点的索引-1。那么对于填不满的二叉树呢?这个计算方式仍然适用,当我们从上往下,从左往右填充二叉树的过程中,第一个叶子节点,一定是序列长度/2, 所以第最后一个非叶子节点的索引就是 arr.len / 2 -1,对于此图数组长度为5,最后一个非叶子节点为5/2-1=1,即为6这个节点

那么如何构建呢?

 我们找到了最后一个非叶子节点,即元素值为6的节点,比较它的左右节点中最大的一个的值,是否比他大,如果大就交换位置。

在这里5小于6,而9大于6,则交换6和9的位置

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

 找到下一个非叶子节点4,用它和它的左右子节点进行比较,4大于3,而4小于9,交换4和9位置

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

 此时发现4小于5和6这两个子节点,我们需要进行调整,左右节点5和6中,6大于5且6大于父节点4,因此交换4和6的位置

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

  此时我们就构造出来一个大根堆,下来进行排序

首先将顶点元素9与末尾元素4交换位置,此时末尾数字为最大值。

排除已经确定的最大元素,将剩下元素重新构建大根堆

一次交换重构如图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

 此时元素9已经有序,末尾元素则为4(每调整一次,调整后的尾部元素在下次调整重构时都不能动)

二次交换重构如图: 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16

最终排序结果:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAb29yaWs=,size_20,color_FFFFFF,t_70,g_se,x_16 

 由此,我们可以归纳出堆排序算法的步骤:

  • 把无序数组构建成二叉堆。
  • 循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶。

当我们删除一个最大堆的堆顶(并不是完全删除,而是替换到最后面),经过自我调节,第二大的元素就会被交换上来,成为最大堆的新堆顶。

正如上图所示,当我们删除值为9的堆顶节点,经过调节,值为6的新节点就会顶替上来;当我们删除值为6的堆顶节点,经过调节,值为5的新节点就会顶替上来.......

由于二叉堆的这个特性,我们每一次删除旧堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点。那么我们只要反复删除堆顶,反复调节二叉堆,所得到的集合就成为了一个有序集合,

堆排序是不稳定的排序,空间复杂度为O(1),平均的时间复杂度为O(nlogn),最坏情况下也稳定在O(nlogn)

5、代码实现

package com.kgf.algorithm.sort;

/***
 * 堆排序
 */
public class HeapSort {

    public static void main(String[] args) {
        int[] nums = {4,6,3,5,9};
        HeapSort hs = new HeapSort();
        hs.heapSort(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
    }

    public void heapSort(int[] nums){
        //将待排序的数组构建成大顶堆,此时,整个序列的最大值就是堆顶的根节点
        int n = nums.length;
        //首先根据完全二叉树的规则,找到最后一个非叶子节点索引,然后开始处理
        for (int i = n/2-1; i >=0; i--) {
            structHeap(nums,i,n);
        }
        //构建好大顶堆之后,开始进行排序
        for (int i = 0; i < n; i++) {
            //首先将堆顶移到最后一位
            int temp = nums[0];
            nums[0] = nums[n-i-1];
            nums[n-i-1] = temp;
            structHeap(nums,0,n-i-1);
        }
    }

    /***
     * 对堆左右节点数据进行比较,交换数据
     * @param nums
     * @param index
     * @param len
     */
    private void structHeap(int[] nums,int index,int len) {
        //记录当前节点的数据
        int temp = nums[index];
        //这里的i表示的是子节点的位置,index的左节点是2*index+1,右节点是2*index+2
        for (int i = 2*index+1; i < len; i=2*index+1) {
            if (nums[i]<nums[i+1] && i+1<len){
                i++;
            }
            if (nums[i]>temp){
                //开始进行数据交换
                nums[index] = nums[i];
                nums[i] = temp;
                index = i;
            }else {
                break;
            }
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值