我们在上次的笔记中记录了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)。