Skip to content

Commit

Permalink
Merge pull request #88 from yasarcereen/yasarcereen/graph-lecture
Browse files Browse the repository at this point in the history
Add graph lecture docs
  • Loading branch information
muratbiberoglu authored Oct 17, 2024
2 parents 658bca1 + 4860d2b commit c400da3
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 0 deletions.
78 changes: 78 additions & 0 deletions docs/graph/breadth-first-search.md
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>
![Breadth First Search Traversal](img/bfs.jpg)
<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.
97 changes: 97 additions & 0 deletions docs/graph/depth-first-search.md
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">
![Depth First Search](img/dfs.jpg)
<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]
Binary file added docs/graph/img/bfs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/graph/img/dfs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/graph/img/directed_acyclic_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/graph/img/shortest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/graph/img/toporder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions docs/graph/index.md
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">
![Directed Acyclic Graph](img/directed_acyclic_graph.png)
<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)
68 changes: 68 additions & 0 deletions docs/graph/shortest-path.md
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>
![Shortest Path](img/shortest.png)
<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});
}
}
}
}
```
72 changes: 72 additions & 0 deletions docs/graph/topological-sort.md
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>
![Topological Order](img/toporder.png)
<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!
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ nav:
- Introduction: introduction/index.md
- Data Structures: data-structures/index.md
- Algorithms: algorithms/index.md
- Graph: graph/index.md
- Dynamic Programming: dynamic-programming/index.md
theme:
name: material
Expand Down

0 comments on commit c400da3

Please sign in to comment.