You are on page 1of 21

#include <iostream> cin.

ignore(10000, '\n'); // ignores up to char or 10000 chars, used after cin >> int endl; // '\n' cout.setf(ios::fixed); // fixed vs. scientific (ios is not a namespace) cout.precision(2); // 2 decimal // This header defines istream and ostream #include <string> string s = "literal"; string u; // default empty string u = s; // assignment (creates a copy) (s == u) // comparison, can use > and < getline(cin, s); // read up to newline, store w/o newline cin >> s; // read up to whitespace w/o using, store w/o whitespace s.size(); // length s.length(); // same as above s[i]; // return char, valid range 0 to size - 1 s += u; // append s.substr(index, length) // returns string starting (inclusive) at index with specified length, // length is optional Escape sequence: \ Important special chars: \n \t \\ \' \" \0 #include <cstring> // Not in the std namespace char s[10] = "Hi"; // length 2, index 2 reserved for \0, need not fill entire array char u[] = "Compiler makes this string just long enough."; strlen(s); strcpy(dest, source); // assignment strncpy(dest, source, limitToCopy); strcmp(a, b); // 0 if equal, pos if a > b (a is lexicographcally after b), neg if a < b srrncmp(a, b, limitToCheck); strcat(dest, source); // concatenate strncat(dest, source, limitToCopy); cout << s; // prints out entire c-string for you, unlike most arrays cin.getline(s, 10); // discards \n, also num of chars to read in must factor the \0 in #include <cctype> isalpha(char) // true if uppercase or lowercase isupper(char) // true iff uppercase (no digits) islower(char) // true iff lowercase (no digits) isdigit(char) // true iff digit (no alpha) toupper(char) // if lowercase, return upper; if else, return char tolower(char) // likewise #include <cassert> assert(a == b); Common to all containers: mycontainer.empty() mycontainer.size() mycontainer.clear() mycontainer.swap() mycontainer.begin() // returns container<type>::iterator mycontainer.end() mycontainer.cbegin() // returns container<type>::const_iterator mycontainer.cend() mycontainer.rbegin() // returns container<type>::reverse_iterator mycontainer.rend( mycontainer.crbegin() // returns container<type>::const_reverse_iterator mycontainer.crend() mycontainer.erase(it) // erases item at iter Common *it it->x ++it --it to all container iterators: // returns type // also it++ // also it--

#include <stack> stack<int> mystack; mystack.top() mystack.push() // pushes to top of stack mystack.emplace(1) // constructs T with args and pushes to top of stack mystack.pop() // pops off top, returns void #include <queue> queue<int> myqueue; myqueue.push() // pushes to back of queue myqueue.back() myqueue.front() // next element to be popped myqueue.pop() myqueue.emplace(1) // see stack #include <vector> // Any insertion/deletion invalidates iterator vector<int> myvector; myvector[0] // same as myvector.at(0) myvector.front() // same as myvector[0] myvector.back() // same as myvector[myvector.size() - 1] myvector.push_back(0) myvector.pop_back() // returns void #include <list> // Only erasing the item at iterator can invalidate an iterator list<int> mylist; mylist.front() mylist.back() mylist.push_front(0) mylist.pop_front() // returns void mylist.push_back(0) mylist.pop_back() // returns void mylist.insert(it, 0) // inserts before the element at it; in other words, now 0 is at it #include <unordered_set> // DOES NOT HAVE REVERSE ITERATORS // Only erasing the item at iterator can invalidate an iterator unordered_set<int> myuset; myuset.insert(0) myuset.find(0) myuset.bucket_count() muuset.load_factor() // returns float, equal to size / bucket_count #include <unordered_map> // DOES NOT HAVE REVERSE ITERATORS // Only erasing the item at iterator can invalidate an iterator undordered_map<string, int> myumap; myumap["weed"] // returns int&, myumap.at("weed") will throw out_of_range if key DNE myumap.insert("weed", 0) myumap.load_factor() // returns float, equal to size / bucket_count myumap.bucket_count() #include <set> // Only erasing the item at iterator can invalidate an iterator set<int> myset; myset.insert(0) myset.find(0) // returns iterator (it == myset.end() if not found) myset.lower_bound(0) // returns iterator either equal or greater than myset.upper_bound(0) // returns iterator less than multiset<int> mymset; // can have duplicate values, same functions as above but also: mymset.equal_range(0) // returns pair<iterator, iterator>, actually set has this too

#include <map> // Only erasing the item an iterator can invalidate an iterator map<string, int> mymap; mymap["weed"] // returns int&, mymap.at("weed") will throw out_of_range if key DNE mymap.insert(std::pair<string, int>("weed", 0)) mymap.find("weed") // returns iterator mymap.lower_bound("weed") // returns iterator either equal or greater than mymap.upper_bound("weed") // returns iterator less than multimap<string, int> mymmap; // can have duplicate values, same functions as above but also: mymmap.equal_range("weed") // returns pair<iterator, iterator>, actually map has this too #include <algorithm> // Pointers on arrays work as iterators too find(it_first, it_last, 0) // [first, last) find_if(it_first, it_last, func) // bool func(int oneArg) sort(it_first, it_last, func) // vectors/arrays only, bool func(int arg1, int arg2), true if arg1 < // arg2 (strictly) #include <fstream> ifstream infile("file.txt") ofstream outfile("file.txt") !ifstream, !ofstream // true if in failure state // file manipulation works just as iostream does for cout and cin // make sure to always pass streams by reference // ifstream inherits istream, ofstream inherits ostream Misc. data types info bool: false is equivalent to double to int initialization int to double initialization double literals: 21.0, 2.1e1 '0' == 48, 'A' == 65, 'a' == 0, true is equivalent to all other numbers (generally 1) is not necessarily supported, however usually implicitly converts is supported 97, 32 difference between upper and lower case

Identifier rules starts with a letter or underscore, then any combination of letters, numbers, and underscores Arrays int a[10]; // Uninitialized, could have any value int a[] = { 1, 2, 3 }; // Alternate declaration, note that you cannot declare an empty array int a[2] = {0}; int a[2] = {}; // set all to 0 Functions int foo(int param1, int[][3][4] param2); // Function declaration/prototype, notice param2 data type void bar(string& param1, const double[] param2); // Pass by reference, read only bool hello(int arg1, double arg2 = 2.0, double arg3 = 3.0); // Default argument bool hello(int arg1, double arg2, double arg3) { } // When defining, do not use default argument Non-void functions must return something. Arrays are by default passed by reference. Passed by reference is not the same as a pointer, but both are done to avoid copying data. Functions that are overloaded must have different num and types of parameters. You can return a constant value to prevent any code from changing the value. Classes, structures, and inheritance class Foo; // prototypes struct Bar; class Foo { ... // defaults to private public: Foo(); // constructor, notice no return type Foo(int x); // overloaded constructor ~Foo(); // destructor void function() const; // will not modify the state of the object virtual void virtualFunc(); int m_val; void wrapper() { virtualFunc(); } private: struct Nested { }; Nested getNested(); ... }; // Don't forget the semi-colon

struct Bar { };

// same as class but defaults to public

Foo::Foo(int x) : m_val(x), other_val(100), some_object(x, 100) { } void Foo::function() const { } Foo::Foo(const Foo& other) { ...

// initializer list

// Copy constructor, follow these parameters exactly // // // // can be called with Foo(other), Foo f(other), Foo f = other; make sure you copy any allocated objects, not just references The copy constructor is also called when returning an object that is not a reference or a pointer!

} Foo& Foo::operator=(const Foo& other) // Assignment operator, follow these parameters exactly { ... // called with a = b; // make sure you delete old allocated objects and copy new ones return *this; // don't forget the return statement too // if you did it right it should look like the destructor and // copy constructor combined } Foo::Nested Foo::getNested() { } // notice the return type scoping

