在我们的数据结构和算法中,快速排序是我们熟悉的经典的排序算法之一。但是可能对于初学者而言,这个算法有点让人云里雾里不知如何理解。下面笔者会详细地讲解这个排序算法,给出具体的算法,图解过程以及数据结构算法的实现。
首先,我们先了解一些快速排序的基本知识:快速排序(Quicksort)是对冒泡排序的一种改进.快速排序由C. A. R. Hoare在1960年提出。
它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序的具体算法步骤(引自百度百科):
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
从这个快速排序的方法中,我们可以看到快速排序的方法涉及到对要排序的数组的“一分为二“的思想,这个折半查找法的思想有点类似,感兴趣的读者可以点此链接转到笔者先前写的折半查找法的博客:二分法查找的算法图解和代码实现
。
例如:
假设待排序的序列为:{arr[s],arr[s+1],arr[s+2]…arr[s+n-1],arr[s+n]},那么我们首先选取任意一个记录作为枢纽,通常选择第一个记录arr[s]为枢纽,然后开始按照下面的规则开始排序:
(1)将所有关键字和这个记录的枢纽比较,比枢纽的值小的元素移动到这个枢纽的位置之前,比枢纽大的元素移动到枢纽的位置在之后。然后这个枢纽的所落在的位置index就是我们要找的分界线,这个分界线index将这个序列分成两部分{arr[t],arr[t+1],…arr[t+index-1]}和{arr[t+index+1},arr[t+index+2},arr[t+index+3]…},那么这个过程就称作一趟快速排序,也叫一次划分。
一趟快速排序的具体做法:
(1)使用两个指针low和high,他们的 初始值分别为low和high,假设枢纽的关键为key=arr[low],这个关键字可以是arr[low]到arr[high]中的任意一个元素,这里选择了arr[low]为关键字。
(2)首先从high所指向的位置从右边开始向左扫描移动,每移动一次high就自减1,在移动的过程中将每个元素arr[high]和关键字key比较,找到第一个比关键字key小的元素arr[high],并将该元素移动到low的位置,即arr[low] =arr[high]。
(3)然后从low所指的位置开始从左往右开始扫描移动,每移动一次low的值就自增1,在移动的过程中,将arr[low]和关键字key比较,找到第一个比关键字大的arr[low],然后将arr[low]移动到arr[high]的位置,即arr[high]=arr[low],重复(2)和(3)这两个步骤直到low=high为止,得到分界线index=low。
下面开始用图解举例子来解析上面的(1)(2)(3)这三个步骤:
有数组:arr={4,2,1,5,7,6,9,8,3}它的初始数组为:
步骤(1):
low=0;
high=8;
key=arr[low]=4;
步骤(2):
high开始从high=8开始从左往右扫描移动,arr[8]=3<key=4,找到第一个比关键字key小的元素,开始将这个arr[8]移动到arr[low]的位置,即arr[low] =arr[high]=3;
步骤(3):
low从low=0开始从左往右扫描移动,arr[0]=key=4,则low++继续向右扫描;
arr[1]=2<key=4,则low++继续向右扫描;
arr[2]=1<key=4,则low++继续向右扫描;
arr[3]=5>key=4,找到第一个比关键字key大的元素arr[3],则将arr[3]移动到high的位置上,而此时high=8,即arr[high]=arr[low]=arr[3]=5;
步骤(2)(3)执行完后,此时low=3,high=8,low<high,则继续循环执行(2)和(3)这两个步骤。
步骤(2):
high=8开始从右往左扫描移动,arr[8]=5>key=4,则继续向左移动,high–;
arr[7]=8>key=4,则继续向左移动,high–;
arr[6]=9>key=4,则继续向左移动,high–;
arr[5]=6>key=4,则继续向左移动,high–;
arr[4]=7>key=4,则继续向左移动,high–,此时high=3,而low=3,所以low=high,此时结束步骤(2)和(3)的操作,并将枢轴关键字key记录到对应的low的位置,即arr[low]=key=4;则第一趟快速排序结束,并且得到关键字的分界线index=low=3;
这时数组还未顺序排序,所以要继续进行快速排序。第二趟快速排序:
inde=3将数组一分为二得到:arr1={3,2,1}和arr2={7,6,9,8,5},这时就得到了两个数组(注意,我们只是在逻辑上将原来的数组分成两个区域来进行操作,并不是真正地将原来的数组分为两个独立的数组,实际上是在同一个数组里面操作,只不过两个部分区域是可以同时进行罢了):
我们依次对这两个数组进行和上面第一趟排序的操作即可。
数组arr1从low=0,high=index-1=2开始快速排序,而arr2从low=index+1=4,high=8开始快速排序。
对于左边部分:
步骤(2):
high从high=2开始从右往左扫描,arr[2]=1<key=3,找到第一个比关键字key小的元素arr[2],则arr[low]=arr[0]=arr[2]=1;
步骤(3):
low从low=0开始从左往右开始扫描移动,arr[0]=1<key=3,则low继续向右移动,low++;
arr[1]=2<key=3,则low继续向右移动,low++,此时low=3=high,跳出步骤(2)(3)的循环,并将枢轴关键字key记录到对应的low的位置,即arr[low]=key=3,并且得到index=low=3这个分界线;
然后对右边部分进行快速排序,其步骤和上面的步骤一致。
key=7,low =4,high=8
步骤(2):
high从high=8开始从右往左扫描,arr[8]=5<key=7,找到第一个比关键字key小的元素arr[8],则arr[low]=arr[4]=arr[8]=5;
步骤(3):
low从low=4开始从左往右开始扫描移动,arr[4]=5<key=7,则low继续向右移动,low++;
arr[5]=6<key=7,则low继续向右移动;
arr[6]=9>key=7,此时找到第一个比关键字key大的值,则移动arr[9]到arr[high]的位置,即arr[high]=arr[6]=9;
此时low=6,high=8,low<high,继续循环步骤(2)和(3);
步骤(2):
high从high=8开始从右往左扫描,arr[8]=9>key=7,则high继续向左扫描移动,high–;
arr[7]=8>key=7,则high继续向左扫描移动,high–此时high=6=low,则跳出步骤(2)(3)的循环,并将枢轴关键字key记录到对应的low的位置,即arr[low]=arr[6]=key=7,并且得到index=low=6这个分界线;
到此,我们发现现在这个数组已经依次递增,快速排序已经得到了最终的排序结果。由此可以看到只需要两趟的快速排序即可将这无序的数组变成有序递增的数组。
经过图解,相信读者对这个快速排序的基本思路和算法已经有了更深刻的认识。
下面是具体代码是实现:
首先是获得分界索引的代码的实现;
int PartIndex(int arr[],int low,int high){//枢轴记录关键字,也可以充当第一个记录的枢轴记录int key = arr[low]; while (low < high){//从右往左扫描移动寻找到第一个比关键字小的元素的索引while (low<high&&key<=arr[high]){high--;}//将找到的比关键字小的元素移动到low的位置arr[low]arr[low] = arr[high];//从左往右扫描移动寻找到第一个比关键字大的元素的索引while (low < high && key >= arr[low]){low++;}//将找到的比关键字大的元素移动到high的位置arr[high]arr[high] = arr[low];}//枢轴记录到位arr[low] = key;//返回枢轴位置的索引,也就是放回分界线索引return low;}
开始快速排序的代码:
void QSort(int arr[],int low,int high){//当low小于hig时开始递归if (low<high){//获取分界线int index = PartIndex(arr,low,high);//递归排序分界线左侧的区域QSort(arr,low,index-1);//递归排序分界线右侧的区域QSort(arr,index+1,high);}}
测试函数:
int main(){int arr[] = {4,2,1,5,7,6,9,8,3};QSort(arr,0,8);cout << "快速排序后的结果:";for (int i = 0; i < 9; i++){cout << arr[i] << " ";}system("pause");return 0;}
输出结果:
下面给出完整的代码(在VS专业版上测试,直接复制可运行):
#include<iostream>using namespace std;int PartIndex(int arr[],int low,int high){//枢轴记录关键字,也可以充当第一个记录的枢轴记录int key = arr[low]; while (low < high){//从右往左扫描移动寻找到第一个比关键字小的元素的索引while (low<high&&key<=arr[high]){high--;}//将找到的比关键字小的元素移动到low的位置arr[low]arr[low] = arr[high];//从左往右扫描移动寻找到第一个比关键字小的元素的索引while (low < high && key >= arr[low]){low++;}//将找到的比关键字大的元素移动到high的位置arr[high]arr[high] = arr[low];}//枢轴记录到位arr[low] = key;//返回枢轴位置的索引,也就是放回分界线索引return low;}void QSort(int arr[],int low,int high){//当low小于hig时开始递归if (low<high){//获取分界线int index = PartIndex(arr,low,high);//递归排序分界线左侧的区域QSort(arr,low,index-1);//递归排序分界线右侧的区域QSort(arr,index+1,high);}}int main(){int arr[] = {4,2,1,5,7,6,9,8,3};QSort(arr,0,8);cout << "快速排序后的结果:";for (int i = 0; i < 9; i++){cout << arr[i] << " ";}system("pause");return 0;}
通过我们对这个快速排序的分析,可以知道,快速排序的平均时间复杂度为
最坏的时间复杂度为O(n^2),空间复杂度为:
。
好了,到此对快速排序的讲解到此结束了,希望能对读者有用,也欢迎大家斧正。
如果觉得《快速排序的算法实现和图解》对你有帮助,请点赞、收藏,并留下你的观点哦!