-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from yasarcereen/yasarcereen/graph-lecture
Add graph lecture docs
- Loading branch information
Showing
11 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
--- | ||
title: Breadth First Search | ||
tags: | ||
- Graph | ||
- Breadth First Search | ||
- BFS | ||
--- | ||
|
||
Breadth First Search (BFS) is an algorithm for traversing or searching tree. (For example, you can find the shortest path from one node to another in an unweighted graph.) | ||
|
||
<figure> | ||
 | ||
<figcaption>An example breadth first search traversal</figcaption> | ||
</figure> | ||
|
||
## Method | ||
|
||
BFS is a traversing algorithm where you should start traversing from a selected node (source or starting node) and traverse the graph layerwise thus exploring the neighbour nodes (nodes which are directly connected to source node). You must then move towards the next-level neighbour nodes. [1] | ||
|
||
• As the name BFS suggests, you are required to traverse the graph breadthwise as follows: | ||
• First move horizontally and visit all the nodes of the current layer | ||
• Add to the queue neighbour nodes of current layer. | ||
• Move to the next layer, which are in the queue | ||
|
||
Example question: Given a unweighted graph, a source and a destination, we need to find shortest path from source to destination in the graph in most optimal way? | ||
|
||
```cpp | ||
#include <bits/stdc++.h> | ||
using namespace std; | ||
|
||
cont int MaxN=100005; // Max number of nodes 5 | ||
|
||
vector <int> adj[MaxN]; | ||
bool mark[MaxN]; | ||
|
||
void bfs(int starting_point,int ending_point) { | ||
memset(mark,0,sizeof(mark)); //clear the cache | ||
queue <pair <int,int> > q; // the value of node | ||
// , and length between this node and the starting node | ||
|
||
q.push_back(make_pair(starting_point,0)); | ||
mark[starting_point]=1; | ||
|
||
while(q.empty()==false) { | ||
pair <int,int> tmp = q.front(); // get the next node | ||
q.pop(); // delete from q | ||
|
||
if(ending_point==tmp.first) { | ||
printf("The length of path between %d - %d : %d\n", | ||
starting_point,ending_point,tmp.second); | ||
return; | ||
} | ||
|
||
for (auto j : adj[tmp.first]) { | ||
if(mark[j]) continue ; // if it reached before | ||
mark[j]=1; | ||
q.push_back(make_pair(j,tmp.second+1)); // add next node to queue | ||
} | ||
} | ||
} | ||
|
||
int main() { | ||
cin » n | ||
|
||
for (int i=0 ; i < m; i++) { | ||
cin » a » b; | ||
adj[a].push_back(b); | ||
} | ||
|
||
cin » start_point » end_point; | ||
bfs(start_point); | ||
return 0; | ||
} | ||
``` | ||
## Complexity | ||
The time complexity of BFS is \(O(V + E)\), where \(V\) is the number of nodes and \(E\) is the number of edges. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
--- | ||
title: Depth First Search | ||
tags: | ||
- Graph | ||
- Depth First Search | ||
- DFS | ||
--- | ||
|
||
Depth First Search (DFS) is an algorithm for traversing or searching tree. (For example, you can check if graph is connected or not via DFS) [2] | ||
|
||
<figure markdown="span"> | ||
 | ||
