COIN-OR::LEMON - Graph Library

Ticket #168: hopcroft_karp_55711f25c446.patch

File hopcroft_karp_55711f25c446.patch, 19.8 KB (added by Poroszkai Daniel, 12 years ago)

Hopcroft-Karp

  • lemon/Makefile.am

    # HG changeset patch
    # User Daniel Poroszkai <poroszd@inf.elte.hu>
    # Date 1328397453 -3600
    # Node ID 55711f25c446935bd8fda1fa3c6b556d3679c041
    # Parent  d6df00b92912ec0c3f5863f7abc7a6f36cc2c2b9
    Hopcroft-Karp algorithm
    
    diff --git a/lemon/Makefile.am b/lemon/Makefile.am
    a b  
    9393        lemon/grid_graph.h \
    9494        lemon/grosso_locatelli_pullan_mc.h \
    9595        lemon/hartmann_orlin_mmc.h \
     96        lemon/hopcroft_karp.h \
    9697        lemon/howard_mmc.h \
    9798        lemon/hypercube_graph.h \
    9899        lemon/karp_mmc.h \
  • new file lemon/hopcroft_karp.h

    diff --git a/lemon/hopcroft_karp.h b/lemon/hopcroft_karp.h
    new file mode 100644
    - +  
     1/* -*- mode: C++; indent-tabs-mode: nil; -*-
     2 *
     3 * This file is a part of LEMON, a generic C++ optimization library.
     4 *
     5 * Copyright (C) 2003-2011
     6 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
     7 * (Egervary Research Group on Combinatorial Optimization, EGRES).
     8 *
     9 * Permission to use, modify and distribute this software is granted
     10 * provided that this copyright notice appears in all copies. For
     11 * precise terms see the accompanying LICENSE file.
     12 *
     13 * This software is provided "AS IS" with no warranty of any kind,
     14 * express or implied, and with no claim as to its suitability for any
     15 * purpose.
     16 *
     17 */
     18
     19#ifndef LEMON_HOPCROFT_KARP_H
     20#define LEMON_HOPCROFT_KARP_H
     21
     22#include <lemon/core.h>
     23#include <list>
     24
     25/// \ingroup matching
     26/// \file
     27/// \brief Hopcroft-Karp algorithm.
     28namespace lemon {
     29
     30  /// \brief The Hopcroft-Karp bipartite matching algorithm
     31  ///
     32  /// Finds maximal matching in a given bipartite
     33  /// graph using the Hopcroft-Karp algorithm,
     34  /// having \f$O(e\sqrt{n})\f$ complexity.
     35  template <typename BPG>
     36  class HopcroftKarp {
     37  public:
     38      /// Type of the bipartite graph
     39    typedef BPG BpGraph;
     40    /// Type of the matching map
     41    typedef typename BPG::template NodeMap<typename BPG::Edge> MatchingMap;
     42  private:
     43    TEMPLATE_BPGRAPH_TYPEDEFS(BpGraph);
     44
     45    const BpGraph& _bpg;
     46    MatchingMap* _matching;
     47    bool _local_matching;
     48
     49  protected:
     50    void createStructures() {
     51      if (!_matching) {
     52        _matching = new MatchingMap(_bpg, INVALID);
     53        _local_matching = true;
     54      }
     55    }
     56
     57    void destroyStructures() {
     58      if (_local_matching) {
     59        delete _matching;
     60      }
     61    }
     62
     63    HopcroftKarp() {}
     64
     65  public:
     66    /// \brief Constructor
     67    ///
     68    /// Constructs the class on the given bipartite graph.
     69    HopcroftKarp(const BpGraph& bpg) : _bpg(bpg),
     70                                       _matching(0),
     71                                       _local_matching(false)
     72    {}
     73
     74
     75    /// \brief Destructor
     76    ///
     77    /// Destructor.
     78    ~HopcroftKarp() {
     79      destroyStructures();
     80    }
     81
     82    /// \brief Sets the matching map
     83    ///
     84    /// Sets the matching map.
     85    /// If you don't use this function before calling \ref init(),
     86    /// an instance will be allocated automatically.
     87    /// The destructor deallocates this automatically allocated map,
     88    /// of course.
     89    /// This member is not to initialize the algorithm with a valid
     90    /// matching; use \ref matchingInit() instead.
     91    ///
     92    /// \return <tt>(*this)</tt>
     93    HopcroftKarp& matchingMap(MatchingMap& map) {
     94      _matching = &map;
     95      _local_matching = false;
     96      return *this;
     97    }
     98
     99    /// \brief Returns a const reference to the matching map
     100    ///
     101    /// Returns a const reference to the matching map, which contains
     102    /// the matching edge for every node (and \c INVALID for the
     103    /// unmatched nodes).
     104    const MatchingMap& matchingMap() const {
     105      return *_matching;
     106    }
     107
     108
     109    /// \brief Initializes the algorithm
     110    ///
     111    /// Allocates the matching map if necessary, and sets
     112    /// to empty matching.
     113    ///
     114    /// \pre \ref init() is not called.
     115    void init() {
     116      createStructures();
     117      if (!_local_matching) {
     118        for (NodeIt it(_bpg); it!=INVALID; ++it) {
     119          _matching->set(it, INVALID);
     120        }
     121      }
     122    }
     123
     124    /// \brief Smarter initialization of the matching.
     125    ///
     126    /// Allocates the matching map if necessary, and initializes
     127    /// the algorithm with a trivially found matching: iterating
     128    /// on every edge, take it in if both endnodes are unmatched.
     129    ///
     130    /// \return Size of the found matching.
     131    ///
     132    /// \note heuristicInit() replaces init() regarding the preconditions.
     133    int heuristicInit() {
     134      init();
     135      int size = 0;
     136
     137      for (BlueNodeIt b_it(_bpg); b_it!=INVALID; ++b_it) {
     138        if ((*_matching)[b_it] == INVALID) {
     139          bool matched = false;
     140          for (IncEdgeIt inc(_bpg, b_it); inc != INVALID && !matched; ++inc) {
     141            if ((*_matching)[_bpg.redNode(inc)] == INVALID) {
     142              _matching->set(_bpg.redNode(inc), inc);
     143              _matching->set(b_it, inc);
     144              matched = true;
     145              ++size;
     146            }
     147          }
     148        }
     149      }
     150      return size;
     151    }
     152
     153    /// \brief Initialize the matching from a map.
     154    ///
     155    /// Allocates the matching map if necessary, and
     156    /// initializes the algorithm with a matching given as a bool edgemap,
     157    /// in which an edge is true if it is in the matching.
     158    ///
     159    /// If the given matching is invalid, some edges will be left out.
     160    ///
     161    /// \return \c false if the given matching is invalid.
     162    ///
     163    /// \note matchingInit() replaces init() regarding the preconditions.
     164    bool matchingInit(const BoolEdgeMap& matching) {
     165      init();
     166      bool valid = true;
     167      for(EdgeIt it(_bpg); it!=INVALID; ++it) {
     168        if (matching[it]) {
     169          Node red = _bpg.redNode(it);
     170          Node blue = _bpg.blueNode(it);
     171          if ((*_matching)[red] != INVALID || (*_matching)[blue] != INVALID) {
     172            valid = false;
     173          } else {
     174            _matching->set(red, it);
     175            _matching->set(blue, it);
     176          }
     177        }
     178      }
     179      return valid;
     180    }
     181
     182    /// \brief Executes an augmenting phase
     183    ///
     184    /// Searches a maximal set of vertex disjoint shortest alternating paths.
     185    /// Meaning:
     186    ///  - alternating: connents an unmatched red- and an unmatched blue node,
     187    ///    and exactly every second edge is in the current matching;
     188    ///  - shortest: contain a minimal number of edges;
     189    ///  - vertex disjoint: every vertex belong to at most one path;
     190    ///  - maximal set: a set of path is maximal when it is not expandable
     191    ///    further (and not when there does not exist a set with
     192    ///    more vertex disjoint shortest alternating paths).
     193    ///
     194    /// After a maximal set is found, it applies the augmenting paths,
     195    /// so edges of the matching are taken out, the others are put in
     196    /// the matching.
     197    ///
     198    /// \return The length of the found augmenting paths, or -1 when none
     199    /// found, which occurs if and only if the current matching is maximal.
     200    ///
     201    /// \pre \ref init() must be called before using this function.
     202    int augment() {
     203      IntNodeMap level(_bpg, -1);
     204      std::list<RedNode> act_rednodes;
     205      std::list<BlueNode> act_bluenodes;
     206
     207      for (RedNodeIt r_it(_bpg); r_it != INVALID; ++r_it) {
     208        if ((*_matching)[r_it] == INVALID) {
     209          act_rednodes.push_front(r_it);
     210        }
     211      }
     212
     213      // Raise this flag when a shortest augmenting path is found.
     214      bool path_found = false;
     215      // This nodelist will contain the end nodes of the possible
     216      // augmenting paths.
     217      std::list<BlueNode> path_ends;
     218
     219      // Layer counter
     220      int cur_level = 0;
     221
     222      // Starting from the unmatched red nodes search for unmatched
     223      // blue nodes, using Bfs (but only on alternating paths).
     224      while (!path_found) {
     225        while (!act_rednodes.empty()) {
     226          RedNode red = act_rednodes.front();
     227          act_rednodes.pop_front();
     228          level[red] = cur_level;
     229          for (IncEdgeIt it(_bpg, red); it!=INVALID; ++it) {
     230            BlueNode blue(_bpg.blueNode(it));
     231            if (level[blue] == -1) {
     232              act_bluenodes.push_front(blue);
     233              level[blue] = cur_level + 1;
     234              path_found |= ((*_matching)[blue] == INVALID);
     235            }
     236          }
     237        }
     238
     239        if (!path_found) {
     240          if (act_bluenodes.empty()) return -1;
     241          while (!act_bluenodes.empty()) {
     242            BlueNode blue = act_bluenodes.front();
     243            act_bluenodes.pop_front();
     244            RedNode red = _bpg.redNode((*_matching)[blue]);
     245            act_rednodes.push_front(red);
     246          }
     247        } else {
     248          for (typename std::list<BlueNode>::iterator it=act_bluenodes.begin();
     249               it != act_bluenodes.end();
     250               ++it) {
     251            if ((*_matching)[*it] == INVALID) path_ends.push_front(*it);
     252          }
     253          // Now path_ends contains nodes that are ends of
     254          // shortest alternating paths.
     255        }
     256        cur_level += 2;
     257      }
     258
     259      // The current_edge structure assures that edges iterated at most once
     260      typename BpGraph::template BlueNodeMap<IncEdgeIt*> current_edge(_bpg, 0);
     261
     262      // Stack for the Dfs
     263      std::list<BlueNode> stack;
     264
     265      // Searching backward, starting from the previously found
     266      // blue nodes, we build vertex disjoint alternating paths.
     267      typename std::list<BlueNode>::iterator n_it = path_ends.begin();
     268
     269      while (n_it != path_ends.end()) {
     270        stack.push_front(*n_it);
     271        path_found = false;
     272        while (!stack.empty() && !path_found) {
     273          BlueNode b = stack.front();
     274          if (current_edge[b] == 0) {
     275            current_edge[b] = new IncEdgeIt(_bpg, b);
     276          } else {
     277            ++(*current_edge[b]);
     278          }
     279          while (*current_edge[b] != INVALID &&
     280                 level[_bpg.redNode(*current_edge[b])] != level[b] - 1) {
     281            ++(*current_edge[b]);
     282          }
     283          if (*current_edge[b] == INVALID) {
     284            stack.pop_front();
     285          } else {
     286            RedNode r = _bpg.redNode(*current_edge[b]);
     287            if ((*_matching)[r] == INVALID) {
     288              path_found = true;
     289            } else {
     290              level[r] = -1; // Do not visit this node again
     291              stack.push_front(_bpg.blueNode((*_matching)[r]));
     292            }
     293          }
     294        }
     295        if (path_found) {
     296          BlueNode next = *n_it;
     297          RedNode r;
     298          BlueNode b;
     299          Edge e;
     300          while (next != INVALID) {
     301            e = *current_edge[next];
     302            b = next;
     303            r = _bpg.redNode(e);
     304            next = (*_matching)[r] == INVALID ?
     305              INVALID :_bpg.blueNode((*_matching)[r]);
     306            _matching->set(b, e);
     307            _matching->set(r, e);
     308          }
     309        }
     310
     311        stack.clear();
     312        ++n_it;
     313      }
     314      return cur_level - 1;
     315    }
     316
     317    /// \brief Executes the algorithm
     318    ///
     319    /// It runs augmenting phases until the optimal solution is reached.
     320    ///
     321    /// \pre \ref init() must be called before using this function.
     322    void start() {
     323      while (augment() > 0) {}
     324    }
     325
     326    /// \brief Runs the algorithm.
     327    ///
     328    /// hk.run() is just a shorthand for:
     329    ///
     330    ///\code
     331    /// hk.heuristicInit();
     332    /// hk.start();
     333    ///\endcode
     334    void run() {
     335      heuristicInit();
     336      start();
     337    }
     338
     339    /// \brief Size of the matching
     340    ///
     341    /// Returns the size of the current matching.
     342    int matchingSize() const {
     343      int size = 0;
     344      for (RedNodeIt it(_bpg); it!=INVALID; ++it) {
     345        if ((*_matching)[it] != INVALID) ++size;
     346      }
     347      return size;
     348    }
     349
     350    /// \brief Return \c true if the given edge is in the matching.
     351    ///
     352    /// This function returns \c true if the given edge is in the current
     353    /// matching.
     354    bool matching(const Edge& edge) const {
     355      return edge == (*_matching)[_bpg.redNode(edge)];
     356    }
     357
     358    /// \brief Return the matching edge incident to the given node.
     359    ///
     360    /// This function returns the matching edge incident to the
     361    /// given node in the current matching or \c INVALID if the node is
     362    /// not covered by the matching.
     363    Edge matching(const Node& n) const {
     364      return (*_matching)[n];
     365    }
     366
     367    /// \brief Return the mate of the given node.
     368    ///
     369    /// This function returns the mate of the given node in the current
     370    /// matching or \c INVALID if the node is not covered by the matching.
     371    Node mate(const Node& n) const {
     372      return (*_matching)[n] != INVALID ?
     373        _bpg.oppositeNode(n, (*_matching)[n]) : INVALID;
     374    }
     375  };
     376
     377}
     378#endif
  • test/CMakeLists.txt

    diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
    a b  
    3434  graph_utils_test
    3535  hao_orlin_test
    3636  heap_test
     37  hopcroft_karp_test
    3738  kruskal_test
    3839  lgf_test
    3940  maps_test
  • test/Makefile.am

    diff --git a/test/Makefile.am b/test/Makefile.am
    a b  
    3232        test/graph_utils_test \
    3333        test/hao_orlin_test \
    3434        test/heap_test \
     35        test/hopcroft_karp_test \
    3536        test/kruskal_test \
    3637        test/lgf_test \
    3738        test/maps_test \
     
    8788test_graph_utils_test_SOURCES = test/graph_utils_test.cc
    8889test_hao_orlin_test_SOURCES = test/hao_orlin_test.cc
    8990test_heap_test_SOURCES = test/heap_test.cc
     91test_hopcroft_karp_test_SOURCES = test/hopcroft_karp_test.cc
    9092test_kruskal_test_SOURCES = test/kruskal_test.cc
    9193test_lgf_test_SOURCES = test/lgf_test.cc
    9294test_lp_test_SOURCES = test/lp_test.cc
  • new file test/hopcroft_karp_test.cc

    diff --git a/test/hopcroft_karp_test.cc b/test/hopcroft_karp_test.cc
    new file mode 100644
    - +  
     1/* -*- mode: C++; indent-tabs-mode: nil; -*-
     2 *
     3 * This file is a part of LEMON, a generic C++ optimization library.
     4 *
     5 * Copyright (C) 2003-2011
     6 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
     7 * (Egervary Research Group on Combinatorial Optimization, EGRES).
     8 *
     9 * Permission to use, modify and distribute this software is granted
     10 * provided that this copyright notice appears in all copies. For
     11 * precise terms see the accompanying LICENSE file.
     12 *
     13 * This software is provided "AS IS" with no warranty of any kind,
     14 * express or implied, and with no claim as to its suitability for any
     15 * purpose.
     16 *
     17 */
     18
     19#include <iostream>
     20#include <sstream>
     21#include <list>
     22
     23#include <lemon/random.h>
     24#include <lemon/hopcroft_karp.h>
     25#include <lemon/smart_graph.h>
     26#include <lemon/concepts/bpgraph.h>
     27#include <lemon/concepts/maps.h>
     28#include <lemon/lgf_reader.h>
     29
     30#include "test_tools.h"
     31
     32using namespace std;
     33using namespace lemon;
     34
     35const int lgfn = 3;
     36const string lgf[lgfn] = {
     37  // Empty graph
     38  "@red_nodes\n"
     39  "label\n"
     40  "\n"
     41  "@blue_nodes\n"
     42  "label\n"
     43  "\n"
     44  "@edges\n"
     45  "  label\n"
     46  "\n",
     47
     48  // No red nodes
     49  "@red_nodes\n"
     50  "label\n"
     51  "\n"
     52  "@blue_nodes\n"
     53  "label\n"
     54  "1\n"
     55  "@edges\n"
     56  "  label\n"
     57  "\n",
     58
     59  // No blue nodes
     60  "@red_nodes\n"
     61  "label\n"
     62  "1\n"
     63  "@blue_nodes\n"
     64  "label\n"
     65  "\n"
     66  "@edges\n"
     67  "  label\n"
     68  "\n",
     69};
     70
     71const string u_lgf =
     72  "@red_nodes\n"
     73  "label\n"
     74  "1\n"
     75  "2\n"
     76  "3\n"
     77  "@blue_nodes\n"
     78  "label\n"
     79  "4\n"
     80  "5\n"
     81  "6\n"
     82  "7\n"
     83  "@edges\n"
     84  "label\n"
     85  "1 4 1\n"
     86  "1 5 2\n"
     87  "2 5 3\n"
     88  "2 6 4\n"
     89  "2 7 5\n"
     90  "3 7 6\n"
     91;
     92
     93
     94void checkHopcroftKarpCompile() {
     95  typedef concepts::BpGraph BpGraph;
     96  BPGRAPH_TYPEDEFS(BpGraph);
     97  typedef HopcroftKarp<BpGraph> HK;
     98
     99  BpGraph graph;
     100  RedNode red;
     101  BlueNode blue;
     102  Node node;
     103  Edge edge;
     104  BoolEdgeMap init_matching(graph);
     105  HK hk_test(graph);
     106  const HK& const_hk_test = hk_test;
     107  HK::MatchingMap matching_map(graph);
     108
     109  hk_test.matchingMap(matching_map);
     110  hk_test.init();
     111  hk_test.heuristicInit();
     112  hk_test.matchingInit(init_matching);
     113
     114  hk_test.start();
     115  hk_test.augment();
     116  hk_test.run();
     117
     118  const_hk_test.matchingSize();
     119  const_hk_test.matching(node);
     120  const_hk_test.matching(edge);
     121  const_hk_test.mate(red);
     122  const_hk_test.mate(blue);
     123  const_hk_test.mate(node);
     124  const HK::MatchingMap& max_matching = const_hk_test.matchingMap();
     125  edge = max_matching[node];
     126}
     127
     128typedef SmartBpGraph BpGraph;
     129typedef HopcroftKarp<BpGraph> HK;
     130BPGRAPH_TYPEDEFS(BpGraph);
     131
     132void checkMatchingValidity(const BpGraph& bpg, const HK& hk) {
     133  BoolBlueNodeMap matched(bpg, false);
     134  for (RedNodeIt it(bpg); it != INVALID; ++it) {
     135    if (hk.matching(it) != INVALID) {
     136      check(hk.mate(hk.mate(it)) == it, "Wrong matching");
     137      matched.set(bpg.asBlueNode(hk.mate(it)), true);
     138    }
     139  }
     140  for (BlueNodeIt it(bpg); it != INVALID; ++it) {
     141    // != is used as logical xor here
     142    check(matched[it] != (hk.matching(it) == INVALID), "Wrong matching");
     143  }
     144
     145}
     146
     147void checkMatchingOptimality(const BpGraph& bpg, const HK& hk) {
     148  list<RedNode> reds;
     149  list<BlueNode> blues;
     150  BoolBlueNodeMap reached(bpg, false);
     151
     152  for (RedNodeIt it(bpg); it != INVALID; ++it) {
     153    if (hk.matching(it) == INVALID) {
     154      reds.push_front(it);
     155    }
     156  }
     157
     158  while (!reds.empty()) {
     159    while (!reds.empty()) {
     160      RedNode red = reds.front();
     161      reds.pop_front();
     162      for (IncEdgeIt it(bpg, red); it != INVALID; ++it) {
     163        BlueNode blue = bpg.blueNode(it);
     164        if (!reached[blue]) {
     165          blues.push_front(blue);
     166          reached.set(blue, true);
     167        }
     168      }
     169    }
     170
     171    while (!blues.empty()) {
     172      BlueNode blue = blues.front();
     173      blues.pop_front();
     174      check(hk.matching(blue) != INVALID, "Suboptimal matching");
     175      reds.push_front(bpg.asRedNode(hk.mate(blue)));
     176    }
     177  }
     178
     179}
     180
     181int main() {
     182  // Checking hard wired extremal graphs
     183  for (int i=0; i<lgfn; ++i) {
     184    BpGraph bpg;
     185    istringstream lgfs(lgf[i]);
     186    bpGraphReader(bpg, lgfs).run();
     187    HK hk(bpg);
     188    hk.run();
     189    checkMatchingValidity(bpg, hk);
     190    checkMatchingOptimality(bpg, hk);
     191  }
     192
     193  // Checking some use cases
     194  BpGraph bpg;
     195  istringstream u_lgfs(u_lgf);
     196  bpGraphReader(bpg, u_lgfs).run();
     197  {
     198    HK hk(bpg);
     199    hk.init();
     200    hk.augment();
     201    hk.start();
     202    checkMatchingValidity(bpg, hk);
     203    checkMatchingOptimality(bpg, hk);
     204  }
     205  {
     206    HK hk(bpg);
     207    hk.heuristicInit();
     208    hk.augment();
     209    hk.start();
     210    checkMatchingValidity(bpg, hk);
     211    checkMatchingOptimality(bpg, hk);
     212  }
     213  {
     214    HK hk(bpg);
     215    HK::MatchingMap m(bpg);
     216    hk.matchingMap(m);
     217    hk.run();
     218    checkMatchingValidity(bpg, hk);
     219    checkMatchingOptimality(bpg, hk);
     220  }
     221  {
     222    HK hk(bpg);
     223    BpGraph::EdgeMap<bool> init_matching(bpg, false);
     224    BpGraph::Edge e;
     225    bpg.first(e);
     226    init_matching[e] = true;
     227    check(hk.matchingInit(init_matching), "Wrong result from matchingInit()");
     228    hk.start();
     229    checkMatchingValidity(bpg, hk);
     230    checkMatchingOptimality(bpg, hk);
     231  }
     232  {
     233    HK hk(bpg);
     234    HK::MatchingMap m(bpg);
     235    hk.matchingMap(m);
     236    BpGraph::EdgeMap<bool> init_matching(bpg, true);
     237    check(!hk.matchingInit(init_matching), "Wrong result from matchingInit()");
     238    hk.start();
     239    checkMatchingValidity(bpg, hk);
     240    checkMatchingOptimality(bpg, hk);
     241  }
     242
     243  // Checking some random generated graphs
     244  const int random_test_n = 10;
     245  const int max_red_n = 100;
     246  const int min_red_n = 10;
     247  const int max_blue_n = 100;
     248  const int min_blue_n = 10;
     249  for (int i=0; i<random_test_n; ++i) {
     250    int red_n = rnd.integer(min_red_n, max_red_n);
     251    int blue_n = rnd.integer(min_blue_n, max_blue_n);
     252    double prob = rnd();
     253    SmartBpGraph bpg;
     254    for (int i=0; i<red_n; ++i) {
     255      bpg.addRedNode();
     256    }
     257    for (int i=0; i<blue_n; ++i) {
     258      bpg.addBlueNode();
     259    }
     260    for (SmartBpGraph::RedNodeIt r(bpg); r!=INVALID; ++r) {
     261      for (SmartBpGraph::BlueNodeIt b(bpg); b!=INVALID; ++b) {
     262        if (rnd() < prob) {
     263          bpg.addEdge(r,b);
     264        }
     265      }
     266    }
     267
     268    HK hk(bpg);
     269    hk.run();
     270    checkMatchingValidity(bpg, hk);
     271    checkMatchingOptimality(bpg, hk);
     272  }
     273
     274
     275  return 0;
     276}
     277
     278