数据结构之排序了如指掌(三)

目录

题外话

正题

 快速排序

Hoare法

Hoare法思路

Hoare法代码详解

挖坑法

挖坑法思路

挖坑法代码

前后指针法

前后指针法思路

前后指针法代码

小结


题外话

我们接着把没有写完的排序内容完成,快速排序其实大同小异,大家好好把思路整理一下

正题

 快速排序

快速排序一共有三种方法

1.Hoare法

2.挖坑法

3.前后指针法

Hoare法

Hoare法是以快速排序创始人Hoaer命名的方法

Hoare法思路

这三种方法都是通过递归排序

1.首先我们创建left和right分别指向0下标和数组最后一个元素下标

2.先建立一个基准值0下标元素,先让right从右到左一个一个去找到比0下标元素小的元素,再让left从左到右去一个一个去找比0下标大的元素

3.当right找到比基准值小的元素,并且left也找到比基准值大的元素之后,我们让right下标元素和left下标元素交换

4.当left和right相遇之后,我们把基准值和其中任意一个下标元素交换,这样基准值左边的元素就比基准值小,基准值右边的元素就比基准值大

5.然后我们再给基准值左边设立left和right然后再去循环这个过程排序,基准值右边也是同理

如果不理解请看下图

看到这两幅图会不会有种二叉树的感觉呢,一层一层的去遍历排序就是这样


Hoare法代码详解

先说一下咱们这个写代码的框架

因为框架真的很重要!!

我们写代码的时候要尽量解耦合,并且提高扩展性

下图A方法中包含B方法

当我们要修改A方法的时候为了正常可以运行,可能会影响到B方法,还要去B方法里修改B方法

而当我们写代码的时候让A和B是一个独立的个体,再由C去调用他们,当修改A或者B中的任意一个代码的时候不会影响到另一方

我们本篇内容要写Hoare法,挖坑法和前后指针法

下面代码大家可以自己观察一下框架

我先列出我们要做的几件事

1.找到基准值,并且使基准值已经交换到数组中间位置

2.从找到的基准值左边去递归数组并排序,并且再次找左边基准值

3.从找到的基准值右边去递归数组并排序,并且再次找右边基准值

4.递归这个过程

代码详解

public void quickSort(int[] arr)
    {
        //调用quick方法并传参数
        quick(arr,0, arr.length-1);
    }


    private void quick(int[] arr,int start,int end)
    {
        //start如果大于等于end说明已经相遇,也说明当前这边已经排完序了
        if (start>=end)
        {
            return;
        }
        //调用Hoare法先让第一次的基准值到数组中间位置
        int pivot=partitionHoare(arr,start,end);
        //然后递归基准值左边并排序
        quick(arr,start,pivot-1);
        //递归基准值右边并排序
        quick(arr,pivot+1,end);
    }

    private int partitionHoare(int[] arr, int left, int right) {
        //先将基准值元素给到tmp
        int tmp=arr[left];
        //并将基准值下标给到i,以免找不到基准值下标,无法交换
        int i=left;
        //当left小于right时
        while (left<right)
        {
            //我们在此处while循环也需要加上left<right条件,否则left可能大于right
            //并且right下标元素大于等于基准值的时候right--
            while (left<right&&arr[right]>=tmp)
            {
                right--;
            }
            //走到这里说明right找到比基准值小的元素
            //left开始找比基准值大的元素,如果left下标元素小于基准值,left++
            while (left<right&&arr[left]<= tmp)
        {
            left++;
        }
            //走到这里说明left找到了比基准值大的元素,right找到了比基准值小的元素,此时将left元素和right元素交换
            //并继续进行while循环
            swap(arr,left,right);
        }
        //走到这里说明left和right相遇,则交换基准值元素和相遇点元素
        swap(arr,i,left);
        //交换之后,此时left就是基准值下标,返回即可
        return left;
    }
    //挖坑法
    private int partitionHole(int[] arr,int left,int right)
    {
        int tmp=arr[left];
        while (left<right)
        {
            while (left<right&&arr[right]>=tmp)
            {
                right--;
            }
            arr[left]=arr[right];

            while (left<right&&arr[left]<= tmp)
        {
            left++;
        }
            arr[right]=arr[left];
        }
        arr[left]=tmp;
        return left;
    }
//交换代码
 public void swap(int[] arr,int a,int b)
    {
        int tmp=arr[a];
        arr[a]=arr[b];
        arr[b]=tmp;
    }

挖坑法

挖坑法思路

1.将基准值保存在临时变量(相当于挖掉挖坑),并且从右边找比基准值小的,左边找比基准值大的

2.从右边找到比基准值小的,放入坑中,此时又出现一个新的坑

3.从左边找比基准值大的,放入坑中,此时又新出现一个坑

4.循环上述过程,当left和right相遇的时候,无论是谁相遇谁,left和right相遇位置一定会有个坑,然后把临时变量也就是基准值放入坑中

5.从左边建立新的基准值递归这个过程

