10-千奇百怪的排序算法

简介: 10-千奇百怪的排序算法


排序算法分类

基于比较的排序

通过比较大小来决定元素间的相对次序

可以证明时间复杂度下界为O(NlogN)——不可能突破这个复杂度达到更快

非比较类排序

不通过比较大小来决定元素间的相对次序

时间复杂度受元素的范围以及分布等多种因素影响,不单纯取决于元素数量N

基于比较的排序

初级排序算法

选择排序(Selection Sort)——“该放哪个数了?“

每次从未排序数据中找最小值,放到已排序序列的末尾

插入排序(Insertion Sort)——“这个数该放哪儿?”

从前到后依次考虑每个未排序数据,在已排序序列中找到合适位置插入

冒泡排序(Bubble Sort)

不断循环扫描,每次查看相邻的元素,如果逆序,则交换

平均时间复杂度均为O(N2)

堆排序

堆排序(Heap Sort)是对选择排序的优化——利用二叉堆高效地选出最小值

建立一个包含所有N个元素的二叉堆

重复N次从堆中取出最小值,即可得到有序序列

时间复杂度O(NlogN)

void heap_sort(vector<int>& a, int n) {
        priority_queue<int> q;
        for(int i = 0; i < n; i++) { 
            q.push(-a[i]);
        }
        for(int i = 0; i < n; i++) { 
            a[i] = -q.top();
            q.pop();
        } 
    }

希尔排序 (不太重要)

希尔排序(Shell Sort)是对插入排序的优化——增量分组插入排序

希尔排序的时间复杂度取决于增量序列(步长序列)的选取目前已知的最好序列可以做到o(N4/3)或o(Nlog2N)

归并排序

归并排序(Merge Sort)是一个基于分治的算法

时间复杂度O(NlogN)

原问题:把数组排序

子问题:把数组前一半、后一半分别排序

然后再合并左右两半(两个有序数组)就可以了

public static void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
        if (l >= r) return;
        int mid = (l + r) >> 1; // (l + r) / 2
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }
    static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; //
        int i = left, j = mid + 1;
        for (int k = 0; k < temp.length; k++) { //
        if (j > right || (i <= mid && arr[i] <= arr[j]))
            temp[k] = arr[i++];
        else
            temp[k] = arr[j++];
        }
        for (int k = 0; k < temp.length; k++) { //
            arr[left + k] = temp[k];
        } 
    }

快速排序

快速排序(Quick Sort)也是一个基于分治的算法

  • 从数组中选取中轴元素pivot
  • 将小元素放在pivot左边,大元素放在右边
  • 然后分别对左边和右边的子数组进行快排

快速排序和归并排序具有相似性,但步骤顺序相反

  • 归并排序:先排序左右子数组,然后合并两个有序数组
  • 快速排序:先调配出左右子数组,然后对左右子数组分别进行排序

随机选取pivot,期望时间复杂度O(NlogN)

快速排序可以通过适当的交换来原地实现数组调配,避免占用额外空间最经典和高效的调配方式叫作 Hoare Partition

public static void quickSort(int[] arr, int l, int r) {
        if (l >= r) return;
        int pivot = partition(arr, l, r);
        quickSort(arr, l, pivot);
        quickSort(arr, pivot + 1, r);
    }
    static int partition(int[] a, int l, int r) {
        int pivot = l + (int)(Math.random() * (r - l + 1));
        int pivotVal = a[pivot];
        while (l <= r) {
            while (a[l] < pivotVal) l++;
            while (a[r] > pivotVal) r--;
            if (l == r) break;
            if (l < r) {
                int temp = a[l]; a[l] = a[r]; a[r] = temp;
                l++; 
                r--; 
            } 
        }
        return r;
    }
void quickSort(vector<int>& arr, int l, int r) {
        if (l >= r) return;
        int pivot = partition(arr, l, r);
        quickSort(arr, l, pivot);
        quickSort(arr, pivot + 1, r);
    }
    int partition(vector<int>& a, int l, int r) {
        int pivot = l + rand() % (r - l + 1);
        int pivotVal = a[pivot];
        while (l <= r) {
            while (a[l] < pivotVal) l++;
            while (a[r] > pivotVal) r--;
            if (l == r ) break;
            if (l < r) {
                swap(a[l++] ,a[r--]);
            }
        }
        return r;
    }

非比较类排序

计数排序(Counting Sort)

计数排序要求输入的数据必须是有确定范围的整数。将输入的数据作为key存储在额外的数组中,然后依次把计数大于1的填充回原数组

时间复杂度O(N+M),N为元素个数,M为数值范围

桶排序( Bucket Sort)

桶排序假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能使用别的排序算法,或是以递归方式继续使用桶排序)

时间复杂度O(N)~o(N^2)

基数排序(Radix Sort)

基数排序把数据切割成一位位数字(O-9),从低位到高位对每一位分别进行计数排序时间复杂度O(NK),K为数字位数

排序的稳定性

对于序列中存在的若干个关键字相等的元素

如果排序前后它们的相对次序一定保持不变,就称排序算法是稳定的否则就称排序算法是不稳定的

插入、冒泡、归并、计数、基数和桶排序是稳定的

选择、希尔、快速、堆排序是不稳定的

排序阵营九宫格

实战

912.排序数组

https://leetcode.cn/problems/sort-an-array/

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        heap_sort(nums,nums.size());
        //quickSort(nums ,0 ,nums.size() - 1);
        //sort(nums.begin(),nums.end());
        return nums;
    }
    void heap_sort(vector<int>& a, int n) {
        priority_queue<int> q;
        for(int i = 0; i < n; i++) { 
            q.push(-a[i]);
        }
        for(int i = 0; i < n; i++) { 
            a[i] = -q.top();
            q.pop();
        } 
    }
    void quickSort(vector<int>& arr, int l, int r) {
        if (l >= r) return;
        int pivot = partition(arr, l, r);
        quickSort(arr, l, pivot);
        quickSort(arr, pivot + 1, r);
    }
    int partition(vector<int>& a, int l, int r) {
        int pivot = l + rand() % (r - l + 1);
        int pivotVal = a[pivot];
        while (l <= r) {
            while (a[l] < pivotVal) l++;
            while (a[r] > pivotVal) r--;
            if (l == r ) break;
            if (l < r) {
                swap(a[l++] ,a[r--]);
            }
        }
        return r;
    }
};
class Solution {
    public int[] sortArray(int[] nums) {
        //mergeSort(nums,0 ,nums.length - 1);
        quickSort(nums,0 ,nums.length - 1);
        return nums;
    }
    // void heap_sort(int a[], int n) {
    //     priority_queue<int> q;
    //     for(int i = 0; i < n; i++) { 
    //         q.push(-a[i]);
    //     }
    //     for(int i = 0; i < n; i++) { 
    //         a[i] = -q.top();
    //         q.pop();
    //     } 
    // }
    public static void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
        if (l >= r) return;
        int mid = (l + r) >> 1; // (l + r) / 2
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }
    static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; //
        int i = left, j = mid + 1;
        for (int k = 0; k < temp.length; k++) { //
        if (j > right || (i <= mid && arr[i] <= arr[j]))
            temp[k] = arr[i++];
        else
            temp[k] = arr[j++];
        }
        for (int k = 0; k < temp.length; k++) { //
            arr[left + k] = temp[k];
        } 
    }
    public static void quickSort(int[] arr, int l, int r) {
        if (l >= r) return;
        int pivot = partition(arr, l, r);
        quickSort(arr, l, pivot);
        quickSort(arr, pivot + 1, r);
    }
    static int partition(int[] a, int l, int r) {
        int pivot = l + (int)(Math.random() * (r - l + 1));
        int pivotVal = a[pivot];
        while (l <= r) {
            while (a[l] < pivotVal) l++;
            while (a[r] > pivotVal) r--;
            if (l == r) break;
            if (l < r) {
                int temp = a[l]; a[l] = a[r]; a[r] = temp;
                l++; 
                r--; 
            } 
        }
        return r;
    }
}

