挖坑法、指针交换法实现快速排序

写在最前

本文用来记录自己的工作、学习遇到的问题, 好记性不如烂笔头, 起的更多的是笔记的作用, 由于本人表达能力、技术水平有限, 本文仅起参考作用, 一切以您实际code为准, 给您带来的不便敬请谅解; 如果发现哪里理解不对或者有问题的地方, 欢迎批评指正.

快速排序

同冒泡排序一样, 快速排序也是交换排序, 但是采用了分治的思想, 所以效率比冒泡排序高很多
冒泡排序每一轮都找出最大的元素到数列的一端; 而快排挑选一个基准元素, 将比基准元素大移动到数列一端, 比基准元素小的移动到另一端, 将数列分成两部分
下面几种实现, 推荐指针交换法(双边指针), 容易理解

时间复杂度

平均时间复杂度为O(nlogn), 最坏时间复杂度为O(n2)

挖坑法实现

基准元素位置相当于一个坑, 右侧的小于基准元素就右侧这个位置为新坑; 左侧的大于基准元素, 左侧这个则为新坑(用新坑的值填旧坑的值, 因为最开始的坑是基准元素, 所以要保存基准元素值, 最后替换最后坑的值)

//挖坑法 递归实现 双向
public static void quickSort1(int[] arr, int startIndex, int endIndex){
    if(startIndex >= endIndex){
        return;
    }
    int pivotIndex = partition1(arr, startIndex, endIndex);
    quickSort1(arr, startIndex, pivotIndex-1);
    quickSort1(arr,pivotIndex+1, endIndex);
}
//挖坑法
public static int partition1(int[] arr, int startIndex, int endIndex){
    //取第一个元素作为基准元素
    int pivot = arr[startIndex];
    int left = startIndex;
    int right = endIndex;
    //坑的位置!!!!!!!! 初始等于pivot的位置
    int index = startIndex;

    while(right >= left){
        //right指针从右向左移动
        while(right >= left){
            if(arr[right] < pivot){
                // 用新坑的值填旧坑的值 新坑为right位置 结束right指针本次循环
                arr[left] = arr[right];
                index = right;
                left++;
                break;
            }
            right--;
        }
        //left指针从左向右移动
        while(right >= left){
            if(arr[left] > pivot){
                // 用新坑的值填旧坑的值 新坑为left位置 结束left指针本次循环
                arr[right] = arr[left];
                index = left;
                right--;
                break;
            }
            left++;
        }
    }
    //用基准元素替换最后坑的位置
    arr[index] = pivot;
    return index;
}

指针交换法实现(双边指针)

指针交换法, 双边遍历, 右边找小于pivot的停下, 左边找大于pivot的停下, 然后左右交换, 最后用pivot和重合位置的元素交换
注意: pivot元素选取影响处理顺序, 见代码注释详细说明

//指针交换法 双边指针 递归实现 双向
public static void quickSort2(int[] arr, int startIndex, int endIndex){
    if(startIndex >= endIndex){
        return;
    }
    int pivotIndex = partition2(arr, startIndex, endIndex);
    quickSort1(arr, startIndex, pivotIndex-1);
    quickSort1(arr,pivotIndex+1, endIndex);
}
//指针交换法 双边遍历, 右边找小于pivot的停下, 左边找到大于pivot的停下, 互换位置, 最后将pivot和指针重合处替换
public static int partition2(int[] arr, int startIndex, int endIndex){
    int pivot = arr[startIndex];
    int left = startIndex;
    int right = endIndex;
    while(right != left){
        // 注意: 因为上面pivot选择的startIndex处的元素, 所以下面顺序必须是先处理right指针, 再处理left指针; 
        // 理解是先处理left指针可能会多换出去一步, 最后pivot和重合点交换的时候不对了
        // pivot如果选择endIndex处的元素, 则先处理left, 后处理right可以
        while(right > left && arr[right] >= pivot){
            right--;
        }
        while(right > left && arr[left] <= pivot){
            left++;
        }
//            while(right > left && arr[right] >= pivot){
//                right--;
//            }
        if(left < right){
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
        }
    }
    //pivot指针和重合点交换 左边都小于pivot  右边都大于pivot 此轮结束
    int p = arr[left];
    arr[left] = arr[startIndex];
    arr[startIndex] = p;
    return left;
}

指针交换法实现(单边指针)

//指针交换法 递归实现 单边
public static void quickSort3(int[] arr, int startIndex, int endIndex){
    if(startIndex >= endIndex){
        return;
    }
    int pivotIndex = partition3(arr, startIndex, endIndex);
    quickSort3(arr, startIndex, pivotIndex-1);
    quickSort3(arr,pivotIndex+1, endIndex);
}
//指针交换法 单边遍历
public static int partition3(int[] arr, int startIndex, int endIndex){
    int pivot = arr[startIndex];
    int mark = startIndex;
    for(int i = startIndex+1; i <= endIndex; i++){
        if(arr[i] < pivot){
            mark++;
            int temp = arr[mark];
            arr[mark] = arr[i];
            arr[i] = temp;
        }
    }
    //注意这里啊
    int temp = arr[mark];
    arr[mark] = pivot;
    arr[startIndex] = temp;
    return mark;
}

指针交换法(双边)(使用栈实现)

绝大多数递归实现的问题都可以使用栈来实现, 因为递归调用本身就是一个函数栈
进入一个新方法, 相当于入栈; 方法返回, 相当于出栈; 递归转化栈, 在栈中存储每一次方法调用的参数

//快排 栈实现 使用的双边、指针交换法
public static void quickSortWithStack(int[] arr, int startIndex, int endIndex) {
    //用一个集合栈来代替递归的函数栈
    Stack<Map<String, Integer>> stack = new Stack<>();
    //整个数列的起止下标 以哈希的形式入栈
    Map rootParam = new HashMap();
    rootParam.put("startIndex", startIndex);
    rootParam.put("endIndex", endIndex);
    stack.push(rootParam);
    //循环结束条件 栈为空时结束
    while(!stack.isEmpty()){
        //栈顶元素出栈 得到起止下标
        Map<String, Integer> param = stack.pop();
        //得到基准元素位置 partition2()方法见上面快排双边指针递归实现的partition2()
        int pivot = partition2(arr, param.get("startIndex"), param.get("endIndex"));
        //根据基准元素分成两部分 每一部分的起止下标入栈
        if(param.get("startIndex") < pivot - 1){
            Map<String, Integer> leftParam = new HashMap<>();
            leftParam.put("startIndex", param.get("startIndex"));
            leftParam.put("endIndex", pivot - 1);
            stack.push(leftParam);
        }
        if(param.get("endIndex") > pivot + 1){
            Map<String, Integer> leftParam = new HashMap<>();
            leftParam.put("startIndex", pivot + 1);
            leftParam.put("endIndex", param.get("endIndex"));
            stack.push(leftParam);
        }
    }
}

参考资料

本文主要是用于自己学习的笔记
参考来源: 小灰的算法之旅

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值