(数据结构)七种常用的排序算法分析及代码实现(上)

直接插入排序

思路分析

  • 1、首先,我们需要从前往后遍历数组。
  • 2、其次,在遍历数组的同时,我们需要将当时所遍历到的数字(存储在tmp的临时变量中,防止因循环的原因而丢失数据)与之前已排序好的部分进行从后往前的比较。

这里写图片描述

  • 3、当比之前的数字更小时,我们需要进行重新排序,即将end处数字放到end+1处,然后将tmp插入至end处。之后,再与前面的数字进行比较,即end- -。
  • 4、当比之前的数字更大时,则直接将数字插入到end+1处即可。然后进行下一个数字的遍历。
  • 5、易知,我们需要两层循环嵌套,来实现我们的插入排序。

代码实现

//直接插入排序
void StraightInsertion(int a[], size_t n)
{
    for (size_t i = 1; i < n; ++i)
    {
        int tmp = a[i];
        for (size_t end = i - 1; end >= 0; --end)
        {
            if (tmp < a[end])
            {
                a[end + 1] = a[end];
                a[end] = tmp;
            }
            else
            {
                a[end + 1] = tmp;
                break;
            }
        }
    }
}

时间复杂度

  • 当插入的数组已是有序时,则算法的最佳时间复杂度为O(N)
  • 当插入的数据是逆序时(针对上述升序排序算法),则有最坏时间复杂度O(N^2)

希尔排序

显而易见的是,直接插入排序算法在数据量较少的时候,效率还不错。但如果涉及到较多数据的排序,时间复杂度则会出现显著的提升。
希尔排序是基于直接插入排序算法的一个优化算法,即在对数组进行排序前,先通过一定操作使数组基本有序。从而在进行后续的直接插入排序操作时,达到降低时间复杂度的目的。

思路分析

  • 1、与直接插入算法相比,我们的主要策略是需要先使数组基本有序。
  • 2、这里采取跳跃分割策略,将相距某个增量(gap)的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序,而不是局部有序的。
    这里写图片描述

  • 3、我们对gap值进行递减循环,即对数组进行多次的子序列排序,这样我们得到的数组就会是一个大体有序的数组。之后再进行直接插入排序的话,可以大大降低排序的时间复杂度。

代码实现

void ShellSort(int* a, size_t n)
{
    assert(a);
    int gap = n;

    while (gap > 1)
    {
        gap = gap / 3 + 1;
        for (size_t i = 0; i < n - gap; ++i)
        {
            int end = i;
            int tmp = a[end + gap];

            while (end >= 0)
            {
                if (tmp < a[end])
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                else
                    break;
            }
            a[end + gap] = tmp;
        }
    }
}

时间复杂度

  • 当插入的数组已是有序时,则算法的最佳时间复杂度为O(N)
  • 当插入的数据是逆序时(针对上述升序排序算法),则有最坏时间复杂度O(N^2)
  • 平均时间复杂度为O(N^1.3)

选择排序

选择排序是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。

这里写图片描述

思路分析

  • 我们拿下标来控制我们的数组,以区间法进行控制
  • min作为哨兵来控制
  • 显然,我们需要用两层循环嵌套来实现
    这里写图片描述

代码实现

//选择排序
void SelectionSort(int a[], size_t n)
{
    for (size_t i = 0; i < n; ++i)
    {
        int min = i;

        //找到当前数组内的最小值
        for (size_t j = i + 1; j < n; ++j)
        {
            if (a[min]>a[j])
                min = j;
        }

        //进行交换
        if (i != min)
        {
            int temp = a[i];
            a[i] = a[min];
            a[min] = temp;
        }
    }
}

时间复杂度

  • 因为两层嵌套的存在,故而时间复杂度稳定在O(N^2)。

堆排序

堆排序就是利用堆来进行排序的方法。
假设我们利用大顶堆来进行排序的实现。大顶堆,显而易见的就是将整个序列内的最大值放在堆顶。将它移走(和最后一个元素进行交换),然后将剩余的n-1个序列重新构造成一个堆,即得到n-1个数中次大的值。
反复迭代执行,便能得到一个有序序列了。

思路分析

  • 1、构建大顶堆
  • 2、利用两次循环,不断进行堆调整和数据间交换。
    这里写图片描述
  • 3、交换数据,然后重新构建堆

代码实现

//向下调整算法
void _AdjustDown(int* a, int i, int n)
{
    int parent = i;
    int child = parent * 2 + 1;
    int temp = a[parent];

    while (child < n)
    {
        if (child + 1 < n
            &&a[child + 1] > a[child])
            ++child;

        if (a[child] < a[parent])
            break;

        a[parent] = a[child];

        parent = child;
        child = parent * 2 + 1;
    }
    a[parent] = temp;
}


//堆排序
void HeapSort(int* a, int n)
{
    //构建大顶堆
    for (int i = (n/2-1); i >= 0; --i)//n/2-1是第一个非叶子结点
    {
        _AdjustDown(a,i,n);
    }

    for (size_t i = n-1; i>0; --i)
    {
        //交换最后一个节点和大顶堆的最大值
        int temp = a[0];
        a[0] = a[i];
        a[i] = temp;

        //剩余的值继续调整
        _AdjustDown(a, 0, i-1);

    }
}

时间复杂度

  • 构建堆的时间复杂度为O(N),取堆顶然后重建堆需要O(LogN),所以整体的时间复杂度为O(nLogN)

冒泡排序

冒泡排序是一种交换排序,其基本思想是:两两比较相邻记录的关键字,如果反序则交换,直至没有反序的记录为止。

思路分析

  • 将每一个关键字与数列最后的关键词开始,从后往前比较。
  • 逐一比较时,将较小值交换到前面,直到最后找到最小值放置在第一个位置。
  • 用标记变量exchange,判断序列是否有序。若有序,则不再进行之后的循环工作。

代码实现


//冒泡排序
void BubbleSort(int* a, size_t n)
{
    assert(a);
    bool exchange = false;
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t end = n-1; end > i ; --end)
        {
            if (a[i]>a[end])
            {
                int temp = a[i];
                a[i] = a[end];
                a[end] = temp;
            }
            exchange = true;
        }

        if (exchange == false)//针对有序数组的优化
            break;
    }
}

时间复杂度

  • 由两层循环嵌套易知,冒泡排序的时间复杂度为O(N^2)。

References

图中动图来源:http://dsqiu.iteye.com/blog/1706817

阅读更多

没有更多推荐了,返回首页