剑指offer之partition算法

简介: 剑指offer之partition算法

1 问题

partition 算法:

从无序数组中选出枢轴点 pivot,然后通过一趟扫描,以 pivot 为分界线将数组中其他元素分为两部分,使得左边部分的数小于等于枢轴,右边部分的数大于等于枢轴(左部分或者右部分都可能为空),最后返回枢轴在新的数组中的位置。

如果原始数组为[5,9,2,1,4,7,5,8,3,6],那么整个处理的过程如下图

20170724223902569.png

Partition 可不只用在快速排序中,还可以用于 Selection algorithm(在无序数组中寻找第K大的值)中.


2 代码实现

我们按照算法需求简单实现如下

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
int partition(vector<int>& vector, int start, int end)
{
    if (vector.size() < 1)
    {
        std::cout << "vector is null or vector size is not normal" << std::endl;
        return -1;
    }
    //一般我们写代码不要这样写死,pivot = vector[0],如果遇到这种写死数字的时候我们确认下是否是可以用变量更加合适
    int pivot = vector[start];
    int index = 0;
    for (int i = start + 1; i < end; ++i)
    {
        if (vector[i] <= pivot)
        {
            ++index;
            swap(vector[index], vector[i]);
        }
    }
    swap(vector[0], vector[index]);
    return index;
}


3 优化

上面实现的效率很低,我们需要优化,如果我们考虑用2个指针的思想,保持头尾两个指针向中间扫描,每次在头部找到大于pivot的值,同时在尾部找到小于pivot的值,然后将它们做一个交换

1)第一种优化代码实现

#include <iostream>
#include <vector>
using namespace std;
/*
 *partition算法 记得如果这里是C++我们传递的是vector类型,我们记得要加引用,
 *不然改变不了数据,这里和java传递ArrayList不一样,ArrayList作为参数可以改变集合里面的值,
 *所以C++如果函数传递非基本数据类型,一半都是带引用的
 */
int partitionOne(vector<int>& vector, int start, int end)
{
    if (start > end)
    {
        std::cout << "vector is empty or start > end" << std::endl;
        return -1;
    }
    int pivot = vector[start];
    while (start < end)
    {
        //我们先从尾巴开始
        while (start < end && pivot <= vector[end])
        {
            --end;
        }
        //这里用的数组赋值,而不是直接用swap交换函数,那么下面的2步也是用数组赋值,而不是用swap交换函数
        vector[start] = vector[end];
        while (start < end && pivot >= vector[start])
        {
            ++start;
        }
        vector[end] = vector[start];
    }
    vector[start] = pivot;
    return start;
}
void printVector(vector<int> v) 
{
    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << v[i] << "\t";
    }
    std::cout << std::endl;
}
int main()
{
    vector<int> v1;
    //[5,9,2,1,4,7,5,8,3,6]
    v1.push_back(5);
    v1.push_back(9);
    v1.push_back(2);
    v1.push_back(1);
    v1.push_back(4);
    v1.push_back(7);
    v1.push_back(5);
    v1.push_back(8);
    v1.push_back(3);
    v1.push_back(6);
    std::cout << "old data print " << std::endl;
    printVector(v1);
    partitionOne(v1, 0, v1.size() - 1);
    std::cout << "after partitionOne" << std::endl;
    printVector(v1);
    return 0;
}

运行结果如下

old data print 
5 9 2 1 4 7 5 8 3 6 
after partitionOne
3 4 2 1 5 7 5 8 9 6 

20170724223902569.png

2)第二种优化代码实现

我们使用交换函数,而不是数组赋值,和上面差不多,我们用swap函数的时候,我们单独定义了2个变量i和j保存了start和end,这样在后面的最后一个swap函数的时候进行swap(vector[i], vector[start]);还有这个函数是返回i,而不是start,不然就有问题。

#include <iostream>
#include <vector>
using namespace std;
void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
void printVector(vector<int> v) 
{
    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << v[i] << "\t";
    }
    std::cout << std::endl;
}
/*
 *partition算法 记得如果这里是C++我们传递的是vector类型,我们记得要加引用,
 *不然改变不了数据,这里和java传递ArrayList不一样,ArrayList作为参数可以改变集合里面的值,
 *所以C++如果函数传递非基本数据类型,一半都是带引用的
 */
