经典的7种排序算法 原理C++实现

简介:
经典的7种排序算法 原理C++实现

排序是编程过程中经常遇到的操作,它在很大程度上影响了程序的执行效率。

7种常见的排序算法大致可以分为两类:第一类是低级排序算法,有选择排序、冒泡排序、插入排序;第二类是高级排序算法,有堆排序、排序树、归并排序、快速排序。

一、低级排序算法

1. 选择排序

排序过程:给定一个数值集合,循环遍历集合,每次遍历从集合中选择出最小或最大的放入集合的开头或结尾的位置,下次循环从剩余的元素集合中遍历找出最小的并如上操作,最后直至所有原集合元素都遍历完毕,排序结束。

实现代码

//选择排序法 
template 
void Sort::SelectSort(T* array, int size) 

    int minIndex;                                                                  
    for(int i = 0; i < size; i++)                                               
    { 
        minIndex = i;                                                              
        for(int j = i + 1; j < size; j++) 
        { 
            if(array[minIndex] > array[j])                                 
            { 
                minIndex = j;                                                     
            } 
        } 
        if(minIndex != i)                                                         
        { 
            Swap(array, i, minIndex); 
        } 
    } 
}

分析总结:选择排序时间复杂度比较高,达到了O(n^2),每次选择都要遍历一遍无序区间。选择排序对一类重要的元素序列具有较好的效率,就是元素规模很大,而排序码却比较小的序列。另外要说明的是选择排序是一种不稳定的排序方法。

2. 冒泡排序

排序过程:冒泡排序的过程形如其名,就是依次比较相邻两个元素,优先级高(或大或小)的元素向后移动,直至到达序列末尾,无序区间就会相应地缩小。下一次再从无序区间进行冒泡操作,依此循环直至无序区间为1,排序结束。

实现代码:

//冒泡排序法 
template 
void Sort::BubbleSort(T* array, int size) 

    for(int i = 0; i < size; i++) 
    { 
        for(int j = 1; j < size - i; j++) 
        { 
            if(array[j] < array[j - 1]) 
            { 
                Swap(array, j, j - 1); 
            } 
        } 
    } 
}

分析总结:冒泡排序的时间复杂度也比较高,达到O(n^2),每次遍历无序区间都将优先级高的元素移动到无序区间的末尾。冒泡排序是一种稳定的排序方式。

3. 插入排序

排序过程:将前面的区间(初始区间为1,包含第一个元素)视作有序区间,然后将有序区间的后一元素插入到前面有序区间的适当位置。直至有有序区间扩展到原区间的大小,排序结束。

实现代码:

//插入排序 
template 
void Sort::InsertSort(T* array, int size) 

    for(int i = 1; i < size; i++) 
    { 
        for(int j = i; j > 0; j--) 
        { 
            if(array[j] < array[j - 1]) 
            { 
                Swap(array, j, j-1); 
            } 
        } 
    } 
}

分析总结:插入排序的时间复杂度达到O(n^2),排序的运行时间和待排序元素的原始排列顺序密切相关。插入排序是一种稳定的排序方法。

二、高级排序算法

1. 快速排序

排序过程:快速排序应该是应用最广泛的排序算法,它是采用了分治的思想(这种思想很重要)。其基本的思想就是任取待排序序列中的某个元素(元素的选取方式在一定程序上会影响实现过程和排序效率)作为标杆,将待排序序列划分为左右两个子序列,左侧元素小于标杆元素,右侧元素大于标杆元素,标杆元素则排在这两个子序列的中间,然后再对这两个子序列重复上述的方法,直至排序结束。

实现代码:

//快速排序 
template 
void Sort::QuickSort(T *array, int left, int right) 

    if(left < right) 
    { 
        int i = left -1, j = right + 1; 
        T mid = array[(left + right) / 2]; 
        while(true) 
        { 
            while(array[++i] < mid); 
            while(array[--j] > mid); 
            if(i >= j) 
            { 
                break; 
            } 
            Swap(array, i, j); 
        } 
        QuickSort(array, left, i - 1); 
        QuickSort(array, j + 1, right); 
    } 
}

分析总结:快速排序的时间复杂度为O(nlogn),是一种不稳定的排序算法。

2. 归并排序

排序过程:归并排序的原理比较简单,也是基于分治思想的。它将待排序的元素序列分成两个长度相等的子序列,然后为每一个子序列排序,然后再将它们合并成一个序列。

实现代码:

