常用的内排序算法与C++实现

《数据结构与算法分析》几乎是所有计算机编程的基础,而在招聘过程中基本上只要是中大型的互联网公司均会考察这方面的内容。在这门知识中最重要的一部分是排序。在VS 2013上简单实现了一些基础算法。事实上每种算法都可以被写成很简短的代码,各种算法在经过很多高手的改进后,也衍生出更多性能更好的方法。但最根本的算法设计思想始终来源于几种最基本的排序算法,因此对这些基础算法的步骤和流程有深刻理解变得非常重要。

参考资料:《数据结构与算法分析》(C++版) Clifford A. Shaffer著

插入排序(insert sorting)

插入排序逐个处理待排序的记录,每个新纪录都与前面已排序的子序列进行比较,并找到相应的位置进行插入。可归纳为以下步骤:

  1. 设待排序数组为a[0, 1, …, n-1],开始时,令a[0]成为1个有序序列(即将第一个元素看成已经被排序),未排序的为a[1..n-1]。令i=1;
  2. 将a[i]插入当前的有序区a[0,…,i-1]中,形成a[0,…,i]的有序区间。
  3. i++,重复第二步直到i==n-1,排序完成。

常用的内排序算法与C++实现_第1张图片

最差情况下,直接插入排序的最大时间代价为θ(n²),最小时间代价为θ(n),平均时间代价为θ(n²)。

冒泡排序(bubble sorting)

冒泡排序通过多次重复比较每对相邻的元素,并按规定的顺序交换他们,最终把数据进行排序。一直重复下去,直到结束。这个算法的过程就像一个个“气泡”被推到数组的顶端。由于每轮循环都只是比较相邻的关键码,所以这是一个比较排序。起泡排序的交换次数与插入排序的交换次数相同。

冒泡排序算法的整体流程如下:

  1. 比较数组中两个相邻的元素。如果元素下标小的值比下标大的值要大,则两者进行交换;
  2. 对数组的每一对相邻元素重复以上过程,从数组的底部比较到顶部。完成这一步后,数组顶部元素是最小的元素;
  3. 顶部最小的元素无需再次比较,对其余的元素重复以上的步骤;
  4. 持续每次对当前数组中越来越少的元素重复上面的步骤,直到没有任何一对元素需要比较。

常用的内排序算法与C++实现_第2张图片

冒泡排序的最大、最小和平均时间代价均为θ(n²)。

冒泡排序经常被用做C语言课程的第一个实验来介绍算法的概念。但其实这是一种比较慢的排序,不如插入排序那样通俗易懂,且没有较好的最佳情况时间代价。

选择排序(selection sorting)

选择排序首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。算法过程如下:

  1. 找到列表中的最小值;
  2. 把它和第一个位置的元素交换;
  3. 列表其余部分重复上面的步骤(从第二个位置开始,且每次加1)。

常用的内排序算法与C++实现_第3张图片

列表被有效地分为两个部分:从左到右的有序部分,和余下待排序部分。

希尔排序(shell sorting)

首先需要知道的是,希尔排序的基本思路是进行一系列操作,最后形成一组基本有序的数据,此时对数据使用插入排序的效率将会大大提高。

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了,此时插入排序可以很快完成。

假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。

常用的内排序算法与C++实现_第4张图片

归并排序(merge sorting)

归并排序基于分治的思想,将一个待排序序列分成两个长度相等的子序列,为每个子序列排序,然后再将两个序列合并起来。合并两个有序子序列的过程成为归并。该算法不依赖于输入数组中元素的组合方式,因此避免了类似快速排序那样的最差情况,其最差、最佳和平均时间代价均为O(n*logn)。归并排序的基本步骤如下:

  1. 如果列表的长度是0或1,那么它已经有序;
  2. 若以上假设不成立,则将未排序的数组平均划分为两个子序列;
  3. 对于每个子序列,递归使用归并排序;
  4. 合并两个子序列,使之整体有序。

常用的内排序算法与C++实现_第5张图片

快速排序(quick sorting)

与归并排序相同,快速排序也采用了分治的思想,把一个待排序数据列表分为两个子列表。步骤是:

  1. 从数据列表中,选择一个元素,称为轴值(pivot);
  2. 重新排序列表,把所有数值小于枢轴的元素排到基准之前,所有数值大于基准的排基准之后(相等的值可以有较多的选择)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 分别递归排序较大元素的子列表和较小的元素的子列表。当列表元素个数<=1时,递归结束。

常用的内排序算法与C++实现_第6张图片

当每个轴值都将数组分成相等的两部分时,将出现快排的最佳情况。这种情况下算法可以一直将数组分割下去,直到最后。

堆排序(Heap sorting)

堆排序假设待排序数组满足整二叉树的结构,根据堆的性质,构造一个最大值堆,然后提取堆中最大的那个元素,把它放到部分有序数组的结尾。提取一个最大元素后,重新构造最大值堆,然后再次提取新堆的最大元素。重复这个过程,直到堆中没有元素。

