Вы находитесь на странице: 1из 30

Practical File

DESIGN & Analysis


of
algorithms

I.K. GUJRAL PUNJAB TECHNICAL UNIVERSITY


KAPURTHALA

Submitted to – Submitted by -
Er. Ankur Bohra Sanamdeep Singh
1731858
1. GCD
#include<iostream>
using namespace std;
int GCD(int a,int b);
int count=1,temp;
int main(){
int a,b;
cout<<"enter the value of a ";
cin>>a;
cout<<"enter the value of b ";
cin>>b;
cout<<"GCD is "<<GCD(a,b);
return 0;
}
int GCD(int a,int b){
int min;
if(a==0 || b==0){
return 0;
}
else if(a>b){
min=b;
}
if(b>a){
min=b;
}
while(count<=min){
if(a%count==0 && b%count==0){
temp=count;
}
count++;
}
return temp;
}

2. Median
// CPP program to find mean and median of
// an array
#include <bits/stdc++.h>
using namespace std;
double findMedian(int a[], int n)
{
// First we sort the array
sort(a, a+n);

// check for even case


if (n % 2 != 0)
return (double)a[n/2];
else
return (double)(a[(n-1)/2] + a[n/2])/2.0;
}
int main()
{
int a[] = { 1, 3, 4, 2, 7, 5, 8, 6 };
int n = sizeof(a)/sizeof(a[0]);
cout << "Median = " << findMedian(a, n) << endl;
return 0;
}

3. Majority element

#include <bits/stdc++.h>
using namespace std;
void findMajority(int arr[], int n)
{
int maxCount = 0;
int index = -1; // sentinels
for(int i = 0; i < n; i++)
{
int count = 0;
for(int j = 0; j < n; j++)
{
if(arr[i] == arr[j])
count++;
}
if(count > maxCount)
{
maxCount = count;
index = i;
}
}
if (maxCount > n/2)
cout << arr[index] << endl;

else
cout << "No Majority Element" << endl;
}

// Driver code
int main()
{
int arr[] = {1, 1, 2, 1, 3, 5, 1};
int n = sizeof(arr) / sizeof(arr[0]);

// Function calling
findMajority(arr, n);

return 0;
}

4. Heap Sort
// C++ program for implementation of Heap Sort
#include <iostream>

using namespace std;


void getdata(int arr[],int size);

void heapify(int arr[], int n, int i)


{
int largest = i; // Initialize largest as root
int l = 2*i + 1; // left child= 2*i + 1
int r = 2*i + 2; // right child= 2*i + 2

// If left child is larger than root


if (l < n && arr[l] > arr[largest])
largest = l;

// If right child is larger than largest so far


if (r < n && arr[r] > arr[largest])
largest = r;

// If largest is not root


if (largest != i)
{
swap(arr[i], arr[largest]);

// Recursive Call
heapify(arr, n, largest);
}
}

void heapSort(int arr[], int n)


{
// Build heap
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);

for (int i=n-1; i>=0; i--)


{
swap(arr[0], arr[i]);

heapify(arr, i, 0);
}
}

void printArray(int arr[], int n)


{
for (int i=0; i<n; ++i)
cout << arr[i] << " ";
cout << "\n";
}

int main()
{ int n;
cout<<"enter no. of elements ";
cin>>n;
int arr[n];

getdata(arr,n);

heapSort(arr, n);

cout << "Sorted array is \n";


printArray(arr, n);
}

void getdata(int arr[],int size){


for(int i=0;i<size;i++){
cin>>arr[i];
}
}

5. Merge Sort

/* C program for Merge Sort */


#include<stdlib.h>
#include<stdio.h>

void merge(int arr[], int l, int m, int r)


{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

/* create temp arrays */


int L[n1], R[n2];

/* Copy data to temp arrays L[] and R[] */


for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1+ j];

/* Merge the temp arrays back into arr[l..r]*/


i = 0; // Initial index of first subarray
j = 0; // Initial index of second subarray
k = l; // Initial index of merged subarray
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
{
arr[k] = L[i];
i++;
}
else
{
arr[k] = R[j];
j++;
}
k++;
}

/* Copy the remaining elements of L[], if there


are any */
while (i < n1)
{
arr[k] = L[i];
i++;
k++;
}

/* Copy the remaining elements of R[], if there


are any */
while (j < n2)
{
arr[k] = R[j];
j++;
k++;
}
}

/* l is for left index and r is right index of the


sub-array of arr to be sorted */
void mergeSort(int arr[], int l, int r)
{
if (l < r)
{
// Same as (l+r)/2, but avoids overflow for
// large l and h
int m = l+(r-l)/2;

// Sort first and second halves


mergeSort(arr, l, m);
mergeSort(arr, m+1, r);

merge(arr, l, m, r);
}
}