int partitionTwo(vector<int>& vector, int start, int end)
{
    if (start > end)
    {
        return -1;
    }
    int i = start;
    int j = end;
    int pivot = vector[start];
    while (i < j)
    {
        //我们先从尾巴开始
        while (i < j && pivot <= vector[j])
        {
            --j;
        }
        //这里用的数组赋值,而不是直接用swap交换函数,那么下面的2步也是用数组赋值,而不是用swap交换函数
        while (i < j && pivot >= vector[i])
        {
            ++i;
        }
        swap(vector[i], vector[j]);
    }
    swap(vector[i], vector[start]);
    //printVector(vector);
    return i;
}
int main()
{
    vector<int> v1;
    //[5,9,2,1,4,7,5,8,3,6]
    v1.push_back(5);
    v1.push_back(9);
    v1.push_back(2);
    v1.push_back(1);
    v1.push_back(4);
    v1.push_back(7);
    v1.push_back(5);
    v1.push_back(8);
    v1.push_back(3);
    v1.push_back(6);
    std::cout << "old data print " << std::endl;
    printVector(v1);
    partitionTwo(v1, 0, v1.size() - 1);
    std::cout << "after partitionOne" << std::endl;
    printVector(v1);
    return 0;
}

运行结果如下

old data print 
5 9 2 1 4 7 5 8 3 6 
after partitionOne
4 3 2 1 5 7 5 8 9 6 


4 总结

我们使用partition算法的时候,从我们上面代码第一次调用来看,我们选择的第一个数字5作为中间轴,然后执行一次后,我们的 partition函数返回的start或者i值都是4,然后我们最后一步把5也插入了vector[4]那里,就是说明我们左边有4个值比当前数字5作为中间轴都小,也能说明这左边的4个值和中间轴数5都是数组里面最小的5个值,如果我们需要求出一个数组里面最小的5个值,我们只需要partition算法返回值是4就行,然后在左边的数组的前5个数字就是这个数组里面最小的5个数,所以这里的数组里面最小的多少K个数确保partition返回的index或者start的关系是:index = K - 1; 或者start = K -1关系,也就是说partition函数返回index或者start值的时候,数组里面从坐标0到index或者start的值就是数组里面最小的元素,也就是index+1个元素。

我们使用partition算法是双指针思想

相关文章
|
6月前
|
设计模式 算法 Java
京东Java高开岗三面算法+数据库+设计模式,复习1个月成功拿offer
京东高级java现场三面,包含:算法、数据库、设计模式、java高级等,尾部有最全BAT高级java面试题目和答案福利,想要的就快来领走吧~(领取方式见文末)
|
1月前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
22 0
|
SQL 算法 架构师
字节算法中了80%!靠着这份GitHub上的算法小抄,成功斩获Offer
前言 最近,GitHub上的算法小抄又火了!已经有不少人靠它手撕算法题,拿下了字节、腾讯等大厂offer
79 0
|
6月前
|
人工智能 算法 程序员
这本“算法宝典”讲得透彻,完全掌握后,我竟拿到字节跳动offer
字节跳动,相信大家都已经对这家公司很熟悉了,尤其是近几年来,对它的认识也在不断刷新,它惊人的发展速度确实让行业内人刮目相看,如今很多年轻人也想要挤进字节跳动,它越来越火热,自然也就越来越难进了!
太可惜了,四面字节跳动,我的offer竟被一道“算法题”给拦截了
算法,在行业里越来越重要,一线互联网公司也非常注重算法,所以在面试时基本上都有涉及到。字节跳动是出了名的爱问算法题,几乎每一面都要问到算法。实际上,现在很多公司都会问算法,尤其是对于应届生来说,要求更高,所以想要进大厂,搞定算法是很重要的。
|
6月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
SQL 算法 架构师
字节算法中了80%!靠着这份GitHub上的算法小抄,成功斩获Offer
前言 最近,GitHub上的算法小抄又火了!已经有不少人靠它手撕算法题,拿下了字节、腾讯等大厂offer
|
6月前
|
SQL 算法 NoSQL
三面头条,靠P9级算法大牛分享的两本算法pdf书籍,轻松拿到offer
头条一面(Java+项目) 1.倒排索引 2.讲讲redis里面的哈希表? 3.happen-before的规则? 4.volatile修饰符,synchronize锁 5.java单例模式的实现,懒汉、饿汉? 6.进程与线程的区别,多进程和多线程的区别?
|
6月前
|
算法 容器
【算法训练营】栈合集(1) 剑指 Offer 31. 栈的压入、弹出序列 || 32. 最长有效括号 || 682. 棒球比赛 || 面试题 03.01. 三合一
【算法训练营】栈合集(1) 剑指 Offer 31. 栈的压入、弹出序列 || 32. 最长有效括号 || 682. 棒球比赛 || 面试题 03.01. 三合一
54 0
|
6月前
|
存储 算法 NoSQL
“三顾字节”,九次面试,只要算法搞得好,大厂offer跑不了
4.29 字节春招截止倒数第二天,杭州Java商业变现部门暑假实习,隔天挂,春招结束(人生的第一份简历,嗯就开始即结束