这次,就让我快速地记住快速排序思想吧

4 篇文章 0 订阅
0 篇文章 0 订阅

应用——>排序,什么意思?-------------> 排序是一种应用,算法的应用,本身不是算法

排序的手段——>交换,什么意思?-------> 交换是排序的本质操作

算法思想——>怎么交换,什么意思?-----> 好的算法实现少量的元素交换,不高明的算法实现大量的元素交换

怎么排序 取决于 怎么交换,什么意思?--> 比较一下 冒泡 和 选择排序,你就会体悟了~~~

自己比较么?当然了~我这篇只讲高级一点的快排,而且,美需要用心自己发现。

言归正传......

一组数字 从大到小 进行快速排序 的思想:

给定一个乱序数列,

从中任意选出一个元素【选择的策略:可以随机选,也可以每次选数组的首元素,我们用单词key或者pivot表示】,

通过 算法 实现元素交换【交换思想:比key/pivot大的元素向整个数组的右侧靠拢,比key/pivot小的元素向整个数组的左侧靠拢,所以这个任意选出的元素key/pivot被称之为是枢纽,因为它是小元素与大元素的分界线】

使得最后

Pivot的左边都是比它小的数据,

Pivot的右边都是比它大的数据。

至此,对pivot的左、右数列分别重复上述过程【策略:尾递归】,

最后整个数列有序。

 

不管快速排序的代码最终怎么实现,快排的思想始终不变,都是选定一个枢纽后,通过交换,让大的在其右,小的在其左,之后对枢纽的左右序列重复上述过程,只不过实现的策略因人而已。 

 

下面 听听 我给大家讲讲故事,通过怎么个找枢纽(这是小问题),选定了枢纽后,怎么个交换大小元素(这是大问题),最后让一个无序的数组齐刷刷地摆在你的面前。

 

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

8

9

10

1

3

5

4

7

 

实现快速排序的策略1:

从Arr[9]开始向左找到第1个比Key = 6的数是Arr[8] = 4,把4覆盖到Arr[0]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

8

9

10

1

3

5

4

7

从Arr[0]开始向右找到第1个比Key = 6的数是Arr[2] = 8,把8覆盖到Arr[8]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

8

9

10

1

3

5

8

7

从Arr[8]开始向左找到第1个比Key = 6小的数是Arr[7] = 5,把5覆盖到Arr[2]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

9

10

1

3

5

8

7

从Arr[2]开始向右找到第1个比Key = 6大的数是Arr[3] = 9,把9覆盖到Arr[7]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

9

10

1

3

9

8

7

从Arr[7]开始向左找到第1个比Key = 6小的数是Arr[6] = 3,把3覆盖到Arr[3]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

3

10

1

3

9

8

7

从Arr[3]开始向右找到第1个比Key = 6大的数是Arr[4] = 10,把10覆盖到Arr[6]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

3

10

1

10

9

8

7

从Arr[6]开始向左找到第1个比Key = 6小的数是Arr[5] = 1,把1覆盖到Arr[4]上,

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

3

1

1

10

9

8

7

从Arr[4]开始向右找到第1个比Key = 6大的数:

Arr[4] = 1 < 6

Arr[5],走到这里,整个数组完整遍历一遍,把Key = 6赋值给Arr[5],

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

4

2

5

3

1

6

10

9

8

7

上面的过程被称之partition,也即一趟快排,

因为上面的过程使得小于6的元素都跑到了6的左边,大于6的元素都跑到了6的右边,

也因此使得元素6放在了最终确定的位置上。

Partition过程的时间复杂度是O(n)。

而整个 快排 的时间复杂度是0(n*logn),所以快排共需要logn数量级趟partition。

之后分别对Arr[0]-Arr[4]即{4,2,5,3,1} 和 Arr[6]-Arr[9]即{10,9,8,7}进行同思路的找枢纽 和 partition过程。

所以,整个 快排 是通过 尾递归 实现的。

如下是C代码实现:

#include <stdio.h>

void print_arr(int *arr, int length)
{
    for(int i=0;i<length;i++){
       printf("%d ",arr[i]);
    }
    printf("\n");
}

void sort(int *a, int left, int right)
{
    if(left >= right){
        return;
    }

    int i = left;
    int j = right;
    int key = a[left];

    while(i<j){// 一次此while内部执行,就是一趟partition
        while(i<j && key<=a[j])
            j--;
        a[i] = a[j];
        while(i<j && key>=a[i])
            i++;
        a[j] = a[i];
    }

    a[i] = key;

    print_arr(a,10);

    sort(a,left,i-1);
    sort(a,i+1,right);
}