class Foobar : public Foo { // access to all public members of Foo public: virtual void virtualFunc(); } void Foo::virtualFunc() { }

// overriding Foo's virtualFunc

// Do not use virtual here

void Foobar::virtualFunc() // Do not use virtual here { } int main() { Foo f1; Foo f2(6); Foo *f = new Foo(6); f1 = Foo(); f1 = Foo(10); cout << f2.m_val << endl; f2.function(); ... Foobar *f3; f3->virtualFunc(); f3->Foo::virtualFunc(); Foo *f4 = f3; f3->virtualFunc(); f3->wrapper(); } class Animal { public: Animal(int a) { ... }; }

// // // // // // //

uses default constructor, do not use parentheses here otherwise it is confused with a function declaration uses overloaded constructor returns a pointer explicit call to default constructor, use parentheses here of course, make sure there is a default constructor explicit call to overloaded constructor

// // // // //

calls Foobar's version calls Foo's version to use polymorphism, you must use pointers/references calls Foobar's version calls Foobar's version of virtualFunc

// must be constructed with arg

class Dog : public Animal { public: Dog() : Animal(10) { ... } // use initializer list to deal with this Dog(int a) : Animal(a) { ... } bool operator<=(const Dog& other) const { } // can access non-public members } bool operator<(const Dog& lhs, const Dog& rhs) const // cannot access private members, not in Dog { } // scope class Abstract { public: virtual void pureVirtual() = 0; } Accessor functions: allow you to read data Mutator functions: changes data If no constructor is declared, default constructor is called: - Built-in data type members are uninitialized - Class type members are default-constructed Destructors are called whenever an object goes away. This may occur when a function with a locally declared object ends either through a return value or reaching the end of the function, or perhaps if delete is explicitly called on the object. What to put in destructors: -Freeing allocated memory -Closing a disk file -Disconnecting from another computer Inner classes are created first, outer classes created last. Inner classes are destroyed last, outer classes are destroyed first. Superclasses are created first, subclasses are created last. Subclasses are destroyed first, super classes are destroyed last. Superclass data is copied first, subclass data is copied last. The practice of making the internal members and functions of a class/struct private is called data abstraction, information hiding, and/or encapsulation. Making functions protected is OK, but making member variables protected violates encapsulation. If you declare a class/struct inside a class/struct, for the return type you must specify the parent class/struct's scope. The parameter list is, however, already in the scope of the class/struct. Initializer lists must be used if you are using an object that does not have a default constructor. Why you use inheritance: -Reusing code -Specialization (adding new functions) -Overriding (redefine existing behavior) Pointers int * a; int * b = new int; int * c = new int(5); int * d = new int[*c]; int z = 0; int * y = &z; int* p1, p2; int *p3, p4; const int * e = new int(5); int const * f = new int(5); int * const g = new int(5); int array[10]; *c;

// This makes Abstract an abstract class, cannot be // constructed

// uninitialized // dynamic array declaration // // // // // // // // get reference operator declare two pointers declare one pointer pointer to an integer that is constant same as above constant pointer to a variable integer array is of type int * const, can be manipulated as such dereference

d++; Object * obj = new Object(); obj->function(); obj->m_data; *(c + 1) = 1; this; delete b; delete [] d;

// // // // // // // // // //

pointer arithmetic, points to next integer new returns a pointer type, parentheses optional if default constructing (unless you want to get into ugly initialization technicalities) shorthand for access from pointer equivalent to c[1] recall that pointer arithmatic is different in a class member function call, returns pointer to the object that is running the function does nothing if b == nullptr

Complex declarations int *p[10]; // array of pointers to an int int (*p)[10]; // pointer to array of an int int **p; // pointer to a pointer to an int void foo(int* &var); // reference to a pointer of int int& *var and int& &var are illegal const int *p; // pointer to a constant int int const *p; // pointer to a constant int int * const p; // constant pointer to an int (you can change the value of *p) Templates template<typename T> // put template on both class and function definintion class Foo { public: swap(T& x, T& y); T m_t; struct bar { }; } template<typename T> void Foo::swap(T& x, T& y) { } // template data type must be used in the formal parameter list template<typename T, typename B> void swap(T& x, B& y) { } template<typename T> inline T Foo<T>::bar(T a) { } // template inline function // template, returning template // dependent struct

template<typename T> typename Foo<T>::bar Foo<T>::returnBar() { }

Entire template function must be in a header file Stacks and queues Stacks can be implemented with an array and a counter that keeps track of the top element. Queues can be implemented with a linked list that has both a head and a tail pointer. You can also use a circular queue implemented with arrays; keep track of the head index, tail index, and the count of objects in your queue. Declaring local variables, passing arguments to functions, and function returns are all placed on a stack. Undoing things in word processors is also done on the stack. Queues can be used for holding data gotten from the internet. Both stacks and queues can be used for solving mazes. Stacks are LIFO (Last-in First-out), queues are FIFO (First-in First-out) Polymorphism Always use virtual on both the base and derived functions if they are to be redefined. (It technically does not matter for the derived function, but it's better if it does.) Always use virtual on destructors. No virtual constructors; you always know the class at time of construction, and they are classlocal anyway. If you forget to make a destructor virtual, the subclass destructor will not be called if the compiler knows only that the object is of the superclass. If the compiler knows that the object is of the subclass, then both destructors will be called.

The default copy constructor and assignment operator will work fine, but if your derived class needs special functionality, it must define its own copy constructors and assignment operators (these should not be virtual): 1.) For the copy constructor, you should instantiate the base class using the initializer list and passing in your argument to the base class's (ie. Sub(const Sub& other) : Super(other)) 2.) For the assignment operator, you should explicitly call Super::operator=(other). If you allow these functions to be implicitly defined, they will automatically call the base class's version for you. In this sense, they are not technically "inherited." vtable is the table of pointers to virtual functions. Recursion Every recursive function must have a base case that solves trivial problems without recursion. Every recursive function must have a simplifying step that converges towards the base case. Recursive functions must not use global, static, or member variables, only local and parameters. When using recursion on linked lists, you will probably be passing in nodes as parameters. You probably won't need a size parameter; just check the next node to see if it's the end of the list or not. Two general categories of recursion: 1.) Split into halves 2.) Remove one element and try again You can write a helper function to make a simpler interface for others to use. Minimax (chess): give it a set depth of moves to test, and while depth isn't 0, try every move and call again; when depth is 0, evaluate the board and find the best move. Linked Lists A series of nodes, where one node can point to the next node (singly-linked) or to the previous node (doubly-linked). You will need at least the head or tail pointer, though having both is convenient. A dummy node goes at the head position and its next pointer is the first node, and its prev pointer is the last node. This means that there are no special cases to deal with. That is, there will be no null pointers to follow if the integrity of the list is maintained. When deleting from a singly-linked list, you will have to declare a temporary variable to remember what node to delete when you are done. Be very careful with the order of your operations in linked lists. Always validate the pointer first. Singly-linked list, adding to the top: no special cases adding to the bottom: case 1: list is empty; case 2: list is not empty adding to arbitrary location: case 1: list is empty; case 2: you're adding to the top; case 3: normal case Singly-linked list, deleting: case 1: deleting first node; case 2: deleting any other node Binary Trees Trees in general may have any number of children, but binary trees only have at most two children Every tree has a root node Every node must have exactly one parent (except the root) Sibling nodes = nodes with same parent Ancestor node = lies between current node and root Subtree = any node and the collection of its subnodes node depth = "level" of a tree, root has depth 0 pruning = removing a section of the tree grafting = adding a section to the tree traversing = iterating though items in a tree Pre-order: current, left, right In-order: left, current, right Post-order: left, right, current Level-order: use a queue

