Download Lecture Notes on Lists - Data Structures | CSCI 1200 and more Study notes Data Structures and Algorithms in PDF only on Docsity!
CSCI-1200 Computer Science II — Fall 2007
Lecture 9 — Lists
Review from Lecture 8
- We wrote several versions of a program to maintain a class enrollment list and an associated waiting list.
- The first version used vectors to store the information. Unfortunately, erasing items from vectors is inefficient.
- In the second version, we explored iterators and iterator operations as a different means of manipulating the contents of the vector.
- This allows us to replace the vector with a list in the third version. There is an erase function for both vectors and lists. The vector erase function does pretty much what we did in our enrollment example program. The list erase function is much more efficient (more on this next week!).
- For the enrollment problem, the list is a better sequential container class than the vector.
Today’s Class
- Returning references to member variables from member functions
- Review of iterators and iterator operations
- STL Lists, erase and insert on lists
- Differences between indices and iterators, differences between lists and vectors
- Example programs using STL Lists
9.1 References and Return Values
- A reference is an alias for another variable. For example:
string a = "Tommy"; string b = a; // a new string is created using the string copy constructor string& c = a; // c is an alias/referece to the string object a
b[1] = ’i’; cout << a << " " << b << " " << c << endl; // outputs: Tommy Timmy Tommy
c[1] = ’a’; cout << a << " " << b << " " << c << endl; // outputs: Tammy Timmy Tammy
The reference variable c refers to the same string as variable a. Therefore, when we change c, we change a.
- Exactly the same thing occurs with reference parameters to functions and the return values of functions. Let’s look at the Student class from Lecture 4 again:
class Student { public: const string& first_name() const { return first_name_; } const string& last_name() const { return last_name_; } // etc....
private: string first_name_; string last_name_; // etc... };
- In the main function we had a vector of students:
vector students;
Based on our discussion of references above and looking at the class declaration, what if we wrote:
string & fname = students[i].first_name(); fname[1] = ’i’
Would the code then be changing the internal contents of the i-th Student object?
- The answer is NO! The Student class member function first_name returns a const reference. The compiler will complain that the above code is attempting to assign a const reference to a non-const reference variable.
- If we instead wrote:
const string & fname = students[i].first_name(); fname[1] = ’i’
Then compiler would complain that you are trying to change a const object.
- Hence in both cases the Student class would be “safe” from attempts at external modification.
- However, the author of the Student class would get into trouble if the member function return type was only a reference, and not a const reference. Then external users could access and change the internal contents of an object! This is a bad idea in most cases.
9.2 The list Standard Library Container Class
- Lists are formed as a sequentially linked structure instead of the array-like, random-access / indexing structure of vectors.
array/vector:
7 5 8 1 9 7 5 8 1 9 0 1 2 3 4
list:
- Lists have push front and pop front functions in addition to the push back and pop back functions of vectors.
- Erase is very efficient for a list, independent of the size of the list (we’ll see why when we learn the implemen- tation details later in the semester).
- We can’t use the standard sort function; we must use a special sort function defined by the list type.
- Lists have no subscripting operation (a.k.a. they do not allow “random-access”).
9.3 Iterators and Iterator Operations — General
- An iterator type is defined by each container class. For example,
vector::iterator v_itr; list::iterator l_itr; string::iterator s_itr;
- An iterator is assigned to a specific location in a container. For example:
v_itr = vec.begin() + i; // i-th location in a vector l_itr = lst.begin(); // first entry in a list s_itr = str.begin(); // first char of a string
Note: We can add an integer to vector and string iterators, but not to list iterators.
- The contents of the specific entry referred to by an iterator are accessed using the * dereference operator:
*v_itr = 3.14; cout << *s_itr << endl; *l_itr = "Hello";
In the first and third lines, *v itr and *l itr are l-values. In the second, *s_itr is an r-value.
9.7 Erase
- Lists and vectors each have a special member function called erase. In particular, given list of ints s, consider the example:
list::iterator p = s.begin(); ++p; list::iterator q = s.erase(p);
- After the code above is executed:
- The integer stored in the second entry of the list has been removed.
- The size of the list has shrunk by one.
- The iterator p does not refer to a valid entry.
- The iterator q refers to the item that was the third entry and is now the second.
p q
p
- To reuse the iterator p and make it a valid entry, you will often see the code written:
list::iterator p = s.begin(); ++p; p = s.erase(p);
- Now we can rewrite the erase from vector function from the Lecture 8 enrollment example:
p = v.erase(p);
- Even though this has the same syntax for vectors and for list, the vector version is O(n), whereas the list version is O(1).
9.8 Insert
- Similarly, there is an insert function for lists that takes an iterator and a value and adds a link in the chain with the new value immediately after the item pointed to by the iterator.
- The call returns an iterator that points to the newly added element. Variants on the basic insert function are also defined.
9.9 Exercise: Erase & Insert
Write a function that takes a list of integers, lst, and an integer, x. The function should 1) remove all negative numbers from the list, 2) verify that the remaining elements in the list are sorted in increasing order, and 3) insert x into the list such that the order is maintained.
9.10 Prime Numbers: Sieve of Eratosthenes
- We will explore the problem of finding all primes less than a given integer, n, and introduce the Sieve of Eratosthenes algorithm.
- The algorithm is a “casting out” algorithm: each new prime is used to cast out all of its multiples from a list of potential primes.
- Finish the skeleton code below which uses lists and iterators.
- Why did we choose to use a list rather than a vector?
#include #include using namespace std;
int main() {
int n; cout << "Enter the upper bound on the set of primes you are interested in: "; cin >> n;
list primes;
// Initialize with everything from 2 to n for (unsigned int i=2; i<=n; ++i) primes.push_back(i);
// p will indicate the current prime list::iterator p = primes.begin();
// step through primes list until it is exhausted while (p != primes.end()) {
// throw out all the numbers that are divisible by the current prime
}
// output out all the primes cout << primes.size() << " primes: " << endl; for (p = primes.begin(); p != primes.end(); p++) { cout << *p << " "; } cout << endl;
return 0; }