Having covered some very important topics like recursion and asymptotic notation, we’re going to continue our journey through algorithm basics with a discussion of randomized algorithms. By definition, the behavior of randomized algorithms incorporates randomness as some part of its formal structure. The purpose of this is probabilistic: it seeks to achieve good performance in the “average case” while its actual performance in any one case will, of course, be a random value governed by the specific but randomly selected input used in that case. Let’s consider an example:
The Hiring Problem
This example comes straight out of the chapter 5 of Intro To Algorithms 3rd Edition. Suppose that I want to hire a new worker at my company and I use an employment agency that charges me a fee for every interview I request and an even larger hiring fee if I actually want to hire an interviewee. I’m ok with paying these fees because I want to have the best possible person for the job at any point in time. Therefore, my strategy is to constantly interview applicants and every time I find someone who I think is even slightly better than the current job-holder, I just fire my current employee and replace them with the applicant. By the way, I should add that I’m a horrible boss.
Now, before I implement this cold-hearted strategy, I want to estimate its cost with all those interview and hiring fees and so I create a method called HIRE-WORKER to represent my proposed strategy. In this procedure, I create a variable called “best” which is intended to hold the value of the best candidate I’ve seen so far. It starts at 0 because there’s no candidate-zero; I haven’t yet interviewed anyone and no nobody could possibly be my best candidate yet. Then, I number off each candidate 1 to n for a total of n candidates with the variable i holding the index value for that candidate. Now, for each candidate, I want to interview them and, if they’re better than the best I’ve seen so far, I want to replace the value of “best” with their index value and hire them. Pretty straightforward. Figure 1 below describes the HIRE-WORKER algorithm. I’ve included some Java code for an implementation example, however, it’s a bit clunky because the HIRE-WORKER algorithm is more analogous to the literal example of hiring people whereas a code implementation requires some extra accommodations which you’ll see in the code comments (for example, i have a function where a user might type in a candidate score representing their impression of a potential hire). Note also, that every time we identify a new best worker, we invoke another method “hireBest” which hires that worker. As is the case with object-oriented programming, that method would be defined elsewhere in the code.
Figure 1
private static void hireWorker {
int best = 0;
int bestScore = 0;
int n = list.length;
for (i = 1; i < n; i++) { String number = bufferedreader.readLine(); // This is a convention for reading int score = Integer.parseInt(number); // user input like candidate scores if (score > bestscore) {
best = i;
hireBest(i); // every time there's a new "best" hire 'em!
}
}
}
We’ve spoken at length about algorithm cost and, heretofore, that cost has been in running time. In this example, we’re going to be less concerned with running time and more concerned with monetary cost: the total cash amount that I have to spend in order to find the best candidate. So let’s look back at this HIRE-WORKER algorithm. Interviewing has some cost, c-interview, while hiring incurs a larger cost that we’ll call c-hire. If we add that m is a variable representing the total number of people that I have to hire in order to find the best worker, then I can say that the total cost of implementing this algorithm—and, in the process, of finding the best hire—is O(c-interview*n + c-hire*m). Recall that this is O-notation, which means that it represents the upper bound when it comes to algorithm cost. So what this tells us is that the worst case cost will be O(c-interview*n + c-hire*n) because we would interview and hire every candidate n. Since the interview cost c-interview is much smaller than the hiring cost, is becomes asymptotically negligible and so we can just reduce the worst case to O(c-hire*n).
Here’s where we begin to see the random, or stochastic, element at play. In the worst of all possible cases, we get a list of potential hires n people in length and it just so happens that they are in order of worst-to-best and therefore we end up hiring every candidate we interview (in which case c-hire*m would equal c-hire*n). But, of course, this won’t always—if ever—be the case. In fact, candidates will come in unpredictable orders and we know that we’ll hire somebody but we don’t know if that magic person will be the first person we see—in which case we will only hire one time—or the last person we see—which would be our worst-case scenario. Notice how this kind of uncertainty makes it really hard to meaningfully describe the running time of this algorithm because, once we say that our hiring cost could be anywhere from c-hire*1 up to c-hire*n, then our O-notation (c-interview*n + c-hire*m) becomes a pretty useless and random upper bound doesn’t it? It establishes a huge and unpredictable range of costs for any specific execution event of this algorithm. So what do we do?
Dealing with Randomness
Well, we just can’t possibly know which input we’ll be working with for any specific instance of this algorithm. What we can do though is make some reasonable assumptions about those inputs, which will allow us to generate an average case scenario. With regard to the hiring algorithm, we can make the assumption that in any case we will be presented with candidates in a random order. If this is the case, then we can also say that our ranked list of applicants has just as much likelihood as anything of being one of the permutations of the list of candidates 1…n since each permutation—that is each possible order of candidates 1 through n—has an equal probability.
It is very important to note here that the hiring agency we’re actually working with probably doesn’t send us randomly ordered applicant lists. In such a real-world situation, these lists might appear in the order of application score (which would bias the order toward more qualified applicants) or it might similarly refer applicants in the order of when they submitted their materials to the agency (in which case the early submitters may also be more organized and therefore more qualified, again biasing the list toward a certain type of applicant). Because our algorithm cannot control this we can instead force the input to be randomized by deliberately randomizing it. For instance, we could scramble each applicant list upon entering it into the computer. This intentional randomization, which will often employ a certain code convention (an object) known as a random number generator is a quintessential example of a truly randomized algorithm. One last thing: while this randomness serves us in an abstract way, it will still be the case that each specific instance of the algorithm must operate upon some ordered list—random or not—and so the specific running time in each case will be different. Because of this, we can’t really say that such-and-such is the running time but, rather, the expected running time.
Analysis
In order to compute the expected number of times we’ll be hiring a new assistant, we first have to introduce a convention known as an indicator random variable. This variable represents the likelihood of a specific event occurring. For example, if we’re talking about flipping a coin, then we can assign a random variable to the event that the coin will land on heads. This variable will, of course, be equal to ½. So for the hiring problem, we’ll randomize the candidates, thus allowing us to say that the indicator random variable for each candidate considered will equal the likelihood of hiring that candidate. So, if we have n candidates, we’ll be considering n interview and potential hiring events and we’ll have n variables.
As we consider candidates from 1 up to n we assign each candidate we’re currently considering an index value i. This way, we can say that the candidate we’re currently considering—whichever candidate that is—is candidate i and we’ll compare that candidate to all the candidates we’ve considered so far which would be candidates numbered 1 through i-1. Because we randomized the candidate list, we can say that each candidate i is equally likely to be the best candidate we’ve seen so far and so the random variable in the case of any candidate i is equal to 1/i because each candidate i has 1/i likelihood of being better than all candidates before her (candidates 1 through i-1 that is).
If our random variable is called H-i for hiring some candidate i then the likelihood for total hires across the whole list of candidates n is equal to H-1 + H-2 +…+ H-n which is equal to 1/i for I = 1…n, or, 1 + ½ +…+1/n. Ultimately, this equals ln(n). Finally, if the overall likelihood of hiring candidates over the whole candidate list is ln(n), then the total hiring cost c-hire*n is equal to c-hire*ln(n). Compared to the previous worst-case of O(c-hire*n) this average case of O(c-hire*ln(n)) is actually more cost-efficient, hence the advantage of algorithm randomization. With this in hand, we can now move on to Figure 2 which shows the new and improved RANDOMIZED-HIRE-WORKER algorithm. As you’ll notice, line 1 of this code calls a method that randomly permutes the list of candidates (“randomize candidate list”). This method is explained with a graphical depiction in figure 2 as well. Things get a bit tricky when it comes to randomization because it involves a concept that we have not covered yet. Therefore, you see some of the code required to implement this map:value sorting but it is incomplete (we’ll cover that topic later on).
Figure 2

