前言
排序算法在算法与数据结构中很常见,在学习排序算法的时候,会涉及到各种核心算法的概念。例如,分治法、随机算法、最佳、最差和平均情况分析,时空权衡等,还有数组、堆和二叉树之类的数据结构。
通常对于一个问题,考察一个算法的好坏取决于它的 算法复杂度
。而算法复杂度又分为时间复杂度
和空间复杂度
。时间复杂度是指:执行算法所需要的计算工作量
。空间复杂度是指:执行这个算法所需要的内存空间
。
如果一个排序算法不需要额外的存储空间,可以直接在原来的数组完成排序操作,这个算法可以被称之为 原地算法
,空间复杂度是
O
(
1
)
O(1)
O(1)。
O
(
1
)
O(1)
O(1)表示只需要常数量级
的空间,不会随着数组大小的变化而变化。
参考链接:https://leetcode-cn.com/problems/sort-an-array/solution/pai-xu-shu-zu-by-
来源:力扣(LeetCode)
1. 冒泡排序
核心思想:第一次,依次比较相邻的两个数,如果后面的数小,就交换位置,直到最大值冒泡到最后面;第二次,依次比较相邻的两个数(比到前N-1个数为止),如果后面数小,就交换位置,直到次大值冒泡到数列倒数第二的位置。依次类推,两个数比较大小,使得较大的数向后沉,较小的数向前冒。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2), 空间复杂度:
O
(
1
)
O(1)
O(1)
代码实现:
vector<int> sortArr(vector<int>& arr)
{
for(int i=0;i<arr.size();i++)
for(int j=0;j<arr.size()-i-1;j++)
{
if(arr[j]>arr[j+1])
swap(arr[j],arr[j+1]);
}
return arr;
}
2. 选择排序
核心思想:首先找到最大值,与最右边的数位置互换;再找前n-1项的最大值,与第n-1个元素的位置互换;依次类推,直到完成升序排列。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2), 空间复杂度:
O
(
1
)
O(1)
O(1)
代码实现:
vector<int> sortArr2(vector<int>& arr)
{
for(int i=0;i<arr.size()-1;i++)
{
int max_index=0;
for(int j=0;j<arr.size()-i;j++)
{
if(arr[j]>arr[max_index])
max_index=j;
}
swap(arr[max_index],arr[arr.size()-1-i]);
}
return arr;
}
3. 插入排序
核心思想:先把前 i-1 个元素按顺序排好,再插入第i个元素。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2), 空间复杂度:
O
(
1
)
O(1)
O(1)
代码实现:
vector<int> sortArr3(vector<int>& arr)
{
for(int i=1;i<arr.size();i++) //从二个元素开始遍历
for(int j=i;j>0&&arr[j]<arr[j-1];j--)
swap(arr[j],arr[j-1]);
return arr;
}
4. 快速排序
核心思想:通过划分将待排序的序列分成前后两部分,其中前一部分的数据都比后一部分的数据要小,然后再递归调用函数对两部分的序列分别进行快速排序,以此使整个序列达到有序。
时间复杂度:
O
(
n
l
o
g
n
)
O(n log n)
O(nlogn), 空间复杂度:最差
O
(
n
)
O(n)
O(n),最优
O
(
l
o
g
n
)
O(logn)
O(logn)
代码实现:
class Solution {
int partition(vector<int>& nums, int l, int r) {
int pivot = nums[r];
int i = l - 1;
for (int j = l; j <= r - 1; ++j) {
if (nums[j] <= pivot) {
i = i + 1;
swap(nums[i], nums[j]);
}
}
swap(nums[i + 1], nums[r]);
return i + 1;
}
int randomized_partition(vector<int>& nums, int l, int r) {
int i = rand() % (r - l + 1) + l; // 随机选一个作为我们的主元
swap(nums[r], nums[i]);
return partition(nums, l, r);
}
void randomized_quicksort(vector<int>& nums, int l, int r) {
if (l < r){
int pos = randomized_partition(nums, l, r);
randomized_quicksort(nums, l, pos - 1);
randomized_quicksort(nums, pos + 1, r);
}
}
public:
vector<int> sortArray(vector<int>& nums) {
srand((unsigned)time(NULL));
randomized_quicksort(nums, 0, (int)nums.size() - 1);
return nums;
}
};
5. 堆排列
核心思想:先将待排序的序列建成 大根堆
,使得每个父节点的元素大于等于它的子节点。此时整个序列最大值即为堆顶元素,我们将其与末尾元素交换,使末尾元素为最大值,然后再调整堆顶元素使得剩下的 n-1 个元素仍为大根堆,再重复执行以上操作我们即能得到一个有序的序列。
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn), 空间复杂度:
O
(
1
)
O(1)
O(1),只需要常数的空间存放若干变量
代码实现:
class Solution {
void maxHeapify(vector<int>& nums, int i, int len) {
for (; (i << 1) + 1 <= len;) {
int lson = (i << 1) + 1;
int rson = (i << 1) + 2;
int large;
if (lson <= len && nums[lson] > nums[i]) {
large = lson;
}
else {
large = i;
}
if (rson <= len && nums[rson] > nums[large]) {
large = rson;
}
if (large != i) {
swap(nums[i], nums[large]);
i = large;
}
else break;
}
}
void buildMaxHeap(vector<int>& nums, int len) {
for (int i = len / 2; i >= 0; --i) {
maxHeapify(nums, i, len);
}
}
void heapSort(vector<int>& nums) {
int len = (int)nums.size() - 1;
buildMaxHeap(nums, len);
for (int i = len; i >= 1; --i) {
swap(nums[i], nums[0]);
len -= 1;
maxHeapify(nums, 0, len);
}
}
public:
vector<int> sortArray(vector<int>& nums) {
heapSort(nums);
return nums;
}
};
6. 归并排列
核心思想:利用了分治
的思想来对序列进行排序。对一个长为 n 的待排序的序列,我们将其分解成两个长度为 n/2 的子序列。每次先递归调用函数使两个子序列有序,然后我们再线性合并两个有序的子序列使整个序列有序。
时间复杂度:
n
l
o
n
g
(
n
)
nlong(n)
nlong(n), 空间复杂度:
O
(
n
)
O(n)
O(n)
程序实现:
class Solution {
vector<int> tmp;
void mergeSort(vector<int>& nums, int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
mergeSort(nums, l, mid);
mergeSort(nums, mid + 1, r);
int i = l, j = mid + 1;
int cnt = 0;
while (i <= mid && j <= r) {
if (nums[i] < nums[j]) {
tmp[cnt++] = nums[i++];
}
else {
tmp[cnt++] = nums[j++];
}
}
while (i <= mid) tmp[cnt++] = nums[i++];
while (j <= r) tmp[cnt++] = nums[j++];
for (int i = 0; i < r - l + 1; ++i) nums[i + l] = tmp[i];
}
public:
vector<int> sortArray(vector<int>& nums) {
tmp.resize((int)nums.size(), 0);
mergeSort(nums, 0, (int)nums.size() - 1);
return nums;
}
};
7. 希尔排序
核心思想:希尔排序可以看作是一个冒泡排序或者插入排序的变形。希尔排序在每次的排序的时候都把数组拆分成若干个序列,一个序列的相邻的元素索引相隔固定的距离gap
,每一轮对这些序列进行冒泡或者插入排序,然后再缩小gap得到新的序列一一排序,直到gap为1。
时间复杂度:
O
(
n
4
3
)
O(n^\frac{4}{3})
O(n34) ~
O
(
n
2
)
O(n^2)
O(n2), 空间复杂度:
O
(
1
)
O(1)
O(1)
代码实现:
vector<int> shellSor(vector<int>& arr) {
int gap = arr.size() >> 1;
while (gap > 0) {
for (int i = 0; i < gap; i++) { // 对每个子序列进行排序
for (int j = i+gap; j < arr.size(); j+=gap) { // 插入排序的部分
int temp = j;
while (temp > i && arr[temp] < arr[temp-gap]) {
swap(arr[temp], arr[temp-gap]);
temp -= gap;
}
}
}
gap >>= 1;
}
return arr;
}
8. 优化后的快排函数sort()
当然,除了以上排序算法外。有时为了方便,我们还可以调用标准库algorithm
中的排序函数sort()
,该函数在原有快速排序算法上进行了改进,我们可以直接使用它进行排序,默认是升序。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool compare(int a, int b){
return a>b;
}
//使用自带排序功能sort(),compare决定升序还是降序。
vector<int> sortArr(vector<int>& arr){
sort(arr.begin(),arr.end()); //升序
//sort(arr.begin(),arr.end(),compare); //降序
return arr;
}
int main(){
int n=0;
cin>>n; //输入序列元素的个数
vector<int> arr_num(n);
for(int i=0; i<n;i++)
cin>>arr_num[i]; //输入元素
arr_num=sortArr(arr_num); //排序
for(int i=0; i<n; i++){
cout<<arr_num[i]<<" "; //打印输出
}
return 0;
}
除此之外,还有计数排序、桶排序、基数排序、二叉树排序等。具体要选择哪一种排序算法,则要根据实际问题,权衡时间和空间复杂度,尽量选择综合性能
更好的排序算法。