C++刷题模板之排序篇

本文详细介绍了C++实现的十大经典排序算法,包括选择排序、插入排序、冒泡排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序和基数排序。每种排序算法都有详细的代码实现和时间复杂度分析,是理解排序算法的良好参考资料。
摘要由CSDN通过智能技术生成

C++刷题笔记

排序算法

  • 1、比较类排序算法:
    • 通过比较来决定元素之间的相对次序,由于其时间复杂度不能突破O(logn),因此也称为非线性时间比较类排序
  • 2、非比较排序算法:
    • 不通过比较来决定元素之间的相对次序,它可以突破基于比较排序的时间下界,以线性时间进行运行,因此也称为线性时间非比较排序
十大经典排序算法(动画演示)
9种经典排序算法可是动画
6分钟看完15中排序算法动画演示

1-比较类排序算法

  • 1、选择排序(selection sort) – 时间复杂度O(n^2)
    • 表现最稳定的排序算法之一,时间复杂度都是一样的
    • 每次找最小值,然后放到待排序的数组的起始位置,然后依次进行操作。
    • C++代码实现:
void selectionSort(vector<int>& num) {
    if(num.empty())
        return ; 
    int n = num.size(); 
    for(int i = 0; i< n -1; ++i) {
        int minIndex = i; 
        for(int j = i +1; j < n; ++j) {
            // 每一次都寻找最小的数字
            if(num[j] < num[minIndex])
                // 将最小的数字的下标进行保存,当然也可以直接进行替换,
                minIndex = j; 
        }
        swap(num[minIndex], num[i]); 
    }
}
  • 2、插入排序(insertion sort) – 时间复杂度 O(n^2)
    • 从前到后逐渐构建有序序列,对于未排序的数据,在已排序序列的之,像前扫描,找到相应的位置并插入
    • C++代码实现:
void insertionSort(vector<int>& num) {
    if(num.empty())
        return ; 
    int n = num.size(); 
    int preIndex, current; 
    for(int i = 1; i< n; ++i) {
        // 从第二个开始记进行插入操作
        preIndex = i -1; 
        // current就是相当于一个临时变量,用来记录当前的值
        current = num[i];
        while(preIndex >= 0 && num[preIndex] > current) {
            num[preIndex +1] = num[preIndex]; 
            preIndex --; 
        }
        num[preIndex +1] = current; 
    }
}
  • 3、冒泡排序(Bubble sort) – 时间复杂度为O(n^2)
    • 嵌套循环,每次查看相邻的元素,如果逆序则交换。
    • C++代码实现:
void bubbleSort(vector<int>& num) {
    int n = num.size(); 
    for(int i = 0; i< n-1; ++i) {
        for(int j = i + 1; j < n ; ++j) {
            // 将相邻的元素进行两两的比较
            if(num[i] > num[j]) {
                swap(num[i], num[j]); 
            }
        }
    }
    //return num;
}
  • 4、希尔排序 shellSort – 时间复杂度为O(nlogn)
    • 1959年shell发明的,是第一个突破O(n^2)的排序算法,也就是剪短插入排序的改进版
    • C++代码实现:
void shellSort(vector<int>& nums) {
    if(nums.empty())
        return ;
    int n = nums.size();
    for(int gap = n /2; gap >0; gap /=2) {
        for(int i = gap; i <n; ++i) {
            int j = i;
            int current = nums[i];
            while( j -gap >=0 && current < nums[j -gap]){
                nums[j] = nums[j -gap];
                j = j - gap;
            }
            nums[j] = current;
        }
    }
}
  • 5、归并排序Merge Sort – 时间复杂度为O(nlogn)
    • 归并排序是建立在归并操作上面的一种有效的排序算法,该算法采用分治法Divide and Conquer。
    • 将已经有序的子序列合并,得到完全的有序的序列。
    • 归并排序是一种稳定的排序方式,和选择排序方式一样,归并排序的性能不受到输入数据的影响,
    • 但是表现比选择排序好的多,时间复杂度始终为O(nlogn),但是需要维阿的内存空间。
