10种排序算法:题目+思路+代码+参考

题目:215. 数组中的第K个最大元素 - 力扣(LeetCode)

1 冒泡排序

时间复杂度O(n^2)

思路:

每次相邻的元素做比较,将较大或较小的数冒泡到最后。

外层循环表示扫描的趟数,每扫描一趟就有一个数放到应该的位置,n个数所以扫描n趟,但当n-1个数都到正确的位置时,最后一个数也一定是在正确的位置,所以扫描n-1趟就行了。

内层循环表示从头到尾扫描,第i趟已经有i个数排好了,所以可以不用再扫描了,扫到n-1-i就行了。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        // 扫描趟数
        for(int i = 0; i < n - 1; i++) {
            // 比较次数
            for(int j = 0; j < n - 1 - i; j++) {
                if(nums[j] < nums[j + 1]) {
                    int t = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = t;
                }
            }
        }
        return nums[k - 1];
    }
}

2 选择排序

时间复杂度O(n^2)

思路:

每次选择出当前位置之后的最大或最小的数和当前位置做交换。

外层循环表示扫描的趟数,每扫描一趟就有一个数到应该的位置,n个数所以扫描n趟,但当n-1个数都到正确的位置时,最后一个数也一定是在正确的位置,所以扫描n-1趟就行了。

内层循环表示从第i + 1个数到结尾,把最大(最小)的数和第i个位置的数做交换。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        //扫描趟数
        for(int i = 0; i < n - 1; i++) {
            // 记录最大的数的下标
            int max = i;
            for(int j = i + 1; j < n; j++) {
                if(nums[j] > nums[max]) {
                    max = j;
                }
            }
            // 交换,放到对应的位置
            int t = nums[max];
            nums[max] = nums[i];
            nums[i] = t;
        }
        return nums[k - 1];
    }
}

3 插入排序

时间复杂度O(n^2)

思路:

把数组分为两部分,前半部分是排好的序列,依次把后面的数插入到前面序列正确的位置。

第0个元素看作排好的序列,所以从第1个元素开始确定位置。

j表示插入的位置,从当前位置开始向前遍历,如果要确认的元素比插入位置的前一个数大(小)就说明这个位置不是插入的位置,把前一个位置处的元素后移。

最后把i元素放到j插入位置处。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        // 第0个数是有序的,从第1个数开始确定
        for(int i = 1; i < n; i++) {
            int cur = nums[i];
            // j 表示插入的位置
            int j = i;
            // 当前数比插入的位置的前一个数大,就不符合,直到小于等于前一个数
            while(j > 0 && cur > nums[j - 1]) {
                nums[j] = nums[j - 1];
                j--;
            }
            // 把插入位置赋值为当前元素
            nums[j] = cur;
        }
        return nums[k - 1];
    }
}

4 希尔排序

时间复杂度O(nlogn)

思路:

是插入排序的优化。

外层循环按照步长每次分组,内层循环在组内进行插入排序。

数据结构——三分钟搞定希尔排序_哔哩哔哩_bilibili

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        int inter = 2;
        // 步长进行分组
        for(int step = n / inter; step >= 1; step /= inter) {
            // 组内进行插入排序
            for(int i = step; i < n; i++) {
                int cur = nums[i];
                int j = i;
                while(j - step >= 0 && cur > nums[j - step]) {
                    nums[j] = nums[j - step];
                    j -= step;
                }
                nums[j] = cur;
            }
        }
        return nums[k - 1];
    }
}

5 归并排序

时间复杂度O(nlogn)

思路:

每次从中间分开,分别排序,之后合并在一起,递归执行,直到元素个数为1。

代码:

class Solution {
    private int[] temp;
    
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        temp = new int[n];
        mergeSort(nums, 0, n - 1);
        return nums[k - 1];
    }
    
    private void mergeSort(int[] nums, int l, int r) {
        if(l >= r) return;
        // 中间分开
        int mid = l + (r - l) / 2;
        // 排序左边
        mergeSort(nums, l, mid);
        // 排序右边
        mergeSort(nums, mid + 1, r);
        // 合并
        int i = l, j = mid + 1, p = 0;
        while(i <= mid && j <= r) {
            if(nums[i] < nums[j]) {
                temp[p++] = nums[j];
                j++;
            }
            else {
                temp[p++] = nums[i];
                i++;
            }
        }
        while(i <= mid) {
            temp[p++] = nums[i++];
        }
        while(j <= r) {
            temp[p++] = nums[j++];
        }
        // 赋值回去
        for(int k = 0; k < p; k++) {
            nums[l + k] = temp[k];
        }
    }
}

6 快速排序

时间复杂度O(nlogn)

思路:

每次随机选一个数作为标准位,这里选择左边界的数,左指针找到比标准位小(大)的数,右指针找到比标准位大(小)的数,两者交换,最终把数组分为两部分,左半部分都是比标准位大(小)的,右半部分都是比标准位小(大)的。继续递归两部分。

代码:

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

    private void quickSort(int[] nums, int l, int r) {
        if(l >= r) return;
        // x 为标准位
        int x = nums[l], i = l - 1, j = r + 1;
        while(i < j) {
            // 找到比标准位小的
            do i++; while(nums[i] > x);
            // 找到比标准位大的
            do j--; while(nums[j] < x);
            if(i < j) {
                int t = nums[i];
                nums[i] = nums[j];
                nums[j] = t;
            }
        }
        // 排序左部分
        quickSort(nums, l, j);
        // 排序右部分
        quickSort(nums, j + 1, r);
    }

}

7 堆排序

时间复杂度O(nlogn)

思路:

使用堆数据结构,建立小根堆,从后向前依次确定位置,将后边的位置处的元素和根位置处的元素做交换,然后在指定范围内调整堆。

【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        heapSort(nums);
        return nums[k - 1];
    }
    private void heapSort(int[] nums) {
        int n = nums.length;
        build(nums);
        // 每次把根元素放到最后,然后从0到排除刚刚放的结点做一个调整,即可得到有序序列
        for(int i = n - 1; i > 0; i--) {
            int t = nums[0];
            nums[0] = nums[i];
            nums[i] = t;
            adjust(nums, 0, i);
        }
    }
    private void build(int[] nums) {
        int n = nums.length;
        // 从最后一个非叶子结点开始,依次调整
        for(int i = n/ 2 - 1; i >= 0; i--) {
            adjust(nums, i, n);
        }
    }
    private void adjust(int[] nums, int root, int n) {
        // 左结点
        int l = 2*root + 1;
        // 右结点
        int r = 2*root + 2;
        // 记录最小的位置
        int min = root;
        if(l < n && nums[l] < nums[min]) {
            min = l;
        }
        if(r < n && nums[r] < nums[min]) {
            min = r;
        }
        if(min != root) {
            int t = nums[min];
            nums[min] = nums[root];
            nums[root] = t;
            // 继续递归调整交换后的位置
            adjust(nums, min, n);
        }
    }
}

8 计数排序

时间复杂度O(n + k),k和存储的元素的最大值和最小值有关

思路

先统计出数组的最大值和最小值,然后将数组范围的数组映射到新的数组中,统计每一个数字出现的次数,最后按照顺序覆盖原数组即可。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        int max = nums[0];
        int min = nums[0];
        // 找到最大值和最小值
        for(int i = 0; i < n; i++) {
            if(nums[i] > max) {
                max = nums[i];
            }
            if(nums[i] < min) {
                min = nums[i];
            }
        }
        int[] bucket = new int[max - min + 1];
        // 统计每一个数字的个数
        for(int i = 0; i < n; i++) {
            bucket[nums[i] - min]++;
        }
        int p = 0;
        // 依次覆盖
        for(int i = bucket.length - 1; i >= 0; i--) {
            int cnt = bucket[i];
            if(cnt != 0) {
                for(int j = 0; j < cnt; j++) {
                    nums[p++] = i + min;
                }
            }
        }
        return nums[k - 1];
    }
    
}

9 桶排序

时间复杂度O(n + k),k和桶选择的排序方法有关。

思路:

将数组中的数字根据范围放到不同的桶中,然后遍历每一个桶,采用排序算法对桶中数字进行排序,并覆盖原数组。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        int max = nums[0];
        int min = nums[0];
        // 找到最大值和最小值
        for(int i = 0; i < n; i++) {
            if(nums[i] > max) {
                max = nums[i];
            }
            if(nums[i] < min) {
                min = nums[i];
            }
        }
        // 分组:元素个数/桶大小 向上取整,不足1的视为1
        List[] bucket = new List[(max - min + 1 + 9)/10];
        Arrays.setAll(bucket, a -> new ArrayList<>());
        // 数字放入桶中
        for(int i = 0; i < n; i++) {
            bucket[(nums[i] - min)/10].add(nums[i]);
        }
        int p = 0;
        for(int i = bucket.length - 1; i >= 0; i--) {
            if(bucket[i].size() != 0) {
                List<Integer> t = bucket[i];
                // 桶中元素排序
                quick_sort(t, 0, t.size() - 1);
                // 覆盖原数组
                for(int j = 0; j < t.size(); j++) {
                    nums[p++] = t.get(j);
                }
            }
        }
        return nums[k - 1];
    }
    // 快速排序
    private void quick_sort(List<Integer> t, int l, int r) {
        if(l >= r) return;
        int x = t.get(l), i = l - 1, j = r + 1;
        while(i < j) {
            do i++; while(t.get(i) > x);
            do j--; while(t.get(j) < x);
            if(i < j) {
                int temp = t.get(i);
                t.set(i, t.get(j));
                t.set(j, temp);
            }
        } 
        quick_sort(t, l, j);
        quick_sort(t, j + 1, r);
    }
}

10 基数排序

时间复杂度O(n*k),k和最大数字的长度有关

思路:

先求出最大值的位数作为最大位数。然后创建一个桶,依次根据每一个数字的个位、十位、百位...放入桶中,桶的0-9装负数,11-19装正数。

从个位开始遍历每一个位数,遍历每一个数字,取出对应的位数放入桶中,然后遍历每一个桶,覆盖原有的数组。循环执行,直到遍历完所有的位数。因为根据位数放入桶再放回数组,保证了低位的有序性,然后在处理高位的时候,低位自然就是有序的。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int n = nums.length;
        int max = nums[0], maxDigit = 0;
        // 求出最长的位数
        for(int i = 0; i < n; i++) {
            if(nums[i] > max) {
                max = nums[i];
            }
        }
        while(max != 0) {
            max = max / 10;
            maxDigit++;
        }
        int dev = 1, mod = 10;
        for(int i = 0; i < maxDigit; i++, dev *= 10) {
            // 创建桶
            List[] count = new List[20];
            Arrays.setAll(count, a -> new ArrayList<>());
            // 把对应的位数放入桶中
            for(int j = 0; j < n; j++) {
                List<Integer> bucket = count[(nums[j]/dev)%mod + 10];
                bucket.add(nums[j]);
            }
            int p = 0;
            // 遍历每一个桶,覆盖原数组
            for(int j = 19; j >= 0; j--) {
                List<Integer> bucket = count[j];
                if(bucket.size() != 0) {
                    for(int num : bucket) {
                        nums[p++] = num;
                    }
                }
            }
        }
        return nums[k - 1];
    }
}

参考

1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)

215. 数组中的第K个最大元素 - 力扣(LeetCode)题解

数据结构——三分钟搞定希尔排序_哔哩哔哩_bilibili

【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值