算法学习笔记4-经典快速排序/随机排序

我们在上次的笔记中记录了patition算法,今天我们来进入今天的正题-快速排序

经典快速排序

我们在上次的笔记中记录了patition算法,今天我们来进入今天的正题-
快速排序每次操作只对其中的一个元素进行操作,
我们来看一个示例:
在这里插入图片描述

算法思路:

从图中我们可以看到:left指针,right指针,base参照数。其实思想是蛮简单的,就是通过第一遍的遍历(让left和right指针重合)来找到数组的切割点。

第一步:首先我们从数组的left位置取出该数(20)作为基准(base)参照物。

第二步:从数组的right位置向前找,一直找到比(base)小的数,如果找到,将此数赋给left位置(也就是将 10赋给20)此时数组为:10,40,50,10,60,left和right指针分别为前后的10。

第三步:从数组的left位置向后找,一直找到比(base)大的数,如果找到,将此数赋给right的位置(也就是40赋给10),此时数组为:10,40,50,40,60,left和right指针分别为前后的40。

第四步:重复“第二,第三“步骤,直到left和right指针重合,最后将(base)插入到40的位置,此时数组值为:10,20,50,40,60,至此完成一次排序。

第五步:此时20已经潜入到数组的内部,20的左侧一组数都比20小,20的右侧作为一组数都比20大,
以20为切入点对左右两边数按照"第一,第二,第三,第四"步骤进行,最终快排大功告成。

代码实现如下:

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

void  swap(int a[],int i,int j);

int partition(int a[],int p,int r);

void quicksort(int a[],int p,int r);

void swap(int a[], int i, int j)
{
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}


int partition(int a[], int p, int r)
{
    int i = p;//左半部分的第一个下标 
    int j = r + 1;//右半部分的第一个下标 
    int x = a[p];//第一个元素 
    
    //whille循环功能是从左到右找更大的,从右到左找更小的。 
    
    while(1){
    	//检查数组是否有序,如果无序,则i和j分别存储前面比a[p]大,后面比a[p]小的元素的值 
        while(i<r && a[++i]<x);
        while(a[--j]>x);
        
        //退出条件,当后指针跑到了前指针的前面退出 
        if(i>=j) break;
        
        //交换值,把a[i]和a[j]的元素进行交换 
        swap(a,i,j);
    
    }
    
    //x到现在还是第一个元素的位置,x是标尺 
    swap(a,j,p);

    return j;
}


void quicksort(int a[], int p, int r)
{
    if(p<r){
        int q = partition(a,p,r);
        quicksort(a,p,q-1);
        quicksort(a,q+1,r);
    }
}



int main()
{
    int i;
    int a[] = {5,13,6,24,2,8,19,27,6,12,1,17};
     int lenght= sizeof(a)/ sizeof(int );
    for(i=0; i<lenght; i++) printf("%d ", a[i]);
    printf("\n");
    quicksort(a, 0, lenght-1);

    for(i=0; i<lenght; i++) printf("%d ", a[i]);
    printf("\n");

    return 0;
}

java代码如下:

package sort;


/**
 * The type Quick sort.
 *
 * @author user
 */
public class QuickSort {
    /**
     * Generate array int [ ].
     *
     * @return the int [ ]
     */
    public static int[] generateArray() {
        int maxValue = 123,minValue =1,randomMaximum=11, randomMinimum =-2;
        int length = (int) (Math.random() * maxValue)+minValue;
        int[] result = new int[length];
        for (int i = 0; i != length; i++) {
            result[i] = (int) (Math.random() * randomMaximum) + randomMinimum;
        }
        return result;
    }

