每日一题32:排序

排序概述

排序用途广泛,比如为数据库查询结果按时间排序,最小生成树算法中对边按权重排序,背包问题中对物品按大小排序等等。排序算法有很多,本文主要记录了冒泡排序、插入排序、快速排序、选择排序、堆排序、归并排序等几种比较流行的算法。

冒泡排序

        //冒泡排序,对数组做n-1趟扫描,每一趟把未就位的元素中的最大的元素
        //放到他正确的位置上,每一趟扫描从输入数组第一个元素开始,依次与
        //它后一个元素比较,如果大于后一元素就交换两者,无论交换与否,在
        //这一趟的最大元素到达他应该在的位置之前,从原来后一元素所在的位
        //置开始继续这一趟扫描
        void BufferSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                //i从后起,最大的元素放到最后一个位置嘛
                for (int i = end; i > start; --i)
                {
                    //j < i,这就是“这一趟的最大元素到达他应
                    //该在的位置之前”的意思
                    for (int j = start; j < i; ++j)
                    {
                        if (comp(values[j + 1], values[j]))
                        {
                            swap(values[j + 1], values[j]);
                        }
                    }
                }
            }
        }

插入排序

        //第一个元素是有序的,如果第二个元素小于第一个元素,交换二者的位置,
        //此时前面的两个元素是有序的,第三个元素看做是一个插入前面两个元素
        //组成的有序表中。每个元素都看做是将其插入到
        //它前面序列中,先将前面大于带插入元素的元素后移一个位置,
        //然后将待插入元素放到空出来的位置上即可。实际上这个过
        //程可以看成是以间隔为1的一趟shell排序
        void InsertSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                // gap = 1的一趟shell排序
                shell_sort_once(values, start, end, 1);
            }
        }

二分插入排序

        //在简单插入排序中每个元素需要从后往前顺序在他前面的有序表中寻找自己的
        //位置,把这个查找过程使用二分查找,就形成了二分插入排序,假设一个元素
        //B应该放在元素A的位置后,而A有多个值,那么应该查找最后一个A的位置,然
        //后把B放到这个A的位置之后
        void BinaryInsertSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                for (int i = start + 1; i <= end; ++i)
                {
                    ValueType temp = values[i];
                    int low = start, high = i - 1, mid = (low + high) / 2;

                    while (low <= high)
                    {
                        if (comp(temp, values[mid])) high = mid - 1;
                        else low = mid + 1;
                        mid = (low + high) / 2;
                    }
                    for (int k = i; k > low; --k) values[k] = values[k - 1];
                    values[low] = temp;
                }
            }
        }

shell排序

        //选定一个间隔gap,按间隔将待排序数分为n/gap组,然后
        //运行一趟shell排序,完成后减小间隔,再运行一次shell排序。
        //不断重复这个过程,直到gap递减为1,以间隔为1运行最后一次
        //shell排序
        void ShellSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                int gap = end - start + 1;
                do
                {
                    gap = gap / 3 + 1;
                    shell_sort_once(values, start, end, gap);
                } while (gap > 1);
            }
        }

        //一趟希尔排序。取定一个间隔gap,则原数组中每隔gap个数的数分为一组,
        //例如,原数组为12 11 10 9 8 7 6 5 4 3 2 1,gap为3,则12 9 6 3为一组,
        //11 8 5 2为一组,10 7 4 1为一组。然后对每一组进行插入排序。插入排序
        //是从第二个数起,按顺序将它插入到前面序列中的合适位置,以12 9 6 7为例,
        //9小于12,所以先将9,复制到一个临时变量中,然后将12后移到9所在的位置,
        //再将9复制到12原来的位置上,这样9 12就排好序了。6比前面两个数都小,先将
        //6复制到临时变量中,然后将9 12都后移一个位置,再将6放到9所在的位置,7小于9和
        //12,大于6,所以将9和12向后移一个位置,把7插入到空出来的位置上,于是排序完成
        void shell_sort_once(ValueType values[], int start, int end, int gap)
        {
            for (int i = start + gap; i <= end; ++i)   //交叉进行
            {
                if (comp(values[i], values[i - gap]))
                {
                    ValueType temp = values[i];
                    int j = i - gap;
                    do
                    {
                        values[j + gap] = values[j];
                        j -= gap;
                    } while (j >= start && comp(temp, values[j]));
                    values[j + gap] = temp;
                }
            }
        }

