<泛> 多路快排

今天写一个多路快排函数模板,与STL容器兼容的。

我们默认为升序排序

因为,STL容器均为逾尾容器,所以我们这里采用的参数也是逾尾的参数

一、二路快排

基本思路

给你一个序列,先选择一个数作为基数,我们要做的是把小于该基数的数字放于左侧,大于该基数的数字放于右侧,最后将此基数放于中间,形成新的序列,我们把左侧序列和右侧序列分别像之前那样做,最后得到的序列即为顺序序列。

过程演示

比如给你一个序列   4  3  1  5  4  7  9  1  8  0

我们采用双指针扫描法,红色代表左指针所指位置,绿色代表右指针所指位置

我们选取一个数作为基数,假设选第一个数,即 flag  为  4

那么,我们把这个位置空出来,用null表示

则,一开始的序列图为

null  3  1  5  4  7  9  1  8  0                 flag  is  4

我们从右指针开始遍历,找到第一个小于flag的数字,填入坑中,然后左指针++,指向下一位然后开始遍历

0  3  1  5  4  7  9  1  8  null                

0  3  1  5  4  7  9  1  8  null         

0  3  1  5  4  7  9  1  8  null    

0  3  1  null  4  7  9  1  8   

0  3  1  null  4  7  9  1  8   

0  3  1  1  4  7  9  null  8   

0  3  1  1  4  7  9  null  8   

0  3  1  1  4  null  9  7  8   

右指针指向9,9>flag,继续左移,然后红绿指针相遇,结束

我们把flag放入null,即形成了新的序列

0  3  1  1  4  4  9  7  8  5

我们把左侧序列0  3  1  1  4

和右侧序列9  7  8  5

分别如上做,即可得到有序序列

C++模板代码:

template<typename value_type, typename value_Ptr>
void quick_sort(value_Ptr begin, value_Ptr end)          //逾尾
{
    if (begin == end)return;

    value_type flag = *begin;

    value_Ptr l = begin, r = end;
    r--;
    while (l < r)
    {
        while (l < r && *r > flag)r--;
        if (l < r)*(l++) = *r;
        while (l < r && *l < flag)l++;
        if (l < r)*(r--) = *l;
    }
    *l = flag;
    quick_sort<value_type>(begin, l);
    quick_sort<value_type>(l + 1, end);
}

测试以及使用

#include <iostream>
#include <vector>
using namespace std;int main()
{
    ios::sync_with_stdio(false);

    int list[10]{ 22,3,1,5,4,7,9,1,8,0 };
    vector<int> v{ list,list + 10 };

    quick_sort<int>(list + 0, list + 10);
    quick_sort<int>(v.begin(), v.end());
    for (auto it : v)cout << it << " ";
    cout << endl;
    for (auto it : list)cout << it << " ";
    cout << endl;

    char list_[10]{ ‘r‘,‘c‘,‘2‘,‘A‘,‘z‘,‘b‘,‘8‘,‘0‘,‘r‘, ‘3‘ };
    vector<char>v_{ list_,list_ + 10 };

    quick_sort<char>(list_ + 0, list_ + 10);
    quick_sort<char>(v_.begin(), v_.end());
    for (auto it : v_)cout << it << " ";
    cout << endl;
    for (auto it : list_)cout << it << " ";
    cout << endl;
}

测试结果

效率为O(N * logN)

如果序列中有很多重复的元素,那么,那一长串的重复值就相当于一个非递减序列,按理说是不需要排序的,但是上述无法识别此情况,它依旧会把这一长串不必处理的序列进行处理,无疑将函数执行效率大打折扣

这时,我们就需要引入三路快排

二、三路快排

基本思路

给你一个序列,我们选择一个基数作为标准,小于基数的元素放于左侧区间,等于基数的元素放于中间区间,大于基数的元素放于右侧区间

整理好之后,我们将左侧区间和右侧区间如上做。

过程描述

我们用四指针扫描

l  r  用于扫描待处理的序列

equ_l   equ_r  用于记录两侧相等的区间

equ_l  = l = begin

r 和equ_r指向右侧