/* UTILITY FUNCTIONS */
/* Function to print an array */
void printArray(int A[], int size)
{
int i;
for (i=0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}

/* Driver program to test above functions */


int main()
{
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr)/sizeof(arr[0]);

printf("Given array is \n");


printArray(arr, arr_size);

mergeSort(arr, 0, arr_size - 1);

printf("\nSorted array is \n");


printArray(arr, arr_size);
return 0;
}

6. Quick Sort

/* C++ implementation of QuickSort */


#include <bits/stdc++.h>
using namespace std;

// A utility function to swap two elements


void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}

/* This function takes last element as pivot, places


the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
int partition (int arr[], int low, int high)
{
int pivot = arr[high]; // pivot
int i = (low - 1); // Index of smaller element

for (int j = low; j <= high - 1; j++)


{
// If current element is smaller than the pivot
if (arr[j] < pivot)
{
i++; // increment index of smaller element
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}

/* The main function that implements QuickSort


arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);

// Separately sort elements before


// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}

/* Function to print an array */


void printArray(int arr[], int size)
{
int i;
for (i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}

// Driver Code
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
cout << "Sorted array: \n";
printArray(arr, n);
return 0;
}

7. Edit Distance
// A Naive recursive C++ program to find minimum number
// operations to convert str1 to str2
#include<bits/stdc++.h>
using namespace std;

// Utility function to find minimum of three numbers


int min(int x, int y, int z)
{
return min(min(x, y), z);
}

int editDist(string str1 , string str2 , int m ,int n)


{
// If first string is empty, the only option is to
// insert all characters of second string into first
if (m == 0) return n;

// If second string is empty, the only option is to


// remove all characters of first string
if (n == 0) return m;

// If last characters of two strings are same, nothing


// much to do. Ignore last characters and get count for
// remaining strings.
if (str1[m-1] == str2[n-1])
return editDist(str1, str2, m-1, n-1);

return 1 + min ( editDist(str1, str2, m, n-1), // Insert


editDist(str1, str2, m-1, n), // Remove
editDist(str1, str2, m-1, n-1) // Replace
);
}

// Driver program
int main()
{
// your code goes here
string str1 = "sunday";
string str2 = "saturday";

cout << editDist( str1 , str2 , str1.length(), str2.length());

return 0;
}

9. Matrix Chain Multiplication


#include<bits/stdc++.h>
using namespace std;
// Matrix Ai has dimension p[i-1] x p[i]
// for i = 1..n
int MatrixChainOrder(int p[], int i, int j)
{
if(i == j)
return 0;
int k;
int min = INT_MAX;
int count;

// place parenthesis at different places


// between first and last matrix, recursively
// calculate count of multiplications for
// each parenthesis placement and return the
// minimum count
for (k = i; k < j; k++)
{
count = MatrixChainOrder(p, i, k) +
MatrixChainOrder(p, k + 1, j) +
p[i - 1] * p[k] * p[j];

if (count < min)


min = count;
}

// Return minimum count


return min;
}

// Driver Code
int main()
{
int arr[] = {1, 2, 3, 4, 3};
int n = sizeof(arr)/sizeof(arr[0]);
cout << "Minimum number of multiplications is "
<< MatrixChainOrder(arr, 1, n - 1);
}

12. Dijkstra’s Algorithm

// A C++ program for Dijkstra's single source shortest path algorithm.


// The program is for adjacency matrix representation of the graph

#include <limits.h>
#include <stdio.h>

// Number of vertices in the graph


#define V 9
int minDistance(int dist[], bool sptSet[])
{
// Initialize min value
int min = INT_MAX, min_index;

for (int v = 0; v < V; v++)


if (sptSet[v] == false && dist[v] <= min)
min = dist[v], min_index = v;

return min_index;
}

// A utility function to print the constructed distance array


