经典排序算法的 C++ 实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34719188/article/details/83931519

排序算法


在计算器科学与数学中,一个排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定排序方式进行排列的一种算法。最常用到的排序方式是数值顺序以及字典顺序。有效的排序算法在一些算法(例如搜索算法与合并算法)中是重要的,如此这些算法才能得到正确解答。排序算法也用在处理文字数据以及产生人类可读的输出结果。基本上,排序算法的输出必须遵守下列两个原则:

  • 输出结果为递增序列(递增是针对所需的排序顺序而言)
  • 输出结果是原输入的一种排列、或是重组

虽然排序算法是一个简单的问题,但是从计算器科学发展以来,在此问题上已经有大量的研究。举例而言,冒泡排序在1956年就已经被研究。虽然大部分人认为这是一个已经被解决的问题,有用的新算法仍在不断的被发明。(例子:图书馆排序在2004年被发表) ------[维基百科]

排序算法一览

项目 内容
理论储备 • 计算复杂性理论 • 大O符号 • 全序关系 • 列表 • 稳定性 • 比较排序 • 自适应排序 • 排序网络 • 整数排序
交换排序 • 冒泡排序 • 鸡尾酒排序 • 奇偶排序 • 梳排序 • 侏儒排序 • 快速排序 • 臭皮匠排序 • Bogo排序
选择排序 • 选择排序 • 堆排序 • 平滑排序 • 笛卡尔树排序 • 锦标赛排序 • 圈排序
插入排序 • 插入排序 • 希尔排序 • 伸展排序 • 二叉查找树排序 • 图书馆排序 • 耐心排序
归并排序 • 归并排序 • 梯级归并排序 • 振荡归并排序 • 多相归并排序 • 列表排序
分布排序 • 美国旗帜排序 • 珠排序 • 桶排序 • 爆炸排序 • 计数排序 • 比较计数排序 • 鸽巢排序 • 相邻图排序 • 基数排序 • 闪电排序 • 插值排序
并发排序 • 双调排序器 • Batcher归并网络 • 两两排序网络
混合排序 • 块排序 • Tim排序 • 内省排序 • Spread排序 • J排序
其他 • 拓扑排序 • 煎饼排序 • 意粉排序

稳定性


当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。

然而,假设以下的数对将要以他们的第一个数字来排序。

(4,1)(3,1)(3,7)(5,6)

在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:

(3,1)(3,7)(4,1)(5,6) ------ 维持次序

(3,7)(3,1)(4,1)(5,6) ------ 次序改变

不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。

稳定的排序

  • 冒泡排序(bubble sort)— O(n2)
  • 插入排序(insertion sort)— O(n2)
  • 鸡尾酒排序(cocktail sort)— O(n2)
  • 桶排序(bucket sort)— O(n);需要 O(k) 额外空间
  • 计数排序(counting sort)— O(n+k);需要 O(n+k)额外空间
  • 归并排序(merge sort)— O(n log n);需要 O(n)额外空间
  • 原地归并排序 — O(nlog2n)
  • 二叉排序树排序(binary tree sort)— O(n log n)期望时间;O(n2)最坏时间;需要 O(n)额外空间
  • 鸽巢排序(pigeonhole sort)— O(n+k);需要 O(k) 额外空间
  • 基数排序(radix sort)— O(nk);需要 O(n)额外空间
  • 侏儒排序(gnome sort)— O(n2)
  • 图书馆排序(library sort)— O(n log n)期望时间;O(n2)最坏时间;需要 (1+ε)n额外空间
  • 块排序(block sort)— O(n log n)

不稳定的排序

  • 选择排序(selection sort)— O(n2)
  • 希尔排序(shell sort)— O(n log2n)
  • C - lover排序算法(Clover sort)— O(n)期望时间, O(n2)最坏情况
  • 梳排序 — O(n log n)
  • 堆排序(heap sort)— O(n log n)
  • 平滑排序(smooth sort)— O(n log n)
  • 快速排序(quick sort)— O(n log n)期望时间, O(n2)最坏情况;对于大的、随机数列表一般相信是最快的已知排序
  • 内省排序(introsort)— O(n log n)
  • 耐心排序(patience sort)— O(n log n+k) 最坏情况时间, O(n+k)空间,也需要找到最长的递增子序列(longest increasing subsequence)

不实用的排序

  • Bogo排序 — O(n * n!),最坏的情况下期望时间为无穷。
  • Stupid排序 — O(n3);递归版本需要O(n2)额外存储器
  • 珠排序(bead sort)— O(n) 或 O(sqrt(n)),但需要特别的硬件
  • 煎饼排序 — O(n),但需要特别的硬件
  • 臭皮匠排序(stooge sort)算法简单,但需要约 n2.7 的时间

