Article directory
foreword
It's not easy to get used to typing after liking it first. This is all done with great care. I hope to get support from you. The likes and support are a very important motivation for me. Don't forget to follow me after reading it! ️️️
We all know that sorting algorithm is an extremely important algorithm in programming high-level languages. Among them, the more important is the quick sort. But in the process of learning quick sort, many people do not understand quick sort well. In fact, there are three ideas of quick sort .
- hoare version
- Digging
- back-and-forth pointer method
For the functions in the C language, qsort()
the blogger gave a detailed introduction in the early blog, and the partners who haven't used it yet can eat it through the portal.
For friends who are not quite clear about the ranking of the nine majors, bloggers here provide a summary portal for everyone.
There is also a blog, which is the source code of the complexity test of the sorting algorithm. The blogger also provides it to you here. We will use it later in the following learning process.
Then the bloggers here will first write some columns full of dry goods!
Data Structure Column: Data Structure This contains a lot of bloggers' summaries of data structure learning. Each article is written with great care. Interested partners can support it!
Algorithm Column: Algorithm This can be said to be the blogger's writing process. It summarizes some classic questions and the algorithm implementation, which is very helpful for exams and competitions!
Likou Brush Question Column: Leetcode wants to hit the partners of ACM, Blue Bridge Cup or college student programming competition. Here are the blogger's brush questions record, I hope it will help you!
C's deep anatomy column: C language deep anatomy For beginners who want to deeply learn the wisdom contained in C language and the underlying implementation of various functions, I believe this column will be helpful to you!
In order to facilitate the testing of partners, the blogger here provides amain()
source code of the function used for testing, and you can directly copy it for testing when testing.
This article uses some C++ wrapper functions, such as swap, max, etc.
So the reader remembers to add the required IO, algorithm and other header files
The programming language used in this blog is C++, but, in order to allow partners who only know C language at present to master the algorithm core,本篇博客没有使用C++包装的vector,全部使用了数组来进行演示。
The code in this blog implements int
the ascending order of type data. Partners who want to achieve descending order or other data type sorting can make modifications on this basis. Our goal in this article is to understand the algorithm idea.
Note: The animations in this blog are all from the Internet
What is Quick Sort
The actual implementation of quick sort:
- Determine one
key
value at a time (generally select the leftmost value of the interval before optimization) - Arrange
key
this value. For example , the first number in the original array is6
that the array is in a[1,2,3,4,5,6,7,8,9,10]
shuffled order, so if it is used as a key, it should return to the position of the subscript6
after a single pass .6
5
- Then recursively
keyi(keyi是key的下标)
repeat the above steps for the divided subinterval array. That is to say, for[begin,keyi-1]
and[keyi+1,end]
these two intervals, arrange their keys. In fact, this is a divide and conquer process, similar to the traversal of a binary tree. - The non-recursive method is actually to use the data structure stack to simulate the recursive process, and the single-pass sorting is the same.
There are three realization ideas for single-pass sorting:
1. Hoare idea
2. Digging pit method
3. Front and back pointer method
Recursive implementation of quicksort
void _QuickSort(int* a, int begin, int end) {
//区间不存在或者只会有一个值不需要再处理
//快排:每次把key弄好,递归解决key两边的数
if (begin >= end)return;
//利用单趟排序找到key并排好,然后得到key的下标keyi
int keyi = PartSort(a, begin, end);
//PartSort即为单趟排序
_QuickSort(a, begin, keyi - 1);
_QuickSort(a, keyi + 1, end);
}
void QuickSort(int* a, int n) {
int begin = 0;
int end = n - 1;
_QuickSort(a, begin, end);
}
Tips: Regarding the implementation of PartSort single-pass sorting, I will discuss the three implementation ideas in detail later.
A non-recursive implementation of quicksort
In fact, to put it bluntly, it is to use the data structure stack to simulate the recursive process, which is actually the same as the non-recursive method of binary tree dfs traversal.
#include<stack>
void QuickSortNonR(int* a, int begin, int end) {
//1.直接改循环
//2.用数据结构栈模拟递归过程
stack<int>st;
st.push(end);
st.push(begin);
//栈里面没有区间了,就结束了
while (!st.empty()) {
int left = st.top();
st.pop();
int right = st.top();
st.pop();
//利用单趟排序找到key并排好,然后得到key的下标keyi
int keyi = PartSort(a, left, right);
//[left,keyi-1]keyi[keyi+1,right]
//怎么迭代
//先入右再入左
//栈里面的区间都会拿出来,单趟排序分割,子区间再入栈
if (left < keyi - 1) {
st.push(keyi - 1);
st.push(left);
}
if (keyi + 1 < right) {
st.push(right);//同样,先入右再入左
st.push(keyi + 1);
}
}
}
//
void QuickSort(int* a, int n) {
int begin = 0;
int end = n - 1;
QuickSortNonR(a, begin, end);
}
Detailed explanation of single-pass sorting
hoare thought
- Let
begin
find elements larger than the reference value from front to back, and stop after finding them; let them find elements smaller than the reference value from back to front, and stop afterend
finding them; if they do not meet: Swap the element at the position with the element at the position ; Swap the reference value and the element at the position after the loop ends .begin
end
begin
end
begin
//hoare版本 O(nlogn)
//1.选出一个key,一般是最左边或者最右边的值
//2.单趟排完之后:要求左边比key小,右边比key大
//左边key,右边先走
//右边key,左边先走
//每次排完一趟,key的位置的值就是准确的了,不用动了
int PartSort1(int* a, int begin, int end) {
//hoare
int left = begin;
int right = end;
int keyi = left;//保存下标,也就是指针是最优的
//下面是单趟
while (left < right) {
//相遇就停下来
//右边先走,找小
while (left < right && a[right] >= a[keyi])--right;//while一定要带等号,否则会死循环
//而且要带多一个调节,否则有可能会越界
//左边再走,找大
while (left < right && a[left] <= a[keyi])++left;
//交换
swap(a[left], a[right]);
}
//交换key和相遇位置换
swap(a[keyi], a[right]);
//为什么左边做key,要让右边先走?
//因为要保证相遇的位置的值比key小
keyi = left;
//[begin,keyi-1]和[keyi+1,end]有序即可
return keyi;
}
Digging
The digging method is actually just a rewrite of the hoare version, and it has not actually changed in the real sense.
The following is the idea of digging a pit:
//挖坑法
//和hoare相同的地方就是-排完单趟-做到key左边比key小,右边比key大
int PartSort2(int* a, int begin, int end) {
int key = a[begin];
int piti = begin;//begin是第一个坑
int left = begin;
int right = end;
while (left < right) {
//右边找小,填到左边的坑
while (left < right && a[right] >= key) {
right--;
}
a[piti] = a[right];
piti = right;//自己变成坑
while (left < right && a[left] <= key){
left++;
}
a[piti] = a[left];
piti = left;
}
//一定相遇在坑的位置
a[piti] = key;
return piti;
}
back-and-forth pointer method
Define two pointers, prev
sum cur
, and define key
the value again. The cur
pointer starts from the left
beginning. If it encounters a key
larger one, it will be filtered out. If it is key
smaller, it will stop. , prev++
and judge whether the prev
sum cur
is equal. If it is not equal, the two values will be exchanged. , the last order is sorted, those smaller than the key are left on the left of the key, and those larger than the key are on the right of the key. representative in the
animationi
cur
s
prev
//前后指针版
int PartSort3(int* a, int begin, int end) {
//排完之后prev之前的比prev小,prev后的比prev大
int prev = begin;
int cur = begin + 1;//一开始cur和prev要错开
int keyi = begin;
while (cur <= end) {
//如果cur的值小于keyi的值
if (a[cur] < a[keyi] && ++prev != cur) {
//只有这种情况要处理一下
swap(a[prev], a[cur]);
}
++cur;
}
//
swap(a[prev], a[keyi]);
//此时prev的位置就是keyi的位置了
keyi = prev;
return keyi;
}
Optimization of Quick Sort
The above three search key
methods, if the array is ordered, the efficiency will be very low:
why, because the efficiency becomes
n+n-1+n-2+n-3.....
so: O(n^2)
when is the efficiency of quick sorting the highest? – When each key is the median of the interval, it is the fastest, so it is a strict divide and conquer, and the efficiency isO(NlogN)
And because recursion is used, if the number is slightly larger, stack overflow will occur.
We can use TestOP to test (see the portal at the beginning of the article for the efficiency test).
If we use Quick Sort to sort order, debug
it may explode when the version is small
and the efficiency will become very low.
So how do we optimize it?
Three optimization schemes:
- Pick a random number as the
key
value - three-digit
- Inter-cell optimization
The first optimization scheme is well understood, and here we focus on the following two methods for optimization.
three-digit
The meaning of taking the middle of the three numbers is to select the first number in the interval, the middle number, and the last number that is not too small or not as the key value of the interval.
//优化-三数取中
int GetMidIndex(int* a, int begin, int end) {
int mid = (begin + end) / 2;
if (a[begin] < a[mid]) {
if (a[mid] < a[end]) {
return mid;
}
else if (a[begin] < a[end]) {
return end;
}
else {
return begin;
}
}
else {
//a[begin]>a[mid]
if (a[mid] > a[end]) {
return mid;
}
else if (a[begin] < a[end]) {
return begin;
}
else {
return end;
}
}
}
Inter-cell optimization
When the interval is relatively small, it is no longer recursively divided to sort the small interval. Other sorts can be considered.
It is recommended to use insertion sort directly.
Here we just need to _QuickSort()
make adjustments to the function just now.
void _QuickSort(int* a, int begin, int end) {
//记录递归次数
callCount++;
//区间不存在或者只会有一个值不需要再处理
//快排:每次把key弄好,递归解决key两边的数
if (begin >= end)return;
//小区间优化
if (end - begin > 10) {
//当区间大于10的时候,继续递归
int keyi = PartSort3(a, begin, end);//每一个partsort负责找到key
_QuickSort(a, begin, keyi - 1);
_QuickSort(a, keyi + 1, end);
}
else {
//当区间较小的时候,直接使用插入排序
InsertSort(a + begin, end - begin + 1);//注意+1和a+begin
}
}
quicksort overall code
In the complete code, only PartSort3()
the three numbers that are used are taken, and the other one is arranged in a single pass. It can also be used directly, just key
replace it, it is very simple.
//优化-三数取中
int GetMidIndex(int* a, int begin, int end) {
int mid = (begin + end) / 2;
if (a[begin] < a[mid]) {
if (a[mid] < a[end]) {
return mid;
}
else if (a[begin] < a[end]) {
return end;
}
else {
return begin;
}
}
else {
//a[begin]>a[mid]
if (a[mid] > a[end]) {
return mid;
}
else if (a[begin] < a[end]) {
return begin;
}
else {
return end;
}
}
}
//hoare版本 O(nlogn)
int PartSort1(int* a, int begin, int end) {
//hoare
int left = begin;
int right = end;
int keyi = left;//保存下标,也就是指针是最优的
//下面是单趟
while (left < right) {
//相遇就停下来
//右边先走,找小
while (left < right && a[right] >= a[keyi])--right;//while一定要带等号,否则会死循环
//而且要带多一个调节,否则有可能会越界
//左边再走,找大
while (left < right && a[left] <= a[keyi])++left;
//交换
swap(a[left], a[right]);
}
//交换key和相遇位置换
swap(a[keyi], a[right]);
keyi = left;
//[begin,keyi-1]和[keyi+1,end]有序即可
return keyi;
}
//挖坑法
int PartSort2(int* a, int begin, int end) {
int key = a[begin];
int piti = begin;//begin是第一个坑
int left = begin;
int right = end;
while (left < right) {
//右边找小,填到左边的坑
while (left < right && a[right] >= key) {
right--;
}
a[piti] = a[right];
piti = right;//自己变成坑
while (left < right && a[left] <= key){
left++;
}
a[piti] = a[left];
piti = left;
}
//一定相遇在坑的位置
a[piti] = key;
return piti;
}
//前后指针版
int PartSort3(int* a, int begin, int end) {
//排完之后prev之前的比prev小,prev后的比prev大
int prev = begin;
int cur = begin + 1;//一开始cur和prev要错开
int keyi = begin;
//加入三数取中的优化
int midi = GetMidIndex(a, begin, end);
swap(a[keyi], a[midi]);//这样换一下,key就变成GetMidIndex()函数的取值了-后面的代码都不变了
while (cur <= end) {
//如果cur的值小于keyi的值
if (a[cur] < a[keyi] && ++prev != cur) {
//只有这种情况要处理一下
swap(a[prev], a[cur]);
}
++cur;
}
swap(a[prev], a[keyi]);
keyi = prev;
return keyi;
}
void _QuickSort(int* a, int begin, int end) {
//区间不存在或者只会有一个值不需要再处理
//快排:每次把key弄好,递归解决key两边的数
if (begin >= end)return;
//小区间优化
if (end - begin > 10) {
int keyi = PartSort3(a, begin, end);//每一个partsort负责找到key
_QuickSort(a, begin, keyi - 1);
_QuickSort(a, keyi + 1, end);
}
else {
InsertSort(a + begin, end - begin + 1);//注意+1和a+begin
}
}
void QuickSortNonR(int* a, int begin, int end) {
//1.直接改循环
//2.用数据结构栈模拟递归过程
stack<int>st;
st.push(end);
st.push(begin);
//栈里面没有区间了,就结束了
while (!st.empty()) {
int left = st.top();
st.pop();
int right = st.top();
st.pop();
int keyi = PartSort3(a, left, right);
//[left,keyi-1]keyi[keyi+1,right]
//怎么迭代
//先入右再入左
//栈里面的区间都会拿出来,单趟排序分割,子区间再入栈
if (left < keyi - 1) {
st.push(keyi - 1);
st.push(left);
}
if (keyi + 1 < right) {
st.push(right);//同样,先入右再入左
st.push(keyi + 1);
}
}
}
void QuickSort(int* a, int n) {
int begin = 0;
int end = n - 1;
_QuickSort(a, begin, end);//这里也可以改成用非递归那个函数
//QuickSortNonR(a, begin, end);
}
end
Seeing this, I believe that my partners have a relatively in-depth understanding of Quick Sort. In this Quick Sort, in fact, the wisdom of many people has penetrated. We have mastered part of this sorting knowledge, and more importantly, we have mastered the subtle algorithms inside. ideas.
If this blog is helpful to you, don't forget to like, follow and bookmark!