排序核心算法(c++)

27 篇文章 0 订阅

排序复杂度汇总

排序算法复杂度汇总
方法平均时间复杂度最好时间复杂度最坏时间复杂度辅助空间稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n)O(n^2)O(1)稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn)-O(n)不稳定
希尔排序O(nlogn)-O(n^2)O(n^1.3)O(n^2)O(1)不稳定

一、平均时间复杂度为O(n^2)的算法

1. 冒泡排序

void BubbleSort(vector<int> &data) {
    // 1 辅助变量
    bool flag; //若某一次循环不需要交换说明已经完全有序,提前结束
    int tmp;
    // 2 正式排序
    for (int i=1; i<data.size(); ++i) {
        flag = true;
        for (int j=0; j<data.size() - i; ++j) {
            if (data[j] > data[j+1]) {
                tmp = data[j];
                data[j] = data[j+1];
                data[j+1] = tmp;
                flag = false;
            }
        }
        if (flag) break;
    }
}

 时间复杂度:平均时间复杂度O(n^2), 最好时间复杂度O(n), 最坏时间复杂度O(n^2)

是否原地算法:额外空间复杂度为O(1),是原地算法

是否稳定排序:是,因为只有交换会影响位置,而相等时不交换因此是稳定的

2. 插入排序

思路:每次从后往前找当前元素的位置,同时移动数据,找到位置后将当前元素放到合适的位置,注意数据保存

void InsertionSort(vector<int> &data) {
    for (int i=1; i<data.size(); ++i) {
        int j = i - 1;
        int curr = data[i]; // 保存当前元素
        // 从后往前找当前元素的正确位置,顺便向后移动数据
        for (; j>=0; --j) {
            if (data[j] > curr) {
                data[j+1] = data[j];
            }
            else {
                break;
            }
        }
        data[j+1] = curr;
    }
}

 时间复杂度:平均时间复杂度O(n^2), 最好时间复杂度O(n), 最坏时间复杂度O(n^2)

是否原地算法:额外空间复杂度为O(1),是原地算法

是否稳定排序:是

3. 选择排序

void SelectionSort(vector<int> &data) {
    for (int i=0; i<data.size(); ++i) {
        int min_idx = i;
        for (int j=i+1; j<data.size(); ++j) {
            if (data[j] < data[min_idx]) min_idx = j;
        }

        if (min_idx != i) {
            int tmp = data[min_idx];
            data[min_idx] = data[i];
            data[i] = tmp;
        }
    }
}

时间复杂度:平均时间复杂度O(n^2), 最好时间复杂度O(n^2), 最坏时间复杂度O(n^2)

是否原地算法:额外空间复杂度为O(1),是原地算法

是否稳定排序:每次都取最小交换,可能改变相同元素的相对位置,不是稳定的

二、平均时间复杂度为O(nlogn)的算法

1.归并排序

参考归并排序思路

void Merge(vector<int> &data, int start1, int end1, int start2, int end2) {
    vector<int> sorted_data;
    int i = start1;
    int j = start2;

    // 1.两个分段都有数据
    while (i <= end1 && j <= end2) {
        int tmp = data[i] < data[j] ? data[i++] : data[j++];
        sorted_data.push_back(tmp);
    }
    // 2.第一个分段还有数据
    while (i <= end1) {
        sorted_data.push_back(data[i++]);
    }
    // 3.第二个人分段还有数据
    while (j <= end2) {
        sorted_data.push_back(data[j++]);
    }
    // 4.拷贝数据
    for (int k=0; k<sorted_data.size(); ++k) {
        data[start1+k] = sorted_data[k];
    }
}
void MergeSortRecursion(vector<int> &data, int start, int end) {
    // 1.终止条件
    if (start >= end) return;
    // 2.划分
    int mid = (start + end) / 2;
    MergeSortRecursion(data, start, mid);
    MergeSortRecursion(data, mid+1, end);
    // 3.合并
    Merge(data, start, mid, mid+1, end);
};
void MergeSort(vector<int> &data) {
    MergeSortRecursion(data, 0, data.size()-1);
}

时间复杂度:平均时间复杂度O(nlogn), 最好时间复杂度O(nlogn), 最坏时间复杂度O(nlogn)

是否原地算法:额外空间复杂度为O(n),不是原地算法

是否稳定排序:是否稳定主要看Merge函数,当前一个分段的数据和后一个分段数据相等时,先将前一个分段数据放入结果中就是稳定的,也就是

int tmp = data[i] <= data[j] ? data[i++] : data[j++];
sorted_data.push_back(tmp);

引申:归并排序利用的是分支思想,利用这种方法可以解决逆序度求解问题, 参考 分治算法介绍

2.快速排序

2.1 自己的实现

思路参考5.快速排序

