算法设计与分析_快速排序
问题
如果待排序的元素中,有比较多重复的元素。例如:有50万个待排序的随机整数,但是其中互不相同的整数仅有500个。
请改进标准快速排序的算法,使得当有大量重复元素时,提高快排的效率。
问题分析与改进思路
(写清对该问题的分析,通过分析得到改进思路)
1. 问题分析
① 标准快速排序的设计思想:选定一个元素作为标准,也就是基准元素,作为划分的依据,一般选定数组最后或第一个元素作为基准元素,把数组进行划分,划分为小于基准元素的部分和大于基准元素的部分,把基准元素放在应该在的正确位置,然后对基准元素的左边部分和右边部分递归执行相同的操作。
② 标准快速排序在排序中的缺点及:
1.partition函数(划分函数)在顺序数组和逆序数组的表现很差:首先,拆分的子问题的规模只比原来减少了1个元素。
2.每次划分只能确定一个元素的位置。
3.在顺序数组和逆序数组上,导致递归树高度增加,非常不平衡,递归树倾斜,如果基准元素不再选用最开头或最末尾元素,而是随机择数组中的元素,这个问题大致可以解决。
4.在顺序数组和逆序数组上,时间复杂度退化,此时时间复杂度为O(N2)
5.在数组中有许多元素元素值相同时,基准元素会将与其相等的其他元素划分带大于基准元素部分或小于基准元素部分,会相对拖慢程序运行时间,如果算法能在一次划分中,确定基准元素的同时,也确定与其相等的元素位置,就可以大大减少运行时间,本问题就是如此。
2. 改进思路
① 明确需要解决的问题1:解决标准快速排序中在待排序数组中有大量重复元素时,排需速度变慢的问题。
② 解决问题的大体规划:在划分后将数组划分为三个部分,即小于基准元素部分,等于基准元素部分,大于基准元素部分,这样就将所有与基准元素相等的元素位置确定,递归时只需要递归小于基准元素区间和大于基准元素区间。
③ 明确需要解决的问题2:解决在顺序数组和逆序数组上,导致递归树高度增加,非常不平衡,递归树倾斜的问题。
④ 解决问题的大体规划:通过将数组中随机一个元素作为基准元素,以打破其顺序性,使得划分后递归树相对平衡。
实验设计
(如何验证你的思路,至少应包括:数据的产生过程、代码实现)
1. 数据的产生过程
(1)快速排序-三路快排涉及基本元素:
num : 数组
randindex: 生成一个范围从left到right的随机下标
flag: 基准值(数值)
le: le表示等于flag的区间中最左边元素下标
re: ge表示大于flag的区间的前一个下标
l: 数组首位置
r: 数据尾位置
(2)划分后三个分区范围定义:
小于基准元素分区:[l ,le-1]
中分区:[lt,ge]
右分区:[gt+1,r]
(3)quicksort和partition代码实现设计:
private static void quicksort(int[] num, int l, int r) {
以l<r递归条件
调用partition(num,l,r)划分函数,可确定所有等于基准元素的位置,并划分为小于、等于、大于基准元素部分。
3.quicksort(num,l,le-1);///和小于基准值区间的元素在进行三路快排
4.quicksort(num,ge+1,r);//和大于基准值区间的元素在进行三路快排
}
private static void partition(int[] num, int l, int r)
{ 分区逻辑:
以flag=num[l]为基准元素
遍历索引i=l+1 、退出条件 i>gt
如果num[i]严格小于flag,就将该元素与等于flag元素区间的最左边元素交换
如果num[i]等于flag,自动放进中间数组,即不发生改变,继续考察下一个未遍历元素
如果num[i]严格大于flag,就将num[i]d的值与大于flag区间的前一个元素交换
}
(算法实现语言不限,但应写出完整程序或核心代码。代码排版请参考一下模板)
2.代码实现:
package monday;
import java.util.Random;//随机数库
import java.util.Scanner;//输入库
public class betterQuicksort {
static int le=0;//le表示等于flag的区间中最左边元素下标
static int ge=0;//ge表示大于flag的区间的前一个下标
public static void main(String[] args) {
int l,r,i,n;//l为待排序数组最左段下标,r为待排数组最右段下标,i为循环变量,n为待排数字个数
System.out.println("请输入需要排序的元素的个数:");
Scanner in=new Scanner(System.in);
n=in.nextInt();
l=0;r=n-1;//初始化l,r.将数组左右两端下标赋值给l,r;
int[] num=new int[n+5];//定义一个足够大的数组
System.out.println("请依次输入需要排序的元素:");
for(i=0;i<n;i++)//将待排序数存入数组
{
num[i]=in.nextInt();
}
quicksort(num,l,r);//调用改进后的快速排序函数,将本问题划分为同样的多个小问题
System.out.println("经过排序后的元素序列为:");
print_num(num,n);//输出排序后数列;
}
//打印数组函数
private static void print_num(int[] num, int n) {
// TODO Auto-generated method stub
int i;
for(i=0;i<n;i++)
System.out.print(num[i]+" ");
System.out.print("\n");
}
//以下为快速排序函数
private static void quicksort(int[] num, int l, int r) {
// TODO Auto-generated method stub
if(l<r)//递归条件
{
partition(num,l,r);//划分函数,可确定所有等于基准元素的位置,并划分为小于、等于、大于基准元素部分。
quicksort(num,l,le-1);//递归小于基准元素的部分
quicksort(num,ge+1,r);//递归大于基准元素的部分
}
}
//下面函数的作用为划分,左部分放<基准元素的值,中间位置放等于基准元素的值,右部分放>基准元素的值
private static void partition(int[] num, int l, int r) {
// TODO Auto-generated method stub
Random random=new Random();//生成随机数
int randindex=l+random.nextInt(r-l);//生成一个范围从left到right的随机下标
swap(num,l,randindex);//将生成的随机下标的值和最左边元素交换,作为基准元素
int flag=num[l];
le=l+1;ge=r;//le表示等于flag的区间中最左边元素下标,ge表示大于flag的区间的前一个下标
int i=l+1;//从基准元素的后一个开始遍历
while(i<=ge)//当i>ge时所有元素已遍历完成
{
if(num[i]<flag)//如果num[i]严格小于flag,就将该元素与等于flag元素区间的最左边元素交换
{
swap(num,le,i);
le++;//等于flag的区间中最左边元素下标发生改变加1;
i++;//考察下一个未遍历元素
}
else if(num[i]==flag)//如果num[i]等于flag,自动放进中间数组,即不发生改变,继续考察下一个未遍历元素
{
i++;//考察下一个未遍历元素
}
else//如果num[i]严格大于flag,就将num[i]d的值与大于flag区间的前一个元素交换
{
swap(num,i,ge);
ge--;//大于flag的元素加一,ge往前移;
}
}
swap(num,l,le-1);//将基准元素插入合适位置 ,即与最后一个小于flag的元素位置交换
le--;//通过自减保持lt表示等于flag的区间中最左边元素下标
}
//两元素交换函数
private static void swap(int[] num, int i, int j) {
// TODO Auto-generated method stub
int t;
t=num[i];
num[i]=num[j];
num[j]=t;
}
}
实验过程与结果
(如何验证改进后代码的效果,至少应该包括改进前后的对比数据,例如程序运行时间等,以验证理论分析)