// p1bc.cpp // CSC 601 - Fall 2002 - Kirby // --------------------------- // An IPartition interface with an abstract factory. // Implemented with a forest with smart adoption and path compression. // Used for benchmarking. // // Bug reports welcome. // // The interfaces. // #include #include #include struct IPartition // An abstract partition. { virtual void unite( int i, int j ) throw ( std::out_of_range ) =0 ; virtual bool sameSet( int i, int j ) const throw ( std::out_of_range ) =0 ; virtual int size() const throw() =0 ; } ; struct IPartitionFactory // An abstract position factory. { virtual IPartition* new_Partition( int n ) const throw ( std::exception ) =0 ; } ; // // The implementation classes. // class CPartitionForest : public IPartition // A partition implemented as a forest. { public: CPartitionForest( int n ) ; // Mutators void unite( int i, int j ) throw ( std::out_of_range ) ; // Inspectors bool sameSet( int i, int j ) const throw ( std::out_of_range ) ; int size() const throw() ; private: mutable std::vector _up ; // _up[i] is parent of i in tree (==i if root) mutable std::vector _count ; // _count[i] is # of nodes in tree rooted at i mutable std::vector _path ; // for use in path compression in _findRoot() int _size ; // # of sets currently in partition bool _isRoot( int i ) const throw ( std::out_of_range ) ; int _findRoot( int i ) const throw ( std::out_of_range ) ; } ; class CPartitionForestFactory : public IPartitionFactory // Concrete factory for construction of CPartitionForest objects. { public: IPartition* new_Partition( int n ) const throw ( std::exception ) { return new CPartitionForest( n ) ; } } ; CPartitionForest::CPartitionForest( int n ) // Construct a partition where every set is a singleton: {0}, {1}, .. {n-1}. : _up( n ) , _count( n ) , _path( 0 ) , _size( n ) { for ( int i=0 ; i < n ; ++i ) { _up.at(i)= i ; _count.at(i) = 1 ; } } void CPartitionForest::unite( int i, int j ) throw ( std::out_of_range ) // Make the smaller tree a child of the larger ("smart adoption"). { int iRoot= _findRoot( i ) ; int jRoot= _findRoot( j ) ; if ( iRoot != jRoot ) { // Items i and j are in different trees. Join smaller tree to root of larger. int bigRoot, littleRoot ; if ( _count.at(iRoot) < _count.at(jRoot) ) { littleRoot= iRoot ; bigRoot= jRoot ; } else { littleRoot= jRoot ; bigRoot= iRoot ; } _up.at( littleRoot )= bigRoot ; _count.at( bigRoot ) += _count.at( littleRoot ) ; _count.at( littleRoot ) = 0 ; _size-- ; } } inline bool CPartitionForest::sameSet( int i, int j ) const throw ( std::out_of_range ) // Are i and j in the same set? { return _findRoot(i) == _findRoot(j) ; } inline int CPartitionForest::size() const throw() // How many sets are currently in partition? { return _size ; } inline bool CPartitionForest::_isRoot( int i ) const throw ( std::out_of_range ) // inlining or not is less crucial here, since our rule for joining small to large produces shallow trees { return _up[i]== i ; // _up.at(i) == i ; } int CPartitionForest::_findRoot( int i ) const throw ( std::out_of_range ) { // Walk up tree, keeping track of items encountered. _path.clear() ; while ( ! _isRoot(i) ) { _path.push_back( i ) ; i= _up.at(i) ; } // Path compression: make each item on path a child of root. for ( int j=0 ; j < _path.size() ; ++j ) _up.at( _path[j] )= i ; return i ; } // // Demonstrations. // #include #include #include #include #include #include #include "Random48c.h" using namespace std ; void workoutABSTRACT( int n, int nsperu, const IPartitionFactory& fac ) // Virtually construct a partition and put it through its paces. // n: # of base elements in partition // nsperu: # of random sameSet queries per unite operation { auto_ptr p( fac.new_Partition( n ) ) ; bool jnk ; for ( int i=0 ; i < n ; ++i ) { p->unite( rand48()%n, rand48()%n ) ; for ( int j=0 ; j < nsperu ; ++j ) jnk |= p->sameSet( rand48()%n, rand48()%n ) ; } } void workoutCONCRETE( int n, int nsperu ) // Construct a partition and put it through its paces. // n: # of base elements in partition // nsperu: # of random sameSet queries per unite operation { CPartitionForest ptn( n ) ; bool jnk ; for ( int i=0 ; i < n ; ++i ) { ptn.unite( rand48()%n, rand48()%n ) ; for ( int j=0 ; j < nsperu ; ++j ) jnk |= ptn.sameSet( rand48()%n, rand48()%n ) ; } } void main() { const int N= 100000 ; // # of base elements in partition const int NSPERU= 10 ; // # of sameSet() queries per unite() operation const int SEED= 2002 ; // will use SAME random sequence for both abstract & concrete tests float tStart, tStop, dt ; // Benchmark the ADT version. cout << "Running concrete workout (N= " << N << ")... " << endl ; srand48( SEED ) ; tStart= clock() ; workoutCONCRETE( N, NSPERU ) ; tStop= clock() ; dt= ( tStop - tStart ) / (float) CLOCKS_PER_SEC ; cout << "Running time: " << dt << " sec.\n" << endl ; // Benchmark the OO version. cout << "Running abstract workout (N= " << N << ")... " << endl ; srand48( SEED ) ; CPartitionForestFactory fac ; tStart= clock() ; workoutABSTRACT( N, NSPERU, fac ) ; tStop= clock() ; dt= ( tStop - tStart ) / (float) CLOCKS_PER_SEC ; cout << "Running time: " << dt << " sec.\n" << endl ; }