LeetCode力扣刷题——千奇百怪的排序算法

排序算法


一、常见的排序算法

        以下是一些最基本的排序算法。虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深自己对算法的基本理解,以及解出由这些排序算法引申出来的题目。

快速排序(Quicksort

        我们采用左闭右开的二分写法。
void quick_sort(vector &nums, int l, int r) {
    if (l + 1 >= r) {
        return; 
    }
    int first = l, last = r - 1, key = nums[first];
    while (first < last){
        while(first < last && nums[last] >= key) {
            --last;
        }
        nums[first] = nums[last];
        while (first < last && nums[first] <= key) {
            ++first;
        }
        nums[last] = nums[first];
    }
    nums[first] = key;
    quick_sort(nums, l, first);
    quick_sort(nums, first + 1, r);
}

归并排序(Merge Sort

void merge_sort(vector &nums, int l, int r, vector &temp) {
    if (l + 1 >= r) {
        return; 
    }
    // divide
    int m = l + (r - l) / 2;
    merge_sort(nums, l, m, temp);
    merge_sort(nums, m, r, temp);
    // conquer
    int p = l, q = m, i = l;
    while (p < m || q < r) {
        if (q >= r || (p < m && nums[p] <= nums[q])) {
            temp[i++] = nums[p++];
        } else {
            temp[i++] = nums[q++];
        } 
    }
    for (i = l; i < r; ++i) {
        nums[i] = temp[i];
    } 
}

插入排序(Insertion Sort

void insertion_sort(vector &nums, int n) {
    for (int i = 0; i < n; ++i) {
        for (int j = i; j > 0 && nums[j] < nums[j-1]; --j) {
            swap(nums[j], nums[j-1]);
        } 
    } 
}

冒泡排序(Bubble Sort

void bubble_sort(vector &nums, int n) {
    bool swapped;
    for (int i = 1; i < n; ++i) {
        swapped = false;
        for (int j = 1; j < n - i + 1; ++j) {
            if (nums[j] < nums[j-1]) {
                swap(nums[j], nums[j-1]);
                swapped = true; 
            } 
        }
        if (!swapped) {
            break;
        } 
    } 
}

选择排序(Selection Sort

void selection_sort(vector &nums, int n) {
    int mid;
    for (int i = 0; i < n - 1; ++i) {
        mid = i;
        for (int j = i + 1; j < n; ++j) {
            if (nums[j] < nums[mid]) {
                mid = j;
            }
        }
        swap(nums[mid], nums[i]);
    } 
}
以上排序代码调用方法为:
void sort() {
    vector nums = {1,3,5,7,2,6,4,8,9,2,8,7,6,0,3,5,9,4,1,0};
    vector temp(nums.size());
    sort(nums.begin(), nums.end());
    quick_sort(nums, 0, nums.size());
    merge_sort(nums, 0, nums.size(), temp);
    insertion_sort(nums, nums.size());
    bubble_sort(nums, nums.size());
    selection_sort(nums, nums.size());
}

二、经典问题 

1. 快速选择

215. 数组中的第K个最大元素

215. Kth Largest Element in an Array

        给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

        请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

        你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

        快速选择一般用于求解 k-th Element 问题,可以在 O ( n ) 时间复杂度, O ( 1 ) 空间复杂度完成求 解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢( pivot )即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为
        数组中第 k 个最大元素其实就是求数组中第 nums.length - k 个最小元素(从第0小开始),我们定义target = nums.length - k。先找一个中枢点(pivot),然后遍历其他数字,小于 pivot 的排左边,大于 pivot 的排右边,中枢点是数组中的第几小的数字就确定了,如果 pivot 与 target 相等,直接返回pivot 位置的数字,如果大于 target ,说明要求的数字在左边部分,否则在右边部分。剩下的就是递归了。
class Solution {
public:
    // 主函数
    int findKthLargest(vector& nums, int k) {
        random_shuffle(nums.begin(),nums.end()); // 打乱数组
        int l = 0,r = nums.size() - 1,target = nums.size() - k;
        while(l <= r){
            int mid = quickSelection(nums,l,r);
            if(mid == target){
                return nums[mid];
            }
            if(mid < target){
                l = mid + 1; 
            }else{
                r = mid - 1;
            }
        }
        return nums[l];
    }
    // 辅函数 - 快速选择
    int quickSelection(vector& nums,int l,int r){
        int i = l + 1,j = r;
        while(true){
            while(i < r && nums[i] <= nums[l]){ // i从左往右寻找大于nums[l]的并停止
                ++i;
            }
            while(l < j && nums[j] >= nums[l]){ // j从右往左寻找小于nums[l]的并停止
                --j;
            }
            if(i >= j){ // i和j相遇或擦肩而过
                break;
            }
            swap(nums[i],nums[j]); // 如果没有break,就像快速排序一样一直交换大小值
        }
        swap(nums[l],nums[j]);  // l归位
        return j; // 返回的是这次快排归位的那个数的下标
    }
};

2. 桶排序

347. 前 K 个高频元素

347. Top K Frequent Elements

        给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

        顾名思义,桶排序的意思是为每个值设立一个桶,桶内记录这个值出现的次数(或其它属 性),然后对桶进行排序。针对样例来说,我们先通过桶排序得到四个桶 [1,2,3,4] ,它们的值分别 为 [4,2,1,1] ,表示每个数字出现的次数。
        紧接着,我们对桶的频次进行排序,前 k 大个桶即是前 k 个频繁的数。这里我们可以使用各种 排序算法,甚至可以再进行一次桶排序,把每个旧桶根据频次放在不同的新桶内。针对样例来说, 因为目前最大的频次是 4 ,我们建立 [1,2,3,4] 四个新桶,它们分别放入的旧桶为 [[3,4],[2],[],[1]] , 表示不同数字出现的频率。最后,我们从后往前遍历,直到找到 k 个旧桶。
class Solution {
public:
    vector topKFrequent(vector& nums, int k) {
        unordered_map counts; // 无序映射,用法count[key]=value
        int max_count = 0;
        for(const int & num : nums){ // foreach循环遍历nums数组,每次遍历时把nums中的值赋给num 
            max_count = max(max_count, ++counts[num]); // 找出每个num出现频次最多的为多少次 
        }

        vector> buckets(max_count + 1);
        // vector>a(row,vector);定义一个row*column二维动态数组,并初始化为0,row必须定义
        for(const auto & p : counts){
            buckets[p.second].push_back(p.first);
            // buckets[出现次数].push_back(当前元素);p.first为key值为num,p.second为value值为++counts[num]的值
        }

        vector ans;
        for(int i = max_count; i >= 0 && ans.size() < k; --i){ // 索引越高,所储存的数组中的元素出现的次数越多
            for(const int & num : buckets[i]){
                ans.push_back(num);
                if(ans.size() == k){
                    break;
                }
            }
        }
        return ans;
    }
};

三、巩固练习

451. 根据字符出现频率排序

451. Sort Characters By Frequency

        给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。

        返回 已排序的字符串 。如果有多个答案,返回其中任何一个。

桶排序的变形题。
class Solution {
public:
    string frequencySort(string s) {
        unordered_map counts; 
        int max_count = 0;

        for(const char & num : s){ 
            max_count = max(max_count, ++counts[num]); 
        }

        vector> buckets(max_count + 1);
        for(const auto & p : counts){
            buckets[p.second].push_back(p.first);
        }

        string ans = "";
        for(int i = max_count; i >= 0 ; --i){ 
            for(const char & num : buckets[i]){
                int tmp = i;
                while(tmp--){
                    ans.push_back(num);
                }
            }
        }
        return ans;
    }
};

75. 颜色分类

75. Sort Colors

        给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

        我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

        必须在不使用库的sort函数的情况下解决这个问题。

很经典的荷兰国旗问题,考察如何对三个重复且打乱的值进行排序。
class Solution {
public:
    // 主函数
    void sortColors(vector& nums) {
        quick_sort(nums,0,nums.size());
    }
    // 辅函数 - 快速排序
    void quick_sort(vector &nums, int l, int r) {
        if (l + 1 >= r) {
            return; 
        }
        int first = l, last = r - 1, key = nums[first];
        while (first < last){
            while(first < last && nums[last] >= key) {
                --last;
            }
            nums[first] = nums[last];
            while (first < last && nums[first] <= key) {
                ++first;
            }
            nums[last] = nums[first];
        }
        nums[first] = key;
        quick_sort(nums, l, first);
        quick_sort(nums, first + 1, r);
    }
};

欢迎大家共同学习和纠正指教

你可能感兴趣的:(LeetCode,数据结构与算法——经典题目,每日一练:经典算法题,算法,leetcode,c++,数据结构,c语言)