Okay, so this video's not about any particular graph problem, not about a, any

particular graph algorithm. Just, sort of, the preliminaries we need to discuss

algorithms on graphs. How do we measure their size? How do we represent them, and

so on. Remember what a graph is, it really has two ingredients. First of all, there's

this set of objects we're talking about. Those might be called vertices.

Synonymously, we might call them nodes. We represent pair wise relationships using

edges. These can be either un-directed in which case, they're ordered pairs or an

edge can be directed from 1 to another. In that case, they're ordered pairs, and we

have a directed graph. Now, when we talk about say, the size of a graph, or the

running time of an algorithm that operates on a graph. We need to think about what we

mean by input size. In particular, for a graph, there's really two different

parameters that control how big it is, unlike an array. For arrays, we just had a

single number, the length. For graphs, we have the number of vertices, and we have

the number of edges. Usually we'll use the notation n for the

number vertices, m for the number of edges. So the next quiz will ask you to

think about how the number of edges m, can depend on the number of vertices, n. So,

in particular, I want you to think about in this quiz, an un-directed graph It has

n vertices. There's no parallel edges. 'Kay, so for a given pair of vertices,

there's either zero or one edge between them. Moreover, let's assume that the

graph is unconnected. 'Kay? So I don't want you to think about graphs

that have zero edges. Now, I haven't defined what a graph is. What it means for

a graph to be connected formally, yet, but I hope you get the idea. It means it's in

one piece, you can't break it into two parts that have no edges crossing between

them. So, for such a graph, no parallel edges, in one piece, n vertices, think

about what is the minimum number of edges it could possibly have, and what is the

maximum number of edg es, as a function of n, that it could possibly have. All right,

so the correct option is the first one The fewest number of edges that a connected

undirected graph we can have is n minus 1, and the maximum number of edges that an

undirected graph with no parallel edges can have is n times n minus 1 over 2,

better known as n choose 2. So why does it need at least n minus 1 edges, if it's

going to be in one piece. Well think about at, adding the edges one at a time. Okay,

on each of the edges of the graph. Now, initially, you just have a graph with zero

edges, the graph has indifferent pieces and isolated vertices has no edges at all.

Now each time you add one edge, what you do is you take two of the existing pieces,

at best, and fuse them into one. So, the maximum decrease you can have in the

number of different pieces of a graph is it can decrease by 1 each time you add an

edge. So from a starting point of n different pieces, you've got to get down

to 1 piece. So that requires the addition of n minus 1 edges. You can also convince

yourself of this best, by drawing a few pictures and noticing that trees achieve

this bound exactly, so for example here is a 4 vertex tree that has 3 edges. So this

is a case where m is indeed, n minus 1. Now, for the upper bound, why can't you

have more than n choose 2? Well, it's clear that the largest number of edges you

can have is for the complete graph. Where every single pair of edges has 1 between

them. Again, there's no parallel arcs and edges are unordered. So, there's at most,

n choose 2 possibilities of where to put an edge. So again, if n equals 4, here

would be an example with a maximum possible number, 6 edges. So, now that

I've got you thinking about how the number of edges can vary with the number of

vertices. Let me talk about the distinction between sparse and dense

graphs. It's important to distinguish between these two concepts because some

data structures and algorithms are better suited for sparse graphs. Other data

structures and algorithms are better suited for dense graphs. So, to make this

precise, let me just put down this very common notation n is the number of

vertices of the graph under discussion, m is the number of inches. This is quite

standard notation. Please get used to it and use it yourself. If you reverse these,

you will confuse a lot of people who have familiarity with graph algorithms and data

structures. Now one thing we learned from the previous quiz is the following. So in

most applications, not all applications, but most applications, m is at least

linear in n. Remember in the quiz we saw is at least n minus 1 if you wanted the

graph to be connected, and it's also big O of n squared. This is under the assumption

that there's no parallel arcs. Now, there are cases where we want to allow parallel

arcs. In fact we'll do that in the contraction algorithm for the min cut

problem. There are cases where we want to allow the number of edges to drop so low,

that the graph breaks into multiple pieces. For example, when we talk about

connected components but more often than not, we're thinking about a connected

