A sorting algorithm is a basic and commonly used algorithm. The sorting algorithm processes a large number of tasks in actual work.
High speed requirements for algorithms.
In general, the performance of the so-called algorithm mainly refers to the complexity of the algorithm, which is generally expressed by the O method. I will
A detailed description is provided.
I would like to give a brief introduction to the sorting algorithm and give an outline for this article.
I will analyze the algorithm from simple to difficult according to the complexity of the algorithm.
The first part is the simple sorting algorithm. later you will see that their common feature is that the algorithm complexity is O (n * n) (because there is no
If the word is used, the superscript and subscript cannot be typed ).
The second part is the advanced sorting algorithm with the complexity of O (log2 (n )). Here we only introduce one algorithm. There are also several
Algorithms are not discussed here because they involve the concepts of trees and stacks.
The third part is similar to the brain. The two algorithms here are not the best (or even the slowest), but the algorithms themselves compare
It is special and worthy of reference (programming ). At the same time, we can also understand this problem from another perspective.
The fourth part is a post-dinner dessert that I gave to you-a template-based general-purpose quick sorting. Because it is a template function
You can sort any data type (Sorry, some Forum experts mentioned here ).
Now, let's start:
I. Simple Sorting Algorithm
The program is relatively simple, so no comments are added. All programs provide complete Running code, and in my VC Environment
. Because there is no content related to MFC and windows, there should be no content on Borland C ++ platforms.
Problem. The running process is illustrated after the code, and it is helpful for understanding.
1. Bubble Method:
This is the most primitive and well-known slowest algorithm. The origin of his name is because its work seems to be bubbling:
# Include <iostream. h>
Void bubblesort (int * pdata, int count)
{
Int itemp;
For (INT I = 1; I <count; I ++)
{
For (Int J = count-1; j> = I; j --)
{
If (pdata [J] <pdata [J-1])
{
Itemp = pdata [J-1];
Pdata [J-1] = pdata [J];
Pdata [J] = itemp;
}
}
}
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Bubblesort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
Reverse Order (worst case)
First round: 10, 9, 8, 7-> 10, 9, 7-> 10, 7, 9-> 7, 10, 9, 8 (three exchanges)
Round 2: 7, 10, 9-> 7, 10, 8-> 7, 8, 9 (2 exchanges)
First round: 7, 8, 10, 9-> 7, 8, 9, 10 (switching once)
Cycles: 6
Number of exchanges: 6
Others:
First round:,->, (exchange twice)
Round 2: 7, 8, 10, 9-> 7, 8, 10, 9-> 7, 8, 10, 9 (0 exchanges)
First round: 7, 8, 10, 9-> 7, 8, 9, 10 (switching once)
Cycles: 6
Number of exchanges: 3
We have given the program section above, and now we analyze it: here, the main part that affects our algorithm performance is loop and exchange,
Obviously, the more times, the worse the performance. From the above program, we can see that the number of cycles is fixed, which is 1 + 2 +... + n-1.
The formula is 1/2 * (n-1) * n.
Note that the O method is defined as follows:
If there is a constant K and the starting point N0, so when n> = N0, F (n) <= K * g (N), F (N) = O (G (n )). (Haha, don't say no
Learning Mathematics well is very important for programming mathematics !!!)
Now let's look at 1/2 * (n-1) * n. When k = 1/2, N0 = 1, g (n) = N * n, 1/2 * (n-1) * n <= 1/2 * n = K * g (n ). So F (N)
= O (G (N) = O (N * n ). So the complexity of our program loop is O (n * n ).
Let's look at the exchange. We can see from the table following the program that the two cases share the same loop and the exchange is different. In fact, the exchange itself is the same as the data source
There is a great relationship between the degree of order. when the data is in reverse order, the number of exchanges is the same as the number of cycles (each cycle will be exchanged ),
The complexity is O (n * n ). When the data is in positive order, there will be no exchange. The complexity is O (0 ). It is in the intermediate state in disordered order. This is precisely because
The reason is that we usually compare the algorithm by the number of cycles.
2. exchange method:
The procedures of the exchange method are the clearest and simplest. Each time, the current elements are compared and exchanged with the subsequent elements one by one.
# Include <iostream. h>
Void exchangesort (int * pdata, int count)
{
Int itemp;
For (INT I = 0; I <count-1; I ++)
{
For (Int J = I + 1; j <count; j ++)
{
If (pdata [J] <pdata [I])
{
Itemp = pdata [I];
Pdata [I] = pdata [J];
Pdata [J] = itemp;
}
}
}
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Exchangesort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
Reverse Order (worst case)
First round: 10, 9, 8, 7-> 9, 10, 8, 7-> 8, 10, 9-> 7, 10, 9, 8 (three exchanges)
Round 2: 7, 10, 9-> 7, 9, 10, 8-> 7, 8, 10, 9 (exchange twice)
First round: 7, 8, 10, 9-> 7, 8, 9, 10 (switching once)
Cycles: 6
Number of exchanges: 6
Others:
First round:,->, (exchange once)
Round 2: 7, 10, 8, 9-> 7, 8, 10, 9-> 7, 8, 10, 9 (exchange once)
First round: 7, 8, 10, 9-> 7, 8, 9, 10 (switching once)
Cycles: 6
Number of exchanges: 3
From the running table, the exchange is almost as bad as the bubble. This is true. The number of loops is the same as that of bubbles.
It is also 1/2 * (n-1) * n, so the complexity of the algorithm is still O (N * n ). Because we cannot give all the information
We can only tell you that they are equally bad in exchange (in some cases, slightly better, in some cases ).
3. Selection Method:
Now we can see a hope: the selection method improves the performance (in some cases)
This method is similar to our human sorting habits: We select the smallest one from the data to exchange the first value, in the saved part.
Select the smallest one and the second one.
# Include <iostream. h>
Void selectsort (int * pdata, int count)
{
Int itemp;
Int IPOs;
For (INT I = 0; I <count-1; I ++)
{
Itemp = pdata [I];
IPOs = I;
For (Int J = I + 1; j <count; j ++)
{
If (pdata [J] <itemp)
{
Itemp = pdata [J];
IPOs = J;
}
}
Pdata [IPOs] = pdata [I];
Pdata [I] = itemp;
}
}
Generally, the bubble is unidirectional, and here it is bidirectional, that is, reverse work is required.
The code looks complicated. After careful consideration, you can see that it is a round-trip method.
The author of this Code thinks this can reduce some exchanges on the basis of bubbling (I don't think so, maybe I am wrong ).
I think this is an interesting piece of code.
# Include <iostream. h>
Void bubble2sort (int * pdata, int count)
{
Int itemp;
Int left = 1;
Int right = count-1;
Int T;
Do
{
// Positive part
For (INT I = right; I> = left; I --)
{
If (pdata [I] <pdata [I-1])
{
Itemp = pdata [I];
Pdata [I] = pdata [I-1];
Pdata [I-1] = itemp;
T = I;
}
}
Left = t + 1;
// Reverse part
For (I = left; I <right + 1; I ++)
{
If (pdata [I] <pdata [I-1])
{
Itemp = pdata [I];
Pdata [I] = pdata [I-1];
Pdata [I-1] = itemp;
T = I;
}
}
Right = T-1;
} While (left <= right );
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Bubble2sort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
2. Shell sorting
This sorting is very complicated and you will know after reading the program.
First, we need a decreasing step. Here we use 9, 5, 3, and 1 (the last step must be 1 ).
The principle is to first sort all the content of 9-1 elements, and then use the same method to sort 5-1 elements.
And so on.
# Include <iostream. h>
Void shellsort (int * pdata, int count)
{
Int step [4];
Step [0] = 9;
Step [1] = 5;
Step [2] = 3;
Step [3] = 1;
Int itemp;
Int K, S, W;
For (INT I = 0; I <4; I ++)
{
K = step [I];
S =-K;
For (Int J = K; j <count; j ++)
{
Itemp = pdata [J];
W = J-K; // calculate the subscript of the last step element
If (S = 0)
{
S =-K;
S ++;
Pdata [s] = itemp;
}
While (itemp <pdata [w]) & (W> = 0) & (W <= count ))
{
Pdata [W + k] = pdata [w];
W = W-K;
}
Pdata [W + k] = itemp;
}
}
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1,-10,-1 };
Shellsort (data, 12 );
For (INT I = 0; I <12; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
Oh, the program looks a little headache. However, it is not very difficult to remove the S = 0 block, so it is much easier to avoid using 0 here.
Code written due to program exceptions due to step size. I think this code is worth reading.
This algorithm is named after the inventor D. l. Shell. According to the reference: "For complex mathematical reasons
Avoid using the power step of 2, which can reduce the algorithm efficiency ." In addition, the complexity of the algorithm is the 1.2 power of N. Because it is very complex and
The reason "beyond the scope of this book" (I don't know the process), we only have the results.
Iv. General sorting based on templates:
I think there is no need to analyze this program. Let's take a look. You can ask questions on the forum.
Mydata. h file
//////////////////////////////////////// ///////////////
Class cmydata
{
Public:
Cmydata (INT index, char * strdata );
Cmydata ();
Virtual ~ Cmydata ();
Int m_iindex;
Int getdatasize () {return m_idatasize ;};
Const char * getdata () {return m_strdatamember ;};
// Here the operator is overloaded:
Cmydata & operator = (cmydata & srcdata );
Bool operator <(cmydata & data );
Bool operator> (cmydata & data );
PRIVATE:
Char * m_strdatamember;
Int m_idatasize;
};
//////////////////////////////////////// ////////////////
Mydata. cpp File
//////////////////////////////////////// ////////////////
Cmydata: cmydata ():
M_iindex (0 ),
M_idatasize (0 ),
M_strdatamember (null)
{
}
Cmydata ::~ Cmydata ()
{
If (m_strdatamember! = NULL)
Delete [] m_strdatamember;
M_strdatamember = NULL;
}
Cmydata: cmydata (INT index, char * strdata ):
M_iindex (INDEX ),
M_idatasize (0 ),
M_strdatamember (null)
{
M_idatasize = strlen (strdata );
M_strdatamember = new char [m_idatasize + 1];
Strcpy (m_strdatamember, strdata );
}
Cmydata & cmydata: Operator = (cmydata & srcdata)
{
M_iindex = srcdata. m_iindex;
M_idatasize = srcdata. getdatasize ();
M_strdatamember = new char [m_idatasize + 1];
Strcpy (m_strdatamember, srcdata. getdata ());
Return * this;
}
Bool cmydata: Operator <(cmydata & Data)
{
Return m_iindex <data. m_iindex;
}
Bool cmydata: Operator> (cmydata & Data)
{
Return m_iindex> data. m_iindex;
}
//////////////////////////////////////// ///////////////////
//////////////////////////////////////// //////////////////
// Main program section
# Include <iostream. h>
# Include "mydata. H"
Template <class T>
Void run (T * pdata, int left, int right)
{
Int I, J;
T middle, itemp;
I = left;
J = right;
// The following comparison calls our overloaded operator functions.
Middle = pdata [(left + right)/2]; // calculates the median value.
Do {
While (pdata [I] <middle) & (I <right) // The number of scans from the left greater than the value
I ++;
While (pdata [J]> middle) & (j> left) // The number of scans from the right side greater than the value
J --;
If (I <= J) // a pair of values is found.
{
// Exchange
Itemp = pdata [I];
Pdata [I] = pdata [J];
Pdata [J] = itemp;
I ++;
J --;
}
} While (I <= J); // If the subscripts on both sides of the scan are staggered, stop (once completed)
// When the left part has a value (left <j), recursive left half edge
If (left <j)
Run (pdata, left, J );
// When the right part has a value (Right> I), recursive Right Half Edge
If (Right> I)
Run (pdata, I, right );
}
Template <class T>
Void quicksort (T * pdata, int count)
{
Run (pdata, 0, Count-1 );
}
Void main ()
{
Cmydata data [] = {
Cmydata (8, "xulion "),
Cmydata (7, "sanzoo "),
Cmydata (6, "wangjun "),
Cmydata (5, "vckbase "),
Cmydata (4, "jacky2000 "),
Cmydata (3, "cwally "),
Cmydata (2, "vcuser "),
Cmydata (1, "isdong ")
};
Quicksort (data, 8 );
For (INT I = 0; I <8; I ++)
Cout <data [I]. m_iindex <"<data [I]. getdata () <"/N ";
Cout <"/N ";
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Selectsort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
Reverse Order (worst case)
First round:,-> (itemp = 9),-> (itemp = 8),-> (itemp = 7, 8, 10 (exchange once)
Round 2:,->, (itemp = 8)-> (itemp = 8), 8, 9, 10 (exchange once)
First round:,-> (itemp = 9), (0 switching)
Cycles: 6
Number of exchanges: 2
Others:
Round 1:,-> (itemp = 8),-> (itemp = 7),-> (itemp = 7), 10, 8, 9 (exchange once)
Round 2:,-> (itemp = 8),-> (itemp = 8), (exchange once)
First round:,-> (itemp = 9), (exchange once)
Cycles: 6
Number of exchanges: 3
Unfortunately, the number of loops required by the algorithm is still 1/2 * (n-1) * n. Therefore, the algorithm complexity is O (n * n ).
Let's look at his exchange. Each outer loop generates only one exchange (only one minimum value ). So F (n) <= N
So we have f (n) = O (n ). Therefore, when data is messy, it can reduce the number of exchanges.
4. insert method:
The insertion method is more complex. The basic working principle is to draw a card, find the corresponding position in the front card, and then continue to the next
# Include <iostream. h>
Void insertsort (int * pdata, int count)
{
Int itemp;
Int IPOs;
For (INT I = 1; I <count; I ++)
{
Itemp = pdata [I];
IPOs = I-1;
While (IPOs> = 0) & (itemp <pdata [IPOs])
{
Pdata [IPOs + 1] = pdata [IPOs];
IPOs --;
}
Pdata [IPOs + 1] = itemp;
}
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Insertsort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
Reverse Order (worst case)
First round: 10, 9, 8, 7-> 9, 10, 8, 7 (switching once) (loop once)
Round 2: 9, 10, 8, 7-> 8, 9, 10, 7 (switching once) (looping twice)
First round: 8, 9, 10, 7-> 7, 8, 9, 10 (switching once) (looping three times)
Cycles: 6
Number of exchanges: 3
Others:
First round: 8, 10, 7, 9-> 8, 10, 7, 9 (0 switching) (1 loop)
Round 2: 8, 10, 7, 9-> 7, 8, 10, 9 (switching once) (looping twice)
First round: 7, 8, 10, 9-> 7, 8, 9, 10 (switching once) (loop once)
Number of cycles: 4
Number of exchanges: 2
The behavior analysis at the end of this section actually creates an illusion that this algorithm is the best in a simple algorithm, but it is not,
Although the number of cycles is not fixed, we can still use the O method. From the above results, we can see that the number of cycles F (n) <=
1/2 * n * (n-1) <= 1/2 * n. So its complexity is still O (N * n) (here, we will explain it if it is not to show these simple
If the order is different, the number of exchanges can still be deduced in this way ). Now let's look at the exchange. In terms of appearance, the number of exchanges is O (n) (derivation is similar
Select method), but each time we want to perform the '=' operation with the same number of times as the inner loop. For a normal exchange, we need three times '='
This is obviously a bit more, so we are wasting time.
In the end, I personally think that the selection method is the best in simple sorting algorithms.
Ii. advanced sorting algorithms:
In the advanced sorting algorithm, we will only introduce this one, and it is also the fastest I know (among the materials I have read.
It still looks like a binary tree. First we select a median value in the middle program, we use the median value in the array, and then
Place the smaller one on the left and the larger one on the right (the specific implementation is to find the two sides and find a pair of backend switches ). Then enable
Use this process (the easiest method-recursion ).
1. Quick sorting:
# Include <iostream. h>
Void run (int * pdata, int left, int right)
{
Int I, J;
Int middle, itemp;
I = left;
J = right;
Middle = pdata [(left + right)/2]; // calculates the median value.
Do {
While (pdata [I] <middle) & (I <right) // The number of scans from the left greater than the value
I ++;
While (pdata [J]> middle) & (j> left) // The number of scans from the right side greater than the value
J --;
If (I <= J) // a pair of values is found.
{
// Exchange
Itemp = pdata [I];
Pdata [I] = pdata [J];
Pdata [J] = itemp;
I ++;
J --;
}
} While (I <= J); // If the subscripts on both sides of the scan are staggered, stop (once completed)
// When the left part has a value (left <j), recursive left half edge
If (left <j)
Run (pdata, left, J );
// When the right part has a value (Right> I), recursive Right Half Edge
If (Right> I)
Run (pdata, I, right );
}
Void quicksort (int * pdata, int count)
{
Run (pdata, 0, Count-1 );
}
Void main ()
{
Int data [] = {10, 9, 8, 7, 6, 5, 4 };
Quicksort (data, 7 );
For (INT I = 0; I <7; I ++)
Cout <data [I] <"";
Cout <"/N ";
}
I have not provided behavior analysis here, because this is very simple. We will analyze the algorithm directly: first, we will consider the ideal situation.
1. The size of the array is the power of 2, so that the split can always be divisible by 2. Assume that it is the k power of 2, that is, K = log2 (n ).
2. Each time we select a value that is just a median value, the array can be classified.
First layer recursion, loop N times, second layer loop 2*(N/2 )......
So there are n + 2 (n/2) + 4 (n/4) +... + N * (n/n) = N +... + n = K * n = log2 (n) * n
Therefore, the algorithm complexity is O (log2 (n) * n)
In other cases, it will only be worse than this case. The worst case is that every time the selected middle is the minimum or maximum value, it will change
Exchange (worse because recursion is used ). But what do you think is the probability of such a situation ?? You are completely
Don't worry about this problem. Practice has proved that quick sorting is always the best in most cases.
If you are worried about this problem, you can use heap sorting. This is a stable O (log2 (n) * n) algorithm, but it is usually slow.
In quick sorting (because the heap needs to be reorganized ).
Iii. Other sorting
1. Bidirectional bubbling: