七种排序算法的C++实现(入门级、进阶级已更新完毕!)保证代码结果反复测试,可以一键运行,没有任何问题!

前言

高铁上实在闲的没事干,所以就把这篇本来打算鸽掉的来开个头,咕咕咕~

排序算法的重要性不言而喻,开玩笑,连你瓜程序设计考试都大概率考到(doge);

建议先在1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)上面对各种排序算法进行了解;

本篇Blog包含七种排序算法:

        1.快速排序;        2.插入排序;        3.选择排序;         4.冒泡排序;        5.堆排序;

        6.归并排序;        7.基数排序;

声明

本篇Blog的排序代码基于C++,使用部分C++特性;

对于需要C版本的,稍作修改就可以;

难度分析

入门级(学了程设怎么也得会):冒泡排序,选择排序,插入排序;

进阶级:快速排序;

高阶级(涉及更复杂的算法思想):堆排序,归并排序,基数排序;

注:以上均为本人主观判断,不代表客观实际;

程序预处理部分

#include <iostream>
#include <vector>
#include <random>
#include <chrono>

using namespace std;

常用函数

其实就是一个swap();

用于替代常用的

template<class Type>{
    Type a=x,b=y;
    Type tmp; 
    tmp=a;a=b;b=tmp;
}

swap的好处是:不用担心交换变量精度的缺失,无需构造临时变量,不会增加空间复杂度;而且方便,不是吗?(doge)

在C语言中,swap定义如下(当然不只是int):

void swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

而在C++中,swap()包含在 std中,同时传入的参数不必是指针,直接是两个变量传入之后就可以交换数值(妙蛙!); 

入门级:

冒泡排序

算法说明

原理:

        把一个无序数组里面的所有元素想象成一个个水管中的气泡,一个数据越大,对应的气泡就越大;而众所周知,气泡在水里,是要上浮的;

        那么,先找到最下面的气泡(对应第一个元素[0]),如果这个气泡比上面一个[1]更大,那么它就会把上面一个挤下;然后再比较[0]和[2],如果更大,就再把它挤下来......以此类推,直到比较完最后一对;

算法步骤:

        比较相邻的元素。如果第一个比第二个大,就交换他们两个。

        对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。(此时这个元素触顶,不再参与排序,d)

        针对所有的元素重复以上的步骤,除了最后一个。

        持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

1.0 原始版本

// 1.0
//bubble sort <unoptimized>
//avg:O(n^2)  best:O(n)   worst:O(n^2)
//space:O(1)
//method:In-place
//stable
template<class Type>
static void bubbleSortBasic(vector<Type>& arr) {
    int size = arr.size() ;
    for (int i = 0; i < size-1; ++i) {
        for (int j = 0; j < size -1-i; ++j) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}
template<class Type>
static void BubbleSortBasic(vector<Type>& arr) {
    bubbleSortBasic(arr);
}

1.1 优化版本

优化点:

        原始版本的冒泡排序,在最外面一重循环时需要遍历所有元素,每个元素都要在第二重循环内对未触顶的元素进行一对对的比较;

        而面对类似于整个数组,只有第一个元素的位置无序,后面的元素全部有序的极端情况,这种全部遍历的行为显然多余;

        因此,通过设置一个Flag,判断第二重循环的某轮两两比较有没有发生交换,如果没有,则说明未触顶的所有元素也已经完全有序,没有待排元素,直接结束排序;

        当然,这样的优化聊胜于无,毕竟只是优化掉了很多次比较大小,本来也不怎么费事,但是我们崩三玩家(划掉)学程设的,当然要凹分凹到底(划掉)尽可能优化程序;

// 1.1
//bubble sort <optimized>
//avg:O(n^2)  best:O(n)   worst:O(n^2)
//space:O(1)
//method:In-place
//stable
template<class Type>
static void bubbleSortOptimized(vector<Type>& arr) {
    int size = arr.size() ;
    for (int i = 0; i < size-1; ++i) {
        bool isSwapped = false;
        for (int j = 0; j < size -1- i; ++j) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                isSwapped = true;
            }
        }
        if (!isSwapped) break;
    }
}
template<typename Type>
static void BubbleSortOptimized(vector<Type>& arr) {
    bubbleSortOptimized(arr);
}

选择排序

算法说明

算法步骤:

        首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

        再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

        重复第二步,直到所有元素均排序完毕。

补充:

        选择排序的效率很低,优化之后也不怎么样,实际应用没什么人用,毕竟不如用冒泡,一样简单;

2.0 原始版本

// 2.0
//selection sort <unoptimized>
//avg:O(n^2)  best:O(n^2)   worst:O(n^2)
//space:O(1)
//method:In-place
//unstable
template<class Type>
static void selectSortBasic(vector<Type>& arr) {
    int size = arr.size();
    for (int i = 0; i < size - 1; i++) {
        int min = i;
        for (int j = i + 1; j <size; j++) {
            if (arr[j] < arr[min]) min = j;
        }
        swap(arr[i], arr[min]);
    }
}
template<class Type>
static void SelectSortBasic(vector<Type>& arr) {
    selectSortBasic(arr);
}

2.1 优化版本

优化点:

        原始的选择排序,什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好;唯一的好处就是不占用额外的内存空间;

        然鹅,这东西如果不大动的话,还是O(n²) 的时间复杂度;大动的话,它就不能叫选择排序,而是混血,或者深度优化之后改叫堆排序(doge);

        所以,这里就给个简单的优化版本了,思路就是找最小值的同时找最大值;

// 2.1
//selection sort <optimized>
//avg:O(n^2)  best:O(n^2)   worst:O(n^2)
//space:O(1)
//method:In-place
//unstable
template<typename Type>
void selectSortOptimized(vector<Type> &arr){
    int len = arr.size();
    for (int left = 0, right = len - 1; left < right; left++, right--){
        int min = left;     // 记录最小值
        int max = right;    // 记录最大值
        for (int index = left; index <= right; index++){
            if (arr[index] < arr[min])
                min = index;
            if (arr[index] > arr[max])
                max = index;
        }
        // 最小值交换
        swap(arr[min], arr[left]);
        // 此处是先排最小值的位置,所以得考虑最大值在最小位置的情况
        if (left == max) max = min;
        swap(arr[max], arr[right]);
    }
}
template<class Type>
static void SelectSortOptimized(vector<Type>& arr) {
    selectSortOptimized(arr);
}

插入排序

算法说明

原理:

        扑克牌玩过吗,一样的;你手上原来一张牌也没有,然后你开始拿牌,等到你拿了两张牌之后,你有了大小的概念,开始区别牌的大小;

        于是拿第三张的时候,你会分辨它是多大,插到合适的位置,后面抽到的牌也一样;

        最后你手上的牌就是按顺序放的了,介就是打牌的智慧吗;

算法步骤:

        将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;

        从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

3.0 原始版本

// 3.0
//insertion sort <unoptimized>
//avg:O(n^2)  best:O(n)   worst:O(n^2)
//space:O(1)
//method:In-place
//stable
template<class Type>
static void insertSortBasic(vector<Type>& arr) {
    int len = arr.size();
    for(int i=1;i<len;i++){
        int key=arr[i];
        int j=i-1;
        while((j>=0) && (key<arr[j])){
            arr[j+1]=arr[j];
            j--;
        }
        arr[j+1]=key;
    }
}
template<class Type>
static void InsertSortBasic(vector<Type>& arr) {
    insertSortBasic(arr);
}

3.1 优化版本

优化点:

        要优化的话,当然是提高效率了;

        原来你是一只手打牌,现在改用两只手,左右脑分别控制,分别排序;

        这就是折半插入,或者叫二分插入,是二分法的实践哒;

// 3.1
//insertion sort <optimized> (Binary search)
//avg:O(n^2)  best:O(n)   worst:O(n^2)
//space:O(1)
//method:In-place
//stable
template<class Type>
static void insertSortOptimized(vector<Type>& arr) {
    int size = arr.size();
    for (int i = 0; i < size; i++) {
        int low = 0;
        int high = i;
        while (low <= high) {
            int mid = (low + high) >> 1;
            if (arr[mid] >= arr[i]) high = mid - 1;
            else low = mid + 1;
        }
        Type tmp = arr[i];
        for (high = i; high > low; high--) {
            arr[high] = arr[high - 1];
        }
        arr[low] = tmp;
    }
}
template<typename Type>
static void InsertSortOptimized(vector<Type>& arr) {
    insertSortOptimized(arr);
}

进阶级:

快速排序

算法说明

算法步骤:

        从数列中挑出一个元素,称为 "基准"(pivot);

        重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

        递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

补充:

        快速排序,快速排序,顾名思义,特点就是快速,而且经过不同程度的优化,效率还能进一步提高。常用,并且对于大多数情况效率优秀;

        然鹅,虽然把快排放在进阶里面,但别以为它就简单,写对一个快排的难度不小的;

4.0 原始版本

// 4.0
//quick sort <unoptimized>
//avg:O(nlogn)  best:O(nlogn)   worst:O(n^2)
//space:O(1)
//method:In-place
//unstable
template<class Type>
static void quickSortBasic(vector<Type>& arr, int low, int high){
    if (low >= high) return;
    Type pivot = arr[low];
    int head = low - 1, tail = high + 1;
    while (head < tail) {
        do head++;  while (arr[head] < pivot);
        do tail--;       while (arr[tail] > pivot);
        if(head<tail) swap(arr[head], arr[tail]);
    }
    quickSortBasic(arr, low, tail );
    quickSortBasic(arr, tail + 1, high);
}
template<class Type>
static void QuickSortBasic(vector<Type>& arr) {
    quickSortBasic(arr, 0, arr.size() - 1);
}