A: The new and improved RANDOMIZED-HIRE-WORKER method. B: The "randomize candidate list" method invoked by line 1 of the RANDOMIZED-HIRE-WORKER method
private static void hireWorker {
randomize(list);
int best = 0;
int bestScore = 0;
int n = list.length;
for (i = 0; i < n; i++) { String number = bufferedreader.readLine(); // This is a convention for reading int score = Integer.parseInt(number); // user input like candidate scores if (score > bestscore) {
best = i;
hireBest(i); // every time there's a new "best" hire 'em!
}
}
}
private static void randomize {
int n = list.length;
int p = new int[n];
for (i = 0; i < n; i++) {
p[i] = randomGenerator.nextInt(100); //call random number generator
}
HashMap map = new HashMap(); //map:value sorting
ValueComparator bvc = new ValueComparator(map);
TreeMap sorted_map = new TreeMap(bvc);
for (i = 0; i < n; i++) {
map.put(list.i , p.i);
}
}
public int compare(Object a, Object b) {
if((Double)base.get(a) < (Double)base.get(b)) {
return 1;
} else if((Double)base.get(a) == (Double)base.get(b)) {
return 0;
} else {
return -1;
}
}
The above example invokes a permutation method to randomize our candidates prior to any interviews. The exact method of permutation is more of a side-note in the context of this post and this map:value implementation is but one example among many. In any case, the point here is that it accomplishes randomization in order to ensure consistency in the average case (this is the goal of randomized algorithms) even though each specific instance will vary. This concludes our intro to randomized algorithms. Next, we’ll begin Algorithms 102, where we will start discussing algorithm design and function more specific to Bioinformatics.