堆通常需要支持两种操作:节点插入和节点删除。这里第一个提取到的最大值元素放到数组的最后一个空位置,第二大的元素放到数组的倒数第二个空位置,以此类推。堆排序完成时,数组元素便是从小到大排列。

常用的内排序算法与C++实现_第7张图片

一般认为,属于性能稳定的排序算法主要有(不限于):

  1. 冒泡排序(bubble sort)
  2. 双向的冒泡排序,又称鸡尾酒排序 (Cocktail sort)
  3. 插入排序 (insertion sort)
  4. 归并排序(merge sort)

不稳定的排序算法主要有(不限于):

  1. 快速排序(quicksort) 虽然快排对于庞大且乱序的数据量拥有几乎最快的排序性能,但实际使用过程中往往受制于最差时间代价而无法被采用。
  2. 选择排序(selection sort) 依赖于原始数据的有序程度。
  3. 希尔排序(shell sort) 实际使用中通常需要确定增量递减的程度,一般来说增量序列为(2^k, 2^(k-1), …, 2,
    1)时该排序并没有太大效果,而使用“增量每次除以3”所选择的序列(…, 121, 40, 13, 4, 1)时效果更好。
  4. 堆排序(heapsort)

通常,数据交换只发生在相邻数据之间的算法是稳定的,而存在不相邻交换的排序算法是不稳定的。但这一说法并不绝对,对于一些的稳定排序算法,可以通过控制交换条件从而变成不稳定排序算法,因此在实现一种具体算法时要重点考虑循环和迭代条件的设定。

除此之外,还有很多相当经典的排序算法,如:桶排序、梳排序、地精排序、奇偶排序、鸽笼排序等等。基本上在CSDN、维基百科等网站上都能找到详细的解析。后续需继续学习和巩固!

文中的图均来自:http://www.open-open.com/lib/view/open1404781467544.html ,该文章给出了更多算法的解释与实现。

作为一种数据结构,这里使用类将以上几种方法简单实现并封装起来,便于日后的复习与使用:

类声明:

#ifndef SORTINGFUNCTIONS_H
#define SORTINGFUNCTIONS_H

#include <iostream>
#include <ctime>
#include <iterator>
#include <vector>
#include <algorithm>
#include <string>

class SortingFunctions {
public:
    SortingFunctions(){};
    ~SortingFunctions(){};
    void bubble(int[], int);                    // 冒泡排序(bubble sorting)
    void selectionSort(int[], int);             // 选择排序(selection sorting)
    void insertSort(int[], int);                // 插入排序(insert sorting)
    void shellSort(int[], int);                 // 希尔排序(shell sorting)
    void mergeSort(int[], int[], int, int);     // 归并排序(merge sorting)
    void quickSort(int[], int, int);            // 快速排序(quick sorting)
    void maxHeapSort(int[], int);               // 堆排序(Heap sorting)

private:
    int partition(int array[], int left, int right);  // 快速排序中的轴值操作
    void merge(int[], int[], int, int); // 把两个有序数组合并成一个有序数组
    void maxHeapFix(int[], int, int);   // 最大值堆调整函数
    void maxHeap(int[], int);           // 建立最大值堆
};

#endif

函数的具体定义:

#include "SortingFunctions.h"

using namespace std;

// 冒泡排序(bubble sorting)
// 最大时间代价:θ(n^2)
// 最小时间代价:θ(n^2)
// 平均时间代价:θ(n^2)
void SortingFunctions::bubble(int array[], int n)
{
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = n - 1; j > 1; j--)
        {
            if (array[j] < array[j - 1])
                swap(array[j], array[j - 1]);
        }
    }
}

// 选择排序(selection sorting)
// 最大时间代价:θ(n^2)
// 最小时间代价:θ(n^2)
// 平均时间代价:θ(n^2)
void SortingFunctions::selectionSort(int array[], int n)
{
    for (int i = 0; i < n - 1; i++)
    {
        int min = i;
        // 内循环,每次循环时找出比array[min]小的元素,将其坐标赋给min作为新的最小元素值
        for (int j = i + 1; j < n; j++)
        {
            if (array[j] < array[min])
                min = j;
        }
        swap(array[i], array[min]);
    }
}

// 插入排序(insert sorting)
// 最大时间代价:θ(n^2)
// 最小时间代价:θ(n)
// 平均时间代价:θ(n^2)
void SortingFunctions::insertSort(int array[], int n)
{
    for (int i = 1; i < n; i++)
    {
        for (int j = i; j > 0; j--)
        {
            if (array[j] < array[j - 1])
                swap(array[j - 1], array[j]);
            else
                break;
        }
    }
}

// 希尔排序(shell sorting)
// 平均时间代价:θ(n^1.5) 增量每次除以2的情况下
void SortingFunctions::shellSort(int array[], int n)
{
    int i, j, k, l;
    for (i = n / 2; i > 0; i /= 2)
    {
        for (j = 0; j < i; j++)
        {
            for (k = j + i; k < n; k += i)
            {
                for (l = k; l > i; )
                {
                    if (array[l] < array[l - i])
                    {
                        swap(array[l], array[l - i]);
                        l -= i;
                    }

                    else
                    {
                        l -= i; break;
                    }
                }
            }
        }
    }
}

