几种常见排序算法的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的主存排序性能不一定有快速排序好

posted @ 2015-10-13 12:37  eversliver  阅读(388)  评论(0编辑  收藏  举报