让l r向中间遍历,遇到不等于的情况和快排一样,留一个空位,依次填补即可,遇到相等的情况,左指针指向的值等于基数,那么就放于equ_l处,且equ_l++,右边同理

当l  r  相遇时,我们让四指针分别向两端移动,equ_l 所指的元素依次和 l 所指的元素交换,右侧同理,即可得到我们想要的序列,即  <  =  >

之后,我们将<区间和>区间分别如上做,即可得到有序序列

泛型模板代码:

template<typename value_type, typename value_Ptr>
void Tri_quick(const value_Ptr& begin, const value_Ptr& end)
{
    static auto swap = [](value_type& l, value_type& r)
    {
        if (l == r)return;
        value_type t{ l };
        l = r;
        r = t;
    };
    value_Ptr equ_l = begin, equ_r = end, l = begin, r = end;
    if (begin >= end || begin >= end - 1)return;       

//上式:如果第二个条件成立的话,表达式1中的 ++l 就会解析 end 迭代器
    value_type flag = *l;
    while (l < r)
    {
        while (l < r && *(++l) < flag)if (l == end - 1)break;    //  表达式1
        while (l < r && flag < *(--r));
        if (*l != *r)    swap(*l, *r);
        if (*l == flag)    swap(*(++equ_l), *l);
        if (*r == flag)    swap(*(--equ_r), *r);
    }
    if (*l > flag)l--;     //此时l==r,可能l的值大于flag,我们不换,但是前面的一定小于等于flag,所以我们--
    while (equ_l > begin)swap(*(equ_l--), *(l--)); //选择大于是因为equ_l如果为容器的begin迭代器,那么不允许--操作
    swap(*equ_l, *l);                                           //所以begin迭代器另做处理
    while (equ_r < end)swap(*(equ_r++), *(r++));
    Tri_quick<value_type>(begin, l);
    Tri_quick<value_type>(r, end);
}

测试及使用

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    int list[10]{ 1,2,1,1,3,5,4,2,1,0 };
    vector<int> v{ list,list + 10 };
    Tri_quick<int>(list + 0, list + 10);
    for (auto it : list)cout << it << " ";
    cout << endl;
    Tri_quick<int>(v.begin(), v.end());
    for (auto it : v)cout << it << " ";
    cout << endl;

    char list_[10]{ ‘r‘,‘c‘,‘2‘,‘A‘,‘z‘,‘b‘,‘8‘,‘0‘,‘r‘, ‘3‘ };
    vector<char>v_{ list_,list_ + 10 };

    Tri_quick<char>(list_ + 0, list_ + 10);
    Tri_quick<char>(v_.begin(), v_.end());
    for (auto it : v_)cout << it << " ";
    cout << endl;
    for (auto it : list_)cout << it << " ";
    cout << endl;
}

这就是今天的多路快排,关于快排有很多不适应的情况,也有很多优化的算法,关于其他的算法,大家可以去其他地方学习,这里不做赘述

如果有什么问题,请于下方评论区留言。

原文地址:https://www.cnblogs.com/lv-anchoret/p/9537640.html

时间: 2024-10-12 02:53:10

<泛> 多路快排的相关文章

Java常见的几种排序算法-插入、选择、冒泡、快排、堆排等

本文就是介绍一些常见的排序算法.排序是一个非常常见的应用场景,很多时候,我们需要根据自己需要排序的数据类型,来自定义排序算法,但是,在这里,我们只介绍这些基础排序算法,包括:插入排序.选择排序.冒泡排序.快速排序(重点).堆排序.归并排序等等.看下图: 给定数组:int data[] = {9,2,7,19,100,97,63,208,55,78} 一.直接插入排序(内部排序.O(n2).稳定) 原理:从待排序的数中选出一个来,插入到前面的合适位置. [java] view plain copy

常见的几种排序算法-插入、选择、冒泡、快排、堆排等