1122.数组的相对排序

https://leetcode.cn/problems/relative-sort-array/

比较类排序

  • 利用哈希表对arr2建立数值到索引的映射
  • 自定义比较函数
class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        unordered_map<int, int> arr2orders;
        for (int i = 0;i < arr2.size(); i++) {
            arr2orders[arr2[i]] = i;
        }
        sort(arr1.begin(), arr1.end(), [&](int x, int y) {
            int xPos = arr2orders.find(x) != arr2orders.end() ? arr2orders[x] : arr2.size();
            int yPos = arr2orders.find(y) != arr2orders.end() ? arr2orders[y] : arr2.size();
            return xPos < yPos || (xPos == yPos && x < y);
        });
        return arr1;
    }
};

非比较类排序计数排序

  • 第一遍把计数数组中出现在arr2的数值填充回arr1
  • 第二遍把剩下的填充回arr1
class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        vector<int> count(1001, 0);
        for (int val : arr1) {
            count[val]++;
        }
        vector<int> ans;
        for (int val : arr2) {
            while (count[val] > 0) {
                ans.push_back(val);
                count[val]--;
            }
        }
        for (int val = 0; val <= 1000; val++) {
            while (count[val] > 0) {
                ans.push_back(val);
                count[val]--;
            }
        }
        return ans;
    }
};

56.合并区间

https://leetcode.cn/problems/merge-intervals/

方法一:对区间进行双关键字排序(左右端点)

然后扫描合并(排序后能合并的区间一定是连续的)

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(),
            [](const vector<int>& a, const vector<int>& b) {
                return a[0] < b[0] || (a[0] == b[0] && a[1] < b[1]);
            });
        vector<vector<int>> ans;
        int farthest = -1;
        int start = -1;
        for(const vector<int>& intervals : intervals) {
            int left = intervals[0];
            int right = intervals[1];
            if (left <= farthest) {
                farthest = max(farthest,right);
            }else {
                if (farthest != -1) {
                    ans.push_back({start, farthest});
                }
                start = left;
                farthest = right;
            }
        }
        ans.push_back({start, farthest});
        return ans;
    }
};

方法二:差分思想、关键事件思想

把每个区间[lr]看作一次+1的覆盖,进一步转化为“l处+1”、“r+1处-1”两个事件

把2n个事件排序、扫描,用一个计数变量记录覆盖次数,0变1、1变0时就找到了合并后的区间端点

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<pair<int, int>> events;
        for (const vector<int>& interval : intervals) {
            events.push_back({interval[0], 1});
            events.push_back({interval[1] + 1, -1});
        }
        sort(events.begin(), events.end());
        int covering = 0;
        int start;
        vector<vector<int>> ans;
        for (const pair<int, int>& event : events) {
            if (covering == 0) start = event.first;
            covering += event.second;
            if (covering == 0) {
                ans.push_back({start, event.first - 1});
            }
        }
        return ans;
    }
};

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

https://leetcode.cn/problems/kth-largest-element-in-an-array/

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // sort(nums.begin(), nums.end());
        // return nums[nums.size() - k];
        return quickSort(nums, 0, nums.size() - 1, nums.size() - k);
    }
private:
    int quickSort(vector<int>& arr, int l, int r, int index) {
        if (l >= r) return arr[l];
        int pivot = partition(arr, l, r);
        if (index <= pivot) return quickSort(arr, l, pivot, index);
        else return quickSort(arr, pivot + 1, r, index);
    }
    int partition(vector<int>& a, int l, int r) {
        int pivot = l + rand() % (r - l + 1);
        int pivotVal = a[pivot];
        while (l <= r) {
            while (a[l] < pivotVal) l++;
            while (a[r] > pivotVal) r--;
            if (l == r) break;
            if (l < r) {
                swap(a[l++], a[r--]);
            }
        }
        return r;
    }
};

104.货仓选址

https://www.acwing.com/problem/content/description/106/

在一条数轴上有N家商店,它们的坐标分别为A~An。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。1 ≤N≤ 100000

#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main() {
    int n;
    long long ans = 0;
    cin >> n;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    sort(a, a + n);
    int pos = a[n / 2];
    for (int i = 0; i< n; i++) ans += abs(pos - a[i]);
    cout << ans << endl;
}

493.翻转对

https://leetcode.cn/problems/reverse-pairs/

class Solution {
    public int reversePairs(int[] nums) {
        ans = 0;
        mergeSort(nums, 0, nums.length - 1);
        return ans;
    }
    void mergeSort(int[] arr, int l, int r) { // sort arr[l..r]
        if (l >= r) return;
        int mid = (l + r) >> 1; // (l + r) / 2
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        calculate(arr, l, mid ,r);
        merge(arr, l, mid, r);
    }
    void calculate(int[] arr, int left, int mid, int right) {
        int j = mid;
        for (int i = left; i <= mid; i++) {
            while (j < right && arr[i] > 2l * arr[j + 1]) j++;
            ans += j - mid;
        }
    }
    void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; //
        int i = left, j = mid + 1;
        for (int k = 0; k < temp.length; k++) { //
        if (j > right || (i <= mid && arr[i] <= arr[j]))
            temp[k] = arr[i++];
        else
            temp[k] = arr[j++];
        }
        for (int k = 0; k < temp.length; k++) { //
            arr[left + k] = temp[k];
        } 
    }
    int ans;
}

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

相关文章
|
2月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
17 2
|
8月前
|
搜索推荐 算法 C#
c#排序算法
c#排序算法
|
10月前
|
算法 搜索推荐 Java
常见排序算法详解(2)
(1) 算法过程 比较相邻的元素。如果第一个比第二个大(升序),就交换它们两个; 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,最后的元素应该会是最大的数;
63 0
|
11月前
|
算法 搜索推荐
排序算法的简单认识
在进行很多便捷算法之前总是要实现对象的有序化,而这就将使用到排序相关的算法,即使目前诸多高级语言已然完成对于排序算法的封装,用户只需导入对应库文件即可调用排序算法完成排序,无需手写排序算法,但具体的排序算法的选择就必须对于排序算法有所认识。本文就将介绍两个简单的排序算法:选择排序与冒泡排序。 选择排序 为什么称为选择排序? 该算法每次都是对于未排序的关键字进行比较,选择出最小或最大的关键字,再对其交换位置,实现一次排序,需进行多次比较。 选择排序法是一种不稳定的排序算法。它的工作原理是每一次从待排序的数据元
46 0
|
12月前
|
搜索推荐
排序算法总结
经典排序算法总结
51 0
|
搜索推荐 算法
|
搜索推荐 程序员 C语言
常见的排序算法(上)
时间如流水,今天就到初阶数据结构最后一个知识章节了,常见的排序算法!在进入这期之前,程爱打篮球的程序猿想说一句,如果有不懂的地方可以反复观看我之前的内容,再还有不懂可以直接找我,帮你安排的妥妥的!
常见的排序算法(上)
|
算法 搜索推荐 C++
一篇解决排序算法
从第一篇《算法概要》开始,到此篇已经经历了将近四个月时间,常见的基础排序已经温习完成
574 0
一篇解决排序算法
|
算法 搜索推荐 Windows
快排序算法(下)
快排序算法(下)
快排序算法(下)
|
搜索推荐 算法
排序算法详解
排序算法详解
134 0
排序算法详解