int nums[] = {11,24,5,32,50,34,54,76};
假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。
序列的一个数作为基准数。选取第一个数6作为基准数。
我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。类似下面这种排列:
3 1 2 5 4 6 9 7 10 8
1 思路1 (每次必须j先出发)
1.1 思路演示
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。
我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即=10),指向数字。
首先哨兵j开始出动。(再友情提醒,每次必须是哨兵j先出发)。
哨兵j一步一步地向左挪动(即j–),直到找到一个小于6的数停下来。
接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。
最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。
接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。
哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:
6 1 2 5 4 3 9 7 10 8
第二次交换结束,“探测”继续。
哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。
哨兵i继续向右移动,此时哨兵i和哨兵j相遇了,“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下:
3 1 2 5 4 6 9 7 10 8
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。
重复上述过程:分别对左右子区间,进行上述递归;
1.2 代码实现
//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大,,,递归
void static QuickSort(int arr[], int start, int end) {
if (start > end) return;
int i, j, base;//设置左右移动哨兵,及基准(枢轴)
i = start, j = end;
base = arr[start];//每一个子区间,第一个元素作为基准
while (i < j) {
while (i < j && arr[j] >= base) j--;//每次都是右边的哨兵先左移,找到比基准小的停止
while (i < j && arr[i] <= base) i++;//然后,左边的哨兵右移,找到比基准大的停止
if (i < j)
swap(arr[i], arr[j]);//此时若i<j,交换
}
//当两个哨兵i,j相遇,将arr[start]与哨兵交换,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
swap(arr[start], arr[j]);
//注意:将arr[start]与哨兵交换,,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组
//到此,第一趟快排结束;
//分别对左右子区间,进行上述递归
QuickSort(arr, start, i - 1);
QuickSort(arr, j + 1, end);
}
swap(arr[start], arr[j]);
注意:将arr[start]与哨兵交换,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组
这个地方,刚开始base与哨兵交换,,,结果第一个元素一直是6不变,,,查了好久才找到原因
测试
void print(int arr[], int n) {
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
void main() {
int nums[] = {11,24,5,32,50,34,54,76};
int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数
cout << "快速排序前: " << endl;
print(nums,n);
QuickSort(nums, 0, n- 1);
cout << "快速排序后: " << endl;
print(nums, n);
}
数组的长度 = 数组总字节数/ 每个元素字节数
int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数
1.3 区间划分和递归调用独立封装
递归、区间划分分开的,
本来想i = start, j = end;可以省去的,直接使用start,end变量;
但是,枢轴与首元交换时,发现首元下边变了,swap(arr[start], arr[j]);
所以,还是要令,i = start, j = end;
//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大(一趟快排)
int Partion(int arr[], int start, int end) {
int i, j, base;//设置左右移动哨兵,及基准(枢轴)
i = start, j = end;
base = arr[start];//每一个子区间,第一个元素作为基准
while (i < j) {
while (i < j && arr[j] >= base) j--;//每次都是右边的哨兵先左移,找到比基准小的停止
while (i < j && arr[i] <= base) i++;//然后,左边的哨兵右移,找到比基准大的停止
if (i < j)
swap(arr[i], arr[j]);//此时若i<j,交换
}
//当两个哨兵i,j相遇,将arr[start]与哨兵交换,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
swap(arr[start], arr[j]);
//注意:将arr[start]与哨兵交换,,,,不是base与哨兵交换,,,虽然base与arr[start]相等,,但目的是要改变数组
//到此,第一趟快排结束;
return i;//返回分界元素下标;;;此时ij是相等的
}
void QuickSort(int arr[], int start, int end) {
if (start < end)
{
int pivot = Partion(arr, start, end);
//分别对左右子区间,进行上述递归
QuickSort(arr, start, pivot - 1);
QuickSort(arr, pivot + 1, end);
}
2 思路2 ( 推荐使用,代码好写,不易错)(j,i到哪边哪边先走,赋值代替交换)
2.1 思路演示
[23,35,15,20,9,5,45,56,11,2]
待排序的数据元素第一个作为基准值元素 key=num[0];
设置双指针first指向区间左端,last指向区间右端。(即上述i,j)
哨兵last先开始,一步一步地向左挪动(即j–),直到找到一个小于key的数停下来。
num[first]=num[last],将这个比key小的数放到左边firt的位置;
然后,哨兵first开始,一步一步地向右挪动(即i++),直到找到一个大于key的数停下来。
num[last]=num[first],将这个比key大的数放到右边last的位置;
重复上述一二步骤
last左移,找到11比基准小,放到fist的位置;
first右移,找到45比基准大,放到last的位置;
last继续左移
fisrt、last相遇,“探测”结束。将基准数23放到相遇的位置;
第一趟排序结束,得到[2,11,15,20,9,5] 23 [56,45,35] 然后对左右子数列进行同样的操作。
2 [11,15,20,9,5] 23 [35,45] 56
2 [5,9] 11 [20,15] 23 35 45 56
2 5 9 11 15 20 23 35 45 56
完成从小到大的排序
2.2 代码实现
单趟快排
//以基准为分界,划分左右子区间,左边的都比基准小,右边都比他大(一趟快排)
int Partion(int arr[], int low, int high) {
int base = arr[low];//每一个子区间,第一个元素作为基准
while (low < high) {
while (low < high && arr[high] >= base) high--;//右边的哨兵先左移,找到比基准小的停止
arr[low] = arr[high]; //将比基准小的数放到左边low的位置;
while (low < high && arr[low] <= base) low++;//然后,左边的哨兵右移,找到比基准大的停止
arr[high] = arr[low]; //将比基准大的数放到右边high的位置;
}
//当两个哨兵i,j相遇,将枢轴(基准)元素放到哨兵位置,此时基准到达最终位置点;且基准为分界,左边的都比基准小,右边都比他大
arr[low] = base;//将枢轴(基准)元素放到最终位置 //其实这里交换,也不影响结果,只是意思变了swap(arr[low], base);
//到此,第一趟快排结束;
return low;//返回分界元素下标;;; 此时low和high相等,随便返回哪个都可以
}
递归调用
void QuickSort(int arr[], int start, int end) {
if (start < end)
{
int pivot = Partion(arr, start, end);
//分别对左右子区间,进行上述递归
QuickSort(arr, start, pivot - 1);
QuickSort(arr, pivot + 1, end);
}
}
测试
void print(int arr[], int n) {
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
void main() {
int nums[] = { 23,35,15,20,9,5,45,56,11,2 };
int n = sizeof(nums)/sizeof(nums[0]); //数组的长度 = 数组总字节数/ 每个元素字节数
cout << "快速排序前: " << endl;
print(nums,n);
QuickSort(nums, 0, n- 1);
cout << "快速排序后: " << endl;
print(nums, n);
}
2.3 用vector代替int arr[]
int arr[] 替换成vector&arr,其余的地方一样
int partition(vector<int>& arr, int low, int high) {
int base = arr[low];
while (low < high) {
while (low < high && arr[high] >= base) high--;
arr[low] = arr[high];
while (low < high && arr[low] <= base)low++;
arr[high] = arr[low];
}
arr[low] = base;
return low;
}
void QuickSort(vector<int> &arr, int start, int end) {
if (start < end) {
int pivot = partition(arr, start, end);
QuickSort(arr, start, pivot - 1);
QuickSort(arr, pivot + 1, end);
}
}
测试
void printVectorInt(vector<int>arr, int n) {
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
void main() {
//vector<int> res = { 11,24,5,32,50,34,54,76 };
vector<int> res = { 1,4,7,8,9,6,3,2,5,10 };
cout << "快速排序前: " << endl;
printVectorInt(res, res.size());
QuickSort(res, 0, res.size() - 1);
cout << "快速排序后: " << endl;
printVectorInt(res, res.size());
}