graph with no parallel edges. And then we can pin down the number of edges m to be

somewhere between the linear and the number of nodes, linear and n and

quadratic in it. Now I'm not going to give you a super formal definition of what a

sparse or a dense graph is, and people are a little loose with this, this terminology

in practice. But basically, sparse means you're closer to the lower bound, closer

to linear. Dense means, you're closer to the upper bound, closer to quadratic. Now

I know this leaves ambiguity when the number of edges is something you know like

n to the 3 halves. usually in that case you'd think of that as a dense graph. So

usually anything which is more than N times logarythmic terms, you'd think of

that as a dense graph. But again, people are a little bit sloppy with this when

they talk about graphs. Next I want to discuss two representations of graphs and

we're mostly going to be using the s econd one in this course, but this first one,

the adjacency matrix, I do want to mention just briefly, just on this slide. This is

the supernatural idea where you represent the edges in a graph using a matrix. Let

me describe it first for undirected graphs. So, the matrix is going to be

denoted by capital A, and it says square n by n matrix where n is the number of

vertices of the graph. And the semantics are the i-jth entry of the matrix is 1. If

and only if there's an edge between the vertices i and j in the graph. I'm

assuming here that the vertices are named 1, 2, 3, 4, et cetera all the way up to n.

It's easy to add bells and whistles to the adjacency matrix to accommodate parallel

edges to accommodate edge weights, which is accommodate directed arcs, directed

edges. If you wanted to have parallel arcs, you could just have Aij denote the

number of arcs that are between i and j. If edges have different weights, you could

just have Aij be the weight of the ij edge. And for the directed graph you could

use plus ones and minus ones. So if the arc is directed from i to j, you'd set i,

Aij to be plus 1. If the arc is directed from j to i, you'd set Aij to minus 1.

There are many metrics by which you can evaluate a data structure, or a

representation. two important ones I want to discuss here. First of all, the number

of resources it requires and in this context, that's the amount of space that

the data structure needs. The second thing is what are the operations of the data

structure supports. So let's just begin with space requirements. What are they for

the adjacency matrix? Alright, so the answer at least with the sort of straight

forward way of storing a matrix is n squared. And this is n dependent of the

number of edges. So you could try to beat this down for sparse graphs using sparse

matrix tricks. But for the basic idea of just actually representing an n by n

matrix, you got n squared entries, you gotta store one bit in each whether the

edge is there or not. So that's going to give yo u n squared space. The constants

are, of course, very small, because you're just storing one bit per entry. But

nonetheless this is quadratic in the number of vertices. Now that's going to be

fine if you have a dense graph, the number of edges is as high as n squared, then

you're not really wasting anything in this representation. But in a sparse graph, if

m is much closer to linear, then this is a super wasteful representation. Let's talk

about the ajacently list representation, this is the, the dominant one we'll be

using in this class. This has several ingredients. So, first you keep track of

both the vertices and the edges as independent entities. So you're going to

have an array, or a list of each. And then we want these two arrays to

cross-reference each other in the obvious way. So given a vertex, you want to know

which edges it's involved in. Given an edge, you want to know what its endpoints

are. So, let's say first, most simply, each edge is going to have two pointers,

one for each of the two endpoints. And in directed graph, of course, it would keep

track of which one is the head and which one is the tail. Now, each vertex is going

to point to all of the edges of which it's a member. Now in an undirected graph, it's

clear what I mean by that. In a directed graph, you could do it in a couple ways.

Generally you'd have a vertex, keep track of all of the edges, for which it is the

tail. That is, all of the edges which you can follow one hop out from the edge. If

you wanted to, you can also have a second array, at a more expense of storage, where

the vertex also keeps track of the edges pointing to it. The edges for which it's

the head. So, let me ask you the same question I did with an adjacency matrix.

What is the space required of an adjacency list, as a function of the number of edges

m, and the number of vertices n, of the graph? So, the correct answer to this

question is the third option, theta of m plus n, which we're going to think of as

linear space in the size of the gra ph. So, this quiz is, is a little tricky. So,

it's explain the answer when we return to the slide with the ingredients of

adjacency lists. And let's compute the space for each of these four ingredients