4.1 优化版本

优化点:

        快排优化的关键在于pivot(基准)的选择与分区数量;

        原始版本里,pivot是每个分区最前面的元素,而在优化版本里,通过真随机在(low,high)中选取显然更优;

        对于分区,原始程序是全程partition 过程使用一个索引值,增加索引值,增加同时处理的数据区域,可以在一定情况下提高算法效率;

        使用两个索引的,称为双路快排;使用三个索引的,称为三路快排;

        下面是较为常见的双路随机快排;

// 4.1
//quick sort <optimized> (2 ways & true random)
//avg:O(nlogn)  best:O(nlogn)   worst:O(n^2)
//space:O(1)
//method:In-place
//unstable
template<class Type>
static void quickSortOptimized(vector<Type>& arr, int low, int high) {
    if (low >= high) return;
    random_device seed;
    mt19937 engine(seed());
    uniform_int_distribution<> distrib(low, high);
    Type pivot = arr[distrib(engine)];
    int head = low - 1, tail = high + 1;
    while (head < tail) {
        do head++;  while (arr[head] < pivot);
        do tail--;       while (arr[tail] > pivot);
        if(head<tail) swap(arr[head], arr[tail]);
    }
    quickSortOptimized(arr, low, tail );
    quickSortOptimized(arr, tail + 1, high);
}
template<typename Type>
static void QuickSortOptimized(vector<Type>& arr) {
    quickSortOptimized(arr, 0, arr.size() - 1);
}

高阶级:

堆排序

算法说明

基础概念:

        堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。

        堆是一个近似 完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

        性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。

        索引计算:

                父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)

                左孩子索引:2*i+1

                右孩子索引:2*i+2

算法步骤:

        首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

        将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

        将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

补充:

        堆排序真挺难理解的,尤其是对于那些对于“堆”没有任何概念的初学者,建议上B站找个视频看看详细过程;

5.0 原始版本

// 5.0
//heap sort <unoptimized>
//avg:O(n^2)  best:O(n)   worst:O(n^2)
//space:O(1)
//method:In-place
//unstable
template<class Type>//维护大根堆
static void heapify(vector<Type>& arr, int size, int i) {//i为此时维护的父节点索引
    int largest = i;
    int lson = i * 2 + 1;
    int rson = lson + 1;

    if (lson < size && arr[largest] < arr[lson])
        largest = lson;
    if (rson < size && arr[largest] < arr[rson])
        largest = rson;
    if (largest != i) {
        swap(arr[largest], arr[i]);
        heapify(arr, size, largest);
    }//当largest==i时结束递归
}
template<class Type>
static void heapSortBasic(vector<Type>& arr) {
    int i;
    int size = arr.size();
    //构建大根堆(从最后一个元素的父节点开始)
    for (i = size / 2 - 1; i >= 0; --i) {
        heapify(arr, size, i);
    }
    for (i = size - 1; i > 0; --i) {
        swap(arr[i], arr[0]);//固定每次维护完的大根堆第一个元素至末尾(即最大元素)
        heapify(arr, i, 0);
    }
}
template<class Type>
static void HeapSortBasic(vector<Type>& arr) {
    heapSortBasic(arr);
}

5.1 优化版本

优化点:

        感觉,原始版本就够好了? 

        暂时没什么优化的想法,所以代码木得;

归并排序

算法说明

        归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

6.0 原始版本

        似乎现在都默认双路了,所以原始代码不写了,木得;

6.1 优化版本

// 6.1
//merge sort <optimized>
//avg:O(nlogn)  best:O(nlogn)   worst:O(nlogn)
//space:O(n)
//method:OuT-place
//stable
template<class Type>
static void merge(vector<Type>& arr, int low, int mid, int high) {
    vector<Type> tmp(high - low + 1);
    int i = low, j = mid + 1, k = 0;//i为arr前一半的下标,j为arr后一半的下标,k为tmp的下标
    while (i <= mid && j <= high) {
        if (arr[i] <= arr[j])
            tmp[k++] = arr[i++];
        else
            tmp[k++] = arr[j++];
    }
    while (i <= mid) {
        tmp[k++] = arr[i++];
    }
    while (j <= high) {
        tmp[k++] = arr[j++];
    }
    k = 0;//fresh k
    //copy tmp
    for (int i = low; i <= high; ++i) {
        arr[i] = tmp[k++];
    }
}
template<class Type>
static void mergeSortOptimized(vector<Type>& arr, int low, int high) {
    if (low < high) {//low==high时结束递归
        int mid = (low + high)/2;
        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);
        merge(arr, low, mid, high);
    }
}
template<class Type>
static void MergeSortOptimized(vector<Type>& arr) {
    mergeSort(arr, 0, arr.size()- 1);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值