vector<int> Merge(vector<int>left, vector<int>right) {
    vector<int>ans;
    int i = 0;
    int j = 0;
    // 进行合并排序
    while(i < left.size() && j < right.size()) {
        if(left[i] < right[j])
            ans.push_back(left[i++]);
        else
            ans.push_back(right[j++]);
    }
    // 对剩下的未排序的数组进行排序
    while(i <left.size())
        ans.push_back(left[i++]);
    while(j <right.size())
        ans.push_back(right[j++]);
    return ans;
}
vector<int> mergeSort(vector<int>& nums) {
    if(nums.size()<2)
        return nums;
    int mid = nums.size() /2;
    vector<int>left(nums.begin(), nums.begin() +mid);
    vector<int>right(nums.begin() + mid, nums.end());
    return Merge(mergeSort(left), mergeSort(right));
}
  • 6、快速排序Quick Sort
    • 快速排序的思想:通过一趟排序将待排序记录分隔成独立的两部分,其中一部分记录的是比pivot小
    • 另一部分是比pivot大,然后分别对着两部分记性排序,使得这个序列有序。
/* 采用的分区递归的方式  */
// 分区操作
int partition(vector<int>& nums, int left, int right){
    int pivot = left;
    int index = pivot +1;
    for(int i = index; i <= right; ++i) {
        if(nums[i] < nums[pivot])
            swap(nums[i], nums[index++]);
    }
    swap(nums[pivot], nums[index -1]);
    return index -1;
}
//快速排序,分区排序
void quickSort(vector<int>& nums, int left, int right) {
    if(nums.empty() || left < 0 || right >= nums.size() || left > right)
        return ;
    int index;
    if(left < right) {
        index = partition(nums, left, right);
        quickSort(nums, left, index-1);
        quickSort(nums, index+1, right);
    }
}

  • 采用直接递归的方式
  • 注意: 这个时候的细节还是挺多的
// 直接采用递归的方式
void QuickSort(vector<int>& nums, int left, int right) {
    if(nums.empty() || left >= right)
        return ;
    int pivot = nums[left];
    int i = left;
    int j = right;
    while(i < j) {
        while(nums[j] >= pivot && i < j)  // 在右边找到一个比pivot小的数
            j --;
        while(nums[i] <= pivot && i < j)  // 在左边找到一个比pivot大的数
            i ++;
        if(i < j)
            swap(nums[i], nums[j]);
    }
    // 将基数归位
    nums[left] = nums[i];
    nums[i] = pivot;
    QuickSort(nums, left, i -1);
    QuickSort(nums, i +1, right);
}
  • 7、 堆排序Heap Sort – 时间复杂度为O(nlogn)
    • 堆排序HeapSort是指利用对这种数据结构所设计的一种排序算法

    • 堆积是一个近似完全二叉树的结构,并同时满足堆积的性质,及子结点的剪枝或者索引总是小于或者大于其父节点的

    • 动画演示如下图所示:
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H9xatgau-1617888009787)(C:/Users/%E6%B6%82%E9%B9%8F/Desktop/849589-20171015223238449-2146169197.gif)]

    • 堆排序HeapSort

    • 1.利用优先队列进行构建堆

void heapSort(vector<int>& nums) {
    if(nums.empty())
        return ;
    priority_queue<int, vector<int>, greater<int>> q_heap;
    for(int i = 0; i< nums.size(); ++i)
        q_heap.push(nums[i]);
    for(int i = 0; i< nums.size(); ++i) {
         nums[i] = q_heap.top();
         q_heap.pop();
    }
}
 - 2.直接构建大顶堆,进行排序
// 不用优先队列,进行创建堆排序
void adjustHeap(vector<int>&nums, int i, int j) {
    for(int k = 2*i +1; k <= j; k = k* 2 +1){
        if(k < j && nums[k] < nums[k +1])
            k++;
        if(nums[k] <= nums[i])
            break;
        swap(nums[k], nums[i]);
        i = k;
    }
}
void createHeap(vector<int>&nums) {
    int n = nums.size();
    for(int i = n /2 -1; i >= 0; --i)
        adjustHeap(nums, i, n-1);
}
void heapSort_2(vector<int>& nums) {
    // 创建堆
    createHeap(nums);
    for(int i = nums.size()- 1; i >0; -- i){
        // 这里和第一个数进行交换,因为堆都是第一个数字在前面的
        swap(nums[i], nums[0]);
        // 将堆的顺序进行调节
        adjustHeap(nums, 0, i -1);
    }
}

