快速排序
快速排序也是分治思想的一种,将一个数组分为两部分独立排序。
快速排序与归并排序是互补的:归并将数组分为两个子数组分别排序,并将子数组有序合并。而快排将数组排序的方式则是当两个数组都有序时真个数组也就有序了。区别就在于递归调用发生在处理整个数组之前(归并),还是之后(快排)。
快排的具体方法为:选择数组头元素为切分元素,循环左边找一个大于它的元素,右边找一个小于他的元素,相交换,直到左右相交,然后与头元素相交换。也就是将数组分为两半一边大于切分,一边小于切分。
然后以递归的方式继续切分上一步分出来的左右子数组。
快排需要的时间为NlgN,于归并相比它的优点是所需空间更小为lgN,缺点是稳定性不高。因此快排也是最常用的排序方式。
代码:
template<typename Item> int Partition(vector<Item>&a, int lo, int hi) { int i = lo, j = hi + 1;//左右标识变量 Item temp = a[lo];//切分元素 while (true) { while (a[++i] < temp)if (i == hi)break; while (temp < a[--j])if (j == lo)break; if (i >= j)break; eaxh(a[i], a[j]); } eaxh(a[lo], a[j]); return j; } template<typename Item> void quicksort(vector<Item>&a, int lo, int hi) { if (hi <= lo)return; //if (hi <= lo+10){ insertsort(a, lo, hi); return; }//改进算法 int j = Partition(a, lo, hi); quicksort(a, lo, j - 1); quicksort(a, j + 1, hi); } template<typename Item> void quicksort(vector<Item>&a) { quicksort(a, 0, a.size() - 1); }改进:
与归并相同,小数组使用插入排序,减少递归的调用。
template<typename Item> void eaxh(Item &a, Item &b) { int t = a; a = b; b = t; } template<typename Item> void insertsort(vector<Item>&a, int lo, int hi) { for (int i = lo + 1; i <= hi; ++i) { Item temp = a[lo]; int j; for (j = i; j >= lo&&temp < a[j - 1]; --j) a[j] = a[j - i]; a[j] = temp; } } 三分快速排序
与二分法相比,将会把与切分元素相同的元素一起保留,然后左右数组继续切分。
而三分快排的作用便是当重复元素量足够大时,减少数组不必要的交换。也就是说三分快排适用于有大量重复元素的数组
代码:
template<typename Item> void Quick3waysort(vector<Item>&a, int lo, int hi) { if (hi <= lo)return; int lt = lo, i = lo + 1, gt = hi;//左标识(切分元素),循环标识,右标识 Item temp = a[lo];//切分元素 while (i <= gt)//数组循环 { if (a[i] < temp)eaxh(a[lt++], a[i++]); //元素小于切分标识,与左标识元素(切分元素)交换, //左标识后移继续指向切分元素,循环标识后移 else if (temp < a[i])eaxh(a[i], a[gt--]); //元素大于切分标识,与右标识交换,右标识前移, //循环标识不动,继续测试交换后的元素 else i++; } Quick3waysort(a, lo, lt - 1); Quick3waysort(a, gt + 1, hi); } template<typename Item> void Quick3waysort(vector<Item>&a) { quicksort(a, 0, a.size() - 1); } 测试:
快排测试结果: 算法|量级101001000100001000001000000快速排序0.1040.1040.1070.1280.3773.008改进0.1040.1050.1070.1270.3552.832三分快排0.1030.1040.1060.1270.3362.822
测试用例可以看出,快速排序的时间复杂度绝对所讲算法中最低的,也就是说快速排序是所讲算法中最快的,但是这都是建立在快排数组足够混乱的情况下,如果快排切分不平衡时,这个程序可能会极为低效,如:第一次切分最小元素,第二次切分第二小的元素,也就是数组有一定顺序的时候可能会出现的问题。为了解决这种问题,我们应该在一开始就打乱数组:
template<typename Item> void quicksort(vector<Item>&a) { random_shuffle(a.begin(), a.end());//打乱数组 quicksort(a, 0, a.size() - 1); } 这虽然会使程序耗费一部分时间,但是为了避免极端情况的出现,完全是必要的,何况相对快排的速度而言,所耗费的时间也完全不在话下。
快排是一种偏爱随机性的算法,这也相应的说明了快排的稳定性会很低。
而在三分的测试用例中,数组范围为(0,100000)如果数据的范围缩小的话,三分快排的速度的提升也会更加明显。