快速排序图解

之前我们在交换类排序中引入了冒泡排序,这次引入了另一种交换类排序,叫做快速排序 。快速排序的优点是原地排序,不占用额外空间,时间复杂度为o(nlogn) 。
当然对于快速排序也有缺点 。对于包含大量重复元素的数组排序效率非常低,时间复杂度会降低到o(n^ 2) 。此时,我们需要使用改进的快速排序-双向快速排序 。在双向快速排序的基础上,进一步优化了三向快速排序 。
快速排序
快速排序的基本思想是通过一遍排序将待排序的数据分成两个独立的部分,其中一部分的所有数据都小于另一部分的所有数据,然后按照这种方法分别对这两部分数据进行快速排序,整个排序过程可以递归进行,使整个数据成为有序序列 。
快速排序步骤如下:
1.以第一个元素为分界点,用l指向它 。
2.遍历右边的元素 。在遍历的过程中,我们排列数组,有的小于v,有的大于v,用j指向小于v和大于v的分界点,用i指向当前访问的元素e,此时数组arr[l1…j]v,arr[j1 … i-1] v 。
3.如果ev,直接在大于v的部分后面合并e,然后我继续比较下面的元素 。
4.如果ev,把e移到j指向的元素的后面元素,然后j,然后我继续比较后面的元素 。
5.这样遍历整个数组一次 。遍历后,数组分为三部分,左边部分为v,中间部分为v,右边部分为v 。
6.比较后我们把l指向的元素和j指向的元素进行交换,这样元素v就很快排序了 。v的左边元素小于v,右边元素大于v 。
现在我们用上面的方法快速排序数组[2,1,4,3,7,8,5,6] 。下图显示了快速排序的整个过程:
快速排序代码:
公共静态无效排序(可比[] arr) {
intn=arr.length
sort(arr,0,n1);
}
//递归使用快速排序对arr的范围进行排序[l…r]
私有静态空排序(可比[] arr,intl,intr) {
if(l=r) {
返回;
}
//分区arr[l…r]并返回p,这样arr[l…p-1]arr[p];arr[p1…r] arr[p]
intp=partition(arr,l,r);
排序(arr,l,p1);
sort(arr,p1,r);
}
私有静态int分区(可比[] arr,intl,intr) {
//比较左边的元素用作校准点
可比v=arr[l];
intj=l;
for(inti=l1;i=r;i) {
if(arr[i] 。compareto(v) 0)
swap(arr,j1,i);
j.
}
}
swap(arr,l,j);
返回j;
}
优化快速排序
经过上面的介绍,我们可以发现快速排序并不能保证每个分割都有相同大小的子阵,所以可能一边小一边大 。对于有序数组,快速排序的时间复杂度变成o(n^ 2),相当于树退化成链表 。下图显示了这一变化:
上面,我们使用左边的第一个元素作为校准元素 。现在我们随机选择一个元素作为校准元素 。此时第一次选择第一个元素的概率为1/n,第二个元素为1/n-1 。以此类推,在出现之前退化成链表的概率是1/n(n-1)(n-2)… 。当n较大时,这个概率几乎为零 。
另一个优化是对小规模数组使用插入排序,因为递归会使小规模问题中的方法调用过于频繁,并且插入排序对于小规模数组非常快 。
优化的快速排序代码:
公共静态无效排序(可比[] arr) {
intn=arr.length
sort(arr,0,n1);
}
//贺盛文贺盛文,-什么停止[l.r]阿云阿云阿云阿云阿云阿云阿云
私有静态void排序(相当于[] arr,intl,intr)}
//云娥与云娥,云娥与云娥
如果(rl=15)}
insertonstart 。sort(arr、l、r);
返回;
}
//-什么停止[l.r]阿悦分割区诶诶哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟,阿忠p,你知道吗?僧儿页:1:停止[p1.r]停止[p]