// 快速排序的一次循环子函数
// 将轴值放到数组的适当的位置
int SortingFunctions::partition(int array[], int left, int right)
{
    int pivot = (left + right) / 2;
    int tmp = array[pivot];
    swap(array[pivot], array[right]); // 把轴值放到最右
    int i = left;
    int j = right;
    while (1)
    {
        // 左边指针i向右移动,直到找到一个大于轴值tmp的值
        while (1)
        {
            // 如果i与j相遇则确定轴值位置,将当前下标返回
            if (i == j)
            {
                array[i] = tmp;
                return i;
            }
            // 若轴值左边元素大于轴值,则与轴值右边j下标元素互换
            if (array[i]>tmp) 
            {
                array[j] = array[i];
                j--;
                break;
            }
            i++;
        }
        // 右边指针j向左移动,直到找到一个小于轴值tmp的值
        while (1)
        {
            // 如果i与j相遇则确定轴值位置,将当前下标返回
            if (i == j)
            {
                array[j] = tmp;
                return j;
            }
            // 若轴值右边元素小于轴值,则与轴值坐边i下标元素互换
            if (array[j]<tmp)
            {
                array[i] = array[j];
                i++;
                break;
            }
            j--;
        }
    }
}

// 快速排序(quick sorting)
// 最大时间代价:θ(n^2)
// 最小时间代价:θ(n*logn)
// 平均时间代价:θ(n*logn)
// 最差情况下退化成冒泡排序法
void SortingFunctions::quickSort(int array[], int left, int right)
{
    if (right <= left)
        return;
    int pivot = SortingFunctions::partition(array, left, right);
    quickSort(array, left, pivot - 1);
    quickSort(array, pivot + 1, right);
}

// 归并排序子函数
// 把两个有序数组合并成一个有序数组
void SortingFunctions::merge(int array[], int temp[], int left, int right)
{
    ;
}

// 归并排序(merge sorting)
// 最大时间代价:θ(n*logn)
// 最小时间代价:θ(n*logn)
// 平均时间代价:θ(n*logn)
void SortingFunctions::mergeSort(int array[], int temp[], int left, int right)
{
    int middle = (left + right) / 2;
    if (left >= right) return;
    mergeSort(array, temp, left, middle);
    mergeSort(array, temp, middle + 1, right);
    for (int i = left; i <= right; i++)
        temp[i] = array[i];
    // 分前后两部分
    int j = left; 
    int k = middle + 1;
    for (int curr = left; curr <= right; curr++)
    {
        if (j == middle + 1)
            array[curr] = temp[k++];
        else if (k > right)
            array[curr] = temp[j++];
        else if (temp[j] < temp[k])
            array[curr] = temp[j++];
        else
        {
            array[curr] = temp[k++];
        }
    }
}

// 最大值堆调整函数
// 用于插入或删除堆中的数值时重组堆的操作
void SortingFunctions::maxHeapFix(int a[], int n, int i)
{
    int j, temp;
    temp = a[i];
    j = i * 2 + 1; // 节点i的子节点为 i*2+1, i*2+2 
    while (j < n)
    {
        if (j + 1 < n && a[j] < a[j + 1])
            j++;

        if (a[j] <= temp)
            break;

        a[i] = a[j];
        i = j;
        j = i * 2 + 1;
    }
    a[i] = temp;
}

void SortingFunctions::maxHeap(int a[], int n)  // 建立最大值堆函数
{
    for (int i = n / 2 - 1; i >= 0; i--) // 数组中最多有(n / 2 - 1)个为非叶节点
        maxHeapFix(a, n, i);
}

void SortingFunctions::maxHeapSort(int a[], int n)
{
    maxHeap(a, n);
    for (int i = n - 1; i >= 1; i--)
    {
        swap(a[i], a[0]);
        maxHeapFix(a, i, 0);
    }
}

以下是测试多种排序算法的主函数:

#include "SortingFunctions.h"

using namespace std;

int main()
{
    SortingFunctions sort;  // 定义一个实体
    srand(time(NULL));
    int num = 50000;        // 待排序数组大小
    int a[50000];
    int b[50000];
    for (int i = 0; i < num; ++i)
    {
        a[i] = rand() % 100000; // 随机生成待排序样本
        b[i] = 0;
    }

    // 排序算法计时
    long start = clock();
    //sort.bubble(a, num);
    //sort.selectionSort(a, num);
    //sort.insertSort(a, num);
    //sort.shellSort(a, num);
    //sort.mergeSort(a, b, 0, num - 1);
    //sort.quickSort(a, 0, num - 1);
    sort.maxHeapSort(a, num);

    long finish = clock();
    double totaltime = (double)(finish - start) / CLOCKS_PER_SEC; // 算法耗时计算
    for (int i = 0; i<num; i++)
        cout << a[i] << " ";
    cout << endl;
    cout << "算法耗时:" << totaltime << 's';
    cout << endl;
    getchar();
    return 0;
}

你可能感兴趣的:(Algorithm,数据结构,C++,sort,vs)