//归并排序 
template 
void Sort::MergeSort(T* array, int left, int right) 

    if(left < right) 
    { 
        int mid = (left + right) / 2; 
        MergeSort(array, left, mid); 
        MergeSort(array, mid + 1, right); 
        Merge(array, left, mid, right); 
    } 

//合并两个已排好序的子链 
template 
void Sort::Merge(T* array, int left, int mid, int right) 

    T* temp = new T[right - left + 1]; 
    int i = left, j = mid + 1, m = 0; 
    while(i <= mid && j <= right) 
    { 
        if(array[i] < array[j]) 
        { 
            temp[m++] = array[i++]; 
        } 
        else 
        { 
            temp[m++] = array[j++]; 
        } 
    } 
    while(i <= mid) 
    { 
        temp[m++] = array[i++]; 
    } 
    while(j <= right) 
    { 
        temp[m++] = array[j++];

    } 
    for(int n = left, m = 0; n <= right; n++, m++) 
    { 
        array[n] = temp[m]; 
    } 
    delete temp; 
}

分析总结:归并排序最好、最差和平均时间复杂度都是O(nlogn),是一种稳定的排序算法。

3. 堆排序

排序过程:堆排序的过程分为两个步骤,第一步是根据初始输入数据,建立一个初始堆;第二步是将堆顶元素与当前无序区间的最后一个元素进行交换,然后再从堆顶元素开始对堆进行调整。

实现代码:

//堆排序 
template 
void Sort::HeapSort(T* array, int size) 

    int lastP = size / 2; 
    //从最后一个有孩子的结点开始建初始堆 
    for(int i = lastP - 1; i >= 0; i--) 
    { 
        HeapAjust(array, i, size); 
    } 
    int j = size; 
    //将堆顶元素和无序区间的最后一个元素交换,再调整堆 
    while(j > 0) 
    { 
        Swap(array, 0, j - 1); 
        j--; 
        HeapAjust(array, 0, j); 
    } 

//调整堆 
template 
void Sort::HeapAjust(T *array, int toAjust, int size) 

    int pos = toAjust; 
    while((pos * 2 + 1) < size) 
    { 
        int lChild = pos * 2 + 1; 
        if(array[lChild] > array[pos]) 
        { 
            pos = lChild;//左孩子大 
        } 
        int rChild = lChild + 1; 
        if(rChild < size && array[rChild] > array[pos]) 
        { 
            pos = rChild;//右孩子更大 
        } 
        if(pos != toAjust) //父结点比其中一个孩子小 
        { 
            Swap(array, toAjust, pos); 
            toAjust = pos; 
        } 
        else 
        { 
            break; 
        } 
    } 
}

分析总结:堆排序的算法时间复杂度为O(nlogn),它是一种不稳定的排序算法。

4. 排序树

排序过程:排序树算法应用了AVL树的原理,只不过排序树不是平衡的,它的特点就是父结点元素总是比左孩子元素要大却比右孩子元素要小。根据这个特点,可以将原数组元素组织成排序树,然后在对排序树进行中序遍历,中序遍历的结果就是排好序的序列。在算法的实现中采用的数组的形式来存储排序树,并采用的三个辅助数组来表示元素与元素之间在树中的关系,这三个辅助数组分别是父结点索引表、左孩子索引个、右孩子索引表。最后采用了递归的方法对排序树进行了中序遍历。

实现代码:

template 
void Sort::TreeSort(T* array, int size) 

    int *parent = new int[size];//父结点子针 
    int *lChild = new int[size];//左孩子子针 
    int *rChild = new int[size];//右孩子子针 
    //将各结点左右子结点指针均置为-1,表示没有左右子结点 
    for(int i = 0; i < size; i++) 
    { 
        lChild[i] = -1; 
        rChild[i] = -1; 
    }

    parent[0] = -1; //将第一个元素作为根结点,其父结点置为-1 
    //从第2个数开始构造树 
    for(int i = 1; i < size; i++) 
    { 
        int last = 0; 
        while(true) 
        { 
            int compare = array[i] - array[last]; 
            if(compare > 0) //比当前值大,进入右子树 
            { 
                if(rChild[last] == -1) 
                { 
                    rChild[last] = i; 
                    parent[i] = last; 
                    break; 
                } 
                else 
                { 
                    last = rChild[last]; 
                } 
            } 
            else //比当前值小,进入左子树 
            { 
                if(lChild[last] == -1) 
                { 
                    lChild[last] = i; 
                    parent[i] = last; 
                    break; 
                } 
                else 
                { 
                    last = lChild[last]; 
                } 
            } 
        } 
    } 
    //保存排序树的中序遍历结果 
    T* midRetrival = new T[size]; 
    TreeMidRetrival(array, midRetrival, 0, lChild, rChild); 
    //将排好序的中序数组复制到原数组 
    for(int i = 0; i < size; i++) 
    { 
        array[i] = midRetrival[i]; 
    } 

