本文中阐述普通排序算法,即冒泡排序法、选择排序法和插入排序法。
用于排序算法中的比较类为:
#ifndef _COMPARE_H
#define _COMPARE_H
template<typename T>
class Compare{
public:
static bool compareLt( T,T );
static bool compareEq( T,T );
static bool compareGt( T,T );
};
template<typename T> bool Compare<T>::compareLt( T lhs,T rhs )
{
if( lhs<rhs )
return true;
else
return false;
}
template<typename T> bool Compare<T>::compareEq( T lhs,T rhs )
{
if( !compareLt( lhs,rhs )&&!compareLt( rhs,lhs ) )
return true;
else
return false;
}
template<typename T> bool Compare<T>::compareGt( T lhs,T rhs )
{
if( !compareLt( lhs,rhs )&&!compareEq( lhs,rhs ) )
return true;
else
return false;
}
#endif
交换函数:
template<typename T> void swapT( T &lhs,T &rhs )
{
T temp=lhs;
lhs=rhs;
rhs=temp;
}
下面开始阐述排序算法:
一、冒泡排序法
对于一个要排序的数组sortedArr,冒泡排序法就是从数组的第一个元素开始,依次比较两个相邻的元素,如果这两个相邻元素的顺序与预定的顺序相反,比如说是从小到大,但是sortedArr[i]>sortedArr[i+1],那么就调用上面的swapT函数将这两个元素交换,那么经过第一遍比较,最大的数就沉到了最下面,也就是成为了数组的最后一个元素;第二遍比较时,就只用比较第一个元素到倒数第二个元素,将第一个元素到倒数第二个元素之间的最大值沉到数组中倒数第二的位置,这样经过arrLength-1遍比较后,就完成了排序过程。
#ifndef BUBBLESORT_H
#define BUBBLESORT_H
#include "sortTest.h"
#include <iostream>
using namespace std;
template<typename T>
class BubbleSort{
public:
void bubbleSortTD( T*,int );
void bubbleSortDT( T*,int );
void bubbleOptim( T*,int );
void printArr( T*,int );
};
/**
* 冒泡排序法
* 从上到下依次排
*/
template<typename T> void BubbleSort<T>::bubbleSortTD( T *sortedArr,int arrLength )
{
for( int i=1;i<arrLength;++i )
{
for( int j=arrLength-1;j>=i;--j )
{
if( Compare<T>::compareLt( sortedArr[j],sortedArr[j-1] ) )
{
swapT( sortedArr[j],sortedArr[j-1] );
}
}
}
}
/**
* 冒泡排序法
* 从下到上依次排
*/
template<typename T>void BubbleSort<T>::bubbleSortDT( T *sortedArr,int arrLength )
{
for( int i=1;i<arrLength;++i )
{
for( int j=0;j<arrLength-i;++j )
{
if( Compare<T>::compareGt( sortedArr[j],sortedArr[j+1] ) )
{
swapT( sortedArr[j],sortedArr[j+1] );
}
}
}
}
/**
* 优化的冒泡排序法
* 将最优情况的时间代价优化为O(n)
*/
template<typename T> void BubbleSort<T>::bubbleOptim( T *sortedArr,int arrLength )
{
for( int i=1;i<arrLength;++i )
{
bool flag=true;
for( int j=0;j<arrLength-i;++j )
{
if( Compare<T>::compareGt( sortedArr[j],sortedArr[j+1] ) )
{
swapT( sortedArr[j],sortedArr[j+1] );
flag=false;
}
}
if( flag )
return;
}
}
template<typename T> void BubbleSort<T>::printArr( T *sortedArr,int arrLength )
{
for( int i=0;i<arrLength;++i )
{
cout<<sortedArr[i]<<" ";
}
}
#endif
其中未优化的冒泡排序法的最好和最差的时间代价均为O(n2),优化后,若初始为正序排列,那么时间代价可以优化为O(n);
二、选择排序法
选择排序法是对冒泡排序法的改进,在冒泡排序法中,每次比较相邻的元素,如果与预定的顺序相反,那么就会将这两个数交换;在选择排序法中,用一个索引值index记录最大值位置,这样,在第一遍比较中,只要最后将sortedArr[index]与sortedArr[arrLength-1]进行交换即可,这样就减少了交换的次数,减少了时间消耗。
#ifndef SELECTSORT_H
#define SELECTSORT_H
#include "sortTest.h"
#include <iostream>
using namespace std;
template<typename T>
class SelectSort{
public:
void commSelect( T *sortedArr,int arrLength );
void printArr( T *sortedArr,int arrLength );
};
/**
* 选择排序法
* 减少了赋值的次数,但没有减少比较的次数
*/
template<typename T> void SelectSort<T>::commSelect( T *sortedArr,int arrLength )
{
for( int i=1;i<arrLength;++i )
{
int maxIndex=0;
for( int j=0;j<arrLength-i+1;++j )
{
if( Compare<T>::compareLt( sortedArr[maxIndex],sortedArr[j] ) )
{
maxIndex=j;
}
}
swapT( sortedArr[maxIndex],sortedArr[arrLength-i] );
}
}
template<typename T> void SelectSort<T>::printArr( T *sortedArr,int arrLength )
{
for( int i=0;i<arrLength;++i )
{
cout<<sortedArr[i]<<" ";
}
}
#endif
三、插入排序法
1、普通插入排序法
对于第i个元素,之前i-1个元素均按照从小到大排列,那么将i先后与之前的i-1、i-2、i-3...0元素进行比较,若i比i-1要小,那么将这两个元素交换,然后再将
第i-1与i-2个元素进行比较....若i大于i-1那么就什么都不做。
2、通过移位来实现插入排序
每次如果第i个元素小于第j( j从i-1到0 )个元素,那么只需要将第j+1个元素赋为array[j]即可,不必将array[j]赋为array[j+1],因此可以使用一个临时变量temp存储array[i],
每次将比temp大的元素向后移,最后将array[j+1]=temp即可。
3、使用哨兵来实现插入排序
在前两个排序算法中,每次都要比较j是否是大于等于0,当数组维数较多时,时间消耗较大,因此可以设置一个哨兵从而不必检测j的值
将数组的第0个元素设为哨兵,每次用array[0]来存放原来temp中存放的值,那么最多判断到array[0],循环必然终止,最后将array[j+1]赋为array[0],即可
插入排序的最优时间时间代价为O(n),最差时间代价为O(n2),平均时间代价也是O(n2),二分插入排序的最优时间代价为O(nlogn),最差为O(n2),因为其并没有减少赋值的次数
#ifndef _INSERTSORT_H
#define _INSERTSORT_H
template<typename T>
class InsertSort{
public:
void commonSort( T *,int );
void placeSort( T*,int );
void sentinelSort( T*,int );
void binaryInsert( T*,int );
void printArray( T *,int );
};
#endif
/**
* 普通插入排序法
* 交换相邻逆序数据
*/
template<typename T> void InsertSort<T>::commonSort( T *sortedArr,int arrLength )
{
Compare<T> compare;
for( int i=1;i<arrLength;++i )
{
int j=i-1;
while( j>=0&& compare.compareGt( sortedArr[j],sortedArr[j+1]) )
{
swapT( sortedArr[j],sortedArr[j+1] );
--j;
}
}
}
/**
* 插入排序法
* 通过移位来实现插入排序
*/
template<typename T> void InsertSort<T>::placeSort( T *sortedArr,int arrLength )
{
Compare<T> compare;
for( int i=1;i<arrLength;++i )
{
T temp=sortedArr[i];
int j=i-1;
while( j>=0&&compare.compareGt( sortedArr[j],temp ) )
{
sortedArr[j+1]=sortedArr[j];
--j;
}
sortedArr[j+1]=temp;
}
}
/**
* 插入排序法
* 使用哨兵 从而不用判断边界条件
* 此时数组的第0个元素不是要排的元素而是用来当做哨兵
*/
template<typename T> void InsertSort<T>::sentinelSort( T *sortedArr,int arrLength )
{
Compare<T> compare;
for( int i=2;i<arrLength;++i )
{
// 使用sortedArr[0]作为哨兵便不用判断j与0的关系
sortedArr[0]=sortedArr[i];
int j=i-1;
while( compare.compareGt( sortedArr[j],sortedArr[0]) )
{
sortedArr[j+1]=sortedArr[j];
--j;
}
sortedArr[j+1]=sortedArr[0];
}
}
/**
* 通过二分法来进行插入排序
*/
template<typename T>void InsertSort<T>::binaryInsert( T *sortedArr,int arrLength )
{
for( int i=1;i<arrLength;++i )
{
int left=0;int right=i-1;
int middle=( left+right )/2;
T temp=sortedArr[i];
while( left<=right )
{
if( Compare<T>::compareLt( sortedArr[middle],temp ) )
{
left=middle+1;
}else{
right=middle-1;
}
middle=( left+right )/2;
}
// 最终left停在的位置就是该元素应该停在的位置
for( int j=i-1;j>=left;--j )
{
sortedArr[j+1]=sortedArr[j];
}
sortedArr[left]=temp;
}
}
template<typename T> void InsertSort<T>::printArray( T *sortedArr ,int arrLength )
{
for( int i=0;i<arrLength;++i )
{
cout<<sortedArr[i]<<" ";
}
}