int printSolution(int dist[])
{
printf("Vertex \t\t Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}
// Function that implements Dijkstra's single source shortest path algorithm
// for a graph represented using adjacency matrix representation
void dijkstra(int graph[V][V], int src)
{
int dist[V]; // The output array. dist[i] will hold the shortest
// distance from src to i

bool sptSet[V]; // sptSet[i] will be true if vertex i is included in shortest


// path tree or shortest distance from src to i is finalized

// Initialize all distances as INFINITE and stpSet[] as false


for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = false;

// Distance of source vertex from itself is always 0


dist[src] = 0;

// Find shortest path for all vertices


for (int count = 0; count < V - 1; count++) {
// Pick the minimum distance vertex from the set of vertices not
// yet processed. u is always equal to src in the first iteration.
int u = minDistance(dist, sptSet);

// Mark the picked vertex as processed


sptSet[u] = true;

// Update dist value of the adjacent vertices of the picked vertex.


for (int v = 0; v < V; v++)
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
&& dist[u] + graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}

// print the constructed distance array


printSolution(dist);
}

// driver program to test above function


int main()
{
/* Let us create the example graph discussed above */
int graph[V][V] = { { 0, 4, 0, 0, 0, 0, 0, 8, 0 },
{ 4, 0, 8, 0, 0, 0, 0, 11, 0 },
{ 0, 8, 0, 7, 0, 4, 0, 0, 2 },
{ 0, 0, 7, 0, 9, 14, 0, 0, 0 },
{ 0, 0, 0, 9, 0, 10, 0, 0, 0 },
{ 0, 0, 4, 14, 10, 0, 2, 0, 0 },
{ 0, 0, 0, 0, 0, 2, 0, 1, 6 },
{ 8, 11, 0, 0, 0, 0, 1, 0, 7 },
{ 0, 0, 2, 0, 0, 0, 6, 7, 0 } };

dijkstra(graph, 0);

return 0;
}

13. Bellman Ford Algorithm


#include <bits/stdc++.h>
struct Edge {
int src, dest, weight;
};

// a structure to represent a connected, directed and


// weighted graph
struct Graph {
// V-> Number of vertices, E-> Number of edges
int V, E;

// graph is represented as an array of edges.


struct Edge* edge;
};
// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E)
{
struct Graph* graph = new Graph;
graph->V = V;
graph->E = E;
graph->edge = new Edge[E];
return graph;
}

// A utility function used to print the solution


void printArr(int dist[], int n)
{
printf("Vertex Distance from Source\n");
for (int i = 0; i < n; ++i)
printf("%d \t\t %d\n", i, dist[i]);
}
void BellmanFord(struct Graph* graph, int src)
{
int V = graph->V;
int E = graph->E;
int dist[V];

// Step 1: Initialize distances from src to all other vertices


// as INFINITE
for (int i = 0; i < V; i++)
dist[i] = INT_MAX;
dist[src] = 0;

for (int i = 1; i <= V - 1; i++) {


for (int j = 0; j < E; j++) {
int u = graph->edge[j].src;
int v = graph->edge[j].dest;
int weight = graph->edge[j].weight;
if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
dist[v] = dist[u] + weight;
}
}

for (int i = 0; i < E; i++) {


int u = graph->edge[i].src;
int v = graph->edge[i].dest;
int weight = graph->edge[i].weight;
if (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {
printf("Graph contains negative weight cycle");
return; // If negative cycle is detected, simply return
}
}

printArr(dist, V);

return;
}

// Driver program to test above functions


int main()
{
/* Let us create the graph given in above example */
int V = 5; // Number of vertices in graph
int E = 8; // Number of edges in graph
struct Graph* graph = createGraph(V, E);

// add edge 0-1 (or A-B in above figure)


graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = -1;
// add edge 0-2 (or A-C in above figure)
graph->edge[1].src = 0;
graph->edge[1].dest = 2;
graph->edge[1].weight = 4;

// add edge 1-2 (or B-C in above figure)


graph->edge[2].src = 1;
graph->edge[2].dest = 2;
graph->edge[2].weight = 3;

// add edge 1-3 (or B-D in above figure)


graph->edge[3].src = 1;
graph->edge[3].dest = 3;
graph->edge[3].weight = 2;

// add edge 1-4 (or A-E in above figure)


graph->edge[4].src = 1;
graph->edge[4].dest = 4;
graph->edge[4].weight = 2;

// add edge 3-2 (or D-C in above figure)


graph->edge[5].src = 3;
graph->edge[5].dest = 2;
graph->edge[5].weight = 5;

// add edge 3-1 (or D-B in above figure)


graph->edge[6].src = 3;
graph->edge[6].dest = 1;
graph->edge[6].weight = 1;

// add edge 4-3 (or E-D in above figure)


graph->edge[7].src = 4;
graph->edge[7].dest = 3;
graph->edge[7].weight = -3;
BellmanFord(graph, 0);

return 0;
}

14. MST
#include <bits/stdc++.h>
using namespace std;

// a structure to represent a weighted edge in graph


class Edge
{
public:
int src, dest, weight;
};
class Graph
{
public:
// V-> Number of vertices, E-> Number of edges
int V, E;
Edge* edge;
};

// Creates a graph with V vertices and E edges


Graph* createGraph(int V, int E)
{
Graph* graph = new Graph;
graph->V = V;
graph->E = E;

graph->edge = new Edge[E];

return graph;
}

// A structure to represent a subset for union-find


class subset
{
public:
int parent;
int rank;
};

int find(subset subsets[], int i)


{

if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);

return subsets[i].parent;
}
void Union(subset subsets[], int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else
{
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}

// Compare two edges according to their weights.


// Used in qsort() for sorting an array of edges
int myComp(const void* a, const void* b)
{
Edge* a1 = (Edge*)a;
Edge* b1 = (Edge*)b;
return a1->weight > b1->weight;
}

// The main function to construct MST using Kruskal's algorithm


void KruskalMST(Graph* graph)
{
int V = graph->V;
Edge result[V]; // Tnis will store the resultant MST
int e = 0; // An index variable, used for result[]
int i = 0; // An index variable, used for sorted edges

qsort(graph->edge, graph->E, sizeof(graph->edge[0]), myComp);

// Allocate memory for creating V ssubsets


subset *subsets = new subset[( V * sizeof(subset) )];

// Create V subsets with single elements


for (int v = 0; v < V; ++v)
{
subsets[v].parent = v;
subsets[v].rank = 0;
}

// Number of edges to be taken is equal to V-1


while (e < V - 1 && i < graph->E)
{
// Step 2: Pick the smallest edge. And increment
// the index for next iteration
Edge next_edge = graph->edge[i++];

int x = find(subsets, next_edge.src);


int y = find(subsets, next_edge.dest);
if (x != y)
{
result[e++] = next_edge;
Union(subsets, x, y);
}
// Else discard the next_edge
}
cout<<"Following are the edges in the constructed MST\n";
for (i = 0; i < e; ++i)
cout<<result[i].src<<" -- "<<result[i].dest<<" == "<<result[i].weight<<endl;
return;
}

// Driver code
int main()
{

int V = 4; // Number of vertices in graph


int E = 5; // Number of edges in graph
Graph* graph = createGraph(V, E);

// add edge 0-1


graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = 10;

// add edge 0-2


graph->edge[1].src = 0;
graph->edge[1].dest = 2;
graph->edge[1].weight = 6;

// add edge 0-3


graph->edge[2].src = 0;
graph->edge[2].dest = 3;
graph->edge[2].weight = 5;
// add edge 1-3
graph->edge[3].src = 1;
graph->edge[3].dest = 3;
graph->edge[3].weight = 15;

// add edge 2-3


graph->edge[4].src = 2;
graph->edge[4].dest = 3;
graph->edge[4].weight = 4;

KruskalMST(graph);

return 0;
}

// This code is contributed by rathbhupendra

15. Occurrence of pattern


// Simple C++ program to count occurrences
// of pat in txt.
#include<bits/stdc++.h>
using namespace std;

int countFreq(string &pat, string &txt)


{
int M = pat.length();
int N = txt.length();
int res = 0;

/* A loop to slide pat[] one by one */


for (int i = 0; i <= N - M; i++)
{
/* For current index i, check for
pattern match */
int j;
for (j = 0; j < M; j++)
if (txt[i+j] != pat[j])
break;

// if pat[0...M-1] = txt[i, i+1, ...i+M-1]


if (j == M)
{
res++;
j = 0;
}
}
return res;
}

/* Driver program to test above function */


int main()
{
string txt = "dhimanman";
string pat = "man";
cout << countFreq(pat, txt);
return 0;
}

16. Karatsuba Algorithm

// C++ implementation of Karatsuba algorithm for bit string multiplication.


#include<iostream>
#include<stdio.h>

using namespace std;

int makeEqualLength(string &str1, string &str2)


{
int len1 = str1.size();
int len2 = str2.size();
if (len1 < len2)
{
for (int i = 0 ; i < len2 - len1 ; i++)
str1 = '0' + str1;
return len2;
}
else if (len1 > len2)
{
for (int i = 0 ; i < len1 - len2 ; i++)
str2 = '0' + str2;
}
return len1; // If len1 >= len2
}
string addBitStrings( string first, string second )
{
string result; // To store the sum bits
int length = makeEqualLength(first, second);
int carry = 0; // Initialize carry

// Add all bits one by one


for (int i = length-1 ; i >= 0 ; i--)
{
int firstBit = first.at(i) - '0';
int secondBit = second.at(i) - '0';

// boolean expression for sum of 3 bits


int sum = (firstBit ^ secondBit ^ carry)+'0';

result = (char)sum + result;


carry = (firstBit&secondBit) | (secondBit&carry) | (firstBit&carry);
}

// if overflow, then add a leading 1


if (carry) result = '1' + result;

return result;
}

// A utility function to multiply single bits of strings a and b


int multiplyiSingleBit(string a, string b)
{ return (a[0] - '0')*(b[0] - '0'); }

long int multiply(string X, string Y)


{
int n = makeEqualLength(X, Y);

// Base cases
if (n == 0) return 0;
if (n == 1) return multiplyiSingleBit(X, Y);

int fh = n/2; // First half of string, floor(n/2)


int sh = (n-fh); // Second half of string, ceil(n/2)

string Xl = X.substr(0, fh);


string Xr = X.substr(fh, sh);

// Find the first half and second half of second string


string Yl = Y.substr(0, fh);
string Yr = Y.substr(fh, sh);

// Recursively calculate the three products of inputs of size n/2


long int P1 = multiply(Xl, Yl);
long int P2 = multiply(Xr, Yr);
long int P3 = multiply(addBitStrings(Xl, Xr), addBitStrings(Yl, Yr));

// Combine the three products to get the final result.


return P1*(1<<(2*sh)) + (P3 - P1 - P2)*(1<<sh) + P2;
}

// Driver program to test above functions


int main()
{
printf ("%ld\n", multiply("1100", "1010"));
printf ("%ld\n", multiply("110", "1010"));
printf ("%ld\n", multiply("11", "1010"));
printf ("%ld\n", multiply("1", "1010"));
printf ("%ld\n", multiply("0", "1010"));
printf ("%ld\n", multiply("111", "111"));
printf ("%ld\n", multiply("11", "11"));
}

17. Convex hull

// A C++ program to find convex hull of a set of points. Refer


// https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for explanation of orientation()
#include <iostream>
#include <stack>
#include <stdlib.h>
using namespace std;

struct Point
{
int x, y;
};
Point p0;
Point nextToTop(stack<Point> &S)
{
Point p = S.top();
S.pop();
Point res = S.top();
S.push(p);
return res;
}
// A utility function to swap two points
int swap(Point &p1, Point &p2)
{
Point temp = p1;
p1 = p2;
p2 = temp;
}

int distSq(Point p1, Point p2)


{
return (p1.x - p2.x)*(p1.x - p2.x) +
(p1.y - p2.y)*(p1.y - p2.y);
}

int orientation(Point p, Point q, Point r)


{
int val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);

if (val == 0) return 0; // colinear


return (val > 0)? 1: 2; // clock or counterclock wise
}
int compare(const void *vp1, const void *vp2)
{
Point *p1 = (Point *)vp1;
Point *p2 = (Point *)vp2;

// Find orientation
int o = orientation(p0, *p1, *p2);
if (o == 0)
return (distSq(p0, *p2) >= distSq(p0, *p1))? -1 : 1;

return (o == 2)? -1: 1;


}
// Prints convex hull of a set of n points.
void convexHull(Point points[], int n)
{
// Find the bottommost point
int ymin = points[0].y, min = 0;
for (int i = 1; i < n; i++)
{
int y = points[i].y;
if ((y < ymin) || (ymin == y &&
points[i].x < points[min].x))
ymin = points[i].y, min = i;
}

// Place the bottom-most point at first position


swap(points[0], points[min]);

p0 = points[0];
qsort(&points[1], n-1, sizeof(Point), compare);
int m = 1; // Initialize size of modified array
for (int i=1; i<n; i++)
{
// Keep removing i while angle of i and i+1 is same
// with respect to p0
while (i < n-1 && orientation(p0, points[i],
points[i+1]) == 0)
i++;

points[m] = points[i];
m++; // Update size of modified array
}
if (m < 3) return;
stack<Point> S;
S.push(points[0]);
S.push(points[1]);
S.push(points[2]);

// Process remaining n-3 points


for (int i = 3; i < m; i++)
{
while (orientation(nextToTop(S), S.top(), points[i]) != 2)
S.pop();
S.push(points[i]);
}

// Now stack has the output points, print contents of stack


while (!S.empty())
{
Point p = S.top();
cout << "(" << p.x << ", " << p.y <<")" << endl;
S.pop();
}
}

// Driver program to test above functions


int main()
{
Point points[] = {{0, 3}, {1, 1}, {2, 2}, {4, 4},
{0, 0}, {1, 2}, {3, 1}, {3, 3}};
int n = sizeof(points)/sizeof(points[0]);
convexHull(points, n);
return 0;
}

Вам также может понравиться