2-非比较类排序算法

  • 8、计数排序Counting Sort – 时间复杂度为O(n+k)
    • 计数排序不是基于比较的排序
    • 其核心在于将输入的数据值转换为键值存储在额外开辟的数组空间中,
    • 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定的范围的整数
    • 该算法是一个稳定的排序算法
// 计数排序
void countingSort(vector<int>& nums) {
    if(nums.empty())
         return ;
    int min_num= nums[0] ;
    int max_num= nums[0];
    int bias;
    for(int i = 1; i< nums.size() ; ++ i){
        //寻找最大值和最小值
        if(nums[i] > max_num)
            max_num = nums[i];
        if(nums[i] < min_num)
            min_num = nums[i];
    }
    // 这里就是为了防止其中又负数的时候
    bias = 0 - min_num;
    vector<int>ans(max_num- min_num +1, 0);
    for(int i = 0; i< nums.size(); ++i) {
        ans[nums[i] + bias] ++;
    }
    // 就是反向的都回去呗
    int index=0;
    int i = 0;
    while(index < nums.size()) {
        if(ans[i] != 0) {
            // 对原数组进行重新赋值
            nums[index] = i - bias;
            ans[i]--;
            index ++;
        }
        else
            i ++;
    }
}
  • 9、桶排序Bucket sort – 时间复杂度为O(n+k)
    • 桶排序是计数排序的升级版,
    • 它利用了函数的映射关系,高效与否的关键就是在这个映射函数的确定。
    • 桶排序的工作原理:假设输入数据服从均匀分布,将数据分到有限舒朗的桶里,每一个桶在分别进行排序(有可能使用别的排序方法
      或者是以递归的放回寺继续使用桶排序)
void bucketSort(vector<int>& nums, int n) {
    if(nums.empty())
        return ;
    //获取数组中的最大值和最小智=值
    int minvalue = nums[0];
    int maxvalue = nums[0];
    for(int i = 0; i< nums.size() ; ++ i){
        if(nums[i] > maxvalue)
            maxvalue = nums[i];
        if(nums[i] < minvalue)
            minvalue = nums[i];
    }
    // 设置桶的数量为默认值5
    int bucket_size = 5;
    n  = n || bucket_size;
    int bucketCount = (maxvalue - minvalue)/ n +1;
    // 通过上面的映射获得桶的数量
    vector<vector<int> >bucket(bucketCount);
    for(int i = 0; i< nums.size(); ++i) {
        // 确定一种映射关系
        bucket[nums[i]- minvalue /n].push_back(nums[i]);
    }
    nums.clear();
    for(int i = 0; i< bucket.size(); ++ i) {
        // 利用sort() 对桶内的数量进行排序
        sort(bucket[i].begin(), bucket[i].end());
        for(int j = 0; j < bucket[i].size(); ++ j)
            nums.push_back(bucket[i][j]);
    }
}
  • 10、 基数排序Radix sort – 时间复杂度为O(n+k)
    • 基数排序是按照低位先排序,然后收集,在按照高位排序,然后在收集,一次类推,直到最高位也排序完事。
    • 算法描述:
      • 取得数组中的最大数,并取得位数
      • 从原始数组开始,从最低位尅是去每一位组成的radix数组
      • 对radix数组机械能给你计数排序(利用计数排序适用于小范围数的特点)
    • 该算法是一个稳定的排序算法
// 获得数组中的最高位数
int max_digits(vector<int>& nums) {
    int d = 10;
    int ans = 1;
    for(int i = 0; i< nums.size(); ++i) {
        if(nums[i] > d) {
            d *= 10;
            ans ++;
        }
    }
    return ans;
}
void radixSort(vector<int>& nums) {
    if(nums.empty())
        return ;
    int digits = max_digits(nums);
    //cout << digits << endl;
    // 最高位决定循环的次数
    vector<vector<int> >vec(10);
    for(int i = 0, d = 1; i< digits; ++i, d *=10) {
        for(int j = 0; j <nums.size(); ++j)
            // 核心部分:是按照顺序从前往后插入的
            vec[(nums[j] /d) % 10].push_back(nums[j]);
        int index = 0;
        for(int k = 0; k< 10; ++k) {
            if(!vec[k].empty()) {
                // 取出来的是时候,是从前往后取的,这样才是排序好的
                for(int t = 0; t < vec[k].size(); ++t)
                    nums[index++] = vec[k][t];
            }
            else
                continue;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值