Содержание:
Theory
Forward_list or singly-linked lists
- Elements in sequence containers are ordered in a strict linear sequence. Individual elements are accessed by their position in this sequence.
- Each element keeps information on how to locate the next element, allowing constant time insert and erase operations after a specific element (even of entire ranges), but no direct random access.
- the new operation allocates dynamic memory to a single node
- after allocating dynamic memory, the constructor is called with the specified parameters
Sequence
Linked list
How to create?
List node class:
template <typename T> struct node { T data; node<T>* next; node(T data, node<T>* next) { this->data = data; this->next = next; } }; |
Пустой список:
node<int>* p = null; |
Adding an item to the top of the list:
p = new node<int>(5,p); |
Example:
p = new node<int>(3,p); |
Function templates with lists
Example add_first function template:
template <typename T> void add_first(T data, node<T>*& p) { p = new node<T>(data,p); } int main() { node<int>* p = nullptr; add_first(5,p); // two stages of compiling a function template add_first(3,p); } |
Creating and Printing a singly-linked (forward) list:
template <typename T> void print(node<T>* p) { while (p) { cout << p-> data; p = p->next; } } int main() { node<int>* p = nullptr; add_first(5,p); add_first(3,p); print(p); } |
Freeing the memory occupied by a singly linked list
template <typename T> void free(node<T>* p) { while (p) { auto p1 = p; p = p->next; delete p1; } } int main() { node<int>* p = nullptr; add_first(5,p); add_first(3,p); print(p); //free(p); //p = nullptr; } |
Complicated:
// forward_list constructors #include <iostream> #include <forward_list> int main () { std::forward_list<int> first; // (1) default: empty std::forward_list<int> second (3,77); // (2) fill: 3 seventy-sevens std::forward_list<int> third (second.begin(), second.end()); // (3) range initialization std::forward_list<int> fourth (third); // (4) copy constructor std::forward_list<int> fifth (std::move(fourth)); // (5) move ctor. (fourth wasted) std::forward_list<int> sixth = {3, 52, 25, 90}; // (6) initializer_list constructor std::cout << "first:" ; for (int& x: first) std::cout << " " << x; std::cout << '\n'; std::cout << "second:"; for (int& x: second) std::cout << " " << x; std::cout << '\n'; std::cout << "third:"; for (int& x: third) std::cout << " " << x; std::cout << '\n'; std::cout << "fourth:"; for (int& x: fourth) std::cout << " " << x; std::cout << '\n'; std::cout << "fifth:"; for (int& x: fifth) std::cout << " " << x; std::cout << '\n'; std::cout << "sixth:"; for (int& x: sixth) std::cout << " " << x; std::cout << '\n'; return 0; } |
Expected output:
first: second: 77 77 77 third: 77 77 77 fourth: fifth: 77 77 77 sixth: 3 52 25 90
(1) — default — Constructs an empty container, with no elements.
(2) — fill constructor — Constructs a container with n
elements. Each element is a copy of val (if provided).
(3) — range constructor — Constructs a container with as many elements as the range [first,last), with each element emplace-constructed from its corresponding element in that range, in the same order.
(4) — copy constructor (and copying with allocator) — Constructs a container with a copy of each of the elements in fwdlst, in the same order.
(5) — move constructor (and moving with allocator) — Constructs a container that acquires the elements of fwdlst
. If alloc
is specified and is different from fwdlst
‘s allocator, the elements are moved. Otherwise, no elements are constructed (their ownership is directly transferred). fwdlst
is left in an unspecified but valid state.
(6) — initializer list constructor — Constructs a container with a copy of each of the elements in il, in the same order.
Syntax:
range (1)
templatevoid assign (InputIterator first, InputIterator last);
fill (2)
void assign (size_type n, const value_type& val);
initializer list (3)
void assign (initializer_listil);
Any elements held in the container before the call are destroyed and replaced by newly constructed elements (no assignments of elements take place).
Parameters:
first
, last
Input iterators to the initial and final positions in a sequence. The range used is [first,last), which includes all the elements between first and last, including the element pointed by first but not the element pointed by last.
The function template argument InputIterator shall be an input iterator type that points to elements of a type from which value_type objects can be constructed.
n
New size for the container.
Member type size_type is an unsigned integral type.
val
Value to fill the container with. Each of the n elements in the container will be initialized to a copy of this value.
Member type value_type is the type of the elements in the container, defined in forward_list as an alias of its first template parameter (T).
il
An initializer_list object. The compiler will automatically construct such objects from initializer list declarators. Member type value_type is the type of the elements in the container, defined in forward_list as an alias of its first template parameter (T).
Example:
#include <iostream> #include <forward_list> int main () { std::forward_list<int> first; std::forward_list<int> second; first.assign (4,15); // 15 15 15 15 second.assign (first.begin(),first.end()); // 15 15 15 15 first.assign ( {77, 2, 16} ); // 77 2 16 std::cout << "first contains: "; for (int& x : first) std::cout << ' ' << x; std::cout << '\n'; std::cout << "second contains: "; for (int& x : second) std::cout << ' ' << x; std::cout << '\n'; return 0; } |
Output:
first contains: 77 2 16 second contains: 15 15 15 15
=
with forward_listSyntax:
copy (1)
forward_list& operator= (const forward_list& fwdlst);
move (2)
forward_list& operator= (forward_list&& fwdlst);
initializer list(3)
forward_list& operator= (initializer_listil);
Assigns new contents to the container, replacing its current contents.
The container preserves its current allocator, except if the allocator traits indicate fwdlst’s allocator should propagate. This allocator is used (through its traits) to allocate or deallocate if there are changes in storage requirements, and to construct or destroy elements, if needed.
Parameters:
fwdlst
A forward_list object of the same type (i.e., with the same template parameters, T and Alloc).
il
An initializer_list object. The compiler will automatically construct such objects from initializer list declarators. Member type value_type is the type of the elements in the container, defined in forward_list as an alias of its first template parameter (T).
Example:
#include <iostream> #include <forward_list> template<class Container> Container by_two (const Container& x) { Container temp(x); for (auto& x:temp) x*=2; return temp; } int main () { std::forward_list<int> first (4); // 4 ints std::forward_list<int> second (3,5); // 3 ints with value 5 first = second; // copy assignment second = by_two(first); // move assignment std::cout << "first: "; for (int& x : first) std::cout << ' ' << x; std::cout << '\n'; std::cout << "second: "; for (int& x : second) std::cout << ' ' << x; std::cout << '\n'; return 0; } |
In the first assignment, second
is an lvalue
: the copy assignment function is called.
In the second assignment, the value returned by by_two(first)
is an rvalue
: the move assignment function is called.
Output:
first: 5 5 5 second: 10 10 10
void resize (size_type n); void resize (size_type n, const value_type& val);
Resizes the container to contain n elements.
If n
is smaller than the current number of elements in the container, the content is trimmed to contain only its first n elements, removing those beyonf (and destroying them).
If n
is greater than the current number of elements in the container, the content is expanded by inserting at the end as many elements as needed to reach a size of n elements. If val
is specified, the new elements are initialized as copies of val, otherwise, they are value-initialized.
Example
// resizing forward_list #include <iostream> #include <forward_list> int main () { std::forward_list<int> mylist = {10, 20, 30, 40, 50}; // 10 20 30 40 50 mylist.resize(3); // 10 20 30 mylist.resize(5,100); // 10 20 30 100 100 std::cout << "mylist contains:"; for (int& x: mylist) std::cout << ' ' << x; std::cout << '\n'; return 0; } |
mylist contains: 10 20 30 100 100
Example
// forward_list::insert_after #include <iostream> #include <array> #include <forward_list> int main () { std::array<int,3> myarray = { 11, 22, 33 }; std::forward_list<int> mylist; std::forward_list<int>::iterator it; it = mylist.insert_after ( mylist.before_begin(), 10 ); // 10 // ^ <- it it = mylist.insert_after ( it, 2, 20 ); // 10 20 20 // ^ it = mylist.insert_after ( it, myarray.begin(), myarray.end() ); // 10 20 20 11 22 33 // ^ it = mylist.begin(); // ^ it = mylist.insert_after ( it, {1,2,3} ); // 10 1 2 3 20 20 11 22 33 // ^ std::cout << "mylist contains:"; for (int& x: mylist) std::cout << ' ' << x; std::cout << '\n'; return 0; } |
Output:
mylist contains: 10 1 2 3 20 20 11 22 33
Doubly linked lists
- Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.
template < class T, class Alloc = allocator> class list;
* Information has taken from: http://www.cplusplus.com/reference/forward_list/forward_list/.
Labs and tasks
To do: Create a linked lists with three elements (nodes) named
root
, first
and second
. Create a function to print the elements of the list.
Expected output:
Lab 0 1 2 3
✍ How to do:
- Create an empty console application. Add the source file and open its code. Include all the necessary libraries and
main
function. - First of all, let’s create a linkled list. It can be done as a structure above the
main
function: - In the
main
function, three linked lists must be declared: - After we’ve declared lists, let’s assign the values:
- Now, let’s create a function to print out the values of the list. It will have an argument — a pointer to the
root
element. Add the following code above the main function: - Call this function inside the
main
function: - Run the application and check the output.
struct LinkedList { int data; LinkedList *next; };
data
is to store a value of the element, and *next
is a pointer to point at the memory address of the next list element.new ; second = new ; third = new ;*root, *second, *third; root =
root->data = 1;
root->next = second;
second->data = 2;
second->next = third;
third->data = 3;
third ->next = NULL;
data
is a value of the element, while next
is a stored address to the next element. void printLinkedList( *root) { while (root) { printf("%d ", root->data); root = root->next; } }
while
loop to iterate over the list’s nodes. Each iteration we assign the address of the next element to the root
. The value of the next
of the last element equals to 0
(NULL
), thus the while loop will stop.printLinkedList(root);
To do: Create a circular list with three nodes. Assign values for nodes and print them out.
Expected output:
List contains the following nodes: 1 2 3
✍ How to do:
- Create an empty console application. Add the source file and open its code. Include all the necessary libraries and
main
function. - First of all, let’s create a structure which represents a node. It can be done above the
main
function: - Now, we’re going to сreate a function to add the first node to the list. This means the list is empty. The function will take two arguments: an address of node to create and the data to store it in node being created. Add the signature of the function above the
main
: - Inside this function it is better to check if the list is really empty. If it is not, we should return the address of the created node to work them inside another function.
- Then, we need to allocate a dynamic memory for a new node. Let’s call this node
temp
: - Now, we must assign data value and address value to the node. We have to do it to make the list circular:
- Now, we’re going to сreate a function to add the node to the list, which is not empty. The function will take two arguments: an address of node to create and the data to store it in node being created. Add the signature of the function above the
main
: - Inside this function it is better to check if the list is empty. If it is, we should return the calling of
addToEmpty
function: - If the function is not empty, we need to allocate a dynamic memory for a new node:
- After, we must assign data value and address value to the node. We have to do it to make the list circular:
- Now we’re going to create a function to traverse the list. It will have single argument — a pointer to the list to be traversed. Add the signeture begore
main
: - Inside the function we should create a pointer to the list:
- After, we’re going to loop over the list nodes and print them out:
- Now, we can turn to the
main
function and create the future list. TheNULL
must be assigned to it (it is empty). - To add the first node we should call the
addToEmpty()
function, since it is empty. To create the second and third nodes we should call theaddEnd()
function. The values must be1
,2
and3
: - At last, we must call the function to print the nodes out:
- Run the application and check the output.
struct Node { int data; struct Node *next; };
struct Node *addToEmpty(struct Node *last, int data) { // ... };
if (last != NULL) return last;
struct Node * temp = new Node;
temp->data = data; // we assign passed data to the node last = temp; // since we have no anything inside list, so last node will be the only one last->next = last; // this line makes link circular; the node actually points to itself (because the only one) return last;
struct Node *addEnd(struct Node *last, int data) { // ... }
if (last == NULL) return addToEmpty(last,data);
struct Node * temp = new Node;
temp->data = data; temp->next = last->next; // the address of the previous node is assigned last->next = temp; // assign the address of the current node to the previous node last = temp; return last;
void traverse(struct Node *first) { //... }
struct Node *tmp = first;
if (first != NULL) { do { cout << tmp->data << " "; tmp = tmp->next; // move to the next node } while (tmp != first); }
struct Node *last = NULL;
last = addToEmpty(last, 1); // data = 1
last = addEnd(last, 2);
last = addEnd(last, 3);
cout << "List contains the following nodes:" << endl; traverse(last->next);
To do: Create a solution to test the methods of a list type. Create three lists and use the following methods with them:
- insert_after
- emplace_after
- reverse
- splice
- sort
- merge
- remove
- remove_if
Expected output:
Lab 3 list 1: 5 4 3 2 list 2: 1 4 7 6 list 3: 9 8 7 1. insert_after method, list1 (insert 8 number in the position 2): 5 8 4 3 2 2. emplace_after method, list3 (add 5 to list3 in the position 2): 9 5 8 7 3. reversed method(list1): 2 3 4 8 5 4. splice method (list1 with list3): after splice, list1: 2 9 5 8 7 3 4 8 5 after splice, list 3: 5. sort method, list 1: 2 3 4 5 5 7 8 8 9 sort method, list 2: 1 4 6 7 6. merged method (list1 and list2): 1 2 3 4 4 5 5 6 7 7 8 8 9 7. remove method, list1 (2 is removed): 1 3 4 4 5 5 6 7 7 8 8 9 8. remove_if method, list1 (numbers>4 are removed): 1 3 4 4
✍ How to do:
Lesson12
. Add file: L12Lab3main.cpp
.int main
function that returns 0
.#include <forward_list> //... forward_list<int> list1 = { 5,4,3,2 }; forward_list<int> list2 = { 1,4,7,6 }; forward_list<int> list3 = { 9,8,7 }; cout << "\n list 1: "; for (auto &elem : list1) cout << elem << " "; cout << "\n list 2: "; for (auto &elem : list2) cout << elem << " "; cout << "\n list 3: "; for (auto &elem : list3) cout << elem << " ";
Test the following functions and consider results:
- insert_after method
- emplace_after
- reverse
- splice_after:
- sort
- merge:
- remove
- remove_if
Insert 8 into 2 position of list1
:
cout << "\n 1. insert_after function, list1 (insert 8 number in the position 2):" << endl; list1.insert_after(list1.begin(), 8); for (auto &elem : list1) cout << elem << " ";
Insert 5 into 2 position of list3
:
cout << "\n 2. emplace_after function, list3 (add 5 to list3 in the position 2):" << endl; list3.emplace_after(list3.begin(), 5); for (auto &elem : list3) cout << elem << " ";
Reverse list1
by yourself and output the result.
You should splice list3
from the position 2 of list2
list1.splice_after(list1.begin(), list3); cout << "\n 4. splice function (list1 with list3):" << endl; cout << "\n after splice, list1:" << endl; for (auto &elem : list1) cout << elem << " "; cout << "\n after splice, list 3:" << endl; for (auto &elem : list3) cout << elem << " "; //must become empty
Sort list1
and list2
by yourself and output the results.
Merge list1
and list2
. Note that lists must be sorted:
list1.merge(list2); cout << "\n 6. merged function (list1 and list2):" << endl; for (auto &elem : list1) cout << elem << " ";
Remove 2
from the list1
:
list1.remove(2); cout << "\n 7. remove function, list1 (2 is removed):" << endl; for (auto &elem : list1) cout << elem << " ";
Remove all the elements which are greater than 4 from the list1
:
list1.remove_if([](int n) { return n > 4; }); cout << "\n 8. remove_if function, list1 (numbers>4 are removed):" << endl; for (auto &elem : list1) cout << elem << " ";