<figcaption>Example of DFS traversal</figcaption> | ||
</figure> | ||
|
||
## Method | ||
|
||
The DFS algorithm is a recursive algorithm that uses the idea of backtracking. It involves exhaustive searches of all the nodes by going ahead, if possible, else by backtracking. | ||
|
||
Here, the word backtrack means that when you are moving forward and there are no more nodes along the current path, you move backwards on the same path to find nodes to traverse. All the nodes will be visited on the current path till all the unvisited nodes have been traversed after which the next path will be selected. [3] | ||
|
||
```cpp | ||
vector<vector<int» adj; // graph represented as an adjacency list | ||
int n; // number of vertices | ||
vector<bool> visited; | ||
void dfs(int v) { | ||
visited[v] = true; | ||
for (int u : adj[v]) { | ||
if (!visited[u]) dfs(u); | ||
} | ||
} | ||
``` | ||
This recursive nature of DFS can be implemented using stacks. The basic idea is as follows: Pick a starting node and push all its adjacent nodes into a stack. Pop a node from stack to select the next node to visit and push all its adjacent nodes into a stack. Repeat this process until the stack is empty. However, ensure that the nodes that are visited are marked. This will prevent you from visiting the same node more than once. If you do not mark the nodes that are visited and you visit the same node more than once, you may end up in an infinite loop. [3] | ||
```cpp | ||
DFS-iterative(G, s): //Where G is graph and s is source vertex let S be stack | ||
S.push(s) //Inserting s in stack | ||
mark s as visited. | ||
while ( S is not empty): | ||
//Pop a vertex from stack to visit next v = S.top( ) | ||
S.pop( ) | ||
//Push all the neighbours of v in stack that are not visited | ||
for all neighbours w of v in Graph G: | ||
if w is not visited : | ||
S.push(w) | ||
mark w as visited | ||
``` | ||
|
||
Example Question: Given an undirected graph, find out whether the graph is strongly connected or not? An undirected graph is strongly connected if there is a path between any two pair of vertices. | ||
|
||
|
||
```cpp | ||
#include <bits/stdc++.h> | ||
using namespace std; | ||
|
||
cont int MaxN=100005; // Max number of nodes | ||
|
||
vector <int> adj[MaxN]; | ||
bool mark[MaxN]; | ||
|
||
void dfs(int k) { | ||
mark[k]=1; // visited | ||
for(auto j : adj[k]) // iterate over adjacent nodes | ||
if(mark[j]==false) // check if it is visited or not | ||
dfs(j); // do these operation for that node | ||
} | ||
|
||
int main() { | ||
cin » n » m; // number of nodes , number of edges | ||
for (int i=0 ; i < m; i++){ | ||
cin » a » b; | ||
adj[a].push_back(b); | ||
adj[b].push_back(a); | ||
} | ||
|
||
dfs(1); | ||
|
||
bool connected=1; | ||
for(int i=1 ; i <= n ;i++) | ||
if(mark[i]==0) { | ||
connected=0; | ||
break; | ||
} | ||
|
||
if(connected) | ||
cout « "Graph is connected" « endl; | ||
else | ||
cout « "Graph is not connected" « endl; | ||
|
||
return 0; | ||
} | ||
``` | ||
## Complexity | ||
The time complexity of DFS is \(O(V+E)\) when implemented using an adjacency list ( with Adjacency Matrices it is \(O(V^2)\)), where \(V\) is the number of nodes and \(E\) is the number of edges. [4] |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
title: Graph | ||
--- | ||
|
||
**Editor:** Kayacan Vesek | ||
|
||
**Reviewers:** Yasin Kaya | ||
|
||
## Introduction | ||
|
||
A graph is a structure amounting to a set of objects in which some pairs of the objects are in some sense "related". The objects correspond to the mathematical abstractions called vertices (also called nodes or points) and each of the related pairs of vertices is called an edge. Typically, a graph is depicted in diagrammatic form as a set of dots for the vertices, joined by lines for the edges. [8] | ||
|
||
Why graphs? Graphs are usually used to represent different elements that are somehow related to each other. | ||
|
||
A Graph consists of a finite set of vertices(or nodes) and set of edges which connect a pair of nodes. G = (V,E) | ||
|
||
V = set of nodes | ||
|
||
E = set of edges(e) represented as e = a,b | ||
|
||
Graph are used to show a relation between objects. So, some graphs may have directional edges (e.g. people and their love relationships that are not mutual: Alice may love Alex, while Alex is not in love with her and so on), and some graphs may have weighted edges (e.g. people and their relationship in the instance of a debt) | ||
|
||
<figure markdown="span"> | ||
 | ||
<figcaption>Figure 1: a simple unweigted graph</figcaption> | ||
</figure> | ||
|
||
### [Depth First Search](depth-first-search.md) | ||
### [Breadth First Search](breadth-first-search.md) | ||
### [Shortest Path](shortest-path.md) | ||
### [Topological Sort](topological-sort.md) | ||
|
||
## References | ||
|
||
1. [https://www.hackerearth.com/practice/algorithms/graphs/breadth-first-search/tutorial/](https://www.hackerearth.com/practice/algorithms/graphs/breadth-first-search/tutorial/) | ||
2. [https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/](https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/) | ||
3. [https://cp-algorithms.com/graph/depth-first-search.html](https://cp-algorithms.com/graph/depth-first-search.html) | ||
4. [https://www.hackerearth.com/practice/algorithms/graphs/depth-first-search/tutorial/](https://www.hackerearth.com/practice/algorithms/graphs/depth-first-search/tutorial/) | ||
5. [Shortest Path. Wikipedia, the free online encyclopedia. Retrieved January 5, 2019](https://www.wikiwand.com/en/articles/Shortest_path_problem) | ||
6. [Topological sort. Geeksforgeeks website. Retrieved January 5, 2019](https://www.geeksforgeeks.org/topological-sorting/) | ||
7. [Topological Sort. Wikipedia, the free online encyclopedia. Retrieved January 5, 2019](https://en.wikipedia.org/wiki/Topological_sorting) | ||
8. [https://en.wikipedia.org/wiki/Graph_theory](https://en.wikipedia.org/wiki/Graph_theory) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
--- | ||
title: Shortest Path Problem | ||
tags: | ||
- Graph | ||
- Shortest Path Problem | ||
- Dijkstra | ||
--- | ||
|
||
## Definition | ||
|
||
Let \(G(V,E)\) be a graph, \(v_i\) and \(v_j\) be two nodes of \(G\). We say a path between \(v_i\) and \(v_j\) is the shortest path if sum of the edge weights (cost) in the path is minimum. In other words, the shortest path problem is the problem of finding a path between two vertices (or nodes) in a graph such that the sum of the weights of its constituent edges is minimized. [5] | ||
|
||
<figure> | ||
 | ||
<figcaption>Example shortest path in graph. Source is A and target is F. Image taken from [5].</figcaption> | ||
</figure> | ||
|
||
We will cover several shortest path algorithms in this bundle. One of them is Dijkstra’s Shortest Path Algorithm but it has some drawbacks: Edge weights should be non-negative for the optimally of the algorithm. We will discover other algorithms in which these condition isn’t necessary, like Floyd-Warshall and Bellman-Ford algorithms. | ||
|
||
## Dijkstra's Shortest Path Algorithm | ||
|
||
Dijkstra’s Shortest Path algorithm is straight forward. In brief we have a set \(S\) that contains explored nodes and \(d\) which contains the shortest path cost from source to another node. In other words, \(d(u)\) represents the shortest path cost from source to node \(u\). The procedure follows as that. First, add source node to set \(S\) which represents the explored nodes and assigns the minimum cost of the source to zero. Then each iteration we add node to \(S\) that has lowest cost \((d(u))\) from unexplored nodes. Let’s say \(S′ = V − S\) which means unexplored nodes. For all nodes in \(S′\) we calculate \(d(x)\) for each node \(x\) is \(S′\) then we pick minimum cost node and add it to \(S\). So how we calculate \(d(x)\)? For any \(x\) node from \(S′\), \(d(x)\) calculated as that, let’s say \(e\) cost of any edge from \(S\) to \(x\) then \(d(x) = min(d(u) + e)\). It is a greedy algorithm. | ||
|
||
Here is the explanation of the algorithm step by step. | ||
|
||
1. Initialize an empty set, distance array, insert source to set. | ||
|
||
2. Initialize a min-heap, put source to heap with key is zero. | ||
|
||
3. While heap is not empty, take the top element from heap and add its neighbours to min-heap. | ||
|
||
4. Once we pick an element from the heap, it is guaranteed that the same node will never be added to heap with lower key value. | ||
|
||
In implementation we can use priority queue data structure in order to increase efficiency. If we put unexplored nodes to min - priority queue where the distance is key, we can take the lowest cost unexplored node in \(O(log(n))\) time which is efficient. | ||
|
||
```cpp | ||
typedef pair<int,int> edge; | ||
typedef vector<edge> adjList; | ||
typedef vector<adjList> graph; | ||
|
||
void dijkstra(graph &g, int s) { | ||
vector<int> dist(g.size(),INT_MAX/2); | ||
vector<bool> visited(g.size(),false); | ||
|
||
dist[s] = 0; | ||
|
||
priority_queue<edge, vector<edge>, greater<edge>> q; | ||
q.push({0, s}); | ||
|
||
while(!q.empty()) { | ||
int v = q.top().second; | ||
int d = q.top().first; | ||
q.pop(); | ||
|
||
if(visited[v]) continue; | ||
visited[v] = true; | ||
|
||
for(auto it: g[v]) { | ||
int u = it.first; | ||
int w = it.second; | ||
if(dist[v] + w < dist[u]) { | ||
dist[u] = dist[v] + w; | ||
q.push({dist[u], u}); | ||
} | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
title: Topological Sort | ||
tags: | ||
- Graph | ||
- Topological Sort | ||
--- | ||
|
||
## Definition | ||
|
||
|
||
Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of vertices such that for every directed edge u->v, vertex u comes before v in the ordering. Topological Sorting for a graph is not possible if the graph is not a DAG [6]. | ||
|
||
There are many important usages of topological sorting in computer science; applications of this type arise in instruction scheduling, ordering of formula cell evaluation when recomputing formula values in spreadsheets, logic synthesis, determining the order of compilation tasks to perform in makefiles, data serialization, and resolving symbol dependencies in linkers. It is also used to decide in which order to load tables with foreign keys in databases [7]. | ||
|
||
There are known algorithms (e.g Kahn’s algorithm) to find topological order in linear time. Below, you can find one of the implementations: | ||
|
||
<figure> | ||
 | ||
<figcaption>For example, a topological sorting of this graph is “5 4 2 3 1 0”. There can be more than one topological sorting for a graph. For example, another topological sorting of the following graph is “4 5 2 3 1 0”. The first vertex in topological sorting is always a vertex with in-degree as 0 (a vertex with no incoming edges)[6].</figcaption> | ||
</figure> | ||
|
||
## Algorithm | ||
|
||
```cpp | ||
typedef vector<int> adjList; | ||
typedef vector<adjList> graph; | ||
typedef pair<int,int> ii; | ||
|
||
void kahn(graph &g) { | ||
vector<int> result; | ||
queue<int> q; | ||
vector<int> degree(g.size(),0); // number of incoming egdes. | ||
for(auto &list: g){ | ||
for(auto &node:list) { | ||
degree[node]++; | ||
} | ||
} | ||
|
||
for(int i=0; i < g.size(); ++i) { | ||
if (degree[i] == 0) | ||
q.push(i); | ||
} | ||
|
||
while( !q.empty()) { | ||
int node = q.front(); | ||
result.push_back(node); | ||
q.pop(); | ||
|
||
for (auto &ng: g[node]) { | ||
degree[ng]--; | ||
if (degree[ng] == 0) | ||
q.push(ng); | ||
} | ||
} | ||
|
||
for(auto &i:result) | ||
cout << i << " "; | ||
cout << endl; | ||
} | ||
int main(){ | ||
graph g(6); | ||
g[1].push_back(0); | ||
g[1].push_back(2); | ||
g[2].push_back(3); | ||
g[3].push_back(4); | ||
g[4].push_back(5); | ||
kahn(g); | ||
return 0; | ||
} | ||
``` | ||
As for time complexity: we traverse all edges in the beginning (calculating degrees) and in the while segment we remove edges (once for an edge) and traverse all nodes. Hence, the time complexity of this algorithm is \(O(V +E)\). Note that this implementation assumes the graph is DAG. Try improving this code to support checking if the graph is DAG! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters