跳转至

排序*

最重要的问题就是 Kth Element

需要日常复习快排。

复习排序算法,以 leetcode 912 排序数组为例复习各种排序算法

排序的本质就是消除逆序数,可以证明对于随机数组,逆序数是 O(n^2)的,而如果采用“交换相邻元素”的办法来消除逆序,每次正好只消除一个,因此必须执行 O(n^2)的交换次数,这就是为什么冒泡、插入等算法只能到平方级别的原因,反过来,基于交换元素的排序要想突破这个下界,必须执行一些比较,交换相隔比较远的元素,使得一次交换能消除多个逆序,希尔、快排、堆排等等算法都是交换比较远的元素,只不过规则各不同罢了.

排序算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
冒泡排序 O(N^2) O(N^2) O(1) 稳定
选择排序 O(N^2) O(N^2) O(1) 不稳定
插入排序 O(N^2) O(N^2) O(1) 稳定
快速排序 O(N\log(N)) O(N^2) O(\log(N)) 不稳定
三路快速排序 O(N\log(N)) O(N^2) O(\log(N)) 不稳定
希尔排序 O(N^{1.3}) O(1) 不稳定
桶排序 O(N) O(N) O(\max - \min) 稳定
堆排序 O(N\log(N)) O(N\log(N)) O(1) 不稳定
归并排序 O(N\log(N)) O(N\log(N)) O(N) 稳定
void bubbleSort(vector<int>& nums) {
    for(int i = 0; i < nums.size(); i++) {
        for(int j = 0 ; j < i; j++) {
            if(nums[j] > nums[i]) {
                swap(nums[i], nums[j]);
            }
        }
    }
}
// 每次选最小的与第一个元素交换,不稳定。
void selectSort(vector<int>& nums) {
        for(int i = 0; i < nums.size(); i++) {
            int minIndex = i;
            for(int j = i + 1; j < nums.size(); j++) {
                if(nums[j] < nums[minIndex]) {
                    minIndex = j;
                }
            }
            swap(nums[i], nums[minIndex]);
        }
    }
// 将 i 插入到左侧的有序序列中,局部其实类似冒泡
// 最好 O(N),即数组有序
void insertSort(vector<int>& nums) {
    for(int i = 0; i < nums.size(); i++) {
        for(int j = i; j > 0 && nums[j] < nums[j - 1]; j--) {
            swap(nums[j - 1], nums[j]);
        }
    }
}
void quickSort(vector<int>& nums, int l, int r) {
    if(l >= r) {
        return;
    }
    swap(nums[l], nums[l + rand() % (r - l + 1)]);
    int finalPos = l + 1;
    // 找到 nums[l] 的值在数组中的最终位置
    // 找到小于基准值的就交换
    for(int i = l + 1; i <= r; i++) {
        if(nums[i] < nums[l]) {
            swap(nums[finalPos++], nums[i]);
        }
    }
    // 需要减 1 是因为要把 finalPos 位置的值交换到 l 位置,要用小于基准值的值交换
    swap(nums[l], nums[--finalPos]);
    quickSort(nums, l, finalPos - 1);
    quickSort(nums, finalPos + 1, r);
}
// 快排的非递归就是用栈存子区间的边界
void quickSort(vector<int>& nums) {
    stack<pair<int, int>> st;
    st.push({0, nums.size() - 1});
    while(!st.empty()) {
        int l = st.top().first;
        int r = st.top().second;
        st.pop();
        swap(nums[l], nums[l + rand() % (r - l + 1)]);
        int finalPos = l + 1;
        for(int i = l + 1; i <= r; i++) {
            if(nums[i] < nums[l]) {
                swap(nums[i], nums[finalPos++]);
            }
        }
        swap(nums[l], nums[--finalPos]);
        if(l < finalPos - 1) {
            st.push({l, finalPos - 1});
        }
        if(r > finalPos + 1) {
            st.push({finalPos + 1, r});
        }
    }
}
// 适用于数据中含大量重复元素
void quickSort(vector<int>& nums, int l, int r) {
    if(l >= r) {
        return;
    }
    int pivot = nums[l + rand() % (r - l + 1)];
    int left = l, right = r;
    // 普通快排的每次找到的是第一个大于基准的位置
    // 三路快排的左指针找到的是第一个等于基准的位置
    for(int i = left; i <= right;) {
        if(nums[i] == pivot) {
            i++;
        }else if(nums[i] < pivot) {
            swap(nums[i++], nums[left++]);
        }else {
            // 当前值大于基准值,与右侧交换,此时右侧还未访问到,所以 i 不递增
            swap(nums[i],nums[right--]);
        }
    }
    // [left, right] 间的值等于基准值
    quickSort(nums, l, left - 1);
    quickSort(nums, right + 1, r);
}
// 缩小增量的插入排序
// 时间复杂度与选取的增量序列有关
void shellSort(vector<int>& nums) {
    int n = nums.size();
    int step = 1;
    while(step < n / 3) {
        step = step * 3 + 1;
    }
    while(step >= 1) {
        for(int i = step; i < n; i++) {
            for(int j = i; j >= step && nums[j - step] > nums[j]; j -= step) {
                swap(nums[j - step], nums[j]);
            }
        }
        step /= 3;
    }
}
// 桶排只适用于最大值与最小值差距不大,且值为整数的数据排序。
vector<int> bucketSort(vector<int>& nums) {
    if(nums.size() == 0) {
        return {};
    }
    int low = *min_element(nums.begin(), nums.end());
    int high = *max_element(nums.begin(), nums.end());
    // 桶的大小
    int bucketSize = high - low + 1;
    vector<int> bucket(bucketSize);
    for(auto num : nums) {
        bucket[num - low]++;
    }
    vector<int> res;
    for(int i = 0; i < bucketSize; i++) {
        for(int j = 0; j < bucket[i]; j++) {
            res.push_back(i + low);
        }
    }
    return res;
}
// 堆是完全二叉树,所以方便用数组表示
// 建堆的时间复杂度为 O(n)
void adjust(vector<int> &nums, int rootPos, int size) {
    while(rootPos + 1 < size - rootPos) {
        int left = 2 * rootPos + 1;
        int right = 2 * rootPos + 2;
        int maxIndex = (right < size && nums[right] > nums[left] ? right : left);
        if(nums[maxIndex] > nums[rootPos]) {
            swap(nums[maxIndex], nums[rootPos]);
        }else {
            // 满足条件则不需要再调整
            break;
        }
        // 当前节点下沉
        rootPos = maxIndex;
    }
}
void headSort(vector<int> &nums) {
    int n = nums.size();
    for(int i = n / 2; i >= 0; i--) {
        adjust(nums, i, n);
    }
    // 出堆操作,把最大值放到最后,size--
    for(int i = n - 1; i > 0; i--) {
        swap(nums[0], nums[i]);
        adjust(nums, 0, i);
    }
}
// 先把左右排序 然后合并
vector<int> mergeSort(vector<int>& nums, int l, int r) {
    if(l > r) {
        return {};
    }
    if(l == r) {
        return {nums[l]};
    }
    vector<int> res(r - l + 1);
    int mid = l + (r - l) / 2;
    auto left = mergeSort(nums, l, mid);
    auto right = mergeSort(nums, mid + 1, r);
    int i = 0, j = 0;
    int cnt = 0;
    while(i < (mid - l + 1) && j < (r - mid)) {
        if(left[i] < right[j]) {
            res[cnt++] = left[i++];
        }else {
            res[cnt++] = right[j++];
        }
    }
    while(i < mid - l + 1) {
        res[cnt++] = left[i++];
    }
    while(j < r - mid) {
        res[cnt++] = right[j++];
    }
    return res;
}

最后更新: July 23, 2022