You can use binary trees to represent arithmetic expressions: each leaf node is an operand all others are operators Binary search trees: left sub-tree must be < node, right sub-tree must be > node Deletion in BST: use a cur pointer and a parent pointer case 1: node is a leaf (trivial) case 2: node has one child -determine which side of the parent's tree the node is on -rewire that side to the child case 3: node has two children -replace with the largest left descendant or smallest right descendant -make sure to apply case 1 or 2 to that descendant when moving it around case x: remember to delete the root pointer too if applicable Huffman encoding uses a binary tree: -compute frequency of byte patterns -find two lowest frequency patterns, and link them as children of a parent node -set parent node's frequency to sum of these two -replace these two patterns with the parent's node -repeat process until you have a binary tree -use the path taken to reach a leaf (ie. byte pattern) as the encryption scheme -ie. 0 means go left, 1 means to right -when decrypting, when you reach a leaf you know the encrypted character is finished -of course there is overhead in adding the encryption scheme to the data file Perfectly balanced trees have a max height of log_2(n) Heights of subtrees differ by at most 1 Self-balancing trees include AVL, 2-3 and red-black trees AVL trees: each node has a balance value -1 if left is higher than right (larger than right), 0 if balanced, 1 if lower than right -must update balance values from new node to root insertion cases (after insertion and updating balances): case 1: all balances are -1 to 1 (trivial) case 2: if not, then rotate nodes -may require double rotation if multiple nodes do not have the right balance values Hash tables collision = two or more values attempt to occupy the same bucket linear probing = on collision, traverse down until you reach an empty bucket -results in a closed hash table with fixed capacity -to look up, use hashing function to find bucket, then traverse until you find exact value -deletion is troublesome--just assume you can't delete anything open hash table = each bucket contains a linked list -better for repeated insertion and deletion -still good performance even if you use all your buckets load = number of expected values / number of buckets closed w/ linear probing: insertion/finding: average # of tries = 1/2 * (1 + 1 / (1 - L)) open: insertion/finding: average # of tries = 1 + L / 2 -open is always more efficient than closed try to use a prime number for bucket number, reduces number of collissions Hash functions must: 1.) be deterministic 2.) should have even distribution 3.) should always be tested for how well it disperses items sample hash functions: CRC32, CRC64, MD5, SHA2 Binary trees vs hash tables Speed: Hash table = O(1), binary trees = O(log_2(n)) Simplicity: Hash table = easy, binary trees = harder Max size: closed hash = limited, open hash = impacted by high load, binary tree = unlimited Space efficiency: hash = can waste a lot of space, binary tree = only uses as much as needed ordering: hash = random, binary tree = ordered

Tables record = a group of related data field = one element of a record key field = unique value across all records for efficient searching, set up indexed trees to search through -store only the index of the record, not the record itself to avoid duplicate data -remember that if you ever update the record, you will need to update the trees -you can actually use any efficient structure, not just a tree Priority Queues/Heaps priority queue = every inserted element has a priority rating -dequeue by highest priority priority queues are efficiently implemented with heaps heaps are implemented using complete binary trees (top N-1 levels are all filled, bottom-most level is filled from the left) Maxheap (max value on top): each node has a value greater than or equal to its children Minheap (min value on top): each node has a value less than or equal to its children Extracting top items: get the top value, overwrite it with the right-most node in the bottom level, delete that node, and repeatedly swap that node until it satisfies the max or min condition -the swapping part is called sifting down Inserting an item: place node with value into the left-most unoccupied node on the bottom level, and swap upwards until it satisfies the max or min condition -the swapping part is called reheapification implemented with arrays: root value is heap[0], bottom-most, right-most node is heap[count - 1], bottom-most, left-most empty spot is heap[count] left child index = 2 * parent index + 1 right child index = 2 * parent index + 2 parent index = (child index - 1) / 2 To heapify an array, traverse backwards, considering each node to be the root of its own subtree, and sift down -start at node N/2 - 1; this indicates the first node with at least 2 nodes -this eliminates half of the trees you need to inspect Graphs stores a set of entities and keeps track of their relationships has vertices/nodes, and edges/arcs (connects two vertices) directed graph: an edge has a specific direction undirected graph: an edge is bi-directional represented with a 2-d array, whose size in both dimensions is the number of vertices (adjacency matrix) each element g[i][j] represents whether an edge exists between i and j forms an adjacency matrix for an undirected graph, g[i][j] == g[j][i] multiplying a matrix by itself gives a resultant matrix that shows which vertices are two edges apart, and doing so again gives you a matrix with vertices three edges apart, and so on represented with an array of linked lists (adjacency list) each list g[n] represents all the edges from vertex n dense graph = many edges, sparse graph = few edges generally, use a matrix on dense graphs, use a list on sparse graphs depth-first traversal via recursion/stacks breadth-first traversal via queues weighted edges = edge has a value associated with it weight of a path = sum of the weights of the edges between two vertices shortest path = particular path with lowest weight Designing OOPs Identify objects Identify operations of objects Associate operations with classes Determine relationships and data Class X uses Class Y Class X has-a Class Y Class X is-a Class Y Determine interactions Undefined behavior uninitialized primitive types out of bounds access return statement never reached variable array length for static array declaration (illegal on many compilers)

