一. 目的
进行快速排序练习,掌握快速排序的原理。
算法证明:
第一步,数组分为四个区,无序小于关键值区A[1]-A[i],无序大于关键值区A[i+1]-A[j],待划分区A[j+1]-A[N-1],关键值区A[N],第一步时,只有两个区域不为空,即带划分区A[1]-A[N-1],关键值区A[N]
第二步:进入循环处理,若存在可以放在无序小区的元素,则将无序大区则将无序大区的下界元素与A[j]交换,此时无序小区的上界增长一。到循环结束序列存在三个不为空区,A[1]-A[i],A[i+1]-A[N-1],将A[i+1]与A[N]交换,此时关键值已经放在合适位置,前面的元素小于关键值,后面的元素大于关键值。
第三步:对前后两个区域进行相同划分,最终每个元素都处在自己的合适位置,算法证明完毕。
二. 实现代码
1、单向划分
//快速排序标准算法
void QuickSort(T testArray[], int nSize){
LogInfo<T> log = LogInfo<T>();
log.ShowState("原始数组为:");
log.ShowArray(testArray, nSize);
QuickSortSub(testArray, 0, nSize - 1);
log.ShowState("最终数组为:");
log.ShowArray(testArray, nSize);
}
void QuickSortSub(T testArray[], int Begin, int End){
if (Begin < End){
//获取中轴位置,该位置元素以及在位,且区分了两个部分,前面为小值区,后面为大值区
int pivot = QuickSortPartition(testArray, Begin, End);
//对两个区域进行快速排序
QuickSortSub(testArray, Begin, pivot - 1);
QuickSortSub(testArray, pivot + 1, End);
}
}
//快速排序的划分,返回划分位置,划分位置的元素已经在该在的位置
int QuickSortPartition(T testArray[], int Begin, int End){
//单个元素或者没有元素则退出
if (Begin >= End) return 0;
T key = testArray[End];
T tmp(0);
//i记录已经放小于哨兵的元素位置
int i = Begin - 1;
for (int j = Begin; j < End; j++){
//若J元素小于少哨兵值,则将i+1与j交换。
if (testArray[j] < key){
i++;
tmp = testArray[i];
testArray[i] = testArray[j];
testArray[j] = tmp;
}
}
testArray[End] = testArray[i + 1];
testArray[i + 1] = key;
return i + 1;
}
2、双向遍历,将大元素放在后端,小元素放在前端
//快速排序双向遍历算法
void QuickSortVerTwoDirection(T testArray[], int nSize){
LogInfo<T> log = LogInfo<T>();
log.ShowState("原始数组为:");
log.ShowArray(testArray, nSize);
QuickSortSubVerTwoDirection(testArray, 0, nSize - 1);
log.ShowState("最终数组为:");
log.ShowArray(testArray, nSize);
}
//双向遍历快速排序
void QuickSortSubVerTwoDirection(T testArray[],int Begin,int End){
if (Begin < End){
//获取划分点
int pivot = QuickSortPartitionTwoDirection(testArray, Begin, End);
QuickSortSubVerTwoDirection(testArray, Begin, pivot - 1);
QuickSortSubVerTwoDirection(testArray, pivot + 1, End);
}
}
//快速排序双向交换法
int QuickSortPartitionTwoDirection(T testArray[], int Begin, int End){
T key = testArray[Begin];//记录划分值
T tmp(0);
while (Begin < End){
//从尾部寻找小于划分值的位置,将该元素放在头部
while (Begin<End && testArray[End]>key)End--;
tmp = testArray[End];
testArray[End] = testArray[Begin];
testArray[Begin] = tmp;
//从头部寻找大于划分元素的位置,将该元素放在尾部
while (Begin < End && testArray[Begin] <= key)Begin++;
tmp = testArray[End];
testArray[End] = testArray[Begin];
testArray[Begin] = tmp;
}
return Begin;
}
3、随机快速排序
//随机化快速排序
void QuickSortVerRandom(T testArray[], int nSize){
LogInfo<T> log = LogInfo<T>();
log.ShowState("原始数组为:");
log.ShowArray(testArray, nSize);
QuickSortSubVerRandom(testArray, 0, nSize - 1);
log.ShowState("最终数组为:");
log.ShowArray(testArray, nSize);
}
//随机版本快速排序
void QuickSortSubVerRandom(T testArray[], int Begin, int End){
if (Begin < End){
//产生随机下标与关键值(结尾元素)交换
int Index = RandomKeyIndex(Begin, End);
if (Index != End){
T tmp = testArray[End];
testArray[End] = testArray[Index];
testArray[Index] = tmp;
}
//获取划分点
int pivot = QuickSortPartitionTwoDirection(testArray, Begin, End);
QuickSortSubVerRandom(testArray, Begin, pivot - 1);
QuickSortSubVerRandom(testArray, pivot + 1, End);
}
}
//随机版本快速排序划分
int QuickSortPartitionRandom(T testArray[], int Begin, int End){
T key = testArray[End];
T tmp(0);
//i记录已经放小于哨兵的元素位置
int i = Begin - 1;
for (int j = Begin; j < End; j++){
//若J元素小于少哨兵值,则将i+1与j交换。
if (testArray[j] < key){
i++;
tmp = testArray[i];
testArray[i] = testArray[j];
testArray[j] = tmp;
}
}
testArray[End] = testArray[i + 1];
testArray[i + 1] = key;
return i + 1;
}
//产生一个数据在Begin与End之间
int RandomKeyIndex(int Begin, int End){
srand((unsigned int)time(NULL));//产生一个随机种子。
int Index = rand() % (End - Begin + 1) + Begin;
return Index;
}
输出结果
三. 遇到难题
1、初次看到标准快速算法,由于太多变量,导致看起来很费力。
2、对于双向快速排序,只需要划分三个区,没有四个区,用四区划分来看着这个算法有些困难。
四. 经验总结
1、快速排序适用和要求序列相同的序列,就会发生退化,退化为冒泡排序,复杂度O(N^2)
2、随机化快速排序不再将指定特定元素作为关键值。产生更好的效果,对待已经有序的序列不再那么糟糕。
3、快速排序主要影响因素是划分的平衡性。
4、快速排序的平均复杂度为O(nlog(n)),算法不稳地。
五. 后续处理
今天要写另外两篇日记,完成排序算法这一块,然后来一个总结。
六. 参考文献
1、算法导论
2、 八大排序算法 http://blog.csdn.net/abcbig/article/details/42774333