Can an integer which must hold the value of n contribute to space complexity? - algorithm

If an algorithm requires an integer which can contain the number n (e.g. counting the size of an input array), that integer must take on the order of log(n) space (right?).
If this is the only space which scales with n, is the space complexity of the algorithm O(logn)?

Formally, it depends on the model of computation you're using. In the classical random access machine model, with a fixed word size, simply storing the length n of the input indeed requires O(log n) space (and simple arithmetic on such numbers takes O(log n) time). On the other hand, in the transdichotomous model, where the word size is assumed to grow logarithmically with the input size n, it only requires O(1) space (and time). Other models may yield yet other answers.
In practice, most analysis of algorithms assumes that simple arithmetic on moderate-size integers (i.e. proportional to the input length) can be done in constant time and space. A practical reason for this, besides the fact that it simplifies analysis, is that in practice the logarithm of the input length cannot grow very large — even a computer capable of counting up from zero to, say, 2256, much less of reading that many bits of input, is probably forever beyond the means of humankind to build using known physics. Thus, for any conceivable realistic inputs, you can simply assume that a machine with a 256-bit word size can store the length of the input in a single word (and that machines with a smaller word size still need only a small constant number of words).

Here n is bounded i.e. n will be 32 bit signed integer as array size has certain limitation. So log(32) is bounded and its O(1)

Related

Do problem constraints change the time complexity of algorithms?