排序是一个非常常见的应用场景,很多时候,我们需要根据自己需要排序的数据类型,来自定义排序算法,但是,在这里,我们只介绍这些基础排序算法,包括:插入排序.选择排序.冒泡排序.快速排序(重点).堆排序.归并排序等等.看下图: 给定数组:int data[] = {9,2,7,19,100,97,63,208,55,78} 一.直接插入排序(内部排序.O(n2).稳定) 原理:从待排序的数中选出一个来,插入到前面的合适位置. package com.xtfggef.algo.sort; public

gogo闯SEO快排教程及应用编程(同步官方)

https://www.zygx8.com/thread-11604-1-1.html 授课内容简介: 两个模块:「SEO套路」和「SEO应用编程」 「SEO套路」模块,分 "域名.内容&模板.关键词.链接.点击" 五个章节,所有的SEO动作都是从这5个点中入手 多一些思路和实战,即:经过分析,出于前提条件X(思路),对网站做Y这个动作(实战),可以大概率提升排名&流量.其中实战部分,大站和灰色站的套路居多 「SEO应用编程」模块,分"基础.SEO数据分析.快

seo快排技术/seo快速排名/搜索引擎快速排名/百度快速排名/搜索引擎快排/百度排名/百度优化/百度快排

seo快排技术/seo快速排名/搜索快速排名/百度快速排名/搜索引擎快排/搜索引擎快速排名技术的强大工具: 泛目录技术是目前最快速有效的办法,增加站点的收录方面,这里推荐莲花泛目录 莲花泛目录,完善的技术支持,代码亲自编写,独立研发.业界领先. 莲花泛目录程序强大之处: 1.内容分类详细2.自动推送URL链接3.内置超强原创内容功能系统,页面深受百度搜索引擎喜爱.4.蜘蛛触发繁殖:蜘蛛触发程序任何页面,程序自动生成独立页面并引导繁殖.5.操作容易简单:全部采用web界面配置,简单鼠标点击操作即可

算法学习——单链表快排

/**  * 以p为轴对start-end间的节点进行快排(包括start && 不包括end):  * 思路:  * 1.将头节点作为轴节点start,从start.next开始遍历,如果节点小于轴start的值,将该节点插入到轴节点后面:  * 2.将轴节点插入合适位置,即找到最后一个小于轴的节点,将该节点与轴节点值互换,此时就链表分为两部分,小于轴节点和大于轴节点:  * 3.递归的遍历2中两部分节点.  *   * @param p  * @param start  * @para

快排的递归和非递归

常用的快排都是用递归写的,因为比较简单,但是可以用栈来实现非递归的快排. 第一种是递归的快排 #include<stdio.h> #include <stdlib.h> #include <time.h> int quick(int a[],int i ,int j) {     int tmp=0,key,b=0;     int im,jm;     im=i;     jm=j;     key=a[i];     if(i>j)         retur

快排,随机快排,双路快排,三路快排的理解

再讲快排之前,首先对于任何一个数组,无论之前是多么杂乱,排完之后是不是一定存在一个数作为分界点(也就是所谓的支点),在支点左边全是小于等于这个支点的,然后在这个支点右边的全是大于等于这个支点的,快排过程就是寻找这个支点过程 先看普通的快排(普通单路快排) 代码如下 let findIndex = (arr, l, len) => { let par = arr[l], j = l for (let i = l + 1; i <= len; i++) { if (arr[i] < par)

Quick Sort(快排)

这是挖坑填补法的演示 快排之挖坑填补法: 1 void Quick(int top/*起始位置*/,int end/*末尾位置*/,int arr[])//挖坑填补法 2 { 3 int i=top,j=end,mark;//i是记住前面的坑 j记住后面的坑 mark记住标准值 4 5 mark=arr[top];//以起始位置作为标准值,同时起始点成为第一个坑 6 if(top>=end)return; 7 while(i<j) 8 { 9 while(i<j)//从后向前找比标准值小

冒泡,快排代码+注释

冒泡: package Sort; public class BubbleSort { public static void main(String[] args) { int[] list = new int[]{12,14,3,24,1,33}; int[] nums = bubbleSort(list); for(int i = 0;i< list.length;i++){ System.out.println(nums[i]); } } public static int[] bubbl