快速排序和归并排序一样,都使用了分治思想,下面是对一个子数组A[p,r]进行快速排序的三步分治过程:
分解:数组A通过PARTITION()算法分区为两个(可能为空)子数组A[p...q-1]和A[q+1,r]使得A[p..q-1中的每一个元素都小于(小于等于)A[q],而A[q]也小于等于(小于)A[q+1...r]中的每一个元素。
解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1,r]进行PARTITION()
合并:因为子数组都是原址排序的,所以不需要合并操作,数组A[p...r]已经有序。
快速排序需要选择一个主元(就是上面的A[q])来充当划分的界限,这里有三种方法进行PARTITION()操作:
1.前后指针法
2.左右指针法
3.挖坑法
一,前后指针法(指针非真指针,这里是使用下标)
我们选第一个元素作主元,也可以选最后一个,代码会与下面有些点点改变,但思想不变
伪代码:
PARTITION1(A,p,r)
x = A[p] //选取A[p]作为主元
i = p
for j = p+1 to r
if A[j] <= x //找到小于等于主元的元素
i = i +1
exchange A[i] with A[j] //交换下标为 i 和 j 的元素
exchange A[i] with A[p]
return i
二,左右指针法:
取头尾位置作为主元(分区后主元左边的元素都比其小(或相等),右边的元素都比其大(或相等)),这里我们取头位置作为主元
伪代码:
PARTITION2(A,p,r)
x = A[p] //选取A[p]作为主元
i = p
j = r
while( i != j )
while(A[ j ] > x&&j!=i) //找小,遇到大的就继续
j = j - 1
while(A[ i ] < = x &&j!=i) //找大,遇到小的就继续
i = i +1
exchange A[i] with A[j] //交换下标为 i 和 j 的元素
exchange A[i] with A[p]
return i
左右指针法有些要注意的方面,就是我们是先找大,还是先找小。这根据我们所选主元的位置有关,我们取第一个元素作主元,假设进行升序排,我们让 i 从前往后找 大于主元的元素,找到之后,让 j 从后往前找 小于等于主元的元素,找到了 就交换A[ i ] 和A [ j ] ,这样进行了几轮交换之后,开始了最一轮,先找大的,i 从前往后找,找着找着和 j 相遇了 ,此时应结束循环,将主元归位 exchange A[i] with A[p],此时问题出现了,j 指针所指的元素是大于主元的,主元又位于第一个元素位置,交换之后,主元左边的元素就不是都小于等于主元。
看图:先找大
SO 我们先找小
看图:
以最后一个元素作主元,思路也是相似的,要诀定好是先找大,还是先找小
三,挖坑法:我们取第一个元素作为主元,也可以取最后一个,思想一样
挖坑法顾名思义就是挖坑
伪代码:
PARTITION2(A,p,r)
x = A[p] //选取A[p]作为主元
t = p //坑的下标
i = p+1
j = r
while( i != j )
while(A[ j ] > x&&i !=j ) //找小于等于主元的元素
j = j - 1
exchange A[ t ] with A[ j ] //找到后填入坑中
t = j //填入元素之前的下标变为新坑
while(A[ i ] <= x &&i !=j ) //找大于主元的元素
i = i +1
exchange A[ t ] with A[ i ] //填入新坑中
t = i //填入的元素又成为新坑
exchange A[ t ] with x // 最后将主元填入坑中
return t
、
四,实现快速排序
伪代码:
QUICKSORT(A, p , r )
if p < r
q = PARTITION( A, p , r)
QUICKSORT(A, p, q-1)
QUICKSORT(A, q+1, r)
我使用的是挖坑法,看图:
五,代码实现
#include<iostream>
using namespace std;
//前后指针法
int PARTITION1(int A[], int p, int r)
{
int x = A[p];
int i = p;
for (int j = p + 1; j <= r; ++j)
{
if (A[j] <= x)
{
++i;
int temp = A[j];
A[j] = A[i];
A[i] = temp;
}
}
A[p] = A[i];
A[i] = x;
return i;
}
//使用前后指针法的快速排序
void QUICKSORT1(int A[], int p, int r)
{
if (p <r)
{
int q = PARTITION1(A, p, r);
QUICKSORT1(A, p, q - 1);
QUICKSORT1(A, q+1,r);
}
}
//左右指针法
int PARTITION2(int A[], int p, int r)
{
int x = A[p];
int i = p;
int j = r;
while (i != j)
{
while (A[j] > x&&i!=j)
{
--j;
}
while (A[i] <= x&&i!=j)
{
++i;
}
int temp = A[j];
A[j] = A[i];
A[i] = temp;
}
A[p] = A[i];
A[i] = x;
return i;
}
//使用左右指针法的快速排序
void QUICKSORT2(int A[], int p, int r)
{
if (p < r)
{
int q = PARTITION2(A, p, r);
QUICKSORT2(A, p, q - 1);
QUICKSORT2(A, q + 1, r);
}
}
//挖坑法
int PARTITION3(int A[], int p, int r)
{
int x = A[p];
int t = p;
int i = p;
int j = r;
while (i != j)
{
while (A[j] > x && i != j)
{
--j;
}
A[t] = A[j];
t = j;
while (A[i] <= x && i != j)
{
++i;
}
A[t] = A[i];
t = i;
}
A[t] = x;
return t;
}
//使用挖坑法的快速排序
void QUICKSORT3(int A[], int p, int r)
{
if (p < r)
{
int q = PARTITION3(A, p, r);
QUICKSORT3(A, p, q - 1);
QUICKSORT3(A, q + 1, r);
}
}
//打印
void Print(int A[], int size)
{
for (int i = 0; i < size; ++i)
{
cout << A[i] << " ";
}
cout << endl;
}
int main()
{
int A[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int B[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int C[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int size = sizeof(A) / sizeof(int);
cout << "要排序的的数组为:" << endl;
Print(A, size);
QUICKSORT1(A, 0, size - 1);
cout << "用前后指针法排完升序后:" << endl;
Print(A, size);
QUICKSORT2(B, 0, size - 1);
cout << "用左右指针法排完升序后:" << endl;
Print(B, size);
QUICKSORT2(C, 0, size - 1);
cout << "用挖坑法排完升序后:" << endl;
Print(C, size);
}
结果
要排序的的数组为:
13 -3 -25 20 -3 -16 -23 18 20 -7 12 -5 -22 15 -4 7
用前后指针法排完升序后:
-25 -23 -22 -16 -7 -5 -4 -3 -3 7 12 13 15 18 20 20
用左右指针法排完升序后:
-25 -23 -22 -16 -7 -5 -4 -3 -3 7 12 13 15 18 20 20
用挖坑法排完升序后:
-25 -23 -22 -16 -7 -5 -4 -3 -3 7 12 13 15 18 20 20