    /**
     * Disorder boolean.
     *
     * @param args the args
     * @return the boolean
     */
    public static boolean disorder(int[] args) {
        for (int i = 0; i < args.length-1; i++) {
            if (args[i]>args[i+1]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Quick sort.
     *
     * @param a     the a
     * @param left  the left
     * @param right the right
     */
    public static void quickSort(int[] a,int left,int right) {
        if(left<right) {
            int temp=qSort(a,left,right);
            quickSort(a,left,temp-1);
            quickSort(a,temp+1,right);
        }
    }

    /**
     * Q sort int.
     *
     * @param a     the a
     * @param left  the left
     * @param right the right
     * @return the int
     */
    public static int qSort(int[] a,int left,int right) {
        //定义基准数,默认为数组的第一个元素
        int temp=a[left];
        //循环执行的条件
        while(left<right) {
            //因为默认的基准数是在最左边,所以首先从右边开始比较进入while循环的判断条件
            //如果当前arr[right]比基准数大,则直接将右指针左移一位,当然还要保证left<right
            while(left<right && a[right]>temp) {
                right--;
            }
            //跳出循环说明当前的arr[right]比基准数要小,那么直接将当前数移动到基准数所在的位置,并且左指针向右移一位(left++)
            //这时当前数(arr[right])所在的位置空出,需要从左边找一个比基准数大的数来填充。
            if(left<right) {
                a[left++]=a[right];
            }
            //下面的步骤是为了在左边找到比基准数大的数填充到right的位置。
            //因为现在需要填充的位置在右边,所以左边的指针移动,如果arr[left]小于或者等于基准数,则直接将左指针右移一位
            while(left<right && a[left]<=temp) {
                left++;
            }
            //跳出上一个循环说明当前的arr[left]的值大于基准数,需要将该值填充到右边空出的位置,然后当前位置空出。
            if(left<right) {
                a[right--]=a[left];
            }
        }
        //当循环结束说明左指针和右指针已经相遇。并且相遇的位置是一个空出的位置,
        //这时候将基准数填入该位置,并返回该位置的下标,为分区做准备。
        a[left]=temp;
        return left;
    }

    /**
     * The entry point of application.
     *
     * @param args the input arguments
     */
    public static void main(String[] args) {
        int testTime = 10000;
        boolean inorder= true;
        for (int i = 0; i <testTime; i++) {
            int[] array = generateArray();
            quickSort(array,0, array.length-1);
            if (!disorder(array)) {
                inorder = false;
                break;
            }
        }
        if (inorder) {
            System.out.println("666666");
        }else {
            System.out.println("333333");
        }
    }
}

算法完成l,是不是还是有点蒙呢,那不妨等下来后再去思考一下这个算法吧。如果还是不太明白的话,请参照链接

分析:
划分出来的区域可能规模不同,跟数据状况有关。算法不算稳定。
划分的规模相同,正常情况下

O(logN).

划分的规模不同,有时会是

O(n^2)

综上所述:经典的快排和数据情况有关,那请我们来了解一下,下面的另一种算法:

随机快排

在数组中随机选取一个值把他和最后一个位置上的数进行交换,然后拿这个随机的数去做划分。

这样的话,你就不能说轻易的找出最差的情况了,那么当然仍然存在两种情况

划分点仍然打的很偏,左右两部分的数据还是很不一致。

打到了中间的位置,划分的两部分是相同的。

但是无论哪种情况,都是一种概率情况,那复杂度就成了一个概率事件。只能用长期期望的形式算出最后的概率表达式。
长期期望值为

O(N*logN)

#include <iostream>
#include<stdlib.h>
#include<time.h>

#define NUMBER 500
using namespace std;

//两数交换
void exchange(int &a,int &b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}
//p表示将数组A排序的起始下标,r是结束下标
int random_patition(int *A,int p,int r)
{
    int temp;
    int i=p-1;

    //产生随机数组下标
    int k= p + rand()%(r -p +1);

    //仍然将随机的枢轴交换到最后
    exchange(A[r],A[k]);
    temp=A[r];
    for(int j=p;j<=r-1;j++)
    {
        if(A[j]<=temp)//保证左边的值永远比temp小 
        {
        	//如果是比第i项大,好,去交换值,这样据保证了左半部分一定是有顺序的,而右面却没有 
            i=i+1;
            exchange(A[i],A[j]);
        }
    }

    //最后主元交换,因为当结束后所有数据都有序了,但是第i+项和末尾项应该位置是相反的,及最后的那一项即为所求项。 
    exchange(A[i+1],A[r]);

    return i+1;
}

//递归调用
void QuickSort(int *A,int p,int q)
{
    if(p<q){
        int r = random_patition(A, p, q);
        QuickSort(A, p, r-1);
        QuickSort(A, r+1, q);
    }

}

int main(void)
{
    srand((unsigned)time(NULL));
    clock_t begin_time,end_time;
    int *p;
    p=new int[NUMBER];
    for(int k=0;k<NUMBER;k++)
    {
        p[k]=rand()%NUMBER;
       //cout << p[k]<<" ";
    }
    cout<<endl;
    cout<<endl;
    begin_time=clock();
    QuickSort(p,0,NUMBER-1);
    end_time=clock();

    for(int i=0;i<NUMBER;i++)
    {
      cout<<p[i]<<" ";
    }
    //cout<<endl<<endl<<end_time-begin_time<<" ms"<<endl;
	return 0;
}

java代码如下

package sort;

import java.util.Random;

/**
 * The type Random quick sort.
 *
 * @author user
 */
public class RandomQuickSort {


    /**
     * Random quick sort.
     *
     * @param arr   the arr
     * @param left  the left
     * @param right the right
     */
    public static void randomQuickSort(int[] arr,int left,int right) {
        if(left < right) {
            int p = randomPartition(arr, left, right);
            randomQuickSort(arr,left,p-1);
            randomQuickSort(arr, p+1, right);
        }
    }

    /**
     * Random partition int.
     *
     * @param arr   the arr
     * @param left  the left
     * @param right the right
     * @return the int
     */
    public  static int randomPartition(int[] arr, int left, int right) {
        int x= new Random().nextInt(right - left + 1) + left;
        swap(arr, x, right);
        return partition(arr, left, right);
    }

    /**
     * Partition int.
     *
     * @param arr   the arr
     * @param left  the left
     * @param right the right
     * @return the int
     */
    public  static int partition(int[] arr, int left, int right) {
        int x = arr[right],p = left - 1;
        for(int i = left ; i < right;i++) {
            if(arr[i] <= x) {
                p++;
                swap(arr,p,i);
            }
        }
        swap(arr, right, p + 1);
        return p+1;

    }

    /**
     * Swap.
     *
     * @param arr   the arr
     * @param left  the left
     * @param right the right
     */
    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }


    /**
     * Disorder boolean.
     *
     * @param args the args
     * @return the boolean
     */
    public static boolean disorder(int[] args) {
        for (int i = 0; i < args.length-1; i++) {
            if (args[i]>args[i+1]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Generate array int [ ].
     *
     * @return the int [ ]
     */
    public static int[] generateArray() {
        int maxValue = 123,minValue =1,randomMaximum=11, randomMinimum =-2;
        int length = (int) (Math.random() * maxValue)+minValue;
        int[] result = new int[length];
        for (int i = 0; i != length; i++) {
            result[i] = (int) (Math.random() * randomMaximum) + randomMinimum;
        }
        return result;
    }

    /**
     * The entry point of application.
     *
     * @param args the input arguments
     */
    public static void main(String[] args) {
        int testTime = 10000;
        boolean inorder= true;
        for (int i = 0; i <testTime; i++) {
            int[] array = generateArray();
            randomQuickSort(array,0,array.length-1);
            if (!disorder(array)) {
                inorder = false;
                break;
            }
        }
        if (inorder) {
            System.out.println("666666");
        }else {
            System.out.println("333333");
        }
    }
}

随机快排的问题较难,希望今后可以完全掌握把。
在算法研究过程中面临不可控的数据样本两种做法:

1.不可控的数据样本—随机,打乱数据。
2.不可控的数据样本–哈希表。

在工程中算法的时间复杂度都为O(N*logN),随机排序是最快的排序算法。随机快排是最长用的快排方式。
,而归并排序输在了要去准备一个辅助数组。

额外空间复杂度为:O(logN)//长期期望是O(logN);

那它的额外空间复杂度又为什么是O(logN)呢。

在程序的信息存储中需要存储断点<划分点>,期盼过程中我们把断点达到中间位置上,在循环过程中共要打logN个断点,所以,及在二分过程中你要分成多少次,你就要几个断点。
在最差的情况下你的复杂度是O(N)

假设例子:
a数组中元素是

1,2,3,4,5,6,7

.
当断点打到了6,它的复杂度就变成log(N)。及为最差情况。所以使用额外空间复杂度又是一个概率,常期期望是log(N)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值