快排光芒下被忽视的Partition函数

本文深入讲解Partition函数在快速排序中的核心作用及优化方法,探讨其在Top-k问题、快速选择算法及荷兰国旗问题中的应用。

1.前言:

看到这篇标题,没有学过快排的人自然是不知道Partition函数的意思和作用,这里附上学习的连接 Lantian的快排总结
我们现在都是被快排蒙蔽了双眼,没有意识到快最核心的划分函数Partition,当然Partition函数也就不止于快排这里,本文就从多方面来为展示Partition函数的本质和扩展作用

2.快排中的Partition及其优化:

在算法导论中我们对Partition函数的定义是这样的:

PARTITION(A, p, r)

A[r]//选择最后一个元素作为比较元素

= p  1//这个慢速移动下标必须设定为比最小下表p1,否则两个元素的序列比如21无法交换

for to r-1//遍历每个元素

{

if (A[j] <= x)//比较

{

1//移动慢速下标

Exchange A[i] with A[j ]//交换

}

}

Exchange A[i+1] with A[r]//交换

 

return 1//返回分割点

这里我就不再过多的赘述算法的具体细节,我在这里强调的是对快排中的Partition的优化
首先,在原本的算法中,假如我们已经选定了一个枢纽元,那么对于所有的比枢纽元小的元素我们都执行了swap操作
这是非常的低效的
在这里我们引入双向的Partition算法,在双向的算法中,我们对于比枢纽院小的元素并不是都执行了swap操作,只有数组右边的一部分的比枢纽元小的元素我们才执行了swap操作,大大减少了swap操作的次数从而降低时间复杂度
含有Partition函数的快速排序,标准算法导论上的描述实现 
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 1005

using namespace std;

int data[N];
int number;

void init_data()
{
	scanf("%d",&number); 
	for(int i=0;i<number;i++) scanf("%d",&data[i]);
}

void partition(int left,int& mid,int right)
{
	int i=left-1;
	int j=left;
	int var=data[right];
	while(j<right)
	{
		if(data[j]<var)
		{
			i++;
			int temp=data[i];
			data[i]=data[j];
			data[j]=temp;
		}
		j++;
	}
	i++;
	int temp=data[i];
	data[i]=var;
	data[right]=temp;
	mid=i;
	return ;
}

void quicksort(int left,int right)
{
	if(left>=right) return ;
	else
	{
		int mid;
		partition(left,mid,right);
		quicksort(left,mid-1);
		quicksort(mid+1,right); 
		return ;
	} 
}

void print()
{
	for(int i=0;i<number;i++) printf("%d ",data[i]);
	printf("\n");
}

int main()
{
	init_data();
	quicksort(0,number-1);
	print();
	return 0;
}
双向的Partition我在我的连接中已经写明了
你们可能对这一点还是有所怀疑,我们来做一做实验:
注意,一下的代码是Python编写的,为了方便实验
import random
from time import *
global data
data=[]
def partition_1():
    global data
    i=-1
    j=0
    var=data[len(data)-1]
    while j<len(data)-1:
        if data[j]<var:
            i+=1
            temp=data[i]
            data[i]=data[j]
            data[j]=temp
        j+=1
    i+=1
    temp=data[i]
    data[i]=var
    data[len(data)-1]=temp

def partition_2():
    global data
    i=0
    j=len(data)-1
    temp=data[0]
    while i!=j:
        while i<j and data[j]>=temp:
            j-=1
        while i<j and data[i]<=temp:
            i+=1
        if i!=j:
            t=data[i]
            data[i]=data[j]
            data[j]=t
    data[0]=data[i]
    data[i]=temp

def test_init():
    global data
    data=eval(input())

def init_data():
    global data
    for i in range(1000000):
        data.append(random.randint(-100000,100000))

init_data()
time1=clock()
partition_1()
print("单向partition耗时:%f"%(clock()-time1))
time1=clock()
partition_2()
print("双向partition耗时:%f"%(clock()-time1))
#test_init()
#partition_2()
实验结果:
单向partition耗时:0.833947
双向partition耗时:0.277176
很显然,在数据量越来越大的时候,我们的双向Partition与单向的Partition比是非常的高效的

3.Top-k Question:

对于Top-k问题已经被问烂了,解法也是很多的
当然我们优秀的解法有:
O(n*logk)的堆排序实现
BST/SBT二叉查找树,自平衡二叉查找树实现:忽略建树时间的话,我们的而时间复杂度是O(logn)
现在我们完全可以利用Partition函数来进行实现:
对于Partition函数的该操作的实现,很多人又叫这种方法快速选择算法(注意选择之后得到的不是有序的数组只是一个有了划分的数组)
算法的本质是通过每次遍历Partition分组操作之后确定Tok-k的位置所在,然后递归查找所在的那一边区域(相当于是BST或者二分查找的思路)
时间复杂度是O(n)
时间复杂度的推导:
T(n)=O(n)+T(n/2)
...log2n=k次迭代
T(n)=O(n)+O(n/2)...O(1)
T(n)=O(n*(1+1/2+...1/n))=O(2*n-2)

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 1005

using namespace std;

int data[N];
int number;
int k;
int pos=-1;   //最终确定的地址 

void print()
{
	for(int i=0;i<=pos;i++) printf("%d ",data[i]);
	printf("\n");
}

void init_data()
{
	scanf("%d%d",&number,&k);
	for(int i=0;i<number;i++) scanf("%d",&data[i]);
}

void quickfind(int left,int right,int n)
{
	if(left>=right) return ;
	else
	{
		int i=left-1;
		int j=left;
		int var=data[right];
		while(j<right)
		{
			if(data[j]<var)
			{
				i++;
				int temp=data[i];
				data[i]=data[j];
				data[j]=temp;
			}
			j++;
		}
		int temp=data[i+1];
		data[i+1]=data[j];
		data[right]=temp;
		if(i+1-left+1==n)
		{
			pos=i+1;
			return ;
		} 
		else if(i+1-left+1<n) quickfind(i+2,right,n-(i+1-left+1));
		else quickfind(left,i,n);
	}
}

4.Dutch national flag problem

荷兰三色旗问题,这种问题是用Partition的典型例子
定义一个数组序列有红白蓝三种颜色,我们要求尽可能快的对数组进行排序,是的相同颜色的元素相邻,并且整体存在红白蓝额顺序
本题是Partition函数的扩展应用,时间复杂度和空间复杂度处理的非常的优秀
算法详见: Leetcode-75
/*
Partiton函数的作用 
简易模拟 Dutch national flag problem */ 
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 1005

using namespace std;

//0<1<2为例 
int data[N];
int number;

void init_data()
{
	scanf("%d",&number);
	for(int i=0;i<number;i++) scanf("%d",&data[i]);
}

void print()
{
	for(int i=0;i<number;i++) printf("%d ",data[i]);
	printf("\n");
}

void swap(int x,int y,int atom)
{
	int temp;
	if(atom==0)
	{
		temp=data[x];
		data[x]=data[y];
		data[y]=temp;
	}
	else
	{
		temp=data[x];
		data[x]=data[y];
		data[y]=temp;
	}
}

void sortColors() {
    int i=-1;
    int j=0;
    int k=number;
    while(j<k)
    {
        if(data[j]==0)
        {
            i++;
            int temp=data[i];
            data[i]=data[j];
            data[j]=temp;
        }
        else if(data[j]==2)
        {
            k--;
            while(j<k&&data[k]==2) k--;
            int temp=data[j];
            data[j]=data[k];
            data[k]=temp;
            if(data[j]==0)
            {
                i++;
                int temp=data[i];
                data[i]=data[j];
                data[j]=temp;
            }
        }
        j++;
    }
}

int main()
{
	init_data();
	print();
	sortColors();
	print();
	return 0;
}

5总结:

Partition函数真的是非常的强大,我们如果只是了解快排的话真的是没有学透Partition函数的精髓,也没有学到快排的算法的本质

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值