Let's say that the algorithm involves iterating through a string character by character.
If I know for sure that the length of the string is less than, say, 15 characters, will the time complexity be O(1) or will it remain as O(n)?
There are two aspects to this question - the core of the question is, can problem constraints change the asymptotic complexity of an algorithm? The answer to that is yes. But then you give an example of a constraint (strings limited to 15 characters) where the answer is: the question doesn't make sense. A lot of the other answers here are misleading because they address only the second aspect but try to reach a conclusion about the first one.
Formally, the asymptotic complexity of an algorithm is measured by considering a set of inputs where the input sizes (i.e. what we call n) are unbounded. The reason n must be unbounded is because the definition of asymptotic complexity is a statement like "there is some n0 such that for all n ≥ n0, ...", so if the set doesn't contain any inputs of size n ≥ n0 then this statement is vacuous.
Since algorithms can have different running times depending on which inputs of each size we consider, we often distinguish between "average", "worst case" and "best case" time complexity. Take for example insertion sort:
In the average case, insertion sort has to compare the current element with half of the elements in the sorted portion of the array, so the algorithm does about n2/4 comparisons.
In the worst case, when the array is in descending order, insertion sort has to compare the current element with every element in the sorted portion (because it's less than all of them), so the algorithm does about n2/2 comparisons.
In the best case, when the array is in ascending order, insertion sort only has to compare the current element with the largest element in the sorted portion, so the algorithm does about n comparisons.
However, now suppose we add the constraint that the input array is always in ascending order except for its smallest element:
Now the average case does about 3n/2 comparisons,
The worst case does about 2n comparisons,
And the best case does about n comparisons.
Note that it's the same algorithm, insertion sort, but because we're considering a different set of inputs where the algorithm has different performance characteristics, we end up with a different time complexity for the average case because we're taking an average over a different set, and similarly we get a different time complexity for the worst case because we're choosing the worst inputs from a different set. Hence, yes, adding a problem constraint can change the time complexity even if the algorithm itself is not changed.
However, now let's consider your example of an algorithm which iterates over each character in a string, with the added constraint that the string's length is at most 15 characters. Here, it does not make sense to talk about the asymptotic complexity, because the input sizes n in your set are not unbounded. This particular set of inputs is not valid for doing such an analysis with.
In the mathematical sense, yes. Big-O notation describes the behavior of an algorithm in the limit, and if you have a fixed upper bound on the input size, that implies it has a maximum constant complexity.
That said, context is important. All computers have a realistic limit to the amount of input they can accept (a technical upper bound). Just because nothing in the world can store a yottabyte of data doesn't mean saying every algorithm is O(1) is useful! It's about applying the mathematics in a way that makes sense for the situation.
Here are two contexts for your example, one where it makes sense to call it O(1), and one where it does not.
"I decided I won't put strings of length more than 15 into my program, therefore it is O(1)". This is not a super useful interpretation of the runtime. The actual time is still strongly tied to the size of the string; a string of size 1 will run much faster than one of size 15 even if there is technically a constant bound. In other words, within the constraints of your problem there is still a strong correlation to n.
"My algorithm will process a list of n strings, each with maximum size 15". Here we have a different story; the runtime is dominated by having to run through the list! There's a point where n is so large that the time to process a single string doesn't change the correlation. Now it makes sense to consider the time to process a single string O(1), and therefore the time to process the whole list O(n)
That said, Big-O notation doesn't have to only use one variable! There are problems where upper bounds are intrinsic to the algorithm, but you wouldn't put a bound on the input arbitrarily. Instead, you can describe each dimension of your input as a different variable:
n = list length
s = maximum string length
=> O(n*s)
It depends.
If your algorithm's requirements would grow if larger inputs were provided, then the algorithmic complexity can (and should) be evaluated independently of the inputs. So iterating over all the elements of a list, array, string, etc., is O(n) in relation to the length of the input.
If your algorithm is tied to the limited input size, then that fact becomes part of your algorithmic complexity. For example, maybe your algorithm only iterates over the first 15 characters of the input string, regardless of how long it is. Or maybe your business case simply indicates that a larger input would be an indication of a bug in the calling code, so you opt to immediately exit with an error whenever the input size is larger than a fixed number. In those cases, the algorithm will have constant requirements as the input length tends toward very large numbers.
From Wikipedia
Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity.
...
In computer science, big O notation is used to classify algorithms according to how their run time or space requirements grow as the input size grows.
In practice, almost all inputs have limits: you cannot input a number larger than what's representable by the numeric type, or a string that's larger than the available memory space. So it would be silly to say that any limits change an algorithm's asymptotic complexity. You could, in theory, use 15 as your asymptote (or "particular value"), and therefore use Big-O notation to define how an algorithm grows as the input approaches that size. There are some algorithms with such terrible complexity (or some execution environments with limited-enough resources) that this would be meaningful.
But if your argument (string length) does not tend toward a large enough value for some aspect of your algorithm's complexity to define the growth of its resource requirements, it's arguably not appropriate to use asymptotic notation at all.
NO!
The time complexity of an algorithm is independent of program constraints. Here is (a simple) way of thinking about it:
Say your algorithm iterates over the string and appends all consonants to a list.
Now, for iteration time complexity is O(n). This means that the time taken will increase roughly in proportion to the increase in the length of the string. (Time itself though would vary depending on the time taken by the if statement and Branch Prediction)
The fact that you know that the string is between 1 and 15 characters long will not change how the program runs, it merely tells you what to expect.
For example, knowing that your values are going to be less than 65000 you could store them in a 16-bit integer and not worry about Integer overflow.
Do problem constraints change the time complexity of algorithms?
No.
If I know for sure that the length of the string is less than, say, 15 characters ..."
We already know the length of the string is less than SIZE_MAX. Knowing an upper fixed bound for string length does not make the the time complexity O(1).
Time complexity remains O(n).
Big-O measures the complexity of algorithms, not of code. It means Big-O does not know the physical limitations of computers. A Big-O measure today will be the same in 1 million years when computers, and programmers alike, have evolved beyond recognition.
So restrictions imposed by today's computers are irrelevant for Big-O. Even though any loop is finite in code, that need not be the case in algorithmic terms. The loop may be finite or infinite. It is up to the programmer/Big-O analyst to decide. Only s/he knows which algorithm the code intends to implement. If the number of loop iterations is finite, the loop has a Big-O complexity of O(1) because there is no asymptotic growth with N. If, on the other hand, the number of loop iterations is infinite, the Big-O complexity is O(N) because there is an asymptotic growth with N.
The above is straight from the definition of Big-O complexity. There are no ifs or buts. The way the OP describes the loop makes it O(1).
A fundamental requirement of big-O notation is that parameters do not have an upper limit. Suppose performing an operation on N elements takes a time precisely equal to 3E24*N*N*N / (1E24+N*N*N) microseconds. For small values of N, the execution time would be proportional to N^3, but as N gets larger the N^3 term in the denominator would start to play an increasing role in the computation.
If N is 1, the time would be 3 microseconds.
If N is 1E3, the time would be about 3E33/1E24, i.e. 3.0E9.
If N is 1E6, the time would be about 3E42/1E24, i.e. 3.0E18
If N is 1E7, the time would be 3E45/1.001E24, i.e. ~2.997E21
If N is 1E8, the time would be about 3E48/2E24, i.e. 1.5E24
If N is 1E9, the time would be 3E51/1.001E27, i.e. ~2.997E24
If N is 1E10, the time would be about 3E54/1.000001E30, i.e. 2.999997E24
As N gets bigger, the time would continue to grow, but no matter how big N gets the time would always be less than 3.000E24 seconds. Thus, the time required for this algorithm would be O(1) because one could specify a constant k such that the time necessary to perform the computation with size N would be less than k.
For any practical value of N, the time required would be proportional to N^3, but from an O(N) standpoint the worst-case time requirement is constant. The fact that the time changes rapidly in response to small values of N is irrelevant to the "big picture" behaviour, which is what big-O notation measures.
It will be O(1) i.e. constant.
This is because for calculating time complexity or worst-case time complexity (to be precise), we think of the input as a huge chunk of data and the length of this data is assumed to be n.
Let us say, we do some maximum work C on each part of this input data, which we will consider as a constant.
In order to get the worst-case time complexity, we need to loop through each part of the input data i.e. we need to loop n times.
So, the time complexity will be:
n x C.
Since you fixed n to be less than 15 characters, n can also be assumed as a constant number.
Hence in this case:
n = constant and,
(maximum constant work done) = C = constant
So time complexity is n x C = constant x constant = constant i.e. O(1)
Edit
The reason why I have said n = constant and C = constant for this case, is because the time difference for doing calculations for smaller n will become so insignificant (compared to n being a very large number) for modern computers that we can assume it to be constant.
Otherwise, every function ever build will take some time, and we can't say things like:
lookup time is constant for hashmaps

What is the complexity of an algorithm if inputs are constrained by a constant number?

I've seen coding problems that are similar to this:
int doSomething(String s)
Where it says in the problem description that s will contain at most one of every character, so s cannot be more than length 26. I think in this case, iterating over s would be constant time.
But I've also seen problems where inputs are constrained to a random large number, like 10^5, just to avoid stack overflows and other weird edge cases. If we are going to consider inputs that are constrained by constants to be constant complexity, shouldn't these inputs also be considered constant complexity?
But it doesn't make sense to me to consider s to be of O(n) complexity either, because there are many problems were people allocate char[26] arrays to hold every letter of the alphabet. How does if make sense to consider an input that we know will be less than or equal to 26 to be of greater complexity than an array of size 26?
The point of analyzing the complexity of algorithms is to estimate how long it will take to run it. If the problem you're trying to solve limits the maximum value of n to a constant, you can consider n to be a constant and you wouldn't be wrong. But would that be useful if you wanted to predict whether an algorithm that does 2^n operations will run in a few seconds for n = 26? On the other hand, if you had an algorithm that does n*m operations and m is at most 3, how useful would it be to include m in the complexity analysis?
Calculating complexity has focus on what is the most critical variable related to the running time. If the running time is dominant by the length of s, it is our main focus of analyzing complexity and that should be in bigO notation. And in that case, of course it's not a constant.
If the input is constrained to a large number like 10^5.
And if the algorithm is getting slower proportional to that input.
for example,
int sort(string s); //length of s is less than 10^5
In this case, depending on what sorting algorithm you use,
the running time will be proportional to the length of s
like O(n^2) or O(nlogn) if n is the length of s
In this case you cannot say it's constant because running time is very different as the length of s is changing.
But if the algorithm inside has nothing to do with the length of s, like it has constant calculation time, then you can say 10^5 constraint is just a constant.

Does the complexity of mergesort/radix sort change when the keys occupy more than a single word of memory

This is a homework problem.So I am looking for hints rather than the solution. Consider a set of n numbers. Each number is 'k' digits long. Suppose 'k' is much much larger and does not fit into a single word of memory. In such a scenario what is the complexity of mergesort and radix sort?
My analysis is - Asymptotic complexity doesn't depend on the underlying architectural details like the number of words a number occupies etc. May be the constant factor changes and algorithms run slower, but the overall complexity remains the same. For instance,in languages like Python that handle arbitrarily long integers, the algorithms remain the same. But a few of my friends argue that as the number of words occupied by a number 'w' grows towards infinity, the complexity does change.
Am I on the right path?
The runtime of an algorithm can indeed depend on the number of machine words making up the input. As an example, take integer multiplication. The computer can compute the product of two one-word numbers in time O(1), but it can't compute the product of two arbitrarily-sized numbers in time O(1) because the machine has to load each word into memory as part of its computation.
As a hint for radix sort versus mergesort - the mergesort algorithm makes O(n log n) comparisons between elements, but those comparisons might not take time O(1) each. How much time does it take to compare two numbers that require k machine words each? Similarly, radix sort's runtime depends on the number of digits in the number. How many rounds of radix sort do you need if you have k machine words in each number?
Hope this helps!
You're sort of correct. This is a large part of why most complexity analysis will (somewhere, at least implicitly) state that it's working with the count of some basic operations, not actual time. You generally take for granted that most (if not all) of those basic operations (e.g., comparison, swapping, math like addition or subtraction, etc.) are constant time, which lets you translate almost directly from operation count (the actual complexity) to time consumed.
To be entirely accurate, however, asymptotic complexity is (should) normally specified in terms of a count of fundamental operations, though, not actual time consumed.

Why doesn't the complexity of hashing and BSTs factor in the time required to process the bytes of the keys?

I have a basic question on the time complexity of basic operations when using hash tables as opposed to binary search trees (or balanced ones).
In basic algorithm courses, which is unfortunately the only type I have studies, I learned that ideally, the time complexity of look-up/insert using Hashtables is O(1). For binary (search) trees, it is O(log(n)) where "n" is the "number" of input objects. So far, hashtable is the winner (I guess) in terms of asymptotic access time.
Now take "n" as the size of the data structure array, and "m" as the number of distinct input objects (values) to be stored in the DS.
For me, there is an ambiguity in the time complexity of data structure operations (e.g., lookup). Is it really possible to do Hashing with a "calculation/evaluation" complexity constant time in "n"? Specifically, if we know we have "m" distinct values for the objects which are being stored, then can the hash function still run faster than "Omega (log(m))"?
If not, then I would claim that the complexity for nontrivial applications has to be O( log ( n ) ) since in practice "n" and "m" are not drastically different.
I can't see a way such function can be found. For example, take m= 2^O( k) be the total number of distinct strings of length "k" bytes. A hash function has to go over all "k" bytes and even if it takes only constant time to do the calculations for each byte, then the overall time needed to hash the input is Omega( k ) = Omega( log( m) ).
Having said that, for cases where the number of potential inputs is comparable to the size of the table, e.g., "m" is almost equal to "n", the hashing complexity does not look like constant time to me.
Your concern is valid, though I think there's a secondary point you're missing. If you factor in the time required to look through all the bytes of the input into the calculation of the time complexity of a BST, you would take the existing O(log n) time and multiply it by the time required for each comparison, which would be O(log m). You'd then get O(log n log m) time for searches in a BST.
Typically, the time complexities states for BSTs and hash tables are not the real time complexities, but the number of "elementary operations" on the underlying data types. For example, a hash table does, on expectation, O(1) hashes and comparisons of the underlying data types. A BST will do O(log n) comparisons of the underlying data types. If those comparisons or hashes don't take time O(1), then the time required to do the lookups won't be O(1) (for hash tables) or O(log n) (for BSTs).
In some cases, we make assumptions about how the machine works that let us conveniently ignore the time required to process the bits of the input. For example, suppose that we're hashing numbers between 0 and 2k. If we assume that we have a transdichotomous machine model, then by assumption each machine word will be at least Ω(k) bits and we can perform operations on machine words in time O(1). This means that we can perform hashes on k bits in time O(1) rather than time O(k), since we're assuming that the word size grows as a function of the problem set.
Hope this helps!
That's a fair point. If your container's keys are arbitrarily large objects, you need a different analysis. However, in the end the result will be roughly the same.
In classic algorithmic analysis, we usually just assume that certain operations (like incrementing a counter, or comparing two values) take constant time, and that certain objects (like integers) occupy constant space. These two assumptions go hand in hand, because when we say that an algorithm is O(f(N)), the n refers to "the 'size' of the problem", and if individual components of the problem have non-constant size, then the total size of the problem will have an additional non-constant multiplier.
More importantly, we generally make the assumption that we can index a contiguous array in constant time; this is the so-called "RAM" or "von Neumann" model, and it underlies most computational analysis in the last four decades or so (see here for a potted history).
For simple problems, like binary addition, it really doesn't matter whether we count the size of the objects as 1 object or k bits. In either case, the cost of doing a set of additions of size n is O(n), whether we're counting objects-of-a-constant-size or bits in variable-size-objects. By the same token, the cost of a hash-table lookup consists of:
Compute the hash (time proportional to key size)
Find the hash bucket (assumed to be constant time since the hash is a fixed size)
Compare the target with each object in the bucket (time proportional to key size, assuming that the bucket length is constant)
Similarly, we usually analyze the cost of a binary search by counting comparisons. If each object takes constant space, and each comparison takes constant time, then we can say that a problem of size N (which is n objects multiplied by some constant) can be solved with a binary search tree in log n comparisons. Again, the comparisons might take non-constant time, but then the problem size will also be multiplied by the same constant.
There is a lengthy discussion on a similar issue (sorting) in the comments in this blog post, also from the Computational Complexity blog, which you might well enjoy if you're looking for something beyond the basics.

What is the meaning of "constant" in this context?

I am currently reading the Introduction to Algorithms book and I have a question in regard to analyzing an algorithm:
The computational cost for merge sort is c lg n according to the book and it says that
We restrict c to be a constant so that the word size does not grow arbirarily (If the word size could grow arbitrarily, we could store huge amounts of data in one word and operate on it all in constant time)
I do not understand the meaning of "constant" here. Could anyone explain clearly what this means?
Computational complexity in the study of algorithms deals with finding function(s) which provide upper and lower bounds for how much time (or space) the algorithm requires. Recall basic algebra in high school where you learned about the general point-slope formula for a line? That formula, y = mx + b, provided two parameters, m (slope), and b (y intercept), which described a line completely. Those constants (m,b) described where the line lay, and a larger slope meant that the line was steeper.
Algorithmic complexity is just a way to describe the upper (and possibly lower) bounds for how long an algorithm takes to run (and/or how much space is required). With big-O (and big-Theta) notation, you are finding a function which provides upper (and lower) bounds for the algorithm costs. The constants are just shifting the curve, not changing the shape of the curve.
We restrict c to be a constant so that the word size does not grow arbirarily (If the word size could grow arbitrarily, we could store huge amounts of data in one word and operate on it all in constant time)
On a physical computer, there is some maximum size to a machine word. On a 32-bit system, that would be 32 bits, and on a 64-bit system, it's probably 64 bits. Operations on machine words are (usually) assumed to take time O(1) even though they operate on lots of bits at the same time. For example, if you use a bitwise OR or bitwise AND on a machine word, you can think of it as performing 32 or 64 parallel OR or AND operations in a single unit of time.
When trying to build a theoretical model for a computing system, it's necessary to assume an upper bound on the maximum size of a machine word. If you don't do this, then you could claim that you could perform operations like "compute the OR of n values in time O(1)" or "add together two arbitrary-precision numbers in time O(1)," operations that you can't actually do on a real computer. Therefore, there's usually an assumption that the machine word has some maximum size so that if you do want to compute the OR of n values, you can still do so, but you can't do it instantaneously by packing all the values into one machine word and performing a single assembly instruction to get the result.
Hope this helps!

Resources