Common mistakes forgetting to use cin.ignore(10000, '\n'); after inputting a number mismatched if and else statements boundary conditions forgetting break in a case statement forgetting the semi-colon after do-while, struct, class, or function declaration misuse of & or const in function headers accessing a variable out of scope using cin on an improper type (arrays, pointers) forgetting to dereference with either * or [] not using == in comparisons using the wrong >> or << operator forgetting return statements forgetting the null byte infinite loops with an array of pointers, not deleting each item individually out of bounds access returning a pointer to a locally defined variable using single quotes on strings, or double quotes on chars forgetting to delete not deleting each element of an array of pointers manually De Morgan's Law !(A || B) == !A && !B !(A && B) == !A || !B Boolean inverses: > and <=, < and >=, == and != Various syntax const int X = 10; (m = 2) // returns 2 int x(50); // alternate initialization syntax static_cast<double>(2) (double)(2) a++ ++a // returns 2.0 // also returns 2.0 // cannot be modified once initialized

// value incremented after use // value incremented before use

if (condition) { ... } else if (condition) { ... } else { ... } switch (expression) // char, int, bool, or enum { case 1: break; default: } enum EnumName { ZERO, ONE, FIVE = 5, SIX } // defaults 0 and increments from last typedef EXISTING_TYPE NEW_TYPE_NAME; #ifndef THING_H // there is also #ifdef #define THING_H ... #endif class dependency; // Use if constructor/members are not called #include "dependency.h" // Otherwise use this // Never include .cpp files, only .h files. // Never put using namespace in a .h file. (called namespace pollution) // Never assume a .h file will include another one for you Important concepts Data structure = data that's operated on by an algorithm Abstract Data Type (ADT) = a coordinated group of data structures, algorithms, and interface functions Object-oriented Programming = using ADTs/classes to build programs

Operator precedence :: (scope resolution operator) . ++ (prefix) & * + << < == && || = ? : throw , ALGORITHM CHEAT SHEET set/map insertion: O(log n) finding: O(log n) deleting: O(log n) queue/stack insertion: O(1) popping: O(1) peeking: O(1) vector insertion (arbitrary): O(n) insertion (bottom): O(1) deletion (arbitrary): O(n) deletion (bottom): O(1) accessing (arbitrary): O(1) finding (arbitrary): O(n) list insertion (arbitrary): O(1) deletion (arbitrary): O(1) accessing (top or bottom): O(1) accessing (arbitrary): O(n) finding (arbitrary): O(n) heap Insertion: O(log n) Extraction: O(log n) Buliding: O(n) // counter-intuitive, but the fact that as you go up in levels, while the number of // swaps you may have to make increases, the number of nodes themselves decreases // causes it to converge in O(n) time Searches Linear search: O(n) Binary search: O(log n) += -= *= /= %= -> -- (prefix) new / >> > != <= >= [] ! delete % () - (unary) delete [] ++ (postfix) -- (postfix) + (unary) sizeof * () (typecast)

Sorts Insertion sort: O(n) for nearly sorted, otherwise O(n^2), can be used on linked lists, stable Selection sort: O(n^2) always, can be used on linked lists, minimizes item swaps, stable Bubble sort: O(n) for nearly sorted, otherwise O(n^2), can be used on linked list, stable Shell sort: O(n^1.25), can be used on linked lists, fixed RAM usage, unstable Merge sort: O(n log n) always, can be used on linked lists, can be parallelized, requires n extra memory usage for merging, stable Quick sort: O(n log n), O(n^2) on nearly sorted (or in reverse) arrays or arrays w/ repeated values, can be used on linked lists, can be parallelized, may require O(n) memory for recursion in worst case, unstable Heap sort: O(n log n) always, 1ow memory requirements, unstable SAMPLE CODE infix to postfix Initialize postfix to empty Initialize the operator stack to empty For each character ch in the infix string Switch (ch) case operand: append ch to end of postfix break case '(': push ch onto the operator stack break case ')': // pop stack until matching '(' While stack top is not '(' append the stack top to postfix pop the stack pop the stack // remove the '(' break case operator: While the stack is not empty and the stack top is not '(' and precedence(ch) <= precedence(stack top) append the stack top to postfix pop the stack push ch onto the stack break While the stack is not empty append the stack top to postfix pop the stack postfix evaluation Initialize the operand stack to empty For each character ch in the postfix string if ch is an operand push the value that ch represents onto the operand stack else // ch is a binary operator set operand2 to the top of the operand stack pop the stack set operand1 to the top of the operand stack pop the stack apply the operation that ch represents to operand1 and operand2, and push the result onto the stack When the loop is finished, the operand stack will contain one item, the result of evaluating the expression

