这几天在网上看到有人总结了4种比较常见简单的排序的算法,并用C#实现了出来。看了之后不由的想起了大学时候学<<数据结构>>的情景, 忍不住用C++实现了一遍,除了冒泡排序, 选择排序, 插入排序,希尔排序之外, 还包括了算法复杂度较好的快速排序与堆排序。 然后用C++强大的模板功能实现了一个基于policy的Sort函数, 很明显,这个Sort函数是以排序算法为policy的。 这里利用了不同的模板技术实作出多个版本的Sort函数,并比较了它们的优劣。
好了,闲话不说, 下面给出6种排序算法, 每种算法用一个class包装, 代码的注释应该能很好的解释这些算法。
// complexity: O(n^2)
// Algorithm: compare adjcent elements and swap if not meet the compare criterion.
template < typename T >
struct BubbleSorter
{
void Sort(T list[], int n)
{
bool bIsDone = false ;
for ( int i = n - 1 ; i > 0 && ! bIsDone; -- i)
{
bIsDone = true ; // if no sway happened, then this list is already sorted.
for ( int j = 0 ; j < i; ++ j)
{
if (list[j + 1 ] < list[j])
{
bIsDone = false ;
int tmp = list[j + 1 ];
list[j + 1 ] = list[j];
list[j] = tmp;
}
}
}
}
};
// sort type: selection sort
// complexity: O(n^2)
// Algorithm: select the minimum element and move it to the front.
template < typename T >
struct SelectionSorter
{
void Sort(T list[], int n)
{
int nIndexOfMin;
for ( int i = 0 ; i < n - 1 ; ++ i)
{
nIndexOfMin = i;
for ( int j = i + 1 ; j < n; ++ j)
{
if (list[j] < list[nIndexOfMin])
nIndexOfMin = j;
}
// swap the minimum element with the front element
if (nIndexOfMin != i)
{
int tmp = list[i];
list[i] = list[nIndexOfMin];
list[nIndexOfMin] = tmp;
}
}
}
};
// sort type: selection sort
// complexity: O(n^2)
// Algorithm: insert the n+1 element to an already sorted n element array
template < typename T >
struct InsertionSorter
{
void Sort(T list[], int n)
{
for ( int i = 1 ; i <= n - 1 ; ++ i)
{
int nInsert = list[i];
int j = i - 1 ;
while (j >= 0 && list[j] > nInsert)
{
list[j + 1 ] = list[j];
-- j;
}
list[j + 1 ] = nInsert;
}
}
};
// sort type: shell sort
// complexity: O(n^1.5...)
// Algorithm: separate the list into several sublist and sort them respectively
// then decrease the number of sublist and sort until only one sublist.
template < typename T >
struct ShellSorter
{
void Sort(T list[], int n)
{
int gap = n / 2 ;
// decrease the gap: gap = gap / 2
while (gap > 0 )
{
// insertion sort in each gap
// in first execution, gap is the second sublist's first element
for ( int i = gap; i <= n - 1 ; ++ i) // sub list execute insertion sort in turn.
{
int tmp = list[i]; // store the element to insert
int j = i;
while (j >= gap && list[j - gap] > tmp) // important: j >= gap, or else j<0 when out of the loop
{
list[j] = list[j - gap];
j = j - gap;
}
list[j] = tmp;
}
gap /= 2 ; // decrease gap
}
}
};
// sort type: quick sort
// complexity: O(n*logn)
// Algorithm: partite the list in 2 sub list based on its first element, pivot, one list greater than pivot
// and another less than pivot, this time pivot is in this right position. do this to each sublist recusively
// until the sublist length equals 0
template < typename T >
struct QuickSorter
{
void Sort(T list[], int n)
{
QuickSort(list, 0 , n - 1 );
}
private :
// partite the list in 2 sublists, nPivot will be in the right position.
int Partition(T list[], int low, int high)
{
int nPivot = list[low];
int nPivotPos = low;
for ( int i = low + 1 ; i <= high ; ++ i)
{
if (list[i] < nPivot && ++ nPivotPos != i) // ++pivotpos != i is very tricky
{
int tmp = list[nPivotPos];
list[nPivotPos] = list[i];
list[i] = tmp;
}
}
// in the previous loop, we just calculate the final pivot position and move the large number
// to the 2nd sublist, small number to the 1st sublist by swap. and after doing that, we put the
// pivot in the right position here.
int t = list[nPivotPos];
list[nPivotPos] = list[low];
list[low] = t;
return nPivotPos;
}
void QuickSort(T list[], int first, int last)
{
if (first < last) // end condition-this sublist just has 1 element
{
int nPivotPos = Partition(list, first, last);
QuickSort(list, first, nPivotPos - 1 );
QuickSort(list, nPivotPos + 1 , last);
}
}
};
// sort type: Heap sort
// complexity: O(n*logn)
// Algorithm: construct a max heap from the list, swap the first element (the heap root) with the last
// element. adjust the previous (n-1) elements to a heap again and swap to its end again, do it
// until the heap has only one element
template < typename T >
struct HeapSorter
{
void Sort(T list[], int n)
{
// make the heap. n/2 is the last non-leaf node.
for ( int i = n / 2 ; i >= 0 ; -- i) FilterDown(list, i, n - 1 );
// move the root element (the last) to the end one by one as the heap decrease its size...
for ( int i = n - 1 ; i >= 1 ; -- i)
{
int tmp = list[ 0 ];
list[ 0 ] = list[i];
list[i] = tmp;
// adjust the heap to be a max heap
FilterDown(list, 0 , i - 1 );
}
}
private :
void FilterDown(T list[], int nStart, int nEndOfHeap)
{
int i = nStart, j = 2 * i + 1 ;
int temp = list[i];
while (j <= nEndOfHeap)
{
if (j + 1 <= nEndOfHeap && list[j] < list[j + 1 ]) j ++ ; // get the larger subnode
if (temp >= list[j]) break ; // do we need swap the larger subnode with the parent node
else {list[i] = list[j]; i = j; j = 2 * i + 1 ;} // adjust its subnode because it is modified
}
list[i] = temp;
}
};
OK, 下面我们希望能用一个模板函数来灵活的调用这些算法,细细一看,我们有两个参数:排序算法与被排序元素类型, 于是, 我们第一个Sort函数的版本应声而出:
// usage: Sort1<BubbleSorter<int>, int>(list, sizof(list)/sizeof(list[0]));
template < class SortPolicy, typename T >
void Sort1(T list[], int n)
{
SortPolicy().Sort(list, n);
};
但是正如注释中所言, 我们要这么调用这个函数:Sort1<BubbleSorter<int>, int>(list, sizof(list)/sizeof(list[0]))这么些模板参数要制定,实在是太不方便了, 而且我们可以看到这里两个int就是冗余了,如果把他们写成不同的类型,就无法保证其正确性(或编译或运行)。我们有必要消除这个冗余, 也许你注意到SortPolicy中的那个int应该可以由第二个模板T参数提供,一个模板参数由另一个模板参数决定,对, 我们需要的就是模板模板参数(template template paramter):
// usage: Sort2<int, BubbleSorter>(list, sizeof(list)/sizeof(list[0]));
template < typename T, template < typename U > class SortPolicy >
void Sort2(T list[], int n)
{
SortPolicy < T > ().Sort(list, n);
}
这样我们就可以这么使用: Sort2<int, BubbleSorter>(list, sizeof(list)/sizeof(list[0])); 嗯, 比Sort1要好些了,但是因为我们知道list的元素类型了,如果这个int能够直接从list推出了, 我们就只需要写一个模板参数了,解决方法出奇的简单,调换模板参数的声明顺序:
// usage: Sort3<BubbleSorter>(list, sizeof(list)/sizeof(list[0]));
template < template < typename U > class SortPolicy, typename T >
void Sort3(T list[], int n)
{
SortPolicy < T > ().Sort(list, n);
}
试试Sort3<BubbleSorter>(list, sizeof(list)/sizeof(list[0]));是不是很更简单了:)
当然,C++中模板太灵活了, 我们还可以有其他实现,并且有的还不比Sort3这个版本差.(如下Sort5). 首先, Sort4用了类似于trait的技巧,排序的元素类型从排序的类中得出:
// template<typename T>
// struct BubbleSorter
// {
// typedef T elementtype;
// void Sort(T list[], int n){...}
// };
// trick: type trait
// usage: Sort4<BubbleSorter<int> >(list, sizeof(list)/sizeof(list[0]));
template < class SortPolicy >
void Sort4(typename SortPolicy::elementtype list[], int n)
{
SortPolicy().Sort(list, n);
};
代码应该能很好的解释它自己了。当然,它还不能从list的元素类型来推出SortPolicy类的模板参数类型。下一个版本Sort5, 可以与Sort3相媲美, 它采用的是成员模板参数:
// struct BubbleSorter
// {
// template<typename T>
// void Sort(T list[], int n){...}
// };
// trick: member template
// usage: Sort5<BubbleSorter>(list, sizeof(list)/sizeof(list[0]));
template < class SortPolicy, typename T >
void Sort5(T list[], int n)
{
SortPolicy().Sort(list, n);
}
这里, 我们的排序的类不再是模板类,但其Sort函数为成员模板函数。这样,该模板函数就能根据其参数list元素类型推导其模板参数类型。
具体使用如下:
void Output( const string & strSortType, T list[], int n)
{
cout << strSortType << " : " ;
for ( int i = 0 ; i < n; ++ i) cout << list[i] << " " ;
cout << endl;
}
int main( int argc, char * argv[])
{
int list[] = { 1 , 2 , 5 , 7 , 0 , 12 , 67 , 0 , 0 , 3 , 6 , 9 };
int n = sizeof (list) / sizeof (list[ 0 ]);
// Bubble Sort
Sort1 < BubbleSorter < int > , int > (list, n); Output( " Bubble Sort " , list, n);
Sort2 < int , BubbleSorter > (list, n); Output( " Bubble Sort " , list, n);
Sort3 < BubbleSorter > (list, n); Output( " Bubble Sort " , list, n);
// Sort4<BubbleSorter<int> >(list, n); Output("Bubble Sort", list, n);
// Sort5<BubbleSorter>(list, n); Output("Bubble Sort", list, n);
// Quick Sort
Sort1 < QuickSorter < int > , int > (list, n); Output( " Quick Sort " , list, n);
Sort2 < int , QuickSorter > (list, n); Output( " Quick Sort " , list, n);
Sort3 < QuickSorter > (list, n); Output( " Quick Sort " , list, n);
// Sort2<QuickSorter<int> >(list, n); Output("Quick Sort", list, n);
// Sort3<QuickSorter>(list, n); Output("Quick Sort", list, n);
return 0 ;
}
好了,至此, 6种排序算法,5种C++模板的应用,在此完成结合。