• 几种常见排序算法的C++描述


    基本的排序算法有如下特点:

    1.几种容易的算法都是以O(N2)排序的

    2.Shell排序编程简单,其也是以O(N2)排序的,在实践中用的很多

    3.复杂的排序算法往往都是按照O(NlogN)尽心排序的

    4.任何通用的排序算法都需要NlogN次比较。

    还有两个定理应该记一下,证明略:

    1. N个互异数组的平均逆序数是N(N-1)/4

    2. 通过交换相邻元素进行排序的任何算法平均都需要O(N^2)的时间

    插入排序

    首先当然是插入排序啦,算法的时间复杂度其实是O(N^2)的

    先来个一般的描述:

     1 template<typename Comparable>
     2 void insertSort(vector<Comparable> & a)
     3 {
     4     int j;
     5     for (int p = 1; p < a.size(); p++)
     6     {
     7         Comparable tmp = a[p];
     8         for (int j = p - 1; j > 0 && tmp < a[j - 1]; j--)
     9             a[j] = a[j - 1];
    10         a[j] = tmp;
    11     }
    12 }

    再就是stl的描述,主要分为四个部分,比较麻烦

     1 template <typename Iterator>
     2 void insertionSort(const Iterator & begin, const Iterator & end)
     3 {
     4     if (begin != end)
     5         insertSortionHelp(begin, end, *begin);//用于解析类型
     6 }
     7 
     8 template <typename Iterator, typename Object>
     9 void insertionSortHelp(const Iterator & begin, const Iterator & end, const Object & obj)
    10 {
    11     insertionSort(begin, end, less<Object>());
    12 }
    13 
    14 
    15 template <typename Iterator, typename Comparator>
    16 void insertionSort(const Iterator & begin, const Iterator & end, Comparator lessThan)
    17 {
    18     if (begin ! = end)
    19         insertionSort(begin, end, lessThan, *begin);
    20 }
    21 
    22 template <typename Iterator, typename Comparator, typename Object>
    23 void insertionSort(const Iterator & begin, const Iterator & end, Comparator lessThan, const Object & obj)
    24 {
    25     Iterator j;
    26     for (Iterator p = begin + 1; p != end; p++)
    27     {
    28         Object tmp = *p;
    29         for (j = p; j != begin && lessThan(tmp, *(j - 1)); --j)
    30             *j = *(j - 1);
    31         *j = tmp;
    32     }
    33     //这整个4个函数组成了一个STL,很麻烦所以一般不用STL界面
    34 
    35 }

    可以讨论一下插入排序的排序的时间复杂度:

    2+3+4+...+n = O(N^2)

    还可以总结出来的一个特点是,如果插入排序的的输入数据预先已经排好序号了,那么内层循环会直接跳出,这样时间复杂度就会被降到O(N).

    也就是说,对于输入几乎被排序了的序列来说,插入排序将很快完成。

    希尔排序

    希尔排序其实相当于插入排序的不同间隔的版本,希尔排序的平均时间复杂度实际很大程度的依赖于增量序列的选择,同样可以证明得知其算法复杂度为O(N^2)的。

     1 template<typename Comparable>
     2 void shellSort(vector<Comparable > & a)
     3 {
     4     for (gap = a.size() / 2; gap > 0; gap /= 2)
     5     {
     6         for (int i = gap; i < a.size(); i++)
     7         {
     8             Comparable tmp = a[i];
     9             int j = i;
    10             for (; j > gap && tmp < a[j - gap]; j -= gap)
    11                 a[j] = a[j - gap];
    12             a[j] = tmp;
    13         }
    14     }
    15 }

    应该注意一点的是,shell排序的最坏情形的复杂度为O(N^2)

    堆排序

      堆排序的算法思想其实很简单,先是建立一个堆,建堆时的时间复杂度实际上是O(N),可以先按照逆序建堆,然后在一个一个的把堆中最大的元素,放到数组的最后一个位置,这个过程的时间复杂度是logN,后面其实相当于还要建立N次堆。所以总的时间复杂度就是Nlog(N)的。要点就是不停的建立新的堆。

     1 template <typename Comparable>
     2 void heapSort(vector<Comparable> & a)
     3 {
     4     for (int i = a.size() / 2; i > 0; i--)
     5         percDown(a, i, a.size());        //首先是建堆
     6     for (int j = a.size() - 1; j > 0; j--)
     7     {                        //这一步从堆中删除最大值到vector的末尾,这样完成了排序
     8         swap(a[0], a[j]);
     9         percDown(a, 0, j);
    10     }
    11 }
    12 
    13 inline int leftChild(int i)
    14 {
    15     return 2 * i + 1;
    16 }
    17 
    18 template<typename Comparable>
    19 void percDown(vector<Comparable> & a, int i, int j)
    20 {
    21     int child;
    22     Comparable tmp;
    23 
    24     for (tmp = a[i], leftChild(i) < n; i = child)
    25     {
    26         child = leftChild(i);
    27         if (child != n - 1 && a[child] < a[child + 1])
    28             child++;
    29         if (tmp < a[child])
    30             a[i] = a[child];
    31         else
    32             break;
    33     }
    34     a[i] = tmp;
    35 }

    归并排序

    归并排序主要使用了递归的思想,即分治策略,可以证明其算法时间复杂度为Nlog(N)的。

     1 template<typename Comparable>
     2 void mergeSort(vector<Comparable & a>)
     3 {
     4     vector<Comparable> tmpArray(a.size());
     5     mergeSort(a, tmpArray, 0, a.size() - 1);
     6 }
     7 
     8 template <typename Comparable>
     9 void mergeSort(vector<Comparable> & a, vector<Comparable> & b, int left, int right)
    10 {
    11     if (left < right)
    12     {
    13         int center = (left + right) / 2;
    14         mergeSort(a, tmpArray, left, center);
    15         mergeSort(a, tmpArray, center + 1, right);
    16         merge(a, tmpArray, left, center + 1, right);
    17     }
    18 }
    19 
    20 template<typename Comparable>
    21 void merge(vector<Comparable> & a, vector<Comparable> tmpArray, int leftPos, int rightPos, int rightEnd)
    22 { //这个下面实际上有一个错误,看的时候要纠正一下
    23     int leftEnd = rightPos - 1;
    24     int tmpPos = leftPos;
    25     int numElements = rightEnd - leftPos + 1;
    26     while (leftPos < leftEnd && rightPos < rightEnd)
    27     {
    28         if (a[leftPos] <= a[rightPos])
    29             tmpArray[tmpPos++] = a[leftPos++];
    30         else
    31             tmpArray[tmpPos++] = a[rightPos++];
    32     }
    33     while (leftPos <= leftEnd)
    34         tmpArray[tmpPos++] = a[leftPos++];
    35     while (rightPos <= rightEnd)
    36         tmpArray[tmppos++] = a[rightPos++];
    37 
    38     for (int i = 0; i < numElements; i++, rightEnd--)//这一步拷贝的方法值得注意
    39         a[rightEnd] = tmpArray[rightEnd];
    40 }  

    快速排序

    其算法复杂度最大不超过O(N^2),平均值为O(Nlog(N))

    注意快排枢纽元的选取。

    将第一个元素作为枢纽元:

    很明显的不太合适,如果待排序内容是随机的,那么这样是没有问题的。但是如果输入是预排序的或者是反序的,那么就很糟糕。对于预排序的,这样可能导致排序时间是二次的,但是实际上没有做什么事情。如果对于反序的,这种枢纽元每次都会产生劣质的分割,应为所有元素不是被分到S1就是都被分到S2.

    随机取一个元素作为枢纽元:

    这是个不错的做法,这样不会一直产生很坏的分割,但是生成随机数是昂贵的,可能会给算法带来很大的负担。

    三数中值分割法:

    可以取左侧,中间,右边的值的和的平均值作为枢纽元。这种做法是比较理智的。

    1 template<typename Comparable>
    2 void quickSort(vector<Comparable> & a)
    3 {
    4     quickSort(a, 0, a.size() - 1);
    5 }
     1 //首先是枢纽元的选取
     2 template<typename Comparable>
     3 const Comparable & median3(vector<Comparable> & a, int left, int right)
     4 {
     5     int center = (left + right) / 2;
     6     if (a[center] < a[left])
     7         swap(a[center], a[left]);
     8     if (a[right] < a[left])            //将最小的元素放到vector的最左边,符合要求
     9         swap(a[left], a[right]);
    10     if (a[right] < a[center])        //将最大的元素放到vector的最右边,这也符合要求
    11         swap(a[center], a[right]);
    12     
    13     swap(a[center], a[right - 1]);    //将枢纽元放在最右边左侧一个的位置上,符合要求
    14     return a[right - 1];
    15 }
    16 
    17 template <typename Comparable>
    18 void quickSort(vector<Comparable> & a, int left, int right)
    19 {
    20     if (left + 10 <= right)
    21     {
    22         Comparable pivot = median3(a, left, right);
    23         int i = left; j = right - 1;
    24         for (;;)
    25         {
    26             while (a[++i] < pivot){}
    27             while (a[--j] > pivot){}
    28             if (i < j)                //如果i,j的前后顺序还没有交换,那么这一轮块排还没有结束
    29                 swap(a[i], a[j])
    30             else break;
    31         }
    32         swap(a[i], a[right - 1]);
    33         quickSort(a, left, i - 1);
    34         quickSort(a, i + 1, right);
    35     }
    36     else
    37         insertionSort(a, left, right);<span style="white-space:pre">            </span>//对于小数组,那么采取插入排序性能较好
    38 }<pre name="code" class="cpp">template<typename Comparable>
    39 void quickSort(vector<Comparable> & a)
    40 {
    41     quickSort(a, 0, a.size() - 1);
    42 }
     1 简单介绍一个快排算法的展开:快速选择其代码段如下所示,其实相当于快速选择找一个固定的数的版本
     2 template<typename Comparable>
     3 void quickSort(vector<Comparable> & a)
     4 {
     5     quickSort(a, 0, a.size() - 1);
     6 }
     7 //首先是枢纽元的选取
     8 template<typename Comparable>
     9 const Comparable & median3(vector<Comparable> & a, int left, int right)
    10 {
    11     int center = (left + right) / 2;
    12     if (a[center] < a[left])
    13         swap(a[center], a[left]);
    14     if (a[right] < a[left])            //将最小的元素放到vector的最左边,符合要求
    15         swap(a[left], a[right]);
    16     if (a[right] < a[center])        //将最大的元素放到vector的最右边,这也符合要求
    17         swap(a[center], a[right]);
    18     
    19     swap(a[center], a[right - 1]);    //将枢纽元放在最右边左侧一个的位置上,符合要求
    20     return a[right - 1];
    21 }
    22 template <typename Comparable>
    23 void quickSelect(vector<Comparable> & a, int left, int right, int k)
    24 {
    25     if (left + 10 <= right)
    26     {
    27         Comparable pivot = median3(a, left, right);
    28         int i = left; j = right - 1;
    29         for (;;)
    30         {
    31             while (a[++i] < pivot){}
    32             while (a[--j] > pivot){}
    33             if (i < j)                //如果i,j的前后顺序还没有交换,那么这一轮块排还没有结束
    34                 swap(a[i], a[j])
    35             else break;
    36         }
    37         swap(a[i], a[right - 1]);
    38         if(k <= i)
    39             quickSelect(a, left, i - 1, k);
    40         else if(k > i+1)
    41             quickSelect(a, i + 1, right, k);
    42     }
    43     else 
    44         insertionSort(a, left, right);
    45 }

    6.对于大元素的数组排序,由于不断移动的成本很高,所以一般会使用移动指针的方式代替直接的大元素的移动

     1 //首先应该声明一个指针类,其中应该包含比较方法,在用这个指针类去调用quicksort就行了
     2 template<typename Comparable>
     3 class Pointer
     4 {
     5 private:
     6     Comparable * pointee;
     7 public:
     8     Pointer(Comparable * rhs = NULL) : pointee(rhs){}
     9 
    10     bool operator<(const Pointer & rhs)cosnt
    11     {
    12         return *pointee < *rhs.pointee;
    13     }
    14 
    15 operator Comparable*()const<span style="white-space:pre">        </span>//转换符号,使得完成从pointer<Comparable>da使得可以
    16 {<span style="white-space:pre">                    </span>//直接使用*pointer,这里已经有着隐含着的Comparator*了
    17 <span style="white-space:pre">    </span>{<span style="white-space:pre">                </span>//定义了这个符号之后就可以完成Pointer与Comparable*之间的双向转换!!
    18 <span style="white-space:pre">        </span>return *pointee;
    19 <span style="white-space:pre">    </span>}
    20 };
     1 template<typename Comparable>
     2 void largeObjectSort(vector<Comparable> & a)
     3 {
     4     vector<Pointer<Comparable>> p(a.size());
     5     int i, j, next;
     6 
     7     for (i = 0; i < a.size(); i++)
     8         p[i] = &a[i];
     9 
    10     quickSort(p);
    11 
    12     //下面再依靠指针指向的情况来将数组重新来排列,其中使用到了滑动算法
    13     for (i = 0; i < a.size(); i++)
    14         if (p[i] != a[i])
    15         {
    16             Comparable tmp = a[i];
    17             for (j = i; p[j] != &a[i]; j = nextj)
    18             {
    19                 nextj = p[j] - &a[0];            //这里使用的滑动算法,可以自己画图演示一下
    20                 a[i] = *p[j];
    21                 p[j] = &a[j];
    22             }
    23             a[j] = tmp;
    24             p[j] = &a[j];
    25         }
    26 }

    //大量元素的排序最好直接使用快排算法,注意不要图省事将第一个元素作为枢纽元
    //如果考虑编程的简洁性可以使用希尔排序
    //堆排序比希尔排序是要慢的
    //插入排序一般只用在小或者是基本上已经排好序的输入数据上
    //归并排序较为麻烦而且对于CPU的主存排序性能不一定有快速排序好

  • 相关阅读:
    如何配置tomcat的环境变量
    如何配置jdk的本地环境
    如何下载最新版本和旧版本的eclipse?
    用eclipse开发需要准备什么?
    eclipse.ini相关问题
    jquery如何遍历table,并对table里的某一个单元格进行操作
    jquery的Layer弹出框操作
    select列表遍历和触发事件
    Spring AOP详细介绍
    简单介绍Spring的ContextLoaderListener
  • 原文地址:https://www.cnblogs.com/-wang-cheng/p/4874254.html
Copyright © 2020-2023  润新知