zoukankan      html  css  js  c++  java
  • 8个排序算法

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    /*
    1、插入排序。稳定
    (1)原理:逐个处理待排序记录,每个记录与前面已排序子序列进行比较,将其插入子序列中正确位置
    (2)复杂度:O(n)-》O(n^2),O(n^2)
    最佳:升序。时间复杂度为O(n)
    最差:降序。时间复杂度为O(n^2)
    平均:对于每个元素,前面有一半元素比它大。时间复杂度为O(n^2)
    
    注意:如果待排序数据已经“基本有序”,使用插入排序可以获得接近O(n)的性能
    */
    void insertsort(int *data, int n)
    {
        if (data == nullptr || n <= 1)
            return;
        for (int i = 1; i < n; i++)
        {
            for (int j = i; j >= 1 && data[j] < data[j - 1]; j--) //升序
                swap(data[j],data[j - 1]);
        }
    }
    /*插入排序优化:设置哨兵保存待排序值,将比其大的都后移一位,最后插入该值*/
    void insertsort_optimization(int *data, int n)
    {
        if (data == nullptr || n <= 1)
            return;
        for (int i = 1; i < n; i++)
        {
            int j = i;
            int tmp = data[j];
            for (; j >= 1 && tmp < data[j - 1]; j--) //升序
                data[j] = data[j - 1];
            data[j] = tmp;
        }
    }
    
    /*
    2、冒泡排序。稳定
    (1)原理:从数组的底部比较到顶部,比较相邻元素。
               如果下面的元素更小则交换,否则,上面的元素继续往上比较。
               这个过程每次使最小元素像个“气泡”似地被推到数组的顶部。
    (2)复杂度(bubsort_optimization):On-》On^2 ,On^2
    */
    void bubsort(int *data, int n)
    {
        if (data == nullptr || n <= 1)
            return;
        for (int i = 0; i < n - 1; i++)
        {
            for (int j = n - 1; j > i; j--)
            {
                if (data[j] < data[j - 1])//升序
                    swap(data[j], data[j - 1]);//#include <algorithm>
            }
        }
    }
    /*
    冒泡排序优化:增加一个变量flag,用于记录一次循环是否发生了交换,如果没发生交换说明已经有序,可以提前结束
                  避免因已经有序的情况下的无意义循环判断
    */
    void bubsort_optimization(int *data, int n)
    {
        if (data == nullptr || n <= 1)
            return;
        bool flag = true;
        for (int i = 0; i < n - 1 && flag; i++)
        {
            flag = false;
            for (int j = n - 1; j > i; j--)
            {
                if (data[j] < data[j - 1])//升序
                {
                    swap(data[j], data[j - 1]);//#include <algorithm>
                    flag = true;
                }
            }
        }
    }
    
    /*
    3、选择排序。不稳定
    (1)原理:每次从未排序的序列中找到最小元素,放到未排序数组的最前面
    (2)复杂度:O(n^2)
    不管数组是否有序,在从未排序的序列中查找最小元素时,都需要遍历完最小序列,所以最差好平均都是O(n^2)
    
    (3)优化:每次内层除了找出一个最小值,同时找出一个最大值(初始为数组结尾)。
    将最小值与每次处理的初始位置的元素交换,将最大值与每次处理的末尾位置的元素交换。
    这样一次循环可以将数组规模减小2,相比于原有的方案(减小1)会更快
    */
    void selectsort(int *data, int n)
    {
        if (data == nullptr || n <= 1)
            return;
        for (int i = 0; i < n - 1; i++)
        {
            int lowindex = i;
            for (int j = i + 1; j < n; j++)
            {
                if (data[j] < data[lowindex])
                    lowindex = j;
            }
            swap(data[i], data[lowindex]);
        }
    }
    
    /*
    4、希尔排序。不稳定(插入排序的改进版本)
    (1)原理:shell排序在不相邻的元素之间比较和交换。
    利用了插入排序的最佳时间代价特性,它试图将待排序序列变成基本有序的,然后再用插入排序来完成排序工作
    
    在执行每一次循环时,Shell排序把序列分为互不相连的子序列,并使各个子序列中的元素在整个数组中的间距相同,
    每个子序列用插入排序进行排序。每次循环增量是前一次循环的1/2,子序列元素是前一次循环的2倍
    最后一轮将是一次“正常的”插入排序(即对包含所有元素的序列进行插入排序)
    
    (2)复杂度:O(n^(1.5))
    选择适当的增量序列可使Shell排序比其他排序法更有效,一般来说,增量每次除以2时并没有多大效果,而“增量每次除以3”时效果更好
    当选择“增量每次除以3”递减时,Shell排序的 平均 运行时间是O(n^(1.5))
    */
    void shellsort(int *data, int n) 
    {
        if (data == nullptr || n <= 1)
            return;
        const int incregap = 3;//增量每次除以3
        /*遍历所有增量大小。incre是增量。每次增量变上次的1/incregap,子序列元素变上次的incregap倍*/
        for (int incre = n / incregap; incre > 0; incre /= incregap)
        {
            for (int i = 0; i < incre; i++)//共有incr个子序列需要分别进行插入排序
            {
                /*对子序列进行插入排序,当增量为1时,对所有元素进行最后一次插入排序*/
                for (int j = i + incre; j < n; j += incre)//每次插入排序都从子序列的第二个值开始,因为认为第一个值已经有序
                {
                    for (int k = j; k > i&&data[k] < data[k - incre]; k -= incre)
                        swap(data[k], data[k - incre]);
                }
            }
        }
    }
    /*
    5、快速排序。不稳定。快速排序是所有内部排序算法中平均性能最优的排序算法
    (1)原理:首先选择一个轴值,小于轴值的元素被放在数组中轴值左侧,大于轴值的元素被放在数组中轴值右侧,
               这称为数组的一个分割(partition)。快速排序再对轴值左右子数组分别进行类似的操作
    
    (2)复杂度:O(nlogn)-》O(n^2),O(nlogn)
    最佳情况:O(nlogn)
    最差情况:每次处理将所有元素划分到轴值一侧,O(n^2)
    平均情况:O(nlogn)   快速排序平均情况下运行时间与其最佳情况下的运行时间很接近,而不是接近其最坏情况下的运行时间。
    
    (3)优化:
    (3.1)最明显的改进之处是轴值的选取,如果轴值选取合适,每次处理可以将元素较均匀的划分到轴值两侧:
    三者取中法:三个随机值的中间一个。为了减少随机数生成器产生的延迟,可以选取首中尾三个元素作为随机值
    (3.2)当n很小时,快速排序会很慢。因此当子数组小于某个长度(经验值:9)时,此时数组已经基本有序,最后调用一次插入排序完成最后处理
      
    (4)选择轴值有多种方法:
    最简单的方法是使用首或尾元素。但是,如果输入的数组是正序或者逆序时,会将所有元素分到轴值的一边。
    较好的方法是随机选取轴值
    */
    int partition(int *data, int start, int end)
    {
        //选择尾元素为轴值。较好的方法是随机选取轴值,然后和尾元素交换
        int small = start - 1;//分割点
        for (int index = start; index < end; index++)
        {
            if (data[index] < data[end])//选择尾元素为轴值
            {
                ++small;
                if (small != index)
                    swap(data[small], data[index]);
            }
        }
        ++small;
        swap(data[small], data[end]);//将轴值从末尾放到分割点
        return small;//返回分割点。其左小于它,右大于它
    }
    void qsort(int *data, int start, int end)
    {
        if (data == nullptr || end <= start)
            return;
        int index = partition(data, start, end);
        qsort(data, start, index - 1);
        qsort(data, index + 1, end);
    }
    
    /*
    6、归并排序。稳定。
    (1)原理:将一个序列分成两个长度相等的子序列,为每一个子序列排序,然后再将它们合并成一个序列。合并两个子序列的过程称为归并
         二路归并:将n个记录分成长度为1的多个子序列,然后两两归并,得到长度为2的子序列,再两两归并,直到得到长度为n的有序序列为止
    
    (2)复杂度:时间:O(nlogn);空间:O(n)
    logn层递归中,每一层都需要O(n)的时间代价,因此总的时间复杂度是O(nlogn),
    该时间复杂度不依赖于待排序数组中数值的相对顺序。因此,是最佳,平均和最差情况下的运行时间
    由于需要一个和带排序数组大小相同的辅助数组,所以空间代价为O(n)
    */
    void mergesortcore(int *data, int *temp, int i, int j)
    {
        if (i == j) return;
        int mid = (i + j) / 2;
        mergesortcore(data, temp, i, mid);
        mergesortcore(data, temp, mid + 1, j);
    
        /*二路归并*/
        //data中是两个分别已排序好的子序列,两个归并排序后放入额外空间temp,再用temp更新对应位置的data
        int index1 = i, index2 = mid + 1, current = i;//二路子序列:index1-mid  index2-j(end)
        while (index1 <= mid && index2 <= j)
        {
            if (data[index1] < data[index2])
                temp[current++] = data[index1++];
            else
                temp[current++] = data[index2++];
        }
        while (index1 <= mid)
            temp[current++] = data[index1++];
        while (index2 <= j)
            temp[current++] = data[index2++];
        for (current = i; current <= j; current++)
            data[current] = temp[current];
    }
    void mergesort(int *data, int size)
    {
        if (data == nullptr || size <= 1)
            return;
        int *temp = new int[size]();//new[] 数组,()初始化为0
        mergesortcore(data, temp, 0, size - 1);
        delete[] temp;
    }
    
    /*
    归并排序优化空间(原地归并排序):O1空间,O(nlogn)时间。
    不用额外空间,两个有序子序列归并时,如果后一个子序列的值小,则前一个子序列全部后移一位,将后一个子序列的小值放到前一个子序列前
    
    【arking原链接测试数据交换2和9就错误,即mid大于左半部分就错误。但其交换两部分内容的方法很好:分别翻转左右部分,再整体翻转】
    */
    void mergesort_optimization_core(int *data, int start, int end)
    {
        if (start == end)
            return;
        int mid = (start + end) / 2;
        mergesort_optimization_core(data, start, mid);
        mergesort_optimization_core(data, mid + 1, end);
    
        int index1 = start, index2 = mid + 1;
        while (index1 <= mid && index2 <= end)//二路子序列:index1-mid;index2-end
        {
            if (data[index1] > data[index2])//移动第一个子序列:从index1到mid全部后移一位,把原index2处内容放到index1处。更新mid和end
            {
                int index2_tmp = data[index2];
                for (int t = index2; t > index1; t--)
                    data[t] = data[t - 1];
                data[index1] = index2_tmp;
                ++mid;
                ++index2;
            }
            ++index1;//无论是否移动第一个子序列都要更新index1
        }
    }
    void mergesort_optimization(int *data, int size)
    {
        if (data == nullptr || size <= 1)
            return;
        mergesort_optimization_core(data,0,size-1);
    }
    
    /*
    完全二叉树:假设一个二叉树有n层,那么如果第1到n-1层的每个节点都达到最大的个数:2,
                且第n层的排列是从左往右依次排开的,那么就称其为完全二叉树
    堆概念:本身是一个完全二叉树,当二叉树的每个节点都大于等于它的子节点的时候,称为大顶堆;
            当二叉树的每个节点都小于它的子节点的时候,称为小顶堆。
            (stl的make_heap默认大顶堆,使用函数对象less<int>(),而一般sort排序默认此函数对象意味着升序)
    堆性质:将堆的内容从左往右,从上至下层次遍历放入数组,
            若一个结点在数组中下标为k,那么它的父结点为(k-1)/2,其子节点为2k+1和2k+2
    
    7、堆排序。不稳定。
    (1)原理:首先根据数组构建最大堆,然后每次“删除”堆顶元素(将堆顶元素移至末尾),并调整剩余元素为最大堆。
               最后得到的序列就是从小到大排序的序列。
    
    (2)复杂度:Onlogn
               (根据已有数组构建堆需要O(n)的时间复杂度,每次删除堆顶并调整堆需要O(logn)的时间复杂度,对数组n的排序需要取n-1次堆顶记录
                所以总的时间开销为,O(n+nlogn),平均时间复杂度为O(nlogn)
    
    (3)应用:
    (3.1)根据已有元素建堆是很快的,如果希望找到数组中第k大的元素,可以用O(n+klogn)的时间,如果k很小,时间开销接近O(n)
    (3.2)从10亿个浮点数当中,选出其中最大的10000个:大数据不适宜载入内存,可使用外排,效率低;也可构建topk的最小堆,每次和根比较,替换根后调整堆
    */
    void heapsortcore(vector<int> &data, int parent, int size)
    {
        int leftchild = 2 * parent + 1;
        if (leftchild < size)//确保有左孩子,否则data[xx]会越界访问
        {
            int rightchild = leftchild + 1;
            int maxchild = leftchild;//左右孩子中大的那个孩子下标    
    
            //存在右孩子,则需取得左右孩子中大的那个节点,与父节点比较,把最大的放到父节点,
            //如果做了交换,则需继续递归调整后续子树
            if (rightchild < size)
            {
                if (data[leftchild] < data[rightchild])
                    maxchild = rightchild;
            }
            if (data[parent] < data[maxchild])
            {
                swap(data[parent], data[maxchild]);
                heapsortcore(data, maxchild, size);//做了交换,继续递归调整后续子树
            }
        }
    }
    void heapsort(vector<int> &data)
    {
        if (data.size() <= 1)
            return;
    
        //从无序数组开始建堆:从最后一个叶子节点的父节点开始
        for (int i = (data.size() - 2) / 2; i >= 0; i--)
        {
            heapsortcore(data, i, data.size());//从(size - 2) / 2开始
        }
    
        //循环将 根节点放到尾部,对除尾部外其余元素重新调整为堆 即可调整数组为有序数组
        //(调整则需只从root开始,和其子节点比较并交换,调整后续子树
        for (int j = data.size() - 1; j > 0; j--)
        {
            swap(data[0], data[j]);
            heapsortcore(data, 0, j);//实际是做了交换,继续递归调整后续子树(和插入到头部做调整不同,插入会改变树结构,可能需要调整全部,而此交换树结构不变,还可重用之前结果,只需调整对应子树)
        }
    }
    
    /*针对已经构建好的堆,插入一个元素到尾部,再和parent比较调整为新的堆,只需调整子树
    (如果插入到头部,后续节点数组坐标改变,有别的本无须调整的分支可能变成不是最小堆,就需要和开始一样从新建堆,复杂度On>Olog)*/
    void insert_heapsort(vector<int> &data, int val)
    {
        //从无序数组开始建堆:从最后一个叶子节点的父节点开始
        for (int i = (data.size() - 2) / 2; i >= 0; i--)
        {
            heapsortcore(data, i, data.size());//从(size - 2) / 2开始
        }
        //针对已经构建好的堆,插入一个元素到尾部,再调整为新的堆,只需调整子树
        data.push_back(val);
        int valindex = data.size() - 1;//从插入元素往前调整
        int parent = (valindex - 1) / 2;
        while (valindex > 0)
        {
            if (data[parent] < data[valindex])
            {
                swap(data[parent], data[valindex]);
                valindex = parent;//为继续调整子树做准备
                parent = (valindex - 1) / 2;
            }
            else
                break;//调整到已经比parent小了,由于本就是一个最大堆,只是做调整子树,此处表明所有父节点都比它大,直接break
        }
        //堆排序输出
        for (int j = data.size() - 1; j > 0; j--)
        {
            swap(data[0], data[j]);
            heapsortcore(data, 0, j);//实际是做了交换,继续递归调整后续子树
        }
    }
    /*
    STL的堆排序sort_heap(vi.begin(), vi.end());
    循环使用pop_heap实现
    */
    void stl_heapsort(vector<int> &data)
    {
        if (data.size() <= 1)
            return;
        //int end = data.size();
        make_heap(data.begin(), data.end());//最大堆
        for (int i = 0; i < data.size(); i++)
        {
            pop_heap(data.begin(), data.end()-i);//将root元素移到尾部,再对其余元素重新调整堆
            //--end;
        }
    }
    
    /*
    8、基数排序。稳定。
    (1)原理:桶排序扩展。将整数按位数切割成不同的数字,然后按每个位数分别比较。数位较短的数前面补零
               低位是有序的,高位中如果有相同的值,则只需在保持稳定的前提下对高位进行排序,结果自然有序。
    (2)复杂度:时间Od(n+k),空间On+k  k为基数
    空间On+k,k为基数10
    时间:需要进行最大数位数d次分配和收集,一趟分配On,一趟收集Ok,总共Od(n+k)
    */
    void radixsortcore(vector<int> &data, int exp)
    {
        //data[i]从后往前数的第exp位值(data[i]/exp)%10  为下标的元素个数
        vector<int> valuecount(10, 0);
    
        for (int i = 0; i < valuecount.size(); i++)//对于较短的数,高位此处计算为0,则后续会排到前面,且相对位置不变
            valuecount[(data[i] / exp) % 10]++;
        
        //valuecount表示data该出现在排序数组中的下标。valuecount[i]表示i之前的数据出现次数,也即当前数本应该在的位置。
        //如果有3个数个位为1,2个数个位为0,则个位为1的3个数下标从3+2-1开始到3+0-1
        for (int i = 1; i < 10; i++)
            valuecount[i] += valuecount[i - 1];
    
        //从数组尾部开始放入排序数组对应位置(根据上面求出的每个值的个数),确保相对位置稳定
        vector<int> tmpdata(data.size(), 0);
        
        for (int i = data.size() - 1; i >= 0; i--)
        {
            tmpdata[valuecount[(data[i] / exp) % 10] - 1] = data[i];
            --valuecount[(data[i] / exp) % 10];
        }
        data = tmpdata;
    }
    void radixsort(vector<int> &data)
    {
        if (data.size() <= 1)
            return;
        int max = -1;
        for (int i = 0; i < data.size(); i++)
        {//找出最大值
            if (data[i] > max)
                max = data[i];
        }
        for (int exp = 1; max / exp > 0; exp *= 10)//exp从个位到十位到...
            radixsortcore(data, exp);
    }
    
    
    
    int main() {
    #if 0
        //int data[] = { 0,1,5,6,2,9,30,4,7,8 };
    
        //insertsort(data, 10);//插入排序。稳定。  时间On-》On^2,On^2
        //insertsort_optimization(data, 10);//插入排序优化。设置哨兵,减少交换
        
        //bubsort(data, 10);//冒泡排序。稳定。  时间On-》On^2 ,On^2
        //bubsort_optimization(data, 10);//冒泡排序优化。增加flag,提前结束
        
        //selectsort(data, 10);//选择排序。不稳定。时间O(n^2)
        
        //shellsort(data,10);//希尔排序(在插入排序的基础上)。不稳定。时间On^1.5
        
        //qsort(data, 0, 9);//快速排序。不稳定。时间Onlogn-》On^2,Onlogn
    
        //mergesort(data, 10);//归并排序。稳定。Onlogn时间,On空间
        //mergesort_optimization(data, 10);//归并排序优化。Onlogn时间,O1空间
        
        for (int i = 0; i < sizeof(data) / sizeof(int); i++)
            cout << data[i] << " ";
        cout << endl;
    #endif
    
    //#if 0
        vector<int> data(10,0);
        for (int i = 0; i < 10; i++)
            data[i] = 9-i;
        cout << "排序前: ";
        for (vector<int>::iterator it = data.begin(); it != data.end(); it++)
            cout <<*it << " ";
        cout << endl;
    
        
        heapsort(data);//堆排序。不稳定。Onlogn
        //stl_heapsort(data);//stl堆排序
        //int val = 10;
        //insert_heapsort(data,val);//插入元素到已经排序好的堆,重新调整为堆,再排序输出
    
        //radixsort(data);//基数排序,针对非负整数。稳定。时间Od(n+k),空间On+k  k为基数
        cout << "排序后: ";
        for (vector<int>::iterator it = data.begin(); it != data.end(); it++)
            cout << *it << " ";
        cout << endl;
    
    //#endif
        return 0;
    }
  • 相关阅读:
    Linq To Entity 的增删改查(技术储备,怕忘了) jerry
    微博内容中的短地址 分析
    使用HttpWebRequest自动更新客户端应用程序[转]
    面向对象的js编程
    获取asp.net解析页面完毕后后的html代码
    js 面向对象继承 动物叫声比赛版
    [译]C# Socket连接请求超时机制
    c# 扫描可疑文件(找到木马)(简)转
    session如何保存在专门的StateServer服务器中
    动态加载script文件 专题
  • 原文地址:https://www.cnblogs.com/beixiaobei/p/10914236.html
Copyright © 2011-2022 走看看