//中序遍历 
template 
void Sort::TreeMidRetrival(T *array, T* temp, int pos, T* lChild, T* rChild) 

    static int i = 0; 
    if(pos != -1) 
    { 
        TreeMidRetrival(array, temp, lChild[pos], lChild, rChild);//遍历左子树 
        temp[i++] = array[pos];//保存当前值 
        TreeMidRetrival(array, temp, rChild[pos], lChild, rChild);//遍历右子树 
    } 
    else 
    { 
        return; 
    } 
}

分析总结:算法中排序树建立的时间复杂度是O(nlogn),中序遍历的时间复杂度是O(n),故排序树排序的时间复杂度为O(nlogn)。排序树的空间复杂度比较高,因为它使用了几个额外的存储空间的存储排序树元素之间的关系。

模板类定义如下所示:

template 
class Sort 

public: 
    void SelectSort(T* array, int size); 
    void InsertSort(T* array, int size); 
    void BubbleSort(T* array, int size); 
    void MergeSort(T* array, int left, int right); 
    void Merge(T* array, int left, int mid, int right); 
    void HeapSort(T *array, int size); 
    void HeapAjust(T *array, int toAjust, int size); 
    void QuickSort(T *array, int left, int right); 
    void TreeSort(T *array, int size); 
    void TreeMidRetrival(T* array, T* temp, int pos, T* lChild, T* rChild); 
    void Swap(T* array, int x, int y); 
};

template //交换两个元素 
void Sort::Swap(T* array, int x, int y) 

    T temp = array[x]; 
    array[x] = array[y]; 
    array[y] = temp; 
}


目录
相关文章
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
52 3
|
2月前
|
机器学习/深度学习 算法 机器人
多代理强化学习综述:原理、算法与挑战
多代理强化学习是强化学习的一个子领域,专注于研究在共享环境中共存的多个学习代理的行为。每个代理都受其个体奖励驱动,采取行动以推进自身利益;在某些环境中,这些利益可能与其他代理的利益相冲突,从而产生复杂的群体动态。
238 5
|
1月前
|
算法 容器
令牌桶算法原理及实现,图文详解
本文介绍令牌桶算法,一种常用的限流策略,通过恒定速率放入令牌,控制高并发场景下的流量,确保系统稳定运行。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
令牌桶算法原理及实现,图文详解
|
14天前
|
存储 人工智能 缓存
【AI系统】布局转换原理与算法
数据布局转换技术通过优化内存中数据的排布,提升程序执行效率,特别是对于缓存性能的影响显著。本文介绍了数据在内存中的排布方式,包括内存对齐、大小端存储等概念,并详细探讨了张量数据在内存中的排布,如行优先与列优先排布,以及在深度学习中常见的NCHW与NHWC两种数据布局方式。这些布局方式的选择直接影响到程序的性能,尤其是在GPU和CPU上的表现。此外,还讨论了连续与非连续张量的概念及其对性能的影响。
36 3
|
19天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法与应用
探索人工智能中的强化学习:原理、算法与应用
|
1月前
|
负载均衡 算法 应用服务中间件
5大负载均衡算法及原理,图解易懂!
本文详细介绍负载均衡的5大核心算法:轮询、加权轮询、随机、最少连接和源地址散列,帮助你深入理解分布式架构中的关键技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
5大负载均衡算法及原理,图解易懂!
|
27天前
|
缓存 算法 网络协议
OSPF的路由计算算法:原理与应用
OSPF的路由计算算法:原理与应用
36 4
|
27天前
|
存储 算法 网络协议
OSPF的SPF算法介绍:原理、实现与应用
OSPF的SPF算法介绍:原理、实现与应用
64 3
|
19天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法及应用
探索人工智能中的强化学习:原理、算法及应用
|
2月前
|
算法 数据库 索引
HyperLogLog算法的原理是什么
【10月更文挑战第19天】HyperLogLog算法的原理是什么
90 1