快速排序

        //以数组中的某个元素作为枢纽,将小于枢纽的元素放到枢纽的左边,
        //大于枢纽的元素都放到枢纽的右边,这个过程叫做划分,然后在枢纽
        //左边的序列和枢纽左边的序列中不断划分,直到划分序列中只含有不
        //多于一个元素,划分结束后,排序也就完成了,快排的关键就是划分,
        //根据不同的划分方式,就得到了不同版本的快排程序
        void QuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                quick_sort(values, start, end);
            }
        }
        void RandomizedQuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                randomized_qsort(values, start, end);
            }
        }
        void Median3QuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                median3_qsort(values, start, end);
            }
        }
        //当一个划分序列元素个数为5~25时,继续使用划分递归地实现划分序列的排序往往
        //没有使用直接插入排序快,所以就可以把插入排序与快排结合起来加快排序速度
        void HybridQuickSort(ValueType values[], int start, int end,int m)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                hybrid_qsort(values, start, end, m);
            }
        }

        //有许多输入序列重复元素很多,极端例子是序列中全部元素都一样,
        //这时候再用原来的快排算法性能就太差了。针对这种情况,可以把与
        //枢纽元素一样大的元素都聚到中间,而在左边放置小于枢纽元素的元素,
        //右边放置大于枢纽元素的元素,然后再左右两个子序列中快排,这样的
        //快排算法叫做三路快排。三路快排关键是三路划分。
        void ThreeWayQuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                three_way_qsort(values, start, end);
            }
        }

        //一趟划分算法
        int partition(ValueType values[], int start, int end)
        {
            //以第一个元素作为枢纽,从数组两头向中间扫描
            ValueType pivot = values[start];
            while (start < end)
            {
                //先从后往前扫描,直到遇到一个小于枢纽的元素
                while (start < end && !comp(values[end], pivot)) --end;
                //把这个小于枢纽的元素放到start指向的位置上,
                //这是合理的,因为这个元素小于枢纽,要放到
                //枢纽最后的位置之前
                values[start] = values[end];
                //此时前指针指向一个小于枢纽的元素,所以下一个循环总是能推进前指针
                //从前往后扫描,直到前指针遇到一个大于枢纽的元素
                while (start < end && !comp(pivot, values[start])) ++start;
                //刚才后指针指向了一个小于枢纽的元素,这个元素放到了前指针推进
                //之前的位置上,前指针推进后,现在指向了一个大于枢纽的元素,
                //正好把它放到后指针指向的位置上
                values[end] = values[start];
                //此时,前指针指向的元素是一个大于枢纽的元素,但是这个元素是无效的,
                //如果start还没有遇到end,那么外层循环会继续,后指针也许就能找到
                //一个小于枢纽的元素,放到start位置上,如果后指针没有找到这样
                //一个元素,那么start会等于end,于是start不再推进,会
                //被自己的值赋值一次,不过这个位置上的元素还是无效的,外层循环结束
            }
            //外层循环结束后,start位置的值是无效的,它前面的值小于或等于枢纽,
            //后面的值大于或等于枢纽,于是把枢纽放到start位置上,就完成了一次划分
            values[start] = pivot;
            //start位置就是枢纽最后所在的位置
            return start;
        }

        //在划分区间随机选一个数作为枢纽,把它和第一个位置的元素交换,
        //再调用partition过程
        int randomized_partition(ValueType values[], int start, int end)
        {
            if (end == start) return start;
            if (end < start) return -1;
            srand(static_cast<unsigned>(time(nullptr)));
            int i = rand();
            i = static_cast<int>((i * 1.0 / RAND_MAX) * (end - start) + start);
            swap(values[i], values[start]);
            return partition(values, start, end);
        }
        //取区间首、尾、中间三个元素,求三个元素的中间值,然后把它和第一个
        //位置的元素交换,再调用partition过程
        int median3_partition(ValueType values[], int start, int end)
        {
            int mid = (start + end) / 2;
            int k = mid;
            if (comp(values[k], values[start]) && comp(values[start],values[end])) k = start;
            else if (comp(values[start], values[k]) && comp(values[end], values[start])) k = end;
            if (k != start) swap(values[k], values[start]);
            return partition(values, start, end);
        }
        //将区间划分为三段,前段是小于枢纽的元素,中间是与枢纽相等的元素,后端是
        //大于枢纽的元素.运行partition过程,有点区别就是如果前指针遇到一个与枢纽
        //相等的元素时,把它放到区间前段,后指针遇到一个与枢纽相等的元素是把它放
        //后段。partition过程运行完后,区间的排列如下(以6为枢纽举例):666234567899666,
        //注意中间有一个枢纽,然后再将两边的枢纽交换到中间来
        void three_way_partition(ValueType values[], int start, int end, int &left_pivot, int &right_pivot)
        {
            ValueType pivot = values[start];
            int i = start,p = start,q = end,j = end;
            while (i < j)
            {

                while (i < j)
                {
                    if (comp(pivot, values[j])) --j;
                    else if (values[j] == pivot) swap(values[q--], values[j--]);
                    else break;
                }
                values[i] = values[j];
                while (i < j )
                {
                    if (comp(values[i], pivot)) ++i;
                    else if (values[i] == pivot)  swap(values[p++], values[i++]);
                    else break;
                }
                values[j] = values[i];
            }
            values[i] = pivot;
            j = i + 1;
            i  -= 1;
            while (i > start && comp(values[i],values[start])) swap(values[i++], values[start++]);
            while (j < end && comp(values[end], values[j])) swap(values[j++], values[end--]);
            left_pivot = i;
            right_pivot = j;
        }

