排序详解(希尔,快排,归并等)

今天集中把几种排序的方法列一下,当然最出名的希尔,快排,归并和其优化当然也是满载

说到希尔排序的话,不得不先提到的就是插入排序了,希尔排序就是对直接插入排序的一种优化,下面就是直接插入排序的思想

直接插入排序

void InsertSort(int *a, size_t size)
{
    assert(a);
    for (int i = 1; i < size; ++i)
    {
        int index = i;
        int tmp = a[index];
        int end = index - 1;
        while (end >= 0 && a[end]>tmp)
        {
            a[end + 1] = a[end];
            end--;
        }
        a[end + 1] = tmp;
    }
}

这就是直接插入排序的代码,思想很简单,代码也很简单

为什么希尔排序比直接插入排序更加优化呢?当需要排序的数组过长的时候,有可能出现,插入数据的时候需要把数据插入到数组头的位置,那么数组中需要移动的数据就太多了,效率很低,但是当数组趋于有序的时候,直接插入排序的效率是很高的,所以希尔排序可以理解为直接插入排序的预排序,让数组更趋于有序,希尔排序的最后一趟排序就是直接插入排序

希尔排序
将一个数组进行分组(就是隔几个元素分为一组)如下图

图中选择每隔两个元素分为一组,隔几个元素(设为gap)一组是有讲究的,会影响到排序的效率的,一会就推荐一种算法

分组之后,对每一组都进行插入排序,执行完一次所有的分组的插入排序之后算作完成一趟排序,然后减少gap的值,直到最后一次gap的值会变为1,成为直接插入排序

下面是代码

void ShellSort(int *a,size_t size)
{
    assert(a);
    int gap = size;
    while (gap > 1)
    {
        gap = gap / 3 + 1;
        for (int i = gap; i < size; ++i)
        {
            int index = i;
            int tmp = a[index];
            int end = index - gap;
            while (end >= 0 && a[end]>a[index])
            {
                a[end + gap] = a[end];
                end -= gap;
            }
            a[end + gap] = tmp;
        }
    }
}


每次对gap的值进行gap=gap/3+1,为啥?因为比较优,具体应该就是数学问题了,我就不太清楚了。。。。

接下来是选择排序,选择选择,就是每一次选出最大(小)值,然后交换到最高(低)的位置,优化!一次不仅可以选出最小的值,还可以选出最大的,同时选出,同时交换,可以提高效率

void SelectSort(int *a,size_t size )
{
    assert(a);
    int min;
    int max;
    for (int i = 0; i<  size; i++)
    {
        min = i;
        max =   size - 1 - i;
        for (int j = i + 1; j<  size - i; j++)
        {
            if (  a[min]>  a[j])
            {
                min = j;
            }
            if (  a[max]<  a[j])
            {
                max = j;
            }
        }
        swap(a[i], a[min]);
        swap(a[size - 1 - i], a[max]);
    }
}

思想啥的就不贴了,毕竟是比较简单和基础的排序了

堆排序

接下来就是堆排序了!什么是堆,这里我就进行简单的介绍了,堆的本质是一个数组,将这个数组看成一个二叉树,很抽象,来个图

顺序把数组弄成二叉树,大堆(每个父亲节点都比孩子节点的值要大),小堆(每个父亲节点都比孩子节点的值要小,上图就是一个小堆),所谓的堆排序就是把待排序的数组先建堆

每一次交换之后将调整的范围缩小一个,这样就能保证,每次交换到最后的数都是大数,并到了自己应该到的位置上去,建堆的过程用到向下调整,,每一次交换之后也要向下调整,堆是一种数据结构,这里就不详解了,之后会整理出堆来,这里介绍堆排序的思想和代码

void AdjustDown(int *a, size_t size, int root)
{
    assert(a);
    int child = root * 2 + 1;
    while (child < size)
    {
        if (child + 1 < size && a[child + 1] > a[child])
        {
            child++;
        }
        if (a[child]>a[root])
        {
            swap(a[child], a[root]);
            root = child;
            child = root * 2 + 1;
        }
        else
        {
            break;
        }
    }
}


void HeapSort(int *a, size_t size)
{
    assert(a);
    for (int i = (size - 2) / 2; i >= 0; --i)
    {
        AdjustDown(a, size, i);
    }
    for (int i = size - 1; i >= 0; --i)
    {
        swap(a[0], a[i]);
        AdjustDown(a, i, 0);
    }
}


接下来就是快排了!!这个被誉为十大算法的家伙!!

快速排序

快排的思想是拆分递归,直到递归到最深层(就一个元素)

int PartionSort(int *a,int left,int right)
{
    int MidIndex = GetMidIndex(a, left, right);
    swap(a[MidIndex], a[right]);
    int key = a[right];
    int begin = left;
    int end = right - 1;
    while (begin < end)
    {
        while (begin < end && a[begin] <= key)
        {
            ++begin;
        }
        while (begin < end && a[end] >= key)
        {
            --end;
        }
        if (begin < end)
        {
            swap(a[begin], a[end]);//begin<end,比key值小的和比key值大的交换
        }
    }
    if (a[begin] > key)
    {
        swap(a[begin], a[right]);
        return begin;
    }
    return right;
}

void QuickSort(int *a,int left,int right)
{
    assert(a);
    if (right - left < 1)
    {
        return;
    }
    int boundary = PartionSort(a,left,right);
    QuickSort(a, left, boundary-1);
    QuickSort(a, boundary + 1, right);

}


但是不够优化,当每次取的key值恰好比较接近最大值或者最小值的时候,分界递归的时候就会出现分布不均匀,导致效率低下,当划分成两边相等的时候自然比较好,所以加上这个部分会比较好

  • 快排优化之一
int GetMidIndex(int *a, int left, int right)
{
    assert(a);
    int mid = left + (right - left) / 2;
    if (a[left] < a[right])
    {
        if (a[mid] < a[left])
        {
            return left;
        }
        else if (a[mid] < a[right])
        {
            return mid;
        }
        else
            return right;
    }
    else
    {
        if (a[mid] < a[right])
        {
            return right;
        }
        else if (a[mid] < a[left])
        {
            return mid;
        }
        else
            return left;
    }
}


三数取中法,代码已经更新过了,所以上边的快排已经是用三数取中优化过的

  •  快排优化之二

当快排递归到比较深层的时候,被分成小部分的区间内已经趋于有序了,那么采用直接插入排序就可以有效的提高效率!!具体做法就是在QuickSort中的if部分修改,改掉递归结束条件,然后加上直接插入排序的代码就好了

  • 快排之三

这个不能算是优化,思想有些不同,这次是从同一边走采用cur和prev两个参数,外层的递归还是不变的,只是一次排序不同了

int PartionSort2(int *a,int left,int right)
{
    int key = a[right];
    int cur = left;
    int prev = left - 1;
    while (cur < right)
    {
        if (a[cur] < key && prev++ != cur)
        {
            swap(a[prev], a[cur]);
        }
        cur++;
    }
    swap(a[prev], a[cur]);
    return prev;
}


  • 忘了把非递归贴上来了,赶紧加上
void QuickSort_NonR(int *a)
{
    stack<testnode> s;
    s.push(testnode(0, 9));
    while (!s.empty())
    {
        testnode top = s.top();
        s.pop();
        int MidIndex = GetMidIndex(a, top._left, top._right);
        swap(a[MidIndex], a[top._right]);
        int key = a[top._right];
        int begin = top._left;
        int end =top._right - 1;
        while (begin < end)
        {
            while (begin < end && a[begin] <= key)
            {
                ++begin;
            }
            while (begin < end && a[end] >= key)
            {
                --end;
            }
            if (begin < end)
            {
                swap(a[begin], a[end]);
            }
        }
        if (a[begin] > key)
        {
            swap(a[begin], a[top._right]);
            s.push(testnode(top._left, begin));
            s.push(testnode(begin + 1, top._right));
        }
    }
}


最后key值还是会跑到大概中间的位置,和他自己应该在的地方比较接近

最后一个排序就是归并排序啦!

归并排序

归并排序一上来就将数组分割成两部分,然后不停的分割,直到一个元素不能再分位置,然后开始合并相邻的两个元素,合并之后当然是有序的,有序之后就可以回到上一层,然后不断的进行合并,最后整个数组都有序啦,也就是说要想合并,两个部分都必须是有序的才行。

就是类似这样的

思想还是不太难理解的

实现这样的思想需要开辟辅助空间,因为当两部分有序的数组合并之后还要是有序的才行,需要一个同等大小的数组暂存一下数据

void MergeSelection(int *a, int *tmp, int begin1, int end1, int begin2, int end2)
{
    int index = begin1;
    while (begin1 <= end1 && begin2 <= end2)
    {
        if (a[begin1] < a[begin2])
        {
            tmp[index++] = a[begin1++];
        }
        else
            tmp[index++] = a[begin2++];
    }
    while (begin1 <= end1)
    {
        tmp[index++] = a[begin1++];
    }
    while (begin2 <= end2)
    {
        tmp[index++] = a[begin2++];
    }
}


void MergeSort(int *a ,int *tmp,int left,int right)
{
    int mid = left + (right - left) / 2;
    if (left < right)
    {

        MergeSort(a, tmp, left, mid);
        MergeSort(a, tmp, mid + 1, right);
        MergeSelection(a, tmp, left, mid, mid + 1, right);

        memcpy(a + left, tmp + left, sizeof(int)*(right - left + 1));
    }
}

tmp是我在测试用例中就开辟好的数组空间,直接作为参数传进去

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值