void QuickSortRecursion(vector<int> &data, int start, int end) {
    // 1.终止条件
    if (start >= end) return;
    // 2.当前处理
    int left = start;
    int right = end;
    int backup = data[left];
    bool direction = false;
    while (left < right) {
        // 从左往右
        if (direction) {
            if (data[left] <= backup) {
                ++left;
            }
            else {
                data[right] = data[left];
                --right;
                direction = false;
            }
        }
        // 从右往左
        else {
            if (data[right] > backup) {
                --right;
            }
            else {
                data[left] = data[right];
                ++left;
                direction = true;
            }
        }
    }
    data[left] = backup;

    // 3.划分递归
    QuickSortRecursion(data, start, left-1);
    QuickSortRecursion(data, left+1, end);
}
void QuickSort(vector<int> &data) {
    QuickSortRecursion(data, 0, data.size()-1);
}

时间复杂度:平均时间复杂度O(nlogn), 最好时间复杂度O(nlogn), 最坏时间复杂度O(n^2)

是否原地算法:额外空间复杂度为O(1),是原地算法,但是递归栈占用空间,空间复杂度O(logn)-O(n)

是否稳定排序:由于每次和哨兵元素比较后可能往两个方向移动,因此快速排序不是稳定算法

2.2 更好的实现

思路:将递归和划分分开,划分使用交换,实现更加巧妙简洁

void swap(vector<int> &data, int i, int j) {
    int tmp = data[i];
    data[i] = data[j];
    data[j] = tmp;
}

/**
 * @brief 划分数组并使得最终i之前元素都小于data[i],i之后元素都大于等于data[i],划分元素为data[r]
 * @param data 数组
 * @param l 左边起始位置
 * @param r 右边结束位置
 * @return 划分点位置
 */
int partition(vector<int> &data, int l, int r) {
    int i = l, j = l; // i标记大数,j遍历数组
    for (; j<r; ++j) {
        if (data[j] < data[r]) {
            if (i != j) {
                swap(data, i, j);
            }
            ++i;
        }
    }
    swap(data, i, r);
    return i;
}

/**
 * @brief 快排递归过程
 * @param data 数据
 * @param l 左边起点位置
 * @param r 右边结束位置
 */
void _quick_sort(vector<int> &data, int l, int r) {
    // 1.终止条件
    if (l >= r) return;
    // 2.划分后继续划分
    int pivot = partition(data, l, r);
    _quick_sort(data, l, pivot-1);
    _quick_sort(data, pivot+1, r);
}

/**
 * @brief 快速排序
 * @param data - 原始数据
 */
void quick_sort(vector<int> &data) {
    _quick_sort(data, 0, data.size()-1);
}

2.3 利用快排思想的问题

leetcode215

class Solution {
private:
    // 划分,使得原区间分成三部分,左区间 哨兵 右区间,左区间的元素都小于等于哨兵,右区间的元素都大于哨兵
    // 返回哨兵最终位置
    int partition(vector<int>& nums, int left, int right) {
        bool direction = false;
        int backup = nums[left];
        while (left < right) {
            if (direction) {
                if (nums[left] <= backup) {
                    ++left;
                }
                else {
                    nums[right--] = nums[left];
                    direction = false;
                }
            }
            else {
                if (nums[right] > backup) {
                    --right;
                }
                else {
                    nums[left++] = nums[right];
                    direction = true;
                }
            }
        }
        nums[left] = backup;
        return left;
    }
    void recursion(vector<int>& nums, int left, int right, int k) {
        // 1. 划分
        int pivot = partition(nums, left, right);
        // 2. 终止或者选取子区间
        if (nums.size() - pivot == k) return;
        else if (nums.size() - pivot > k) {
            recursion(nums, pivot+1, right, k);
        }
        else {
            recursion(nums, left, pivot-1, k);
        }
    }
public:
    int findKthLargest(vector<int>& nums, int k) {
        recursion(nums, 0, nums.size()-1, k);
        return nums[nums.size()-k];
    }
};

时间复杂度O(n),空间复杂度O(1) 

2.4 三色旗问题

三色旗问题

思路:先划分0和非0,在划分1和2,每次划分类似于快排的partition,知识判断条件变了,快排是选择最后一个元素,而这一题是指定target,因此最后这道题也不用交换,循环包含最后一个元素

class Solution {
private:
    int partition(vector<int> &nums, int start, int target) {
        int i = start;
        for (int j = start; j < nums.size(); ++j) {
            if (nums[j] == target) {
                if (i != j) {
                    swap(nums[i], nums[j]);
                }
                ++i;
            }
        }
        return i; //下一个划分起点
    }
public:
    void sortColors(vector<int>& nums) {
        // 1.划分0和大于0
        int pivot = partition(nums, 0, 0);
        // 2.划分1和2
        partition(nums, pivot, 1);
    }
};

时间复杂度O(n),而且是一趟扫描

空间复杂度O(1) 

3. 堆排序

堆相关博客

三、平均时间复杂度为O(n)的算法

1. 桶排序

2. 计数排序

3. 基数排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值