选择排序

        //先在输入数组中选择最小的值,如果它不是第一个元素,就把它与第一个位置上
        //的元素交换,在剩余的子数组继续这个过程,直到倒数第二个元素。这个过程就是
        //先选择最小的元素,然后选择第二小的元素……因此叫做选择排序
        void SelectSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                select_sort(values, start, end);
            }
        }

        void select_sort(ValueType values[], int start, int end)
        {
            for (int i = start; i < end; ++i)
            {
                int k = i;
                ValueType val = values[k];
                for (int j = i; j < end; ++j)
                {
                    if (comp(values[j + 1],val))
                    {
                        val = values[j + 1];
                        k = j + 1;
                    }
                }
                if (k != i) swap(values[k], values[i]);
            }
        }

堆排序

        //堆排序也算是一种选择排序,只是利用堆的性质加速了每一趟选择最小
        //元素的过程。因为最小堆的第一个元素是最小的,所以先在整个输入数组
        //中建堆,则最小的元素被放到了第一个位置,在剩下的元素序列中建堆,
        //不断重复这个过程,直到倒数第二个元素
        void HeapSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                for (int i = start; i < end; ++i)
                {
                    build_heap(&values[i], 0, end - i);
                }
            }
        }
        [这篇文章记录了堆的相关知识](http://blog.csdn.net/liao_jian/article/details/45721119)

归并排序

        //假设有两个排好序的序列,将它们和在一起,并要保持顺序性,那么就可以
        //这么做:每次从第一个序列或第二个序列中取走一个元素,哪个序列中的第
        //一个元素小就取哪个,当一个序列取完之后,把另一个序列直接接到按元素
        //取走顺序放置的那个序列后面,就得到了一个合并的有序列表。这个过程叫
        //做合并。把输入数组每两个元素合并一次,得到n/2个序列,最后一个序列可
        //能只有一个元素,把每个得到的序列看做一个元素,继续两两合并,直到合并
        //完所有的元素
        void MergeSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                merge_sort(values, start, end);
            }
        }

        void merge_sort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int connect = (start + end) / 2;
                merge_sort(values, start, connect);
                merge_sort(values, connect + 1, end);
                merge(values, start, connect, end);
            }
        }

        //归并排序归并阶段操作
        void merge(ValueType values[], int start, int connect, int end)
        {
            int length1 = connect - start + 1,
                length2 = end - connect;
            //只需将前半段元素复制出来就行了,右半段就放在
            //原来的序列中,这样行得通是因为复制完前半段后,
            //将排序结果写回原序列时,要么把复制出来的元素
            //重新写回去,位置刚刚够。要么从原区间去一个数
            //放到前面,此时后半段区间空出一个位置,使得原
            //区间中剩余的可用位置数正好等于前半区间此时剩余
            //元素的个数
            ValueType* left = new ValueType[length1],    //每次调用merge都会分配内存,其实可
                                                         //以使用一个全局内存空间以提高性能
                *right = &values[connect + 1];// new ValueType[length2];
            for (int i = start,j = 0; i <= connect; )
                left[j++] = values[i++];
            int i = start, j = 0, k = 0;
            while ( j < length1 && k < length2)
            {
                if (comp(left[j], right[k])) values[i++] = left[j++];
                else values[i++] = right[k++];
            }
            //前半区间还有剩余,那么说明后半区间已经取空,
            //直接把前半区间剩余元素复制到原区间中,否则,
            //后半区间还有剩余,并且已经就位
            while ( j < length1)
            {
                values[i++] = left[j++];
            }
            delete []left;
        }

归并排序还可以进一步改善,在每一次合并中都要开辟内存、释放内存,这个开销还是比较大的,可以使用一个全局的n/2 + 1大小的内存空间替换,这样就免去了内存申请与释放的消耗,整个排序过程做一次就够了。

完整实现代码

#ifndef _SORTER_H_
#define _SORTER_H_

#include "../include/Functor.h"
#include <cstdlib>
#include <time.h>
using namespace MyDataStructure;

namespace MyTools
{
    template<typename Value,typename Compare = MyDataStructure::less<Value>>
    class Sorter
    {
    public:
        typedef Value ValueType;
    public:

        /**************************************************************/
        /*                                                            */
        /*                 以下说明均以从小到大排序为例               */
        /*                                                            */
        /**************************************************************/

