排序算法总结

1.绪论 

排序是算法题中常见的操作,本文将介绍各种排序算法的思想和实现。

2. 选择排序

2.1 思想

选择排序其实就是从左往右遍历,假设当前处理的位置为i,则i左侧为已经拍好序的元素,i右侧为待排序的元素,每次从待排序中元素选择出最大值,与i位置进行交换。

可以看出选择排序每次处理,左边已经是最终排序的元素,右侧是待排的元素,只需要从待排元素中的找到最小值放到i处就可以了。

2.2 代码

private void  selectedSortedAlgorithm(int nums[]){
        //i为正在处理的元素,应该放入剩余元素最小值
        for (int i = 0; i < nums.length - 1; i++) {
            //寻找[i,nums.length)中的最小值
            int minIndex = i;
            for (int j = i; j < nums.length - 1; j++) {
                if(nums[j] < nums[minIndex]) {
                    minIndex = j;
                }
            }
            //交换元素
            swap(nums, i, minIndex);
        }
    }

    private void swap(int[] nums, int i, int minIndex) {
        int temp = nums[minIndex];
        nums[minIndex] = nums[i];
        nums[i] = temp;
    }

选择排序的时间复杂度为O(nlogn)。

3.插入排序

3.1 思想

插入拍序其实就是从左向右遍历,假设当前轮待处理元素为i,则[0,i)为局部有序的元素,[i,length - 1)为待处理元素。所以将i元素放到[0,i)的正确位置上即可。

可以看出,插入排序,i左侧[0,i)为局部有序的序列,他们的顺序并不是最终顺序。

3.2 代码

  private void insertSortedAlgorithm(int nums[]){
        for (int i = 1; i < nums.length - 1; i++) {
            //i为当前处理的元素
            for(int j = i; j > 0 ; j--) {
                if(nums[j] < nums[j - 1]) {
                    swap(nums,j,j-1);
                }
            }
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[j];
        nums[j] = nums[i];
        nums[i] = temp;
    }

插入排序的时间复杂度为O(nlogn)。

4. 归并排序

4.1 思想

归并排序其实是利用分治的思想,每次将区间平分为左右两个区间。只要左右区间为有序的区间,利用merge操作(即将两个有序数合并成一个有序数组),便能保证最后得到的区间是有序的。可以看出,利用分支的思想,最后得到的是合并两个数字,这两个数字本身就是有序的。

4.2 代码

    public int[] sortArray(int[] nums) {
        // 假设现在有两个数组
        sortArrayIncr(nums, 0, nums.length - 1);
        return nums;
    }

    // 将[l,r]的元素进行排序
    private void sortArrayIncr(int[] nums, int l, int r) {
        if (l >= r) {
            return;
        }
        // 左边元素排序
        int p = (l + r) / 2;
        // 对[l,p]进行排序
        sortArrayIncr(nums, l, p);
        // 对[p+1,r]进行排序
        sortArrayIncr(nums, p + 1, r);
        // merge操作[l,p],[p+1,r]
        merge(nums, l, p, r);
    }

    private void merge(int[] nums, int l, int p, int r) {
        int arr[] = new int[r - l + 1];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = nums[l + i];
        }
        // [0,p-l]和[p-l+1, r]
        int m = 0;
        int n = p - l + 1;
        int k = l;
        for (; k <= r; k++) {
            if (m > p - l) {
                nums[k] = arr[n++];
            } else if (n > r - l) {
                nums[k] = arr[m++];
            } else if (arr[n] > arr[m]) {
                nums[k] = arr[m++];
            } else {
                nums[k] = arr[n++];
            }
        }

    }

5.快速排序

5.1 思想

快速排序其实也是采用分治的思想,首先获取一个元素e,然后采用partition操作,将整个数组中比元素e小的元素放在左边,比元素e大的元素放在右边。然后递归,左边执行该操作,右边也执行该操作,最后便能排好序。

那partition操作是如何进行的呢,如图所示

                             

假设的[l+1,p)<=e,此时p表示下一个要处理的元素,[q,r]的元素都大于e。所以如果待处理元素arr[p]<=e的话,直接p++,如果arr[p]>e的话,直接交换arr[q-1]和arr[p]。

5.2 代码

class Solution {
    public int[] sortArray(int[] nums) {
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    private void quickSort(int[] nums, int left, int right) {
        if (left >= right) {
            return;
        }
        // 执行partition操作
        int p = partition(nums, left, right);
        // 左边排序
        quickSort(nums, left, p - 1);
        // 右边排序
        quickSort(nums, p + 1, right);

    }

    private int partition(int[] nums, int left, int right) {
        // 随机一个元素和第一个元素交换
        Random random = new Random();
        swap(nums, left, random.nextInt(right - left) + left);
        // 取出待处理元素
        int e = nums[left];
        // [left+1,p) <=e,待处理元素为[p,q)
        int p = left + 1;
        // [q,right] > e
        int q = right + 1;
        // 无待处理元素
        while (p < q) {
            if (nums[p] <= e) {
                p++;
            } else {
                // 交换p和q-1的元素
                q--;
                swap(nums, p, q);
            }
        }
        // 其中p-1为最后一个小于e的元素
        swap(nums, left, p - 1);
        return p - 1;
    }

    private void swap(int[] nums, int p, int q) {
        int num = nums[q];
        nums[q] = nums[p];
        nums[p] = num;
    }
}

5.3 优化

5.3.1 如果数组本来就近乎有序

这个时候,由于递归树会失衡,可能退化为O(n*n)的时间复杂度。所以我们在获取元素e的时候可以不直接获取第一个元素,而是从待排序数组中随机取一个元素,作为元素e。

5.3.2 如果数组有大量重复元素

大量重复元素的话,可以考虑3路快排,即小于e的占一个区间,等于e的占一个区间,大于e的占一个区间。

6.排序思想的应用

快排的partition操作和归并排序的merge操作,是一种很好的思想,在算法题中右很多应用。

6.1 leetcode 215 

6.1.1 题目描述

6.1.2 思路

可以利用的快排的partition操作,假设partition操作完成后,会找到第p大的元素,如果k小于p,便在[0,p]的范围内搜索第k大的元素,如果k>p,便在[p+1,nums.length-1]的范围内搜索第k大的元素。

6.1.3 代码

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return findKthLargest(nums, 0, nums.length - 1, nums.length - k);
    }

    // 寻找[l,r]位置下标为k的元素
    public int findKthLargest(int[] nums, int l, int r, int k) {

        // partition
        int p = partition(nums, l, r);

        if (k == p) {
            return nums[k];
        }
        // [l,p-1]寻找下标为k的元素
        if (k < p) {
            return findKthLargest(nums, l, p - 1, k);
        } else {
            // [p+1,r]寻找k-p-1大元素
            return findKthLargest(nums, p + 1, r, k);
        }

    }

    // 将[l,r]执行partition
    private int partition(int[] nums, int l, int r) {
        Random random = new Random();
        swap(nums, l, random.nextInt(r - l + 1) + l);
        int e = nums[l];
        // [l+1,p) <= e
        int p = l + 1;
        // [q,r] > e
        int q = r + 1;
        while (p < q) {
            if (nums[p] <= e) {
                p++;
            } else {
                q--;
                swap(nums, p, q);
            }
        }
        swap(nums, p - 1, l);
        return p - 1;

    }

    private void swap(int[] nums, int p, int q) {
        int tmp = nums[p];
        nums[p] = nums[q];
        nums[q] = tmp;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值