separately. Most of them are straightforward. For example, consider the

first ingredient. This is just an array, or a list of the n vertices. And we just

need constant space per vertex to keep track of its existence. So this is going

to be theta of n, linear in the number of vertices. Similarly, for the m edges, we

just need linear space in the number of edges to remember their existence. So

that's going to be theta of m. Now, each edge has to keep track of both of its

endpoints. So that's two pointers, but two is a constant. For each of the m edges, we

have a constant space to keep track of endpoints. So that's going to give us

another theta of m constant per edge. Now, this fourth case, you might be feeling

kind of nervous, because a vertex, in principle could have edges involving all n

minus 1 of the vertices. So the number of point or is it a single vertex could be

theta of n. Also you could have you know, you do have n vertices that could be theta

of n squared. And certainly in something like a complete graph you really would

have that function. But the point is in sparse graphs n, n squared is way overkill

to the space needed by this fourth set of pointers. Actually, if you think about it

for each pointer in the fourth category, a vertex pointing to a given edge, there is

a pointer in the third category pointing in the opposite direction, from that edge

back to that vertex. So, there's actually a one to one correspondence. Between

pointers in the third category, and pointers in the fourth category. Since the

third category has space theta of m, so does all of the pointers in the fourth

category. So adding up over the four ingredients, we have one theta of n, and

three theta of ms, so that's going to give us overall a theta of m plus n. If you

prefer, another way you could think about this would be theta of the max of m and n.

These are the same up to a constant factor. Now, as we discussed in a previous

slide. Often, m is going to be bigger than n, but I wanted to do a generic analysis

here, which applies even if the graph is not connected, even, even if it is in

multiple pieces. So the space of the adjacency list is within a constant factor

the same as the number of ingredients in the graph, the number of vertices plus the

number of edges. So in that sense, that's exactly what you want. Now being

confronted with these two graph representations that I've shown you I'm

sure you're asking, well, which one should you remember? Which one should you use?

And the answer, as it so often is, is it depends. It depends on two things. It

depends on the density of your graph. It depends on how m compares to n. And it

also depends on what kind of operations that you support, want to support. Now

given what we're covering in this class, and also the motivating applications I

have in mind I can give you basically a clean answer to this question for the

purposes of these five weeks. Which is we're going to be focusing on adjacency

lists. The reason we're going to focus on adjacency lists in this class, is both, is

for both of these reasons, both because of the operations we want and both because of

the graph density and motivating applications. So, first of all, most of

the graph primitives, not all, but most, will be dealing with graph search and

adjacency lists are perfect for doing graph search. You get to a node. You

follow an outgoing arc. You go to another node. You follow an outgoing arc and so

on. And so, adjacency lists are the perfect thing to do graph search.

Adjacency matrices are definitely good for certain kinds of graph operations. But

they're not things we're really going to be covering in this class. So that's

reason one. Reason two is, a lot of the motivations for graph primitives these

days comes from massive, massive networks. I mentioned earlier how the web ca n be

fruitfully thought of as a directed graph. Where the vertices are individual web

pages. And directed arcs correspond to hyperlinks, going from the page with the

hyperlink, pointing to the one that the hyperlink goes to. Now, it's hard to get

an exact measurement of the web graph, but a conservative lower bound on the number

of vertices is something like 10 billion. So that's 10 to the 10. Now that's pushing

the limits of what computers can do, but it's within the limits. So if you work

hard, you can actually operate on graphs with 10 to the 10 nodes. Now, suppose we

use an adjacency matrix representation. So if n is 10 to the 10, then n squared is

going to be like 10 to the 20. And now we're getting close to the estimated

number of atoms in the known universe. So that is clearly not feasible now and it's

not going to be feasible ever. So the adjacency matrix representation is totally

out for, huge sparse graphs like the web graph. Adjacency lists, well, the degree,

on average, in the web, is thought to be something like 10. So, the number of edges

is only going to be something like 10 to the 11. And then the adjacency of this

representation will be proportional to that. And again, that's really pushing

what we can do with current technology, but it is within the limits, so using that

representation we can do non-trivial computations on graphs, even at the scale

of the web graph.