        //冒泡排序,对数组做n-趟扫描,每一趟把未就位的元素中的最大的元素
        //放到他正确的位置上,每一趟扫描从输入数组第一个元素开始,依次与
        //它后一个元素比较,如果大于后一元素就交换两者,无论交换与否,在
        //这一趟的最大元素到达他应该在的位置之前,从原来后一元素所在的位
        //置开始重复上述过程
        void BufferSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                //i从后起,最大的元素放到最后一个位置嘛
                for (int i = end; i > start; --i)
                {
                    //j < i,这就是“这一趟的最大元素到达他应
                    //该在的位置之前”的意思
                    for (int j = start; j < i; ++j)
                    {
                        if (comp(values[j + 1], values[j]))
                        {
                            swap(values[j + 1], values[j]);
                        }
                    }
                }
            }
        }

        //第一个元素是有序的,如果第二个元素小于第一个元素,交换二者的位置,
        //此时前面的两个元素是有序的,第三个元素看做是一个插入前面两个元素
        //组成的有序表中,加上它自己的位置,所以共有三个位置,所以第三个元
        //素可以找到自己位置,这个过程一直重复到输入数组的最后一个元素
        //但是实际上这个过程可以看成是以间隔为1的一趟shell排序
        void InsertSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                // gap = 1的一趟shell排序
                shell_sort_once(values, start, end, 1);
            }
        }

        //在简单插入排序中每个元素需要从后往前顺序在他前面的有序表中寻找自己的
        //位置,把这个查找过程使用二分查找,就形成了二分插入排序,假设一个元素
        //B应该放在元素A的位置后,而A有多个值,那么应该查找最后一个A的位置,然
        //后把B放到这个A的位置之后,这样可以减少元素移动的次数
        void BinaryInsertSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                for (int i = start + 1; i <= end; ++i)
                {
                    ValueType temp = values[i];
                    int low = start, high = i - 1, mid = (low + high) / 2;

                    while (low <= high)
                    {
                        if (comp(temp, values[mid])) high = mid - 1;
                        else low = mid + 1;
                        mid = (low + high) / 2;
                    }
                    for (int k = i; k > low; --k) values[k] = values[k - 1];
                    values[low] = temp;
                }
            }
        }
        void ShellSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                int gap = end - start + 1;
                do
                {
                    gap = gap / 3 + 1;
                    shell_sort_once(values, start, end, gap);
                } while (gap > 1);
            }
        }

        //以数组中的某个元素作为枢纽,将小于枢纽的元素放到枢纽的左边,
        //大于枢纽的元素都放到枢纽的右边,这个过程叫做划分,然后在枢纽
        //左边的序列和枢纽左边的序列中不断划分,直到划分序列中只含有不
        //多于一个元素,划分结束后,排序也就完成了,快排的关键就是划分,
        //根据不同的划分方式,就得到了不同版本的快排程序
        void QuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                quick_sort(values, start, end);
            }
        }
        void RandomizedQuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                randomized_qsort(values, start, end);
            }
        }
        void Median3QuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                median3_qsort(values, start, end);
            }
        }
        //当一个划分序列元素个数为5~25时,继续使用划分递归地实现划分序列的排序往往
        //没有使用直接插入排序快,所以就可以把插入排序与快排结合起来加快排序速度
        void HybridQuickSort(ValueType values[], int start, int end,int m)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                hybrid_qsort(values, start, end, m);
            }
        }

        //有许多输入序列重复元素很多,极端例子是序列中全部元素都一样,
        //这时候再用原来的快排算法性能就太差了。针对这种情况,可以把与
        //枢纽元素一样大的元素都聚到中间,而在左边放置小于枢纽元素的元素,
        //右边放置大于枢纽元素的元素,然后再左右两个子序列中快排,这样的
        //快排算法叫做三路快排。三路快排关键是三路划分。
        void ThreeWayQuickSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                three_way_qsort(values, start, end);
            }
        }

        //先在输入数组中选择最小的值,如果它不是第一个元素,就把它与第一个位置上
        //的元素交换,在剩余的子数组继续这个过程,直到倒数第二个元素。这个过程就是
        //先选择最小的元素,然后选择第二小的元素……因此叫做选择排序
        void SelectSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                select_sort(values, start, end);
            }
        }
        //堆排序也算是一种选择排序,只是利用堆的性质加速了每一趟选择最小
        //元素的过程。因为最小堆的第一个元素是最小的,所以先在整个输入数组
        //中建堆,则最小的元素被放到了第一个位置,在剩下的元素序列中建堆,
        //不断重复这个过程,直到倒数第二个元素
        void HeapSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                for (int i = start; i < end; ++i)
                {
                    build_heap(&values[i], 0, end - i);
                }
            }
        }

        //假设有两个排好序的序列,将它们和在一起,并要保持顺序性,那么就可以
        //这么做:每次从第一个序列或第二个序列中取走一个元素,哪个序列中的第
        //一个元素小就取哪个,当一个序列取完之后,把另一个序列直接接到按元素
        //取走顺序放置的那个序列后面,就得到了一个合并的有序列表。这个过程叫
        //做合并。把输入数组每两个元素合并一次,得到n/2个序列,最后一个序列可
        //能只有一个元素,把每个得到的序列看做一个元素,继续两两合并,直到合并
        //完所有的元素
        void MergeSort(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                merge_sort(values, start, end);
            }
        }

        //公开划分过程是因为划分算法还有其他应用,比如查找最小第k个数
        int Partition(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                return partition(values, start, end);
            }
            return -1;
        }
        int RandomizedPartition(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                return randomized_partition(values, start, end);
            }
            return -1;
        }
        int Median3Partition(ValueType values[], int start, int end)
        {
            if (check_array_range(start, end, end - start + 1))
            {
                return median3_partition(values, start, end);
            }
            return -1;
        }

    public:
        Compare comp;
    private:
        bool check_array_range(int start, int end,int n)
        {
            if (n <= 0 ||
                start < 0 ||
                end < 0 || start > end
                || n > end - start + 1)
                return false;
            return true;
        }

        //一趟划分算法
        int partition(ValueType values[], int start, int end)
        {
            //以第一个元素作为枢纽,从数组两头向中间扫描
            ValueType pivot = values[start];
            while (start < end)
            {
                //先从后往前扫描,直到遇到一个小于枢纽的元素
                while (start < end && !comp(values[end], pivot)) --end;
                //把这个小于枢纽的元素放到start指向的位置上,
                //这是合理的,因为这个元素小于枢纽,要放到
                //枢纽最后的位置之前
                values[start] = values[end];
                //此时前指针指向一个小于枢纽的元素,所以下一个循环总是能推进前指针
                //从前往后扫描,直到前指针遇到一个大于枢纽的元素
                while (start < end && !comp(pivot, values[start])) ++start;
                //刚才后指针指向了一个小于枢纽的元素,这个元素放到了前指针推进
                //之前的位置上,前指针推进后,现在指向了一个大于枢纽的元素,
                //正好把它放到后指针指向的位置上
                values[end] = values[start];
                //此时,前指针指向的元素是一个大于枢纽的元素,但是这个元素是无效的,
                //如果start还没有遇到end,那么外层循环会继续,后指针也许就能找到
                //一个小于枢纽的元素,放到start位置上,如果后指针没有找到这样
                //一个元素,那么start会等于end,于是start不再推进,会
                //被自己的值赋值一次,不过这个位置上的元素还是无效的,外层循环结束
            }
            //外层循环结束后,start位置的值是无效的,它前面的值小于或等于枢纽,
            //后面的值大于或等于枢纽,于是把枢纽放到start位置上,就完成了一次划分
            values[start] = pivot;
            //start位置就是枢纽最后所在的位置
            return start;
        }

        //在划分区间随机选一个数作为枢纽,把它和第一个位置的元素交换,
        //再调用partition过程
        int randomized_partition(ValueType values[], int start, int end)
        {
            if (end == start) return start;
            if (end < start) return -1;
            srand(static_cast<unsigned>(time(nullptr)));
            int i = rand();
            i = static_cast<int>((i * 1.0 / RAND_MAX) * (end - start) + start);
            swap(values[i], values[start]);
            return partition(values, start, end);
        }
        //取区间首、尾、中间三个元素,求三个元素的中间值,然后把它和第一个
        //位置的元素交换,再调用partition过程
        int median3_partition(ValueType values[], int start, int end)
        {
            int mid = (start + end) / 2;
            int k = mid;
            if (comp(values[k], values[start]) && comp(values[start],values[end])) k = start;
            else if (comp(values[start], values[k]) && comp(values[end], values[start])) k = end;
            if (k != start) swap(values[k], values[start]);
            return partition(values, start, end);
        }
        //将区间划分为三段,前段是小于枢纽的元素,中间是与枢纽相等的元素,后端是
        //大于枢纽的元素.运行partition过程,有点区别就是如果前指针遇到一个与枢纽
        //相等的元素时,把它放到区间前段,后指针遇到一个与枢纽相等的元素是把它放
        //后段。partition过程运行完后,区间的排列如下(以6为枢纽举例):666234567899666,
        //注意中间有一个枢纽,然后再将两边的枢纽交换到中间来
        void three_way_partition(ValueType values[], int start, int end, int &left_pivot, int &right_pivot)
        {
            ValueType pivot = values[start];
            int i = start,p = start,q = end,j = end;
            while (i < j)
            {

                while (i < j)
                {
                    if (comp(pivot, values[j])) --j;
                    else if (values[j] == pivot) swap(values[q--], values[j--]);
                    else break;
                }
                values[i] = values[j];
                while (i < j )
                {
                    if (comp(values[i], pivot)) ++i;
                    else if (values[i] == pivot)  swap(values[p++], values[i++]);
                    else break;
                }
                values[j] = values[i];
            }
            values[i] = pivot;
            j = i + 1;
            i  -= 1;
            while (i > start && comp(values[i],values[start])) swap(values[i++], values[start++]);
            while (j < end && comp(values[end], values[j])) swap(values[j++], values[end--]);
            left_pivot = i;
            right_pivot = j;
        }
        //一趟希尔排序。取定一个间隔gap,则原数组中每隔gap个数的数分为一组,
        //例如,原数组为12 11 10 9 8 7 6 5 4 3 2 1,gap为3,则12 9 6 3为一组,
        //11 8 5 2为一组,10 7 4 1为一组。然后对每一组进行插入排序。插入排序
        //是从第二个数起,按顺序将它插入到前面序列中的合适位置,以12 9 6 7为例,
        //9小于12,所以先将9,复制到一个临时变量中,然后将12后移到9所在的位置,
        //再将9复制到12原来的位置上,这样9 12就排好序了。6比前面两个数都小,先将
        //6复制到临时变量中,然后将9 12都后移一个位置,再将6放到9所在的位置,7小于9和
        //12,大于6,所以将9和12向后移一个位置,把7插入到空出来的位置上,于是排序完成
        void shell_sort_once(ValueType values[], int start, int end, int gap)
        {
            for (int i = start + gap; i <= end; ++i)   //交叉进行
            {
                if (comp(values[i], values[i - gap]))
                {
                    ValueType temp = values[i];
                    int j = i - gap;
                    do
                    {
                        values[j + gap] = values[j];
                        j -= gap;
                    } while (j >= start && comp(temp, values[j]));
                    values[j + gap] = temp;
                }
            }
        }
        void quick_sort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int pivot = partition(values, start, end);
                quick_sort(values, start, pivot - 1);
                quick_sort(values, pivot + 1, end);
            }
        }
        void randomized_qsort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int pivot = randomized_partition(values, start, end);
                randomized_qsort(values, start, pivot - 1);
                randomized_qsort(values, pivot + 1, end);
            }
        }
        void median3_qsort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int pivot = median3_partition(values, start, end);
                median3_qsort(values, start, pivot - 1);
                median3_qsort(values, pivot + 1, end);
            }
        }
        void three_way_qsort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int left, right;
                three_way_partition(values, start, end, left, right);
                three_way_qsort(values,start, left);
                three_way_qsort(values, right, end);
            }
        }
        void hybrid_qsort(ValueType values[], int start, int end,int m)
        {
            if (end - start <= m)
                shell_sort_once(values, start, end,1);
            else
            {
                int pivot = randomized_partition(values, start, end);
                hybrid_qsort(values, start, pivot - 1,m);
                hybrid_qsort(values, pivot + 1, end,m);
            }
        }
        void select_sort(ValueType values[], int start, int end)
        {
            for (int i = start; i < end; ++i)
            {
                int k = i;
                ValueType val = values[k];
                for (int j = i; j < end; ++j)
                {
                    if (comp(values[j + 1],val))
                    {
                        val = values[j + 1];
                        k = j + 1;
                    }
                }
                if (k != i) swap(values[k], values[i]);
            }
        }
        void sift_down(ValueType values[],int start, int end)
        {
            int i = start, j = 2 * i + 1;
            ValueType temp = values[i];
            while (j <= end)
            {
                if (j < end && comp(values[j + 1],values[j] )) ++j;
                if (!comp(values[j],temp )) break;
                else
                {
                    values[i] = values[j];
                    i = j;
                    j = 2 * i + 1;
                }
            }
            values[i] = temp;
        }
        void build_heap(ValueType values[],int start,int end)
        {
            for (int i = (end - 1) / 2; i >= start; --i)
            {
                sift_down(values,i, end);
            }
        }
        //归并排序归并阶段操作
        void merge(ValueType values[], int start, int connect, int end)
        {
            int length1 = connect - start + 1,
                length2 = end - connect;
            //只需将前半段元素复制出来就行了,右半段就放在
            //原来的序列中,这样行得通是因为复制完前半段后,
            //将排序结果写回原序列时,要么把复制出来的元素
            //重新写回去,位置刚刚够。要么从原区间去一个数
            //放到前面,此时后半段区间空出一个位置,使得原
            //区间中剩余的可用位置数正好等于前半区间此时剩余
            //元素的个数
            ValueType* left = new ValueType[length1],    //每次调用merge都会分配内存,其实可
                                                         //以使用一个全局内存空间以提高性能
                *right = &values[connect + 1];// new ValueType[length2];
            for (int i = start,j = 0; i <= connect; )
                left[j++] = values[i++];
            /*for (int i = connect + 1, j = 0; i <= end;)
                right[j++] = values[i++];*/
            int i = start, j = 0, k = 0;
            while ( j < length1 && k < length2)
            {
                if (comp(left[j], right[k])) values[i++] = left[j++];
                else values[i++] = right[k++];
            }
            //前半区间还有剩余,那么说明后半区间已经取空,
            //直接把前半区间剩余元素复制到原区间中,否则,
            //后半区间还有剩余,并且已经就位
            while ( j < length1)
            {
                values[i++] = left[j++];
            }
            /*while( k < length2)
            {
                values[i++] = right[k++];
            }*/
            delete []left;
            //delete []right;
        }

        void merge_sort(ValueType values[], int start, int end)
        {
            if (start < end)
            {
                int connect = (start + end) / 2;
                merge_sort(values, start, connect);
                merge_sort(values, connect + 1, end);
                merge(values, start, connect, end);
            }
        }
        void swap(ValueType& val1, ValueType& val2)
        {
            ValueType temp = val1;
            val1 = val2;
            val2 = temp;
        }
    };
}

#endif

测试代码

// SorterTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "../Tool/Sorter.h"
#include <iostream>
using namespace MyTools;
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    int v1[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    Sorter<int> sorter;
    sorter.BufferSort(v1, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v1[i] << ‘ ‘;
    }
    cout << endl;

    int v2[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.InsertSort(v2, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v2[i] << ‘ ‘;
    }
    cout << endl;

    int v3[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.BinaryInsertSort(v3, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v3[i] << ‘ ‘;
    }
    cout << endl;

    int v4[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.ShellSort(v4, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v4[i] << ‘ ‘;
    }
    cout << endl;

    int v5[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.QuickSort(v5, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v5[i] << ‘ ‘;
    }
    cout << endl;

    int v6[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.RandomizedQuickSort(v6, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v6[i] << ‘ ‘;
    }
    cout << endl;

    int v7[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.Median3QuickSort(v7, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v7[i] << ‘ ‘;
    }
    cout << endl;

    int v8[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.HybridQuickSort(v8, 0, 9,5);
    for (int i = 0; i < 10; ++i)
    {
        cout << v8[i] << ‘ ‘;
    }
    cout << endl;

    int v9[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.SelectSort(v9, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v9[i] << ‘ ‘;
    }
    cout << endl;

    int v10[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.HeapSort(v10, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v10[i] << ‘ ‘;
    }
    cout << endl;

    int v11[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
    sorter.MergeSort(v11, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v11[i] << ‘ ‘;
    }
    cout << endl;

    Sorter<int, MyDataStructure::greater<int>> sorter1;
    sorter1.MergeSort(v11, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v11[i] << ‘ ‘;
    }
    cout << endl;

    sorter.ThreeWayQuickSort(v11, 0, 9);
    for (int i = 0; i < 10; ++i)
    {
        cout << v11[i] << ‘ ‘;
    }
    cout << endl;

    return 0;
}

测试程序运行截图:

同样的输入,不同的排序方法,得到了同样的排序结果。

总结

快速排序、归并排序、堆排序都是O(nlgn)级别的排序算法,但是一般情况下快速排序快于另外两个算法,将插入排序与快速排序混合的排序算法性能很好,STL中实现的就是这一排序算法。选择排序、冒泡排序、插入排序这三类排序算法都是O(n^2)级别的算法,但是一般来讲选择排序性能最好,因为这个算法元素移动次数最少。shell排序是一种插入排序,它是改进改进插入排序的:元素少的数组,插入排序较快(对应于gap较大时),对于基本排序的数组,插入排序也较快(对应于gap较小时)。实际上还有一些O(n)级别的排序算法,如基数排序、桶排序、计数排序等,但是这些排序算法排序对象有限制,所以暂不记录了。

时间: 2024-10-25 10:28:52

每日一题32:排序的相关文章

经典算法题每日演练——第二十四题 梳排序

原文:经典算法题每日演练--第二十四题 梳排序 这篇再看看一个经典的排序,梳排序,为什么取名为梳,可能每个梳都有自己的gap吧,大梳子gap大一点,小梳子gap小一点. 上一篇我们看到鸡尾酒排序是在冒泡排序上做了一些优化,将单向的比较变成了双向,同样这里的梳排序也是在冒泡排序上做了一些优化. 冒泡排序上我们的选择是相邻的两个数做比较,就是他们的gap为1,其实梳排序提出了不同的观点,如果将这里的gap设置为一定的大小, 效率反而必gap=1要高效的多. 下面我们看看具体思想,梳排序有这样一个1.

经典算法题每日演练——第二十二题 奇偶排序

原文:经典算法题每日演练--第二十二题 奇偶排序 这个专题因为各种原因好久没有继续下去了,MM吧...你懂的,嘿嘿,不过还得继续写下去,好长时间不写,有些东西有点生疏了, 这篇就从简单一点的一个“奇偶排序”说起吧,不过这个排序还是蛮有意思的,严格来说复杂度是O(N2),不过在多核的情况下,可以做到 N2 /(m/2)的效率,这里的m就是待排序的个数,当m=100,复杂度为N2 /50,还行把,比冒泡要好点,因为重点是解决问题的奇思妙想. 下面我们看看这个算法是怎么描述的,既然是奇偶,肯定跟位数有

经典算法题每日演练——第二十三题 鸡尾酒排序

原文:经典算法题每日演练--第二十三题 鸡尾酒排序 这篇我们继续扯淡一下鸡尾酒排序,为了知道为啥取名为鸡尾酒,特意看了下百科,见框框的话,也只能勉强这么说了. 要是文艺点的话,可以说是搅拌排序,通俗易懂点的话,就叫“双向冒泡排序”,我想作为码农的话,不可能不知道冒泡排序, 冒泡是一个单向的从小到大或者从大到小的交换排序,而鸡尾酒排序是双向的,从一端进行从小到大排序,从另一端进行从大 到小排序. 从图中可以看到,第一次正向比较,我们找到了最大值9. 第一次反向比较,我们找到了最小值1. 第二次正向

老男孩教育每日一题-第68天-实现172.16.1.0/24段所有主机通过124.32.54.26外网IP共享上网

题目 实现172.16.1.0/24段所有主机通过124.32.54.26外网IP共享上网 解答: echo 1 > /proc/sys/net/ipv4/ip_forward iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j SNAT --to-source 124.32.54.26 iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j MASQUERADE 注释说明: 一.参数说明: -t:指

老男孩教育每日一题-第126天-通过shell脚本打印乘法口诀表

问题背景: 生成9*9乘法表 [[email protected] ~]# seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}' 1x1=1 1x2=2   2x2=4 1x3=3   2x3=6   3x3=9 1x4=4   2x4=8   3x4=12  4x4=16 1x5=5

C语言每日一题之No.9

再做决定之前,我还是做好自己该做的.我不希望几年后会悔恨自己为什么在最该努力的时候不愿意吃苦.尊敬的女王陛下,请接题: 一.题目:有已按升序排好顺序的字符串a,编写程序将字符串s中的每个字符按升序的规则插到字符串a中,最后输出"abdefghjkmnptwy". 二.思路:既然是已经排好序的,就用二分法查找的思想 将字符串s中的每个字符依次作为key拿来和字符串a做比较并且插入 三.程序 1 #include <stdio.h> 2 #include <string.

C语言每日一题之No.3

题目:从键盘输入一个字符串,按照字符顺序从小到大进行排序,并要求删除重复字符.如输入"ad2f3adjfeainzzzv",则输出"23adefijnvz" 思路:先定义一个字符数组用来存储字符串 讲数字转化成字符(ASCII) 排序 进行遍历,删除重复字符 程序: C语言每日一题之No.3,布布扣,bubuko.com

C语言每日一题之No4.

这几天老大也没安排我什么项目,于是想正好趁着空补C.当然,是利用晚上加班时间,白天正常上班时间还是学习公司的平台. 今儿个突然弱弱的感觉到在公司补C是件很低级的事情,哪怕是在加班时间都会被喷,因为大家在关心Linux玩得顺溜不顺溜的情况下,我在补C,万恶的C.想想也是,这种最最基础的C语言只能自己挤出时间来补了,在公司最起码也得学点高端点的,比如Linux,如果作为一个软件开发人员,你不会Linux还搞毛线啊? 好吧,工作一天了,今日吐槽完毕,人生因吐槽而舒畅爽快 ,神一样的存在.此时此刻就是回

C语言每日一题之No.8

正式面对自己第二天,突然一种强烈的要放弃的冲动,在害怕什么?害怕很难赶上步伐?害怕这样坚持到底是对还是错?估计是今天那个来了,所以身体激素有变化导致情绪起伏比较大比较神经质吧(☆_☆)~矮油,女人每个月总有这么几天的....晚上闺蜜打电话来,共同探讨了作为单身女性身在一线城市的生活,互相安慰互相关心,心里一下子就温暖了许多.总在这个时候,你会觉得,这个冷静的城市里你不是一个人在行走,还有另一颗心牵挂着你.嘿嘿,回来该学习还学习.现在不管坚持是对的还是错的,你都踏上了研发这条不归路,那就一条黑走到