preface
In the first half of this course, when talking about linked list and stack queue, there are many implementation methods. For example, linked list can be realized by using pointer and structure, or stack and queue can be realized directly through STL library. However, this course mainly focuses on array simulation for the following reasons:
Efficiency issues. The efficiency of array simulation is relatively high. If you use the structure and pointer to realize the linked list, you have to create a new node every time you save it, but this efficiency is very slow. Generally, there are 100000 levels, so it takes too much time to implement it with dynamic linked list, and all of it is spent on creating new nodes.
As shown in the figure above, this dynamic storage method is generally not used. However, this can be used for optimization. The optimization method is to directly initialize the number of nodes first, that is, do not go to new. In this way, the principle is similar to that of array, saving the time of new.
One. Linked list and adjacency list
1. Using array to simulate single linked list, the most important thing is to simulate adjacent list
(1) Function of adjacency table
one ⃣ storage diagram
two ⃣ Storage tree
(2) Method of array simulating single linked list
First, we need two arrays E and ne. The former is used to store elements. The subscript of e represents the sequence number of the node, and the content represents the elements stored in the node. The ne array stores the position of the next node, its subscript represents the sequence number of the node, and the sequence number of the next node pointed by the node is stored in it.
(3) The template code is as follows:
one ⃣ Firstly, set the necessary element array e,he, header pointer head and pointer idx
two ⃣ Write initialization function. At first, the head is - 1, because - 1 represents the number at the end of the list. IDX is 0, pointing to the first.
three ⃣ ﹥ write the insertion function.
The first insertion case: the insertion is the head node. There are three steps. The first step is to store the data to the node e[idx]=new pointed to by the current IDX. The second step is to point the ne of the new point to the next node that the original head points to. Step 3: head refers to returning to the new head node. Step 4, idx++
The second insertion case: insert the point after the point with node k. There are four steps. The first step is to save the data of the new node, e[idx]=new; The second step is to point the ne of the new node to the ne of the k node; The third step is to point the ne of the k node to the new node; Step 4 idx++
four ⃣ Write the delete node function.
The first case: delete the node behind node K. Directly point ne of the node K to one, that is, ne[k]=ne[ne[k]]; In the second case, you can delete the head node directly by head=ne[head]
The code of the second case is as follows:
The total code is as follows:
// head storage chain header, e [] storage node value, ne [] storage node next pointer, idx indicates which node is currently used int head, e[N], ne[N], idx; // initialization void init() { head = -1; idx = 0; } // Insert a number a in the chain header void insert(int a) { e[idx] = a, ne[idx] = head, head = idx ++ ; } // To delete a header node, you need to ensure that the header node exists void remove() { head = ne[head]; }
2. Using array to simulate double linked list, the most important thing is to optimize some problems
(1) Method of simulating double linked list with array
First, three arrays are needed. e,l,r, e are used to store what the elements of the node are, l is used to store who the left side of the node points to, and r is used to store who the right side of the node points to. Then, an idx is used as a pointer to traverse the linked list. Note that we directly take e[o] as the head and the last node as the tail
(2) The template code is as follows:
one ⃣ Set the necessary array.
two ⃣ Write initialization function.
three ⃣ ﹥ write the insertion function. When inserting, you can insert the right side or the left side.
The first case: insert a new node to the right of k; There are five steps: the first step is to save the node data, e[idx]=new; The second step is to point the right side of the new node to the right side of the original node; The third step is to point the left of the new node to the left of the original node; The fourth step is to point the left of the next node of the original node to the new node; Step 5 point the right side of the original node to the new node.
In the second case, a new node is inserted to the left of k. Directly insert a new node on the right side of the first node of k, then call the first case.
four ⃣ ﹥ write deletion function.
Directly point the right side of the node to the left side of the node, and point the left side of the node to the right side of the node.
The total code is as follows:
// e [] represents the value of the node, l [] represents the left pointer of the node, r [] represents the right pointer of the node, and idx represents which node is currently used int e[N], l[N], r[N], idx; // initialization void init() { //0 is the left endpoint and 1 is the right endpoint r[0] = 1, l[1] = 0; idx = 2; } // Insert a number x to the right of node a void insert(int a, int x) { e[idx] = x; l[idx] = a, r[idx] = r[a]; l[r[a]] = idx, r[a] = idx ++ ; } // Delete node a void remove(int a) { l[r[a]] = l[a]; r[l[a]] = r[a]; }
II. Stack and queue
1. Definition of stack and queue: stack is first in first out, and queue is first in first out.
2. Various operations of stack: directly simulate with array, and then indicate with a pointer to. Insert operation: stk[tt++]=x. Pop up operation: tt -. Judge whether the stack is empty: if (tt > 0) not empty else empty. Stack top: skt[tt]
// tt indicates the top of the stack int stk[N], tt = 0; // Insert a number to the top of the stack stk[ ++ tt] = x; // Pop a number from the top of the stack tt -- ; // Stack top value stk[tt]; // Determine whether the stack is empty if (tt > 0) { }
3. Various operations of queue: insert elements at the end of the queue and pop elements at the head of the queue. q[N],hh,yy=-1. Insert operation: q[++tt]=x;
Pop up hh + +; Judge whether the queue is empty: if (hh < = TT) not empty else empty; Take out the head and tail elements q[hh],q[tt].
// hh stands for the head of the team and tt stands for the tail of the team int q[N], hh = 0, tt = -1; // Insert a number to the end of the queue q[ ++ tt] = x; // Pop up a number from the head of the team hh ++ ; // Value of team leader q[hh]; // Determine whether the queue is empty if (hh <= tt) { }
Circular queue:
// hh indicates the head of the team and tt indicates the last position at the end of the team int q[N], hh = 0, tt = 0; // Insert a number to the end of the queue q[tt ++ ] = x; if (tt == N) tt = 0; // Pop up a number from the head of the team hh ++ ; if (hh == N) hh = 0;//That's the only difference // Value of team leader q[hh]; // Determine whether the queue is empty if (hh != tt) { }
4. Monotone stack:
(1) Corresponding question type: given a sequence, find out where the number on the left (right) of each number is nearest to him and smaller (larger) than it. Examples are as follows:
(2) Violent practices:
(3) Optimization practices:
First, consider whether it is monotonic. Suppose you scan with the pointer starting from scratch, because you are looking for the smallest element nearest to him on the left of an element, so if the new element is larger than him, you won't go forward. Therefore, this is monotonic. We can use a stack. We can set a monotonic stack, that is, Each time an element is read in, first locate the pointer at the stack head to the nearest one smaller than the element. If the pointer at the top of the stack is not empty, it means that the smallest one has been found. Just output the result directly, and then add it to the monotone stack. If it is not found, output - 1, and then compare it, If this element is small, add it to the station
(4) Knowledge points learned in this section (key points):
First: simple CIN and cout are about ten times slower than scanf, so if there are many inputs and outputs, scanf is very recommended
Second: in the main function or where CIN and cout are used, add:
cin.tie(0); ios::sync_with_stdio(false);
In this way, the read-in and read-out time can be compared with scanf
(5) The total code is as follows:
Common model: find out that the nearest one on the left of each number is larger than it/Small number int tt = 0; for (int i = 1; i <= n; i ++ ) { while (tt && check(stk[tt], i)) tt -- ; stk[ ++ tt] = i; }
5. Monotonic queue:
Template question: the of sliding window is the maximum and minimum value
Use the queue to maintain the sliding window: the first step is to slide the sliding queue to the right, slide a team leader in, and then a team leader out. Find monotonicity: when traversing in turn, as long as the front point is larger than the back point, it can not be selected, so it can be deleted directly. This becomes a monotonous queue.
One idea of monotone queue and monotone stack is:
First consider the simple algorithm of this problem, the violence simulation side; Then consider which elements in this simple algorithm are useless, delete these useless elements, and then see whether the remaining elements constitute monotonicity; If there is monotonicity, you can do optimization - take extreme values, take both ends, and use dichotomy to find elements.
Note: if the container of STL does not turn on O2 optimization, it will be 2 slower than that of array simulation
Common model: find the maximum value in the sliding window/minimum value int hh = 0, tt = -1; for (int i = 0; i < n; i ++ ) { while (hh <= tt && check_out(q[hh])) hh ++ ; // Judge whether the team leader slides out of the window. Note that since q[hh] stores subscripts, it is necessary to judge i-k+1. In this expression, I represents the current subscript and K represents the length of the sliding window. Therefore, the starting point coordinates of the sliding window of the current coordinates can be. Therefore, when i-k+1 > q[hh], it is already slid out, and hh + + is required; while (hh <= tt && check(q[tt], i)) tt -- ; q[ ++ tt] = i; }
The code of the sliding window example is as follows:
Note: when judging whether the sliding window slides out, if you move one cell at a time in the title, you can use if, but if the title requires moving multiple cells at a time, you should use while
Three. KMP
1. Definition:
Give a long string S[N] and a template string P[M], use the template string to match the long string, and output true if there is a part containing the template string in the long string;
2. Thinking direction:
(1) Let's think about how violent algorithms do
(2) Then think about how to optimize
3. Violence algorithm:
4. Optimization direction
Preprocess the template string P[N] to find out the position where the suffix of a point is the same as the prefix of the point; In this way, when the sub template string matches the long string, if the matching fails to a certain position of the long string, we know the maximum length that can be moved, because we find the position where a point suffix in the template string is the same as the point prefix.
This is the meaning of the next array in KMP. next[i] indicates that the suffix in the string of this section is equal to the prefix starting from 1. What is the longest length
The matching process is as follows:
5. The code is as follows:
Note:
one ⃣ The S array is used to store the long string array and the P array is used to store the template array, and both of them store data from 1
two ⃣ When p matches s data, in order to ensure no error, P[j+1] and S[i] are used for matching. Therefore, when scanning, P array is scanned from 0, so P[j+1] is valuable. S array is scanned from 1, because s array stores data from 1
The code is as follows:
// s [] is the long text, p [] is the pattern string, n is the length of s, and m is the length of p Find the of pattern string Next Array: In fact, it is a match between yourself and yourself P As a long string, one P As template string for (int i = 2, j = 0; i <= m; i ++ ) //i start from 2, because if next[2] is unsuccessful, start directly from the beginning, that is, i=1, so it doesn't matter. Just start from 2 { while (j && p[i] != p[j + 1]) j = ne[j];//If the new bit is not matched successfully, the loop continues to skip next [], until the next successful bit is found or selected to the first place of the template if (p[i] == p[j + 1]) j ++ ;//Match successful ne[i] = j;//Since the data is stored from 1, the subscript j represents the length } // matching for (int i = 1, j = 0; i <= n; i ++ ) { while (j && s[i] != p[j + 1]) j = ne[j]; if (s[i] == p[j + 1]) j ++ ; if (j == m) { j = ne[j]; // After successful matching, the logic jumps to the next ne[j] to continue matching } }