常用排序算法比较


在这里插入图片描述

算法实现


插入排序

// 一般方法
void insertSort(int array[], int arraySize) {
    int i, j;
    int tmp;

    for(i = 1; i < arraySize; i++) {
        tmp = array[i];
        j = i - 1;
        while(j >= 0 && tmp > array[j]) {           /*从大到小排序,因此改变判断条件*/
            array[j + 1] = array[j--];
        }
        array[j + 1] = tmp;                     /*将元素tmp插入指定位置*/
    }
}

// 模板方法
template<typename T>
void insertionSort(T arr[], int n){

    for( int i = 1 ; i < n ; i ++ ) {

        // 寻找元素arr[i]合适的插入位置
        // 写法1
//        for( int j = i ; j > 0 ; j-- )
//            if( arr[j] < arr[j-1] )
//                swap( arr[j] , arr[j-1] );
//            else
//                break;

        // 写法2
//        for( int j = i ; j > 0 && arr[j] < arr[j-1] ; j -- )
//            swap( arr[j] , arr[j-1] );

        // 写法3
        T e = arr[i];
        int j; // j保存元素e应该插入的位置
        for (j = i; j > 0 && arr[j-1] > e; j--)
            arr[j] = arr[j-1];
        arr[j] = e;
    }

    return;
}

冒泡排序

// 一般方法
void bubbleSort(int array[],int arraySize)
{
    int i,j,tmp ,flag = 1;
    for(i=0;i<arraySize-1 && flag == 1;i++){       /*arraySize个元素的序列执行arraySize-1趟冒泡排序*/
        flag = 0;								   /*flag初始化为0*/
        for(j=0;j<arraySize-i;j++) {
            if(array[j]<array[j+1]) {              /*数据交换,将较小的数据往后交换,实现从大到小排序*/
                tmp = array[j+1];
                array[j+1] = array[j];
                array[j] = tmp;
                flag = 1;						  /*发生数据交换,标志flag置为1*/
            }
        }
    }
}

// 模板方法
template<typename T>
void bubbleSort( T arr[] , int n){

    bool swapped;
    //int newn; // 理论上,可以使用newn进行优化,但实际优化效果较差

    do{
        swapped = false;
        //newn = 0;
        for( int i = 1 ; i < n ; i ++ )
            if( arr[i-1] > arr[i] ){
                swap( arr[i-1] , arr[i] );
                swapped = true;

                // 可以记录最后一次的交换位置,在此之后的元素在下一轮扫描中均不考虑
                // 实际优化效果较差,因为引入了newn这个新的变量
                //newn = n;
            }

        //n = newn;

        // 优化,每一趟Bubble Sort都将最大的元素放在了最后的位置
        // 所以下一次排序,最后的元素可以不再考虑
        // 理论上,newn的优化是这个优化的复杂版本,应该更有效
        // 实测,使用这种简单优化,时间性能更好
        n --;

    }while(swapped);
}

选择排序

// 一般方法
void selectSort(int array[], int arraySize) {
    int i, j, min, tmp;
    for(i = 0; i < arraySize - 1; i++) {
        min = i;
        for(j = i + 1; j < arraySize; j++) { /*在未排序的子序列中找到最小的元素位置*/
            if(array[j] < array[min]) {
                min = j;          /*用min记录下最小元素的位置*/
            }
        }
        if(min != i) {                    /*最小的元素不位于子序列的第1个位置*/
            tmp = array[min] ;
            array[min] = array[i];       /*元素的交换*/
            array[i] = tmp;
        }
    }
}

// 模板方法
template<typename T>
void selectionSort(T arr[], int n){

    for(int i = 0 ; i < n ; i ++){

        int minIndex = i;
        for( int j = i + 1 ; j < n ; j ++ )
            if( arr[j] < arr[minIndex] )
                minIndex = j;

        swap( arr[i] , arr[minIndex] );
    }
}

希尔排序

// 一般方法
void shellSort(int array[],int arraySize)
{
    int i, j, flag ,gap = arraySize;
    int tmp;
    while(gap > 1){
		gap = gap/2;                    /*按照经验值,每次缩小增量一半*/
		do{                             /*子序列可以使用冒泡排序*/
			flag = 0;
			for(i=0;i<arraySize-gap;i++){
				j = i + gap;
				if(array[i]>array[j]) {        /*子序列按照冒泡排序方法处理*/
					tmp = array[i];            /*交换元素位置*/
					array[i] = array[j];
					array[j] = tmp;
					flag = 1;             /*设置标志flag*/
				}
			}
		}while(flag !=0);                 /*改进了的冒泡排序法*/
    }
}

