10分钟学会七大排序(2)

五、冒泡排序

public void bubbleSort(int[] array){
        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);
                }
            }
        }
    }

性能分析

1. 时间复杂度
最好:O(N)   数据有序
最坏:O(N^2) 数组逆序
平均:O(N^2)。

2.空间复杂度:O(1)
3.稳定性:不稳定


六、快速排序(重要)

原理

  1. 从待排序区间选择一个数,作为基准值(pivot);
  2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。

从前往后,找第一个比基准值大的元素
从后往前,找第一个比基准值小的元素
交换这两个位置的元素。

	public void quickSort(int[] array){
        //参数的含义表示针对数组中的那段区间进行排序
        //由于有基准值,采用前闭后闭区间
        quickSortHelper(array,0,array.length - 1);
    }
    public void quickSortHelper(int[] array,int left,int right){
        if(left >= right){
            //逼近条件很重要
            //闭区间,有一个元素或者没有元素
            return;
        }
        //区间整理的方法:选取基准值,小于基准值的放在左侧,大于基准值的放在右侧
        int index = partition(array,left,right);//整理完毕后基准值的下标
        quickSortHelper(array,left,index - 1);
        quickSortHelper(array,index + 1,right);
    }
    public int partition(int[] array,int left,int right){
        //3w2s
        int baseIndex = right;
        int baseValue = array[baseIndex];//数组最右侧元素,因为是前闭后闭的
        while (left < right){//联想到swap
            while (left < right && array[left] <= baseValue){
                left++;
            }
            //走到这步,left指向的元素是从左往右走,第一个大于基准值的数
            while (left < right && array[right] >= baseValue){
                right--;
            }
            //走到这步,right指向的位置,就是第一个小于基准值的数

            swap(array,left,right);
        }
        //循环结束,两者重合
        swap(array,left,baseIndex);
        return left;
    }

如何证明这里的left对应的元素是比基准值大的

swap(array,left,baseIndex);

循环结束有两种情况

  1. 由于right–导致的循环结束,此时就是right没有找到比基准值小的元素,但是和left撞上了,并且left的元素是比基准值大的,此时跳出大循环,left指向的元素比基准值大
  2. 由于left++导致的循环结束,此时就是left没有找到比基准值大的元素,但是和right撞上了,为什么此时的right还是比基准值大呢,因为上一层循环left和right交换了。