6.从右边建立新的基准值递归这个过程

以上觉得我写的无法理解清楚,请看下图

所有的思路就是这样,就是先把基准值挖坑,然后从右边找,从左边找去填坑,然后相遇位置一定是坑,把基准值放入坑,递归左边和右边即可

挖坑法代码

public void quickSort(int[] arr)
{
    //调用quick方法并传参数
    quick(arr,0, arr.length-1);
}

private void quick(int[] arr,int start,int end)
{
    //start如果大于等于end说明已经相遇,也说明当前这边已经排完序了
    if (start>=end)
    {
        return;
    }
    //调用挖坑法,先让第一次的基准值到数组中间位置
    int pivot=partitionHole(arr,start,end);
    //然后递归基准值左边并排序
    quick(arr,start,pivot-1);
    //递归基准值右边并排序
    quick(arr,pivot+1,end);
}
private int partitionHole(int[] arr,int left,int right)
{
    //默认为最左边为基准值,将基准值挖坑放入临时变量
    int tmp=arr[left];
    //当left没有和right相遇
    while (left<right)
    {
        //当left没有和right相遇并且right还没有找到比基准值小的元素
        while (left<right&&arr[right]>=tmp)
        {
            //向左继续寻找
            right--;
        }
        //走到这说明找到了,然后把坑填上
        arr[left]=arr[right];
        //当left和right没有相遇,并且left没有找到比tmp大的元素
        while (left<right&&arr[left]<= tmp)
    {
        //继续向右寻找
        left++;
    }
        //找到了,此时将right位置的坑填上
        arr[right]=arr[left];
    }
    //走到这里说明left和right相遇了,把基准值填入相遇点
    arr[left]=tmp;
    //返回基准值
    return left;
}

前后指针法

这个方法和上述两个方法略有不同

前后指针法思路

1.让0下标作为基准值

2.left指向0下标,right指向数组最后一个元素

3.创建prev指向left,创建cur指向prev+1

4.当cur没有走完数组最后一个元素,也就是cur<=right

5.让cur一直从左往右去找到比cur小的元素并且满足++prev!=cur,说明prev再往前走一个,此时prev等于cur

6.prev只有在cur找到比基准值小的元素才会往前走,而cur无论有没有找到prev都会往前走

7.prev没有往前走,也就说明了,cur往前走的时候,找到的元素大于基准值

8.如果满足上面条件,就交换cur和prev元素,cur继续往前走,直到走到right位置

9.当走完right,让基准值和prev元素交换,最后返回prev,继续递归遍历prev左边和prev右边

这就有一种cur推着prev走的感觉,只有cur满足条件,prev才有可能往后走

画个图大家就懂了

大家只要清楚的明白cur和prev的满足条件即可

前后指针法代码

public void quickSort(int[] arr)
{
    //调用quick方法并传参数
    quick(arr,0, arr.length-1);
}
private void quick(int[] arr,int start,int end)
{
    //start如果大于等于end说明已经相遇,也说明当前这边已经排完序了
    if (start>=end)
    {
        return;
    }
    //先让第一次的基准值到数组中间位置
    int pivot=partition(arr,start,end);
    //然后递归基准值左边并排序
    quick(arr,start,pivot-1);
    //递归基准值右边并排序
    quick(arr,pivot+1,end);
}
private int partition(int[] arr,int left,int right)
{
    //让prev指向left
    int prev=left;
    //cur指向prev+1
    int cur=prev+1;
    //当cur没有走完right
    while (cur<=right)
    {
        //重点!!  如果cur中的元素比基准值小并且++prev后所在的位置不是cur的位置
        if (arr[cur]<arr[left]&&arr[++prev]!=arr[cur])
        {
            //才会交换prev和cur元素值
            swap(arr,prev,cur);
        }
        //cur不管满不满足以上条件都会往后走,而prev只有满足arr[cur]<arr[left]这个条件,才会往后走,因为是短路与
        //前面条件不满足,不会进行后面条件判断
        cur++;
    }
    //当cur走完right位置,就把基准值和prev交换位置,返回基准值下标
    swap(arr,left,prev);
    return prev;
}

小结

我们可以看这三个方法代码,他们被调用的时候都是通过quick去调用,而且调用三个方法的时候只需要把调用方法名字修改一下就可以了,很方便

我们已经讲完了

直接插入排序(摆扑克牌)

希尔排序(分组摆扑克牌)

冒泡排序(相邻两个元素只要左边比右边大就交换位置,每趟会将数组现存最大元素放到最后面)

堆排序(建立大根堆,然后堆顶元素必然是堆中最大的元素,然后将堆顶元素和最后一个元素交换位置,除去交换的元素,将交换后的堆重新建立大根堆并循环以上过程)

选择排序(找基准值然后排序,再从基准值左边找基准值排序,再从基准值右边找基准值排序)

昨晚熬了个小夜,今天更要加油努力!!!!

喜欢的话记得给个三连!!!(点赞关注收藏)

有问题请在评论区留言或者私信!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值