// 模板方法
template<typename T>
void shellSort(T arr[], int n){

    // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
    int h = 1;
    while( h < n/3 )
        h = 3 * h + 1;

    while( h >= 1 ){

        // h-sort the array
        for( int i = h ; i < n ; i ++ ){

            // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
            T e = arr[i];
            int j;
            for( j = i ; j >= h && e < arr[j-h] ; j -= h )
                arr[j] = arr[j-h];
            arr[j] = e;
        }

        h /= 3;
    }
}

归并排序

// 使用优化的归并排序算法, 对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort2(T arr[], int l, int r){

    // 优化2: 对于小规模数组, 使用插入排序
    if( r - l <= 15 ){
        insertionSort(arr, l, r);
        return;
    }

    int mid = (l+r)/2;
    __mergeSort2(arr, l, mid);
    __mergeSort2(arr, mid+1, r);

    // 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
    // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
    if( arr[mid] > arr[mid+1] )
        __merge(arr, l, mid, r);
}

template<typename T>
void mergeSort2(T arr[], int n){

    __mergeSort2( arr , 0 , n-1 );
}

快速排序

方法一
void swap(int *a, int *b) {
    /*交换序列中元素的位置*/
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}

void quickSortArray(int array[], int s, int t) {
    int low, high;
    if(s < t) {
        low = s;
        high = t + 1;
        while(1) {
            do low++;
            while(array[low] >= array[s] && low != t);    /*array[s]为基准元素,重复执行low++操作*/
            do high--;
            while(array[high] <= array[s] && high != s);  /*array[s]为基准元素,重复执行high--操作*/
            if(low < high)
                swap(&array[low], &array[high]);          /*交换array[low]和array[high]的位置*/
            else
                break;
        }
        swap(&array[s], &array[high]);                  /*将基准元素与array[high]进行交换*/
        quickSortArray (array, s, high - 1);            /*将基准元素前面的子序列快速排序*/
        quickSortArray (array, high + 1, t);            /*将基准元素后面的子序列快速排序*/
    }
}

void quickSort(int array[], int arraySize) {
    quickSortArray(array, 0, arraySize - 1);
}
方法二
// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition(T arr[], int l, int r){

    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    swap( arr[l] , arr[rand()%(r-l+1)+l] );

    T v = arr[l];
    int j = l;
    for( int i = l + 1 ; i <= r ; i ++ )
        if( arr[i] < v ){
            j ++;
            swap( arr[j] , arr[i] );
        }

    swap( arr[l] , arr[j]);

    return j;
}

// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition2(T arr[], int l, int r){

    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    swap( arr[l] , arr[rand()%(r-l+1)+l] );
    T v = arr[l];

    // arr[l+1...i) <= v; arr(j...r] >= v
    int i = l+1, j = r;
    while( true ){
        // 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
        // 思考一下为什么?
        while( i <= r && arr[i] < v )
            i ++;

        // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
        // 思考一下为什么?
        while( j >= l+1 && arr[j] > v )
            j --;

        // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
        // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html

        if( i > j )
            break;

        swap( arr[i] , arr[j] );
        i ++;
        j --;
    }

    swap( arr[l] , arr[j]);

    return j;
}

// 对arr[l...r]部分进行快速排序
template <typename T>
void _quickSort(T arr[], int l, int r){

    // 对于小规模数组, 使用插入排序进行优化
    if( r - l <= 15 ){
        insertionSort(arr,l,r);
        return;
    }

    // 调用双路快速排序的partition
    int p = _partition2(arr, l, r);
    _quickSort(arr, l, p-1 );
    _quickSort(arr, p+1, r);
}

template <typename T>
void quickSort(T arr[], int n){

    srand(time(NULL));
    _quickSort(arr, 0, n-1);
}

三路快速排序

// 递归的三路快速排序算法
template <typename T>
void __quickSort3Ways(T arr[], int l, int r){

    // 对于小规模数组, 使用插入排序进行优化
    if( r - l <= 15 ){
        insertionSort(arr,l,r);
        return;
    }

    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
    swap( arr[l], arr[rand()%(r-l+1)+l ] );

    T v = arr[l];

    int lt = l;     // arr[l+1...lt] < v
    int gt = r + 1; // arr[gt...r] > v
    int i = l+1;    // arr[lt+1...i) == v
    while( i < gt ){
        if( arr[i] < v ){
            swap( arr[i], arr[lt+1]);
            i ++;
            lt ++;
        }
        else if( arr[i] > v ){
            swap( arr[i], arr[gt-1]);
            gt --;
        }
        else{ // arr[i] == v
            i ++;
        }
    }

    swap( arr[l] , arr[lt] );

    __quickSort3Ways(arr, l, lt-1);
    __quickSort3Ways(arr, gt, r);
}