如果要取左侧的元素为基准值,1.从后往前找比基准值小的2.从前往后找比基准值大的
(取右侧的元素为基准值,1.从前往后找比基准值大的2.从后往前找比基准值小的。

public int partition(int[] array,int left,int right){
        //3w2s
        int baseIndex = left;
        int baseValue = array[baseIndex];//数组最右侧元素,因为是前闭后闭的
        while (left < right){
            while (left < right && array[right] >= baseValue){
                right--;
            }
            //走到这步,right指向的位置,就是第一个小于基准值的数
            while (left < right && array[left] <= baseValue){
                left++;
            }
            //走到这步,left指向的元素是从左往右走,第一个大于基准值的数


            swap(array,left,right);
        }
        //循环结束,两者重合
        swap(array,right,baseIndex);
        return right;
    }

性能分析

1. 时间复杂度:最坏O(N^2)(逆序,没法达到递归二分的效果)
			  最好:O(N*log(N))
			  平均:O(N*log(N))
			  
2.空间复杂度:由于进行了递归,需要调用栈保存层次的关系,需要占据空间的
				最好:O(N)
				最坏:O(log(N))
				平均:O(log(N))

稳定性:不稳定


快速排序的优化

  1. 如果当前逆序的话,用上面的快速排序效率低
  2. 如果基准值选的不好,也会影响性能
  3. 元素数量多,递归深度大,栈就存不下了

优化手段
1.优化取基准值:三元素取中(第一个元素,最后一个元素,中间位置的元素)
2.如果递归深度达到一定层次,不再递归,对当前的待排序区间使用其他排序算法(堆排序)
3.如果当前待排序区间比较小(left,right),使用插排


非递归实现快速排序

用栈的思想来实现递归的操作
代码如下:

public void quickSortByLoop(int[] array){
        //1.先创建一个栈,栈里面存的是待处理区间的下标
        Stack<Integer> stack = new Stack<>();
        stack.push(array.length - 1);
        stack.push(0);//此时根节点入栈
        
        while (!stack.isEmpty()){
            //3.取栈顶元素,栈顶元素就是我们要处理的区间
            int left = stack.pop();//左侧的区间
            int right = stack.pop();
            if(left >= right){
                //一个元素或空,不符合要求,
                continue;
            }
            int index = partition(array,left,right);
            //接下来将左右两个区间入栈
            stack.push(index-1);
            stack.push(left);

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

用栈来模拟递归的过程


七、归并排序

这和我之前写过的一道题很像,叫做归并两个有序链表变成一个有序链表

	public void mergeSort(int[] array){
		//辅助方法,有三个参数,是区间(前闭后开)
        mergeSortHelper(array,0,array.length);
    }
    public void mergeSortHelper(int[] array,int left,int right){
        //类似于后序
        if(left >= right || right - left == 1){
            //划分的数组为空或着只有一个元素
            return;
        }
        int mid = (left + right)/2;
        //[left,mid)
        //[mid,right)
        mergeSortHelper(array,left,mid);//递归左区间数组
        mergeSortHelper(array,mid,right);//递归右区间数组
        merge(array,left,mid,right);//合并
    }
    public void merge(int[] array,int left,int mid ,int right){
        //[left,mid)
        //[mid,right)
        int length = right - left;//创建数组的长度
        int[] output = new int[length];
        int outputIndex = 0;
        int i = left;//想当于链表的cur1,cur2,记录下标的位置
        int j = mid;
        while (i < mid && j < right){//类似cur1和cur2 都不为空
            if(array[i] <= array[j]){
                output[outputIndex++] = array[i++];
            }else {
                //否则将后面的元素添入
                output[outputIndex++] = array[j++];
            }
        }
        while (i < mid ){
            output[outputIndex++] = array[i++];
        }
        while (j < right){
            output[outputIndex++] = array[j++];
        }
        for (int k = 0; k < length; k++) {
            array[left + k] = output[k];
        }
    }

    public static void main(String[] args) {
        SortDemo sortDemo = new SortDemo();
        int[] array ={9,5,2,7,6,5,1,8};
        sortDemo.mergeSort(array);
        System.out.println(Arrays.toString(array));
    }

性能分析

1. 时间复杂度:O(n * log(n))              数据不敏感
2. 空间复杂度:O(N)                          数据不敏感
3. 稳定性:稳定(目前只有插入排序,冒泡排序和这个归并排序是稳定的)

优化总结
在排序过程中重复利用两个数组,减少元素的复制过程

归并排序

 1. 数据允许在外存中,外部排序,核心思路就是归并排序
 2. 归并排序也是一种高效的给链表进行排序的算法
 3. 也是各种标准库中稳定排序算法的主要实现方式

非递归版本(了解即可)
借助下标,对整个数组进行分组。

	public 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) {
                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^2)O(N^2)O(N^2)O(1)不稳定
插入排序O(N)O(N^2)O(N^2)O(1)不稳定
选择排序O(N^2)O(N^2)O(N^2)O(1)稳定
希尔排序O(N)O(N^1.3)O(N^2)O(1)不稳定
堆排序O(N*log(N))O(N*log(N))O(N*log(N))O(1)不稳定
快速排序O(N*log(N))O(N*log(N))O(N^2):逆序O(log(N))~O(N)不稳定
归并排序O(N*log(N))O(N*log(N))O(N*log(N))O(N)稳定
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值