preface
We all know that the first step to enter a large factory is often to tear up a common sort by hand, but these sorts have been learned for a long timeDS
After the end of the exam, I ran to the barbecue and was forgotten. In order to help you quickly pick up the eight orders, I want to write down this topic. I hope it will help you.
Note: this blog post is suitable for those who have learned the following eight ranking, who use it to quickly review (final exam, written exam) (quickly pick out the key points, and help hand tearing algorithm at the same time). If there is no foundation, you’d better listen to the class well, and come back to see it after learning!
catalogue

Half minute sorting (in
IDE
Write it in half a minute with your help Bubble sort
 Select sort
 Insert sort
 Shell Sort

Two minute sorting (needs to be constructed, so in the
IDE
It’s better to write it in two minutes with the help of Quick sort
 Cardinal sort
 Heap sort
 Merge sort
learning method
The knowledge point of learning methods is to play hooligans. Any algorithm book has such sort source code, and there are too many versions of these eight sort on the Internet. However, I find that they have such characteristics:
 use
template
Written, not suitable for fast memory and learning  Variable definition confusion, leaving the code
I try my best to write down the main training methods and ideas in this blog for the convenience of memory.
Methods (learning three times)
 According to the source code knock again, tune through
 Delete it, write it yourself, write it out
bug
Or where it doesn’t work, debug it yourself. If it doesn’t work, look at the source code, check it, and mark the pit you stepped on  Write it on the paper and put it on the paper
IDE
The inspection steps are the same as the second step.
After these steps, the eight basic sorting is basically no problem. Now I’ll explain these eight orders one by one.
Half minute sorting
I want to make it clear that I will talk about a thing called boundary wall. Let me give an example first, so as not to be difficult to understand. I declare that this is not a professional term in advance, just to facilitate me to understand this knowledge. For example, $12345  98763653274, assuming that the left part of the vertical line is in order, we think that the boundary wall is in the position of the vertical line, such as the bubbling below If you choose, they will move steadily to the right (of course, if you put the ordered element on the far right, it will move to the left).
Bubble sort
The first sorting should be noted, first of all, check whether the incoming array is legal. The whole source code is as follows
void bubble_sort(int* data, int n) {
/*
Bubble sort
*/
If (data = = nullptr  n < = 1) // verify the validity of the array
return;
bool flag = true;
for (int i = 0;i < n  1&&flag;i++) {
flag = false;
for (int j = 0; j < n  i  1; J + +) {// note that it's ni + 1
If (data [J] > data [J + 1]) {// if it is large, it should be changed, indicating that it should be small
swap(data[j], data[j + 1]);// This is a j!
flag = true;
}
}
}
return;
}
I’m using the optimized version here, to understand theflag
If there is not even one exchange, it means that each element is strictly monotonic (and transitive) and can be exchanged in advancebreak
。
The key to writing correctly lies in:
for(int i=0;i<n;i++)
for(int j=0;j<ni+1;j++)
if(data[j]>data[j+1])
swap(xxxx)
Especially in the inner ringni+1
Andif
It’s easy to compare the front and back elementsj
Hand can’t writei
How to avoid these mistakes? To understand what the boundary means here:
 Outer ring
i
In essence, it specifies how thick the boundary wall is after each round (how many elements need no further adjustment), so you can write forward or backward in the inner circle. Of course, I write backward  The inner ring bubbles inside the boundary wall, one element at a time,
ni+1
If you can’t think of an increase or a decrease, but you have a general impression, you might as well try to use the boundary to put thisj
Can be constrained in the normal domain  Make sure
j+1
Don’t cross the line.
There are so many things to pay attention to. In fact, it’s not very difficult. Go and write it again!
Select sort
Ah, I always confuse selection sort and insertion sort. I feel that their styles are a little similar
 Selection sorting is to select one in each round and insert it next to the boundary wall
 Insertion sort is to select the insertion at the front of the boundary wall in each round in a sequence that has been arranged.
About sorting by selection:
void select_sort(int* data, int n) {
/*
Select sort
*/
if (data == nullptr  n <= 0)
return;
for (int i = 0;i < n;i++) {
int max_idx = i;
for (int j = i;j < n;j++) {
if (data[j] < data[max_idx])
max_idx = j;
}
swap(data[max_idx], data[i]);
}
return;
}
Is it also very simple? Why do you say that? We should understand it in this way
 The outer ring thickens the boundary wall layer by layer, making the optional range smaller and smaller.
 The inner circle is to select a maximum value, so in essence, it is to select a maximum value in the inner circle within the agreed range of the outer circle, and finally exchange it with the boundary wall element.
In other words,swap
You can use it#include<algorithm>
Inside, you can also write by yourself. If you write by yourself, don’t forget to add quotation marks.
Insert sort
The source code is as follows:
void insert_sort(int* data, int n) {
/*
Insert sort
*/
//Suppose the front is in order
if (data == nullptr  n <= 0)
return;
for (int i = 0;i < n1;i++) {
for (int j = i + 1;j >= 0;j) {
if (data[j] < data[j  1])
swap(data[j], data[j  1]);
}
}
return;
}
The idea of memorizing it is to simulate a shuffling process (shuffled $ $not shuffled cards)
 The front of the boundary wall has been arranged in order. Imagine it as a inserted card, and the back is the card to be inserted
 The outer ring is to gradually increase the number of cards that have been inserted
i
The inner circle is to choose the smallest card from the remaining cards that have not been inserted (the range has been constrained by the boundary wall).
However, this card insertion is very vivid, but it is difficult to achieve. In this algorithm, I bubble forward one by one, exchange multiple times to reach its own position, which can also achieve the effect of card insertion. Another more efficient way is to use a temporary number to record the current number to be inserted, and then move the inserted cards back one by one, because the front is orderly, At last, we can find its destination. At that time, we can insert the temporary number.
Shell Sort
At the beginning, I also found it difficult to understand the order, especially when I looked at the diagram, I picked it out layer by layer, and then jumped at different intervals, making people unable to touch their hair (only bald).
But when I handwritten this algorithm, I found that it was not so difficult. To put it bluntly, it was a mutation selection sorting. How to say it was mutation?
 The original choice is to see one by one, now it is across
gap
Go and see  Well, we know the inner dependency
gap
The outer layer is boundgap
Let’sgap
Multiple decrement is OK, let it gradually become fine, how to choose this multiple can, of course, choose the appropriate prime number is better, I write here is $3 $.
void shell_sort(int* data, int n) {
if (data == nullptr  n <= 0)
return;
const int incregap = 3;// This is the interval change
for (int incre = n / incregap;incre > 0;incre /= incregap) {
for (int i = 0;i < n;i += incre) {
int max_idx = i;
for (int j = i;j < n;j += incre) {
if (data[j] < data[max_idx])
max_idx = j;
}
swap(data[max_idx], data[i]);
}
}
}
You see, when we look from the inside out, it’s not that hard:
I need an inner layer togap
Choose Sort $\ rightarrow $to find thisgap
$rightarrow $thisgap
Decrease by a multiple.
The difference from the previous sorting is the outer layergap
That’s why it’s more appropriate to finish it in half a minute.
Two minute sorting
Quick sort
In fact, I don’t like quick sort very much. I think it’s very difficult to write. In order to attach importance to this man, I put it in two minutes to sort. Well, after understanding its principle, it’s not so difficult. There’s a routine.
The core of quicksort isDivide and Conquer
To understand how to divide and conquer, and then solve the subproblem.
First of all, we have to remember that quick sort is:
 Draw a wall between them
 Left quick row
 Right quick row
According to this, the main function comes out
void quick_sort(int* data, int begin, int end) {
if (data == nullptr  end <= begin)
return;
int mid = partition(data, begin, end);
quick_sort(data, begin, mid  1);
quick_sort(data, mid + 1, end);
}
It’s easy, isn’t it? Let’s put it firstpartition
，quick_sort
They all look like black boxes
Go ahead and line them up according to the range. The one higher than this person stands on the left and the one lower stands on the right.
All right, these people on the left go and continue to line up like this
These people on the right, what are you doing in a daze!
Right, what makes the brain AChE is how to segment
int partition(int* data, int begin, int end) {
int shift = begin  1;
for (int idx = begin;idx < end;idx++) {
if (data[end] > data[idx]) {
++shift;
swap(data[shift], data[idx]);// Stack them one by one from the beginning
}
}
++shift;
swap(data[end], data[shift]);
return shift;
}
amongshift
It’s an anchor that’s waiting for the exchange all the time. Let’s make itend
This corresponding person serves as a comparison point.
With each exchange, let the anchor increase, so that the moment to the left of the anchor is faster than the moment to the leftend
The corresponding element is small (note that this is very important! Image meansAre smaller than end
$$Other elements
）When it comes to the end of the day, it means that all of them are finished, and that’s itAre smaller than end
$$Are larger than end
, butend
At the end of the day:
++shift;
swap(data[end], data[shift]);
be accomplished!
Cardinal sort
Originally, I wanted to be lazy, so I consulted a blog and found that it was too complicated to write. It was more convenient to tear it by hand, so I went to write it (if the scalp of the following one is numb, you can see the subsequent disassembly first)
void radix_sort_core(int* data, int n, int base) {
queue<int>box[10];
for (int i = 0;i < n;i++) {
box[(data[i] / base) % 10].push(data[i]);
}
int shift = 0;
for (int i=0;i < 10;i++) {
while (!box[i].empty()) {
data[shift++] = box[i].front();
box[i].pop();
}
}
}
void radix_sort(int* data, int n) {
if (data == nullptr  n <= 0)
return;
int max_val = 1;
for (int i = 0;i < n;i++)
if (data[i] > max_val)
max_ val = data[i];// Maximum value found
for (int base = 1; max_ val / base > 0; Base * = 10) {// bit by bit increase
radix_sort_core(data, n, base);
}
}
What is the cardinal sort about?
First, let’s look at thisradix_sort
Our first step is to find a maximum value. We take this maximum value as a gatekeeper to ensure that the cardinality will not exceed the maximum value, so that we can stop in time. OK, then let’s let the base increase by $ times10 $. Is this a very natural thing, because cardinal sorting is to pass sorting from low to high (of course, internal sorting is stable to support our great cause). Of course, what should we do after increasing the cardinality? Is it necessary to sort every round? This sort is in theradix_sort_core
Inside:
We are decimal numbers, so we set up $10 boxes, and each box has a queue (used as a linked list). In this way, we need to satisfy $Filo $, which is also what we need to sort. We first insert the elements into the boxes where the corresponding remainder is, and then go through them in order, that is, one by onepop()
To empty, this completes a round of sorting, in order to save space, in fact, the results are covered and placed in the originaldata
It’s just in your space.
All right! Go and write it down again.
Heap sort
In my opinion, this sort is the most difficult one among the eight sorts. For the definition of heap, let me briefly say:
 The largest heap is a complete binary tree
 Each element of the largest heap is larger than
child
The elements should be large (that is, each subtree is the largest heap)
OK, and we need to master an idea, the idea of laziness, how to be lazy? In fact, all operations are heap building. If you delete it, take the tail element to the top of the heap, and then you go to build the heap. If you insert it, insert the new element into the last position, and then you go to build the heap. In a word, to master an idea, how to arrange the heap has nothing to do with you, You just need to:
Operate (insert, delete)
Build the reactor ();
So the order is:
Number of cycle elements:
top();
pop();
Build the reactor ();
Does it mean that things are simple? Isn’t it good? You have to build a pile. This is the most difficult part. OK, let’s get out of bed and start building the pile
I don’t want to write classes. It’s more comfortable to write structs
struct max_heap {
int* h;// Heap data, stored in an array, starting with subscript 1
int size;// How many elements are there
max_heap(int*h,int size) {
this>size = size;
this>h = new int[size + 1];
for (int i = 0;i < size;i++)
this>h[i + 1] = h[i];
create_heap();
}
create_heap(){
//Main contents of reactor construction
}
}
You see what I said? Is it the same with constructors? Copy the incoming array and call it without saying a wordJiandui ()
。
If you can’t get around it, come on, start building piles
void create_heap() {
int parent = 1;
int child = 2;
for (int node = size / 2;node >0;node) {
parent = node;
child = node * 2;
int tmp = h[parent];// The core idea is mobile parent
for (;child <= size;child *= 2) {
if (h[child] < h[child+1]&&child+1<=size)
child++;
If (TMP > H [child]) // find a suitable location and insert parent
break;
h[parent] = h[child];
parent = child;
}
h[parent] = tmp;
}
}
First, let’s defineparent
，child
In fact, it doesn’t matter what it is. It’s just afraid that it will be empty and may make mistakes. So you might as well define it as $1 $and $2 $first. Start the cycle
Let’s start with the first non leaf node. The goal is to make every subtree a big root heap. Recall our second property. Therefore, the leaf node does not need to be arranged. If you put it in, it will consume the number of cycles, which is not costeffective.
Then, let’s look for the current situationparent
The largest of the two child nodes on hand is our first judgment statement. We need to know that our mission is to give thisparent
A suitable destination, which is the core task, means that after putting it in, the subtree is the big root pile, and then, if the most primitive one is put inparent
If the value is satisfied, it will be OK to put it in the current position. Then this place is its destination, so I put itbreak
If not, then continue to look down, how to go down, that is, firstchild
Move the value up one level and use thefor
Internalchild <= size;child *= 2)
Something moves it down, so it goes to the end.
Whoo! Finally finished, let’s add something in the corner.
int top() {
return h[1];
}
void pop() {
h[1] = h[size];
create_heap();
}
void insert(int x) {
h[++size] = x;
create_heap();
}
Because it is a large root heap, we want to make an ascending sequence
void heap_sort(int* data, int n) {
max_heap mh(data, n);
int* tmp = new int[n];
for (int i = 0;i < n;i++) {
int a = mh.top();
tmp[i] = a;
mh.pop();
}
for (int i = 0;i < n;i++) {
data[n  i  1] = tmp[i];// TMP is in positive order
}
delete[]tmp;
}
be accomplished!
Is it a bit messy? I’ll post the whole code below:
struct max_heap {
int* h;
int size;
max_heap(int*h,int size) {
this>size = size;
this>h = new int[size + 1];
for (int i = 0;i < size;i++)
this>h[i + 1] = h[i];
create_heap();
}
void create_heap() {
int parent = 1;
int child = 2;
for (int node = size / 2;node >0;node) {
parent = node;
child = node * 2;
int tmp = h[parent];// The core idea is mobile parent
for (;child <= size;child *= 2) {
if (h[child] < h[child+1]&&child+1<=size)
child++;
If (TMP > H [child]) // find a suitable location and insert parent
break;
h[parent] = h[child];
parent = child;
}
h[parent] = tmp;
}
}
int top() {
return h[1];
}
void pop() {
h[1] = h[size];
create_heap();
}
void insert(int x) {
h[++size] = x;
create_heap();
}
void print() {
for (int i = 1;i <= size;i++)
printf("%d%c", h[i], (i == size) ? '\n' : ' ');
//cout << this>top()<< endl;
}
};
void heap_sort(int* data, int n) {
max_heap mh(data, n);
int* tmp = new int[n];
for (int i = 0;i < n;i++) {
int a = mh.top();
tmp[i] = a;
mh.pop();
}
for (int i = 0;i < n;i++) {
data[n  i  1] = tmp[i];// TMP is in positive order
}
delete[]tmp;
}
Merge sort
It’s the last ranking. I’m a little excited. So am I! I’m tired. In fact, I think merge sort is a bit like quick sort, but its division and rule looks a little different
Merge sort left
Merge sort right
Together!
OK, so what it says is:
void merge_sort(int* data, int begin, int end) {
if (data == nullptr  end <= begin)
return;
int mid = (end + begin) / 2;// Notice the plus sign
merge_sort(data, begin, mid);
merge_sort(data, mid + 1, end);
merge(data, begin, mid, end);
}
Look insideint mid=(end+begin)/2
It’s a plus sign. I made a mistake at the beginning and couldn’t get it out.
Well, then the most important thing is insidemerge
It’s the most difficult. Ah, do you feel that history is surprisingly similar (recallpartition
）。merge
The essence of the algorithm is to create a new array (not to mention the in situ algorithm), and then I select relatively small elements from the left and right to put them into the new array one by one, right? The complexity is $o (n)_ 1+n_ 2) $where $n_ 1 $and $n_ 2 $is the length of the left and right arrays respectively. In fact, according to the relationship, their lengths differ by $1 $or equal, or they can be combined.
But our task is not to analyze them. You just need to know that it is linear complexity. Are you thinking, have we found a sorting method of linear complexity? Shocked! Don’t be silly HHH, it’s feasible to do this because every subproblem is in order. If it’s out of order, then… You can try it, it won’t work.
Well, let’s get down to business. After we put in all the comparable parts, assuming that there are still remnants, we just put the remnants in directly.
It’s troublesome to say, but it’s easy to write!!!
Template:
We define the left boundary, the middle boundary and the right boundary asbegin
、mid
、end
。
While (left pointer does not reach mid, right pointer does not reach end)
Find the smaller value and write it in;
The pointer moves to the right
While (left pointer does not reach mid)
Copy it in
The pointer moves to the right:
While (the right pointer does not reach end)
Copy it in
The pointer moves to the right:
They are not very dual, super aesthetic, easy to write out!
void merge(int* data, int begin, int mid, int end) {
if (data == nullptr  end <= begin)
return;
int shift_1 = begin;
int shift_2 = mid+1;
int shift = 0;
int* tmp = new int[end  begin + 1];
while (shift_1 <= mid && shift_2 <= end) {
if (data[shift_1 ]<data[shift_2])
tmp[shift++] = data[shift_1++];
else
tmp[shift++] = data[shift_2++];
}
while (shift_1 <= mid)
tmp[shift++] = data[shift_1++];
while (shift_2 <= end)
tmp[shift++] = data[shift_2++];
for (int i = 0;i < end  begin + 1;i++)
data[begin + i] = tmp[i];
delete[]tmp;
}
summary
This blog post is not for beginners. Naturally, there are many places that are not summarized from the perspective of beginners. I write more from my own experience in the process from being confused to completely picking up (including some self talk). I hope it can be helpful to everyone and I hope you can have more discussions. Finally, don’t forget the complexity analysis, For complexity, please refer to:
Summary of data structure difficulties and key points