template <typename T>
void quickSort3Ways(T arr[], int n){

    srand(time(NULL));
    __quickSort3Ways( arr, 0, n-1);
}

堆排序

方法一
#include "stdio.h"

void adjust(int k[],int i,int n)
{
    int j;
    int tmp;
    tmp = k[i-1];
    j = 2 * i ;
    while(j<=n)
    {
        if(j<n && k[j-1]>k[j])
        {
            j++;				/* j为i的左右孩子中较小孩子的序号*/
        }
        if(tmp<=k[j-1])
        {
            break;				/*tmp为最小的元素,则不需要元素的交换*/
        }
        k[j/2-1] = k[j-1];				/*交换元素位置*/
        j = 2 * j ;
    }
    k[j/2-1] = tmp;
}

void heapSort(int k[],int n)
{
    int i,j;
    int tmp;
    for(i=n/2;i>=1;i--)
    {
        adjust(k,i,n);			/*将原序列初始化成一个小顶堆*/
    }

    for(i=n-1;i>=0;i--)
    {
        tmp = k[i];			/*调整序列元素*/
        k[i] = k[0];
        k[0] = tmp;
        adjust(k,1,i);
    }
}

main()
{
    int i,k[10]= {5,2,12,6,9,0,3,6,15,20};
        printf("The orginal data array is\n") ;
    for(i=0;i<10;i++)                        /*显示原序列之中的元素*/
        printf("%d ",k[i]);
    heapSort(k,10);                         	/*快速排序*/
    printf("\nThe result of heap sorting for the array is\n");
    for(i=0;i<10;i++)                       	/*显示排序后的结果*/
        printf("%d ",k[i]);
    getchar();
	getchar();
}
方法二
template<typename Item>
class MaxHeap{

private:
    Item *data;
    int count;
    int capacity;

    void shiftUp(int k){
        while( k > 1 && data[k/2] < data[k] ){
            swap( data[k/2], data[k] );
            k /= 2;
        }
    }

    void shiftDown(int k){
        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[j+1] > data[j] ) j ++;
            if( data[k] >= data[j] ) break;
            swap( data[k] , data[j] );
            k = j;
        }
    }

public:

    // 构造函数, 构造一个空堆, 可容纳capacity个元素
    MaxHeap(int capacity){
        data = new Item[capacity+1];
        count = 0;
        this->capacity = capacity;
    }

    // 构造函数, 通过一个给定数组创建一个最大堆
    // 该构造堆的过程, 时间复杂度为O(n)
    MaxHeap(Item arr[], int n){
        data = new Item[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    ~MaxHeap(){
        delete[] data;
    }

    // 返回堆中的元素个数
    int size(){
        return count;
    }

    // 返回一个布尔值, 表示堆中是否为空
    bool isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一个新的元素 item
    void insert(Item item){
        assert( count + 1 <= capacity );
        data[count+1] = item;
        shiftUp(count+1);
        count ++;
    }

    // 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
    Item extractMax(){
        assert( count > 0 );
        Item ret = data[1];
        swap( data[1] , data[count] );
        count --;
        shiftDown(1);
        return ret;
    }

    // 获取最大堆中的堆顶元素
    Item getMax(){
        assert( count > 0 );
        return data[1];
    }
};

// heapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序
// 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)
// 整个堆排序的整体时间复杂度为O(nlogn)
template<typename T>
void heapSort1(T arr[], int n){

    MaxHeap<T> maxheap = MaxHeap<T>(n);
    for( int i = 0 ; i < n ; i ++ )
        maxheap.insert(arr[i]);

    for( int i = n-1 ; i >= 0 ; i-- )
        arr[i] = maxheap.extractMax();

}


// heapSort2, 借助我们的heapify过程创建堆
// 此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)
// 堆排序的总体时间复杂度依然是O(nlogn), 但是比上述heapSort1性能更优, 因为创建堆的性能更优
template<typename T>
void heapSort2(T arr[], int n){

    MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
    for( int i = n-1 ; i >= 0 ; i-- )
        arr[i] = maxheap.extractMax();

}

猜你喜欢

转载自blog.csdn.net/qq_34719188/article/details/83931519