infix to postfix and postfix evaluation #include <string> #include <stack> #include <cctype> #include <cassert> using namespace std;

bool isDigitOrCloseParen(char ch) { return isdigit(ch) || ch == ')'; } int precedence(char ch) // Precondition: ch is in "|&!(" { static const string ops = "|&!("; static const int prec[4] = { 1, 2, 3, 0 }; size_t pos = ops.find(ch); assert(pos != string::npos); // must be found! return prec[pos]; } const int RET_OK_EVALUATION = 0; const int RET_INVALID_EXPRESSION = 1; int evaluate(string infix, const bool values[], string& postfix, bool& result) // Evaluates a boolean expression // If infix is a syntactically valid infix boolean expression, then postfix // is set to the postfix form of that expression, result is set to the value // of the expression (where in that expression, each digit k represents // element k of the values array), and the function returns zero. If infix // is not a syntactically valid expression, the function returns 1. (In // that case, postfix may or may not be changed, but result must be // unchanged.) { // First convert infix to postfix postfix = ""; stack<char> operatorStack; char prevch = '|'; // pretend the previous character was an operator for (size_t k = 0; k < infix.size(); k++) { char ch = infix[k]; switch(ch) { case ' ': continue; // do not set prevch to this char case '(': case '!': if (isDigitOrCloseParen(prevch)) return RET_INVALID_EXPRESSION; operatorStack.push(ch); break; case ')': if ( ! isDigitOrCloseParen(prevch)) return RET_INVALID_EXPRESSION; for (;;) { if (operatorStack.empty()) return RET_INVALID_EXPRESSION; char c = operatorStack.top(); operatorStack.pop(); if (c == '(') break; postfix += c; } break;

// too many ')'

case '|': case '&': if ( ! isDigitOrCloseParen(prevch)) return RET_INVALID_EXPRESSION; while ( ! operatorStack.empty() && precedence(ch) <= precedence(operatorStack.top()) ) { postfix += operatorStack.top();

operatorStack.pop(); } operatorStack.push(ch); break; default: // had better be a digit if (!isdigit(ch)) return RET_INVALID_EXPRESSION; if (isDigitOrCloseParen(prevch)) return RET_INVALID_EXPRESSION; postfix += ch; break; } prevch = ch; } // end of expression; pop remaining operators if ( ! isDigitOrCloseParen(prevch)) return RET_INVALID_EXPRESSION; while ( ! operatorStack.empty()) { char c = operatorStack.top(); operatorStack.pop(); if (c == '(') return RET_INVALID_EXPRESSION; // too many '(' postfix += c; } if (postfix.empty()) return RET_INVALID_EXPRESSION; // empty expression // postfix now contains the converted expression // Now evaluate the postfix expression stack<bool> operandStack; for (size_t k = 0; k < postfix.size(); k++) { char ch = postfix[k]; if (isdigit(ch)) operandStack.push(values[ch-'0']); else { bool opd2 = operandStack.top(); operandStack.pop(); if (ch == '!') operandStack.push(!opd2); else { bool opd1 = operandStack.top(); operandStack.pop(); if (ch == '&') operandStack.push(opd1 && opd2); else if (ch == '|') operandStack.push(opd1 || opd2); else // Impossible! return RET_INVALID_EXPRESSION; // pretend it's an invalid expression } } } if (operandStack.size() != 1) // Impossible! return RET_INVALID_EXPRESSION; // pretend it's an invalid expression result = operandStack.top(); return RET_OK_EVALUATION; } recursive algorithms // Return true if any of the array elements is negative, false // otherwise. bool anyNegative(const double a[], int n) { if (n <= 0)

return false; if (a[0] < 0) return true; return anyNegative(a+1, n-1); } // Return the number of negative elements in the array. int countNegatives(const double a[], int n) { if (n <= 0) return 0; int t = (a[0] < 0); // 1 if true, 0 if false return t + countNegatives(a+1, n-1); } // Return the subscript of the first negative element in the array. // If no element is negative, return -1. int firstNegative(const double a[], int n) { if (n <= 0) return -1; if (a[0] < 0) return 0; int k = firstNegative(a+1, n-1); if (k == -1) return -1; return 1 + k; // element k of "the rest of a" is element 1+k of a } // Return the subscript of the smallest element in the array. If // more than one element has the same smallest value, return the // smallest index of such an element. If the array is empty, // return -1. int indexOfMin(const double a[], int n) { if (n <= 0) return -1; if (n == 1) return 0; int k = 1 + indexOfMin(a+1, n-1); // indexOfMin can't return -1 here return a[0] <= a[k] ? 0 : k; // Here's an alternative for the last two lines above: // int k = indexOfMin(a, n-1); // indexOfMin can't return -1 here // return a[k] <= a[n-1] ? k : n-1; } // // // // // // // // // // // // // // // bool { If all n2 elements of a2 appear in the n1 element array a1, in the same order (though not necessarily consecutively), then return true; otherwise (i.e., if the array a1 does not include a2 as a not-necessarily-contiguous subsequence), return false. (Of course, if a2 is empty (i.e., n2 is 0), return true.) For example, if a1 is the 7 element array 10 50 40 20 50 40 30 then the function should return true if a2 is 50 20 30 or 50 40 40 and it should return false if a2 is 50 30 20 or 10 20 20 includes(const double a1[], int n1, const double a2[], int n2) if (n2 <= 0) return true; if (n1 < n2) return false; // If we get here, a1 and a2 are nonempty if (a1[0] == a2[0]) return includes(a1+1, n1-1, a2+1, n2-1); else

