Using STL to implement DFS/BFS algorithms-policy-based Class Design

Source: Internet
Author: User
Use STL to implement DFS/BFS Algorithms -- Policy-based Class DesignBefore introducing the boost. multi_index container, I think it is necessary to sort out the DFS/BFS code first. One of the reasons for this change is that I recently read Andrei Alexandrescu's modern c ++ design, which is deeply inspired, the first chapter of the book describes policy-based class design, so I also want to refer to this design method to rewrite the code once. Another reason is that the queue container and stack container are used in the original code. They will delete the searched nodes. If we want to re-query the nodes in the container, all nodes must be retained, so the two containers cannot be used. I want to use an STL container close to boost. multi_index to rewrite it, and then gradually transition to the real multi_index container. Let's first talk about What containers to use. At first I also thought about using the vector container. However, because our final goal is the multi_index container, it cannot be randomly accessed like a vector, it can only implement the list container function. To facilitate the transition to multi_index, I finally chose list. Another reason that vector cannot be used is that DFS requires that elements be inserted in the middle of the container (we will soon see this), and vector is not suitable for this operation. Without the queue and stack, we need to use an iterator to distinguish the searched and unsearched nodes in the list. I put the searched and unsearched nodes on both ends of the List, and the iterator points to their demarcation line, that is, the node before the iterator is the searched node, the following node is not searched. When we need to extract the next node for processing, we only need to move the iterator behind it. For the current node, call nextstep () to obtain the next layer of node, it depends on DFS or BFs to determine how to insert the list. If it is DFS, insert the new node to the end of iterator; if it is BFs, insert the new node to the back end of the list. When the iterator reaches the end of the list, the search is complete. It may be more intuitive to use a diagram: If we have initial problem status A, from a, we can generate the status B, C, and D of the next layer (or next step, the status E, F, and G of the next layer can also be generated from B. For DFS, our search order should be A, B, E ..., The order of status generation should be a, B, c, d, e, f, g ..., When we use list as the storage status tree, we need to use an iterator (the arrow in the figure) to point to the current processing status. We put initial State A into the list container, and the arrow points to it, and then iterate in the following way: Read the elements pointed by the arrow (a State node ), generate the next layer of nodes (all possible next statuses) based on this state and insert them one by one to the end of the positions pointed by the arrows (the insertion sequence of nodes on the same layer is not very important. For ease of expression, let's assume that we add data to the stack in the order of D, C, and B), move the arrow behind one cell, and so on, read B, and generate the insert arrow behind E, F, and G. In this way, the list and arrow act like the stack. The elements on the left of the arrow are processed state nodes, and the elements on the right are state nodes to be processed. Let's take a look at BFs. The correct search order should be a, B, c, d, e ..., This order is exactly the same as the order of status generation. We still use list as the container for storing the status tree, but the positions for inserting new nodes are different. We put initial State A into the list container, and the arrow points to it, and then iterate in the following way: Read the elements pointed by the arrow (a State node ), generate the next node (all possible next state) based on this state, insert them to the end of the list one by one, and then move the arrow to the end of the list. Then, read B, generate E, F, and g and insert them to the end of the list. In this way, the list and arrow act like a queue. Similar to DFS, the element on the left of the arrow is the State node that has been processed, and the element on the right is the State node to be processed. Now let's take a look at how to design a policy-based DFS/BFS search algorithm. First, we should design a class template, rather than a previous function template. This class template provides a member function for users to call to complete the search. My name for this class template is statespacetreesearch, which is as follows: Template <class state, template <class> class searchinserter, template <class> class checkdup> class statespacetreesearch {...} It is designed to accept three template parameters, each representing a strategy to solve a specific problem. The first template parameter state is a problem state class written for a specific problem (for example, sudokustate, queenstate, and sokostate we have seen before ), its data members should be used to save the data required to represent the problematic status (for example, sudokustate uses a two-dimensional integer array to represent the status of the sudoku game, while queenstate uses a one-dimensional integer array to represent the Board state, and so on. Its member functions should contain at least nextstep () and istarget (), the former is used to calculate the various possible states of the next step from a State, and the latter is used to determine whether the current State meets the requirements of the answer. Of course, for convenience or other requirements (such as the requirements for re-checking algorithms ), the problematic status class may also need to provide other member functions (such as operator <, operator>, operator =, operator <, hash (), and so on ). The second template parameter searchinserter is a template parameter used to specify whether to use BFS or DFS. As we analyzed earlier, if list is used to store state nodes, the difference between BFS and DFS lies only in the insertion method of new nodes. We need to provide an insert method for BFS and DFS respectively (this is the policy). When the user uses different insert methods to instantiate statespacetreesearch, BFs or DFS is selected. The third template parameter checkdup is also a template parameter. It is good, and is used to specify a policy for re-checking the status. We have provided four re-checking policies in the previous version (you do not know whether you can remember them, they are nocheckdup, sequencecheckdup, ordercheckdup, and hashcheckdup ), here we also use these four policies. However, a small change is about hashcheckdup. In the previous version, checkdup is used by the caller to select an appropriate re-query method, and an instance is used to generate a function object, then, pass the function object as a function call parameter to the function template of the DFS/BFS algorithm. The instantiation code of the checkdup object is the responsibility of the user, so that the code can be more flexible, but it increases the burden on the user (the user may write wrong code ). After the policy-based design is re-performed, the user only needs to specify the re-query policy, and does not need to take charge of the instance to produce the checkdup object. Statespacetreesearch is responsible for the instantiation of checkdup objects. In this way, various checkdup policies must be instantiated in the same format. When I looked back at the code of the four re-check policies, I found that nocheckdup, sequencecheckdup, and ordercheckdup all have only one template parameter, hashcheckdup has two template parameters. Although its second template parameter has a default value, it still causes compilation errors of Some compilers. Therefore, we need to make hashcheckdup take only one template parameter. It is not too difficult to do so. You will understand the following code. // Simulate the form, used to check whether the status node has repeated template <class T> struct nocheckdup: STD: unary_function <t, bool> {bool operator () (const T &) const {return false ;}}; // use a vector container to check whether the status node is repeated, linear Complexity // requires the status class to provide operator = template <class T> class sequencecheckdup: STD: unary_function <t, bool> {typedef vector <t> cont; cont States _; public: bool operator () (const T & S) {typename cont: iterator I = find (States _. begin (), States _. E Nd (), S); if (I! = States _. end () // The status already exists, repeating {return true;} States _. push_back (s); // The status is not repeated and the returned false ;}}; // use the set container to check whether the status node is repeated, logarithm complexity // The required status class provides operator <template <class T> class ordercheckdup: STD: unary_function <t, bool> {typedef set <t> cont; cont States _; public: bool operator () (const T & S) {typename cont: iterator I = States _. find (s); if (I! = States _. end () // The status already exists, repeating {return true;} States _. insert (I, S); // The status is not repeated, and the return false ;}}; // similar operation, use the hash_set container to check whether the status node is repeated // The status class provides operator = and the hash () member function template <class T> class hashcheckdup: STD: unary_function <t, bool> {struct hashfcn {size_t operator () (const T & S) const {return S. hash () ;}}; typedef hash_set <t, hashfcn> cont; cont States _; public: hashcheckdup (): States _ (100, hashfcn () {} Bool operator () (const T & S) {If (States _. Find (s )! = States _. end () // The status already exists, repeating {return true;} States _. insert (s); // The status is not repeated. Return false; }}; the first three checkdup versions are the same as those of the old version, while the last hashcheckdup has a small change. The new interface requires the status class to provide operator = and hash () member functions, which are used to calculate the hash value. Hashcheckdup defines a nested function object class hashfcn. It calculates the hash value by calling the hash () member function provided by the state class and returns it to the hash_set container. The other parts are the same as the old version, and the changes are small. Now, all the four re-check policies have the same template parameter format. Let's look back at the searchinserter corresponding to BFS and DFS respectively. Their code is as follows: // The new node insertion Policy template corresponding to the BFS algorithm <class cont> class bfsinserter {public: bfsinserter (cont & C): C _ (c) {} typedef typename cont: iterator; typedef typename cont: value_type; void operator () (iterator it, const value_type & V) {C _. push_back (V); // Insert the new node to the end of the list, that is, after the unsearched node} PRIVATE: cont & C _;}; // The new node insertion Policy template <class cont> class dfsinserter {Public: dfsinserter (cont & C): C _ (c) {} typedef typename cont: iterator; typedef typename cont: value_type; void operator () (iterator it, const value_type & V) {C _. insert (++ it, V); // before the new node is inserted to the unsearched node} PRIVATE: cont & C _ ;}; bfsinserter and dfsinserter are both function object class templates, they use the container that saves the status space tree as the template parameter and provide an operator () operator. This operator function accepts an iterator pointing to the container and a value to be inserted into the container, it is responsible for executing the insert action. BFS and DFS correspond to different insertion policies respectively. The available searchinserter and checkdup policies are ready. before entering the implementation section of statespacetreesearch, let's discuss a small problem, it is about the order and default value of the two template parameters searchinserter and checkdup. Andrei Alexandrescu said in modern c ++ design that we should put the policies that are most likely to be explicitly specified by users first, at the same time, the user may use a policy as the default value of this policy. So the first question is: What default values should be used by searchinserter and checkdup respectively? In my opinion, many actual problems require a solution with a minimum number of steps. In this case, we should use BFS instead of DFS, so I chose bfsinserter as the default value of searchinserter. As for checkdup, I chose nocheckdup with the least impact on performance as the default value. Question 2: Which of the following statements is explicitly specified for searchinserter and checkdup? I think it seems similar, so I arranged the current order at random. Go to statespacetreesearch. Let's repeat it again. We usually solve a specific search problem. Besides the state, searchinserter, and checkdup mentioned above, what else do we need? By the way, it is an initial state (it should be a State object) and a callback function (which can also be regarded as a policy) about finding the answer ). The initial status should undoubtedly be passed in as a function call parameter, but what about the callback function? Should it also become a type of statespacetreesearch policy? In my opinion, the three parts of State, searchinserter, and checkdup have formed a specific solution. Users should use these three components to create a specific problem (such as pushing boxes) the solution is as follows: statespacetreesearch <sokostate, bfsinserter, ordercheckdup> sokosearch; and when you need to answer a specific question (such as the question of the push box of the item body) of this question, you should execute this solution sokosearch and pass in the question, waiting for sokosearch to return the answer, such as sokosearch (initstate); then, should the callback function executed after the answer be within the scope of the problem solution or the scope of the specific question solution? I think it will be more flexible to put it into the latter. For example, for the same question (such as a push box), we can use a sokosearch object to answer multiple specific questions, and different callback functions can be selected for each question. For example, sokosearch (initstate1, printanswerandcontinue); sokosearch (initstate2, printanswerandstop); so I designed statespacetreesearch as a function object template, that is, it provides an operator (), the operator function accepts two parameters: one is the initial state of the problem, and the other is the callback function after the answer is found. The return value of the function is an integer, indicating the number of answers found at the end of the search. The Code of statespacetreesearch is as follows: // status space tree search template // state: problematic status class, which provides nextstep () and istarget () member functions // searchinserter: you can select BFS or DFS // checkdup: status check algorithm. You can select nocheckdup, hashcheckdup, ordercheckdup, and Other templates <class state, template <class> class searchinserter = bfsinserter, template <class> class checkdup = nocheckdup> class statespacetreesearch {public: typedef list <State> cont; typedef typename cont: iterator; templat E <class func> int operator () (const State & initstate, func afterfindsolution) const // initstate: initialization state, class State should provide member functions nextstep () and istarget (), // nextstep () returns all possible states in the Next Step Using Vector <State>. // istarget () is used to determine whether the current State meets the required answers. // afterfindsolution: similar to the syntax, after a valid answer is found, it accepts a // const State & and returns a bool value. True indicates that the search is stopped, and // false indicates that the search is continued. // return: number of answers found {checkdup <State> checkdup; cont states; searchinserter <cont> INS Erter (States); States. push_back (initstate); iterator head = states. begin (); // point to the vector <State> nextstates; int n = 0; // number of answers found in the record bool stop = false; while (! Stop & head! = States. end () {State S = * head; // search for a node nextstates. clear (); S. nextstep (nextstates); // generates the next node from the search point for (typename vector <state >:: iterator I = nextstates. begin (); I! = Nextstates. end (); ++ I) {if (I-> istarget () {// locate a target State + + N; If (stop = afterfindsolution (* I )) // process the result and decide whether to stop {break;} else {// not the target status. Then, determine whether to put it in the search queue if (! Checkdup (* I) // only puts non-repeated statuses into the search queue {inserter (Head, * I) ;}}++ head; // move the pointer to the next element} return n ;}; I think the comments in the Code have already been explained a lot. I will only briefly explain them here. Statespacetreesearch uses list <State> as the container for storing the state space tree. However, this container is not a data member of statespacetreesearch, but a local variable in the operator () operator function. That is to say, the container is generated when the search is executed each time. The container is destroyed after the search, and will not be retained between the two searches. In this way, the instance generates a statespacetreesearch class that can be searched multiple times. The specific search is executed by the operator () operator function, where checkdup and searchinserter of the specified policy are instantiated and used. The code in operator () is written according to the analysis of DFS and BFs at the beginning of this article. The following uses the new DFS/BFS algorithm: statespacetreesearch <sokostate, bfsinserter, ordercheckdup> sokosearch; int n = sokosearch (initstate, printanswer, I also paste the old version of code: ordercheckdup <sokostate> checkdup; int n = breadthfirstsearch (initstate, printanswer, checkdup); if you are a user, which method is more convenient for you to use?

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.