Personally take you to tear up the eight basic sorting (c + + code)

Time:2021-7-19

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 timeDSAfter 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 (inIDEWrite 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 theIDEIt’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:

  • usetemplateWritten, 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 outbugOr 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 paperIDEThe 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 n-i + 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 theflagIf 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<n-i+1;j++)
        if(data[j]>data[j+1])
            swap(xxxx)

Especially in the inner ringn-i+1AndifIt’s easy to compare the front and back elementsjHand can’t writeiHow to avoid these mistakes? To understand what the boundary means here:

  • Outer ringiIn 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,n-i+1If 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 thisjCan be constrained in the normal domain
  • Make surej+1Don’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,swapYou 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 < n-1;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 insertediThe 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 acrossgapGo and see
  • Well, we know the inner dependencygapThe outer layer is boundgapLet’sgapMultiple 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 togapChoose Sort $\ rightarrow $to find thisgap$rightarrow $thisgapDecrease by a multiple.
The difference from the previous sorting is the outer layergapThat’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 ConquerTo 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 firstpartitionquick_sortThey 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;
}

amongshiftIt’s an anchor that’s waiting for the exchange all the time. Let’s make itendThis 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 leftendThe 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, butendAt 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_sortOur 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_coreInside:
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 originaldataIt’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 thanchildThe 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 defineparentchildIn 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 cost-effective.
Then, let’s look for the current situationparentThe largest of the two child nodes on hand is our first judgment statement. We need to know that our mission is to give thisparentA 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 inparentIf the value is satisfied, it will be OK to put it in the current position. Then this place is its destination, so I put itbreakIf not, then continue to look down, how to go down, that is, firstchildMove the value up one level and use theforInternalchild <= 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)/2It’s a plus sign. I made a mistake at the beginning and couldn’t get it out.
Well, then the most important thing is insidemergeIt’s the most difficult. Ah, do you feel that history is surprisingly similar (recallpartition)。
mergeThe 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 asbeginmidend

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