【算法之美】不要再用冒泡、选择、插入排序了,丢不起这人!


前言
排序算法在算法与数据结构中很常见,在学习排序算法的时候,会涉及到各种核心算法的概念。例如,分治法、随机算法、最佳、最差和平均情况分析,时空权衡等,还有数组、堆和二叉树之类的数据结构。
通常对于一个问题,考察一个算法的好坏取决于它的 算法复杂度。而算法复杂度又分为时间复杂度空间复杂度。时间复杂度是指:执行算法所需要的计算工作量。空间复杂度是指:执行这个算法所需要的内存空间
如果一个排序算法不需要额外的存储空间,可以直接在原来的数组完成排序操作,这个算法可以被称之为 原地算法,空间复杂度是 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 ( 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 ( 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 ( 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 ( n ) O(n) ,最优 O ( l o g n ) 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 ( 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) 空间复杂度 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 ( n 2 ) O(n^2) , 空间复杂度: 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;
} 

除此之外,还有计数排序桶排序基数排序二叉树排序等。具体要选择哪一种排序算法,则要根据实际问题,权衡时间和空间复杂度,尽量选择综合性能更好的排序算法。

发布了55 篇原创文章 · 获赞 431 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wjinjie/article/details/105370404