int main()
{
    int arr[10] = {6,2,8,9,10,1,3,5,4,7};
    print_arr(arr,10);
    printf("*****************************\n");
    sort(arr,0,9);
    printf("*****************************\n");
    print_arr(arr,10);
    printf("*****************************\n");
}
程序的执行结果:

初始数组:6 2 8 9 10 1 3 5 4 7

partition 1st 结果:4 2 5 3 1 6 10 9 8

partition 2nd 结果:1 2 3 4 5 6 10 9 8 7

partition 3rd 结果:同2nd结果

partition 4th 结果:同2nd结果

partition 5th 结果:1 2 3 4 5 6 7 9 8 10

partition 6th 结果:同5th结果

partition 7th 结果:1 2 3 4 5 6 7 8 9 10

还是这个初始数组:

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

8

9

10

1

3

5

4

7

 

实现快速排序的策略2:

随机选中数组中的某个元素作为partition过程的枢纽pivot,比如Arr[2] = 8,

选中后,把这个pivot = Arr[2] = 8 与数组的最后一个元素Arr[9] = 7交换,

即我们自己规定,在每次partition之前把选中的key/partition放在数组的最后位置

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

9

10

1

3

5

4

8

接着,我们自己定义一个 小于等于区间,里面要存放小于等于pivot的数组元素,

它的首地址就是Arr,其实它是Arr的开头部分,我们用Arr本身来维护这个小于等于区间

现在它的初始长度为0,里面什么也没有,

我们从Arr[0]开始向后遍历数组

Arr[0] = 6 < (pivot = 8),需要加入到小于等于区间中,而 小于等于区间 现在长度为0,后面的元素就是Arr[0],

所以把Arr[0]本身和本身进行交换,

同时小于等于区间长度增1,即为1,小于等于区间现在就是Arr的第一个元素;

Arr[1] = 2 < (pivot = 2),小于等于区间 后面的元素就是Arr[1],

所以把Arr[1]本身和本身进行交换,

同时小于等于区间长度增1,为2,小于等于区间现在就是Arr的前两个元素;

Arr[2] = 7 < (pivot = 2),小于等于区间 后面的元素就是Arr[2],

所以把Arr[2]本身和本身进行交换,

同时小于等于区间长度增1,为3,小于等于区间现在就是Arr的前三个元素;

Arr[3] = 9 > (pivot = 2),继续向后遍历;

Arr[4] = 10 > (pivot = 2),继续向后遍历;

Arr[5] = 1 < (pivot = 2),小于等于区间 后面的元素是Arr[3],

把Arr[5]与Arr[3]进行交换,

同时小于等于区间长度增1,为4,

现在Arr为

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

1

10

9

3

5

4

8

而 小于等于区间 为其 前4个元素;

Arr[6] = 3 < (pivot = 2),小于等于区间 后面的元素是Arr[4],

把Arr[6]与Arr[4]进行交换,

同时小于等于区间长度增1,为5,

现在Arr为

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

1

3

9

10

5

4

8

而小于等于区间 为其 前5个元素;

之后,我简言之,

Arr[7] = 5 < pivot,交换Arr[7]与Arr[5];

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

1

3

5

10

9

4

8

 

Arr[8] = 4 < pivot,交换Arr[8]与Arr[6]

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

1

3

5

4

9

10

8

 

Arr[9] = 8 = pivot,也交换Arr[7]与Arr[9]

数组Arr下标

0

1

2

3

4

5

6

7

8

9

对应元素

6

2

7

1

3

5

4

8

10

9

 

至此数组一趟遍历完毕,也完成了一次Partition,

可以发现小于等于区间的最后一个元素一定是pivot

枢纽8位于其应在在位置Arr[7],

之后对Arr[0]-Arr[6] 和 Arr[8]-Arr[9]进行相同的操作,通过尾递归实现。

如下是java代码实现:

说明:Math.random()返回带正号的double值,范围在[0.0, 1.0)

	public static void quickSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	public static void process(int[] arr, int left, int right) {
		if (left < right) {
			int random = left + (int) (Math.random() * (right - left + 1));
			swap(arr, random, right);
			int mid = partition(arr, left, right);
			process(arr, left, mid - 1);
			process(arr, mid + 1, right);
		}
	}

	public static int partition(int[] arr, int left, int right) {
		int pivot = left - 1;
		int index = left;
		while (index <= right) {
			if (arr[index] <= arr[right]) {
				swap(arr, ++pivot, index);
			}
			index++;
		}
		return pivot;
	}

	public static void swap(int[] arr, int index1, int index2) {
		int tmp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = tmp;
	}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值