// rest of a1, rest of a2

return includes(a1+1, n1-1, a2, n2); }

// rest of a1, all of a2

bool pathExists(string maze[], int nRows, int nCols, int sr, int sc, int er, int ec) { if (sr == er && sc == ec) return true; maze[sr][sc] = '@'; if (maze[sr-1][sc] return true; if (maze[sr+1][sc] return true; if (maze[sr][sc-1] return true; if (maze[sr][sc+1] return true; return false; } // Return the number of ways that all n2 elements of a2 appear // in the n1 element array a1 in the same order (though not // necessarily consecutively). The empty sequence appears in a // sequence of length n1 in 1 way, even if n1 is 0. // For example, if a1 is the 7 element array // 10 50 40 20 50 40 30 // then for this value of a2 the function must return // 10 20 40 1 // 10 40 30 2 // 20 10 40 0 // 50 40 30 3 int countIncludes(const double a1[], int n1, const double a2[], int n2) { if (n2 <= 0) return 1; if (n1 < n2) return 0; // If we get here, a1 and a2 are nonempty int t = countIncludes(a1+1, n1-1, a2, n2); // rest of a1, all of a2 if (a1[0] == a2[0]) t += countIncludes(a1+1, n1-1, a2+1, n2-1); // rest of a1, rest of a2 return t; } // Exchange two doubles void exchange(double& x, double& y) { double t = x; x = y; y = t; } // // // // // // // // // // // // // // // void { Rearrange the elements of the array so that all the elements whose value is > splitter come before all the other elements, and all the elements whose value is < splitter come after all the other elements. Upon return, firstNotGreater is set to the index of the first element in the rearranged array that is <= splitter, or n if there is no such element, and firstLess is set to the index of the first element that is < splitter, or n if there is no such element. In other words, upon return from the function, the array is a permutation of its original value such that * for 0 <= i < firstNotGreater, a[i] > splitter * for firstNotGreater <= i < firstLess, a[i] == splitter * for firstLess <= i < n, a[i] < splitter All the elements > splitter end up in no particular order. All the elements < splitter end up in no particular order. split(double a[], int n, double splitter, int& firstNotGreater, int& firstLess) // anything non-'.' will do == '.' == '.' == '.' == '.' && && && && pathExists(maze, nRows, nCols, sr-1, sc, er, ec)) pathExists(maze, nRows, nCols, sr+1, sc, er, ec)) pathExists(maze, nRows, nCols, sr, sc-1, er, ec)) pathExists(maze, nRows, nCols, sr, sc+1, er, ec))

if (n < 0) n = 0; // It will always be the case that just before evaluating the loop // condition: // firstNotGreater <= firstUnknown and firstUnknown <= firstLess // Every element earlier than position firstNotGreater is > splitter // Every element from position firstNotGreater to firstUnknown-1 is // == splitter // Every element from firstUnknown to firstLess-1 is not known yet // Every element at position firstLess or later is < splitter firstNotGreater = 0; firstLess = n; int firstUnknown = 0; while (firstUnknown < firstLess) { if (a[firstUnknown] < splitter) { firstLess--; exchange(a[firstUnknown], a[firstLess]); } else { if (a[firstUnknown] > splitter) { exchange(a[firstNotGreater], a[firstUnknown]); firstNotGreater++; } firstUnknown++; } } } // // // void { Rearrange the elements of the array so that a[0] >= a[1] >= a[2] >= ... >= a[n-2] >= a[n-1] If n <= 1, do nothing. order(double a[], int n) if (n <= 1) return; // Split using a[0] as the splitter (any element would do). int firstNotGreater; int firstLess; split(a, n, a[0], firstNotGreater, firstLess); // sort the elements > splitter order(a, firstNotGreater); // sort the elements < splitter order(a+firstLess, n-firstLess); } Linked list operations // Advancing ptr if (ptr != nullptr) ptr = ptr->next; // Check if ptr is last node if (ptr != nullptr && ptr->next == nullptr) return true; // Get next node's data if (ptr != nullptr && ptr->next != nullptr) return ptr->next->value; // Get head node's data if (m_head != nullptr) returb m_head->value;

// Check if list is empty return m_head == nullptr; Linked list with dummy node operations // Initialize m_head = new Node; m_head->m_next = m_head; m_head->m_prev = m_head; // Delete every node Node *p = m_head->m_prev while (m_size >= 0) { Node *toDelete = p; p = p->m_prev; delete toDelete; } // Delete arbitrary node p->m_prev->m_next = p->m_next; p->m_next->m_prev = p->m_prev; delete p; m_size--; // Search list Node *p; for (p = m_head->m_next; p != m_head && p->m_key != key; p = p->m_next) ; return p; // Returning m_head means not found // Insertion (to tail of list, but technically arbitrary) p->m_prev = m_head->m_prev; p->m_prev->m_next = p; p->m_next = m_head; m_head->m_prev = p; m_size++; Binary search tree operations // Left rotation, flip sides for right rotation Node *p = m_root->m_right; m_root->m_right = p->m_left; p->m_left = m_root; m_root = p; // Deletion // Let us assume curNode already points to the to be deleted node, and parent is its parent if (curNode->m_left == nullptr && curNode->m_right == nullptr) { if (curNode == parent->m_left) parent->m_left = nullptr; else parent->m_right = nullptr; delete curNode; } else if (curNode->m_left == nullptr) // grandchild on right node { if (curNode == parent->m_left) parent->m_left = curNode->m_right; else parent->m_right = curNode->m_right; delete curNode; } else if (curNode->m_right == nullptr) // grandchild on left node { if (curNode == parent->m_left) parent->m_left = curNode->m_left; else parent->m_right = curNode->m_left; delete curNode; }

else {

// we arbitrarily choose the largest node in the left subtree, over the smallest node in the // right subtree Node *replacement = curNode->m_left; while (replacement->m_right != nullptr) replacement = replacement->m_right; curNode->m_val = replacement->m_val; // and any other copying that must happen deleteNode(replacement); // alternatively, replacement is guaranteed to have either no // children or a left child, so you can manually code that in

} Sorts and searches Selection sort for (int i = 0; i < n; i++) { // Find the smallest value int min = i; for (int j = i + 1; j < n; j++) if (a[j] < a[min]) min = j; // Swap smallest value with i swap(a[i], a[min]); } Insertion sort for (int i = 1; i < n; i++) { // Move ith element as far down as possible for (int j = i; j > 0; j--) { if (a[j] >= a[j - 1]) break; swap(a[j], a[j - 1]); } } Bubble sort bool swapped; do { swapped = false; // At the end of the for loop, the max value will be at the top of the array for (int i = 0; i + 1 < n; j++) { if (a[i] > a[i + 1]) { swap(a[i], a[i + 1]); swapped = true; } } } while (swapped); Shell sort for (int h = 8; h >= 1; h /= 2) // can be any decreasing sequence as long as it ends with h = 1 { bool swapped; do { swapped = false; for (int i = 0; i + h < n; i++) { if (a[i] > a[i + h]) { swap(a[i], a[i + 1]); swapped = true; } } } while (swapped); }

Quick sort int partition(int a[], int low, int high) { int pi = low; int pivot = a[low]; do { while (low <= high && a[low] <= pivot) low++; while (a[high] > pivot) high++; if (low < high) swap(a[low], a[high]); } while (low < high); swap(a[pi], a[high]); pi = high; return pi; } void quicksort(int a[], int from, int to) { if (to - from >= 1) { int pivotIndex = partition(a, from, to); quicksort(a, from, pivotIndex - 1); quicksort(a, pivotIndex + 1, to); } } Merge sort void merge(int a[], int n1, int n2) { int *temp = new int[n1 + n2]; int i = 0, j = 0, k = 0; int *b = a + n1; // This is the second half of array while (i < n1 || j < n2) { if (i == n1) temp[k++] = b[j++]; else if (j == n2) temp[k++] = a[i++]; else if (a[i] < b[j]) temp[k++] = a[i++]; else temp[k++] = b[j++]; } for (i = 0; i < n1 + n2; i++) a[i] = temp[i]; delete [] temp; } void mergesort(int a[], int from, int to) { if (from - to <= 0) return; int firstHalfTo = (from + to) / 2; mergesort(a, from, firstHalfTo); mergesort(a, firstHalfTo + 1, to); merge(a, firstHalfTo - from + 1, to - firstHalfTo); }

Heap sort void siftDown(int i, int n) { // sift element i down while (left(i) < n) { if (a[i] < a[left(i)] || a[i] < a[right(i)]) { if (a[left(i)] > a[right(i)]) { swap(a[i], a[left(i)]); i = left(i); } else { swap(a[i], a[right(i)]); i = right(i); } } else break; } } void heapSort(int a[], int n) { // heapify // initial condition avoids all single node trees for (int i = n / 2 - 1; i >= 0; i--) siftDown(i, n); // extract and reheapify for (int i = n - 1; i >= 0; i--) { swap(a[0], a[i]); siftDown(0, i); } }

You might also like