Minimax algorithm with weird behaviour when depth is high - c

I am writing a connect 4 ai using minimax and alpha-beta pruning. When I set the depth to something low like 10, it works as expected. When I set it to something higher like 20, the minimax algorithm takes too long time as expected because there is too many nodes to search. But when I set the depth to something high like 40, it finds a move really fast, and the move is often really bad. The number of nodes visited also stays the same with different high depths like 40 and 50. What could be the reason for this? Also, sometimes, the AI plays worse at higher depths than at lower depths. What could be the reason for this? The minimax function takes the depth into account when returning the evaluation, so it should favor the longest path to a loss/bad score and it should take the shortest path to win/good score.
Minimax function:
int c4_minimax(bool is_maximizing, int depth, int alpha, int beta) {
if(c4_check_win(bitboard[(move_count) & 1])) {
if(is_maximizing) {
return MAXIMUM;
}
else {
return MINIMUM;
}
}
else if(c4_is_board_full()) {
return 0;
}
else if(depth == 0) {
return c4_evaluate();
}
bool* valid_moves = c4_get_valid_moves();
if(is_maximizing == true) {
int max_eval = MINIMUM;
for(int i = 0;i < COLS;i++) {
if(valid_moves[explore_order[i]] == true) {
c4_make_move(explore_order[i]);
int eval = c4_minimax(false, depth - 1, alpha, beta);
c4_undo_move();
max_eval = MAX(max_eval, eval);
alpha = MAX(alpha, eval);
if(beta <= alpha) {
break;
}
}
}
return max_eval - depth;
}
else if(is_maximizing == false){
int min_eval = MAXIMUM;
for(int i = 0;i < COLS;i++) {
if(valid_moves[explore_order[i]] == true) {
c4_make_move(explore_order[i]);
int eval = c4_minimax(true, depth - 1, alpha, beta);
c4_undo_move();
min_eval = MIN(min_eval, eval);
beta = MIN(beta, eval);
if(beta <= alpha) {
break;
}
}
}
return min_eval + depth;
}
return 0;
}
//initial call:
c4_minimax(false, initial_depth, MINIMUM, MAXIMUM);

Related

How to find the minimum number of coins needed for a given target amount(different from existing ones)

This is a classic question, where a list of coin amounts are given in coins[], len = length of coins[] array, and we try to find minimum amount of coins needed to get the target.
The coins array is sorted in ascending order
NOTE: I am trying to optimize the efficiency. Obviously I can run a for loop through the coins array and add the target%coins[i] together, but this will be erroneous when I have for example coins[] = {1,3,4} and target = 6, the for loop method would give 3, which is 1,1,4, but the optimal solution is 2, which is 3,3.
I haven't learned matrices and multi-dimensional array yet, are there ways to do this problem without them? I wrote a function, but it seems to be running in an infinity loop.
int find_min(const int coins[], int len, int target) {
int i;
int min = target;
int curr;
for (i = 0; i < len; i++) {
if (target == 0) {
return 0;
}
if (coins[i] <= target) {
curr = 1 + find_min(coins, len, target - coins[i]);
if (curr < min) {
min = curr;
}
}
}
return min;
}
I can suggest you this reading,
https://www.geeksforgeeks.org/generate-a-combination-of-minimum-coins-that-results-to-a-given-value/
the only thing is that there is no C version of the code, but if really need it you can do the porting by yourself.
Since no one gives a good answer, and that I figured it out myself. I might as well post an answer.
I add an array called lp, which is initialized in main,
int lp[4096];
int i;
for (i = 0; i <= COINS_MAX_TARGET; i++) {
lp[i] = -1;
}
every index of lp is equal to -1.
int find_min(int tar, const int coins[], int len, int lp[])
{
// Base case
if (tar == 0) {
lp[0] = 0;
return 0;
}
if (lp[tar] != -1) {
return lp[tar];
}
// Initialize result
int result = COINS_MAX_TARGET;
// Try every coin that is smaller than tar
for (int i = 0; i < len; i++) {
if (coins[i] <= tar) {
int x = find_min(tar - coins[i], coins, len, lp);
if (x != COINS_MAX_TARGET)
result = ((result > (1 + x)) ? (1+x) : result);
}
}
lp[tar] = result;
return result;
}

How do I change the binary search function to intake the compare?

I am having trouble with the logic of combining these two stipulations in my assignment. How do I change the below binary search function to intake the compareTo the coordinate structures. I wrote it wrong the first time because I used the original string locations. I also dont understand how the compareTo function is suppose to keep track of the length away from the target. Which is confusing to me because the way it reads below for the compareTo function says the opposite to me and asking me to compare individual x and y coordinates. how am I suppose to do that if i am just using the pointers? Am i just passing coordinates *ptrPt1 into the binary search? Number 3 is the most confusing mess of words.
Context for the function relationship:
You must write a function compareTo which takes in two pointers, ptrPt1 and ptrPt2, to
coordinate structs and returns a negative integer if the point pointed to by ptrPt1 is closer to you
than the point pointed to by ptrPt2, 0 if the two locations pointed to by both are identical locations,
and a positive integer if the point pointed to by ptrPt1 is farther from you than the point pointed to
by ptrPt2. Exceptions to this will be when the two pointers are pointing to points that are the same
distance from you, but are distinct points. In these cases, if ptrPt1's x coordinate is lower than
ptrPt2's x coordinate, a negative integer must be returned. Alternatively, if ptrPt1's x coordinate is
greater than ptrPt2's x coordinate a positive integer must be returned. Finally, if the x coordinate
of both points is the same, if ptrPt1's y coordinate is lower than ptrPt2's y coordinate, a negative
integer must be returned. If ptrPt1's y coordinate is greater than ptrPt2's y coordinate, a positive
integer must be returned.
Since your location must be used for sorting, please make the variable that stores your x and y
coordinates global. Your program should have no other global variables.
A Binary Search function must be used when answering queries.
int binarysearch(int searchval, int* array, int length) {
int low = 0, high = length-1;
// Search while there is a valid search space.
while (low <= high) {
int mid = (low+high)/2;
// Value is too small.
if (searchval < array[mid])
high = mid-1;
// too big.
else if (searchval > array[mid])
low = mid+1;
// found it!
else
return 1;
}
// Never found it.
return 0;
}
int
compareTo(coordinates *ptrPt1, coordinates *ptrPt2) {
if (ptrPt1 > ptrPt2)
return -1;
if(ptrPt1 == ptrPt2)
return 0;
if(ptrPt1 < ptrPt2)
return 1;
}
Your compareTo needs to be refactored.
Comparing the addresses of the structs [vs. the X/Y coordinates within them] is incorrect.
For compareTo, it must first compute the distance from an arbitrary reference point (e.g.) self for each of the two points passed as arguments. Per the problem definition, self can [and should] be a [the only] global.
It gets the distance to self for each of the two [argument] points. It chooses the closer of these two points [if they are different].
If the two points are the same distance from the self point, it first chooses the one with the lower X coordinate value. If the X coordinates are the same for the two points, it chooses the one that has the lower of the two Y values.
Thus, it's a three step process.
Your binarysearch needs to be refactored. Upon mismatch/failure, it returns 0. But, zero is a valid index/value for a match. So, it needs to return -1 on failure.
There are some issues with the problem definition.
Issue (1):
It's not clear [to me] what "rank" is supposed to be. The only thing that makes sense is that "rank" is the index into the list that is sorted by compareTo.
Issue (2):
It's not clear what "distance" means. It could be (e.g.):
sqrt((p1->x - p2->x)**2 + (p1->y - p2->y)**2)
But, that uses floating point, and it may be overkill for this problem.
Another "distance" is the manhattan distance which is just the sum of the absolute differences of the X and Y values of the two coordinates:
abs(p1->x - p2->x) + abs(p1->y - p2->y)
Issue (3):
I think that two sorted lists are required.
One sorted by compareTo. Another sorted just by X/Y coordinates.
This is because it is required to use a binary search when matching a search coordinate. Because the search coordinate does not know the rank, it can't use the compareTo list and must use the X/Y list.
There are two possible approaches.
This can be achieved by using two lists that are either pointers or indices into the person list. The binarysearch should be modified to accept an array of indices/pointers.
Or, it can be achieved by sorting the person list by compareTo, recording the rank in the coordinate struct and then resorting the list by X/Y coordinates. The binarysearch should be modified to accept an array of coordinates.
I've chosen to use the latter approach.
And, I've added some test code to generate a randomized input file, if desired.
I've just implemented a simple insertion sort [algorithm is a cut-n-paste from the wikipedia entry for insertion sort]. So, you'll still have to code up the combined merge/insertion sort logic.
Spoiler Alert: Below is the complete/refactored code:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <time.h>
typedef struct {
int x;
int y;
int rank;
} coord_t;
// maximum coordinate
#ifndef COORDMAX
#define COORDMAX 10000
#endif
// max # of [infected] people
#ifndef PERSONMAX
#define PERSONMAX 106
#endif
#define SEARCHMAX (2 * (PERSONMAX - 1)) // max # of search coordinates
#define THRESHMAX 30 // maximum threshold
coord_t self; // coordinates of tracer
typedef int (*cmpfnc_p)(const coord_t *,const coord_t *);
int opt_d; // 1=debug
int opt_f; // distance mode (0=manhattan, 1=sqrt)
unsigned int opt_R; // random fill
void gentest(FILE *fi);
// disti -- get distance from given coordinate to self (manhattan distance)
int
disti(const coord_t *pt)
{
int dif;
int tot = 0;
dif = pt->x - self.x;
if (dif < 0)
dif = -dif;
tot += dif;
dif = pt->y - self.y;
if (dif < 0)
dif = -dif;
tot += dif;
return tot;
}
// distf -- get distance from given coordinate to self (floating pt distance)
int
distf(const coord_t *pt)
{
double dif;
double tot = 0;
int rtn;
dif = pt->x - self.x;
dif *= dif;
tot += dif;
dif = pt->y - self.y;
dif *= dif;
tot += dif;
tot = sqrt(tot);
// scale result
// FIXME -- this is untested and may not be necessary
tot *= INT_MAX;
tot /= COORDMAX;
rtn = round(tot);
return rtn;
}
// dist -- get distance from given coordinate to self
int
dist(const coord_t *pt)
{
int tot;
if (opt_f)
tot = distf(pt);
else
tot = disti(pt);
return tot;
}
// compareAbs -- compare two coordinates for lowest X/Y values
int
compareAbs(const coord_t *p1,const coord_t *p2)
{
int cmp;
do {
// use lower X coordinate
cmp = p1->x - p2->x;
if (cmp)
break;
// use lower Y coordinate
cmp = p1->y - p2->y;
if (cmp)
break;
} while (0);
return cmp;
}
// compareTo -- compare two coordinates for distance from self and then position
int
compareTo(const coord_t *p1,const coord_t *p2)
{
int cmp;
do {
// compare distance to self
cmp = dist(p1) - dist(p2);
if (cmp)
break;
// compare against absolute coordinates
cmp = compareAbs(p1,p2);
} while (0);
return cmp;
}
// sortswap -- swap array elements
void
sortswap(coord_t *p1,coord_t *p2)
{
coord_t tmp;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// sortinsert -- insertion sort
void
sortinsert(coord_t *list,int count,cmpfnc_p cmp)
{
for (int i = 1; i < count; ++i) {
for (int j = i; j > 0; --j) {
if (cmp(&list[j - 1],&list[j]) <= 0)
break;
sortswap(&list[j - 1],&list[j]);
}
}
}
// sortany -- outer sort routine
void
sortany(coord_t *list,int count,int threshold,cmpfnc_p cmp)
{
// TODO: do mergesort
if (count < threshold) {
}
// finish with insertion sort
sortinsert(list,count,cmp);
}
// binarysearch -- perform binary search on coordinate list
int
binarysearch(const coord_t *search,const coord_t *array,int length,
cmpfnc_p cmpfnc)
{
int low = 0;
int high = length - 1;
int match = -1;
// Search while there is a valid search space.
while (low <= high) {
int mid = (low + high) / 2;
int cmp = cmpfnc(search,&array[mid]);
// found it
if (cmp == 0) {
match = mid;
break;
}
// Value is too small.
if (cmp < 0)
high = mid - 1;
// too big.
else
low = mid + 1;
}
return match;
}
// main -- main program
int
main(int argc,char **argv)
{
const char *file = NULL;
char *cp;
FILE *fi;
int person_count;
int search_count;
int threshold;
coord_t *pt;
coord_t *person_list;
coord_t *search_list;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'f':
opt_f = ! opt_f;
break;
case 'R':
cp += 2;
opt_R = (*cp != 0) ? atoi(cp) : time(NULL);
printf("R=%u\n",opt_R);
srand(opt_R);
break;
}
}
// get/open input file
do {
fi = stdin;
if (argc <= 0) {
if (opt_R)
fi = stdout;
else
fi = stdin;
break;
}
file = *argv;
fi = fopen(file,opt_R ? "w" : "r");
if (fi == NULL) {
perror(file);
exit(1);
}
} while (0);
// generate test data
if (opt_R) {
gentest(fi);
fclose(fi);
exit(0);
}
fscanf(fi,"%d %d %d %d %d",
&self.x,&self.y,&person_count,&search_count,&threshold);
person_list = calloc(person_count,sizeof(*person_list));
if (person_list == NULL) {
perror("person_list");
exit(1);
}
search_list = calloc(search_count,sizeof(*search_list));
if (search_list == NULL) {
perror("search_list");
exit(1);
}
// read in coordinates of all people
for (int idx = 0; idx < person_count; ++idx) {
pt = &person_list[idx];
fscanf(fi,"%d %d",&pt->x,&pt->y);
}
// read in all search coordinates
for (int idx = 0; idx < search_count; ++idx) {
pt = &search_list[idx];
fscanf(fi,"%d %d",&pt->x,&pt->y);
}
// get the ranking
sortany(person_list,person_count,threshold,compareTo);
// remember the ranking and print the ranked list
for (int idx = 0; idx < person_count; ++idx) {
pt = &person_list[idx];
pt->rank = idx;
if (opt_d)
printf("%d %d dist=%d rank=%d\n",pt->x,pt->y,dist(pt),idx);
else
printf("%d %d\n",pt->x,pt->y);
}
// reorder list for search points
sortany(person_list,person_count,threshold,compareAbs);
// perform all queries
for (int idx = 0; idx < search_count; ++idx) {
pt = &search_list[idx];
int match = binarysearch(pt,person_list,person_count,compareAbs);
if (match < 0) {
printf("%d %d not found\n",pt->x,pt->y);
continue;
}
pt = &person_list[match];
printf("%d %d found at rank %d\n",pt->x,pt->y,pt->rank);
}
if (file != NULL)
fclose(fi);
free(person_list);
free(search_list);
return 0;
}
// gencoord -- generate a random coordinate
void
gencoord(coord_t *pt)
{
int val;
int neg;
for (int mode = 0; mode <= 1; ++mode) {
val = rand();
neg = (val & 1);
val >>= 1;
val %= (COORDMAX + 1);
if (neg)
val = -val;
if (mode == 0)
pt->x = val;
else
pt->y = val;
}
}
// genrand -- genrate a random number in the inclusive range
int
genrand(int lo,int hi)
{
int val;
val = rand();
val %= (hi + 1);
if (val < lo)
val = lo;
return val;
}
// gensame -- decide if coordinate already in use
int
gensame(coord_t *pt,coord_t *list,int length)
{
int match;
do {
// coordinate may _not_ be the starting/self point
match = (compareAbs(pt,&self) == 0);
if (match)
break;
// coordinate may not match any previous point in the list
for (int idx = 0; idx < length; ++idx) {
match = (compareAbs(pt,&list[idx]) == 0);
if (match)
break;
}
} while (0);
return match;
}
// gentest -- generate a random test file
void
gentest(FILE *fi)
{
int val;
int threshold;
int person_count;
int search_count;
int same;
coord_t *person_list;
coord_t *pt;
coord_t tmp;
gencoord(&self);
person_count = genrand(2,PERSONMAX);
search_count = genrand(1,SEARCHMAX);
threshold = genrand(1,THRESHMAX);
fprintf(fi,"%d %d %d %d %d\n",
self.x,self.y,person_count,search_count,threshold);
person_list = calloc(person_count,sizeof(*person_list));
if (person_list == NULL) {
perror("person_list");
exit(1);
}
// generate coordinates of all people
fprintf(fi,"\n");
for (int idx = 0; idx < person_count; ++idx) {
pt = &person_list[idx];
pt->rank = 0;
// ensure [proposed] coordinate is unique
same = 1;
while (same) {
gencoord(pt);
same = gensame(pt,person_list,idx);
}
fprintf(fi,"%d %d\n",pt->x,pt->y);
}
// generate search coordinates
fprintf(fi,"\n");
for (int idx = 0; idx < search_count; ++idx) {
pt = &tmp;
val = rand();
val %= 100;
// generate a random point that is _not_ a person or self (10% of the
// time)
if (val < 10) {
same = 1;
while (same) {
gencoord(pt);
same = gensame(pt,person_list,person_count);
}
}
// randomly select an infected person
else {
val = genrand(0,person_count - 1);
pt = &person_list[val];
}
fprintf(fi,"%d %d\n",pt->x,pt->y);
}
free(person_list);
}

Finding the shortest path (between source and destination) with the least number of edges

I am trying to write a program that finds a minimum-length path between a two vertices in a graph, selecting from among such paths one of those that traverses the fewest edges. I used Dijkstra's algorithm with several modifications (below).
The output supposed to be: 0->3->4, but instead, my program prints 0->4.
Why do I get the wrong output?
#include<stdio.h>
#include<string.h>
#define INFINITY 9999
#define n 5
#define s 0
#define d 4
void Dijkstra(int Graph[n][n], int _n,int _s, int _d);
int main()
{
int Graph[n][n] = {
{0, 6, 5, 1, INFINITY},
{6, 0, 3, INFINITY, INFINITY},
{5, 3, 0, 2, 5},
{1, INFINITY, 2, 0, 6},
{INFINITY, INFINITY, 5, 6, 0}
};
Dijkstra(Graph,n,s,d);
getchar();
return 0;
}
void Dijkstra(int Graph[n][n], int _n,int _s, int _d)
{
int distance[n], parent[n], visited[n], edge[n]={0}, mindistance,
nextnode= _s, i, j,temp[n][n], res[n];
//parent[] stores the predecessor of each node
//edge[] stores the number of edged of every vertex's shortest path
for (i = 0; i < n; i++) //create the temp matrix
for (j = 0; j < n; j++)
if (Graph[i][j] == INFINITY)
temp[i][j] = INFINITY;
else
temp[i][j] = Graph[i][j];
for(i=0;i<n;i++)
{
distance[i] = INFINITY; //initialize distance
parent[i] = _s; //initialize parent
visited[i] = 0;
if (distance[i] > 0 && distance[i] < INFINITY)
edge[i]++;
}
distance[_s] = 0;
visited[_s] = 1;
while (visited[_d] == 0)
{
//nextnode gives the node at minimum distance
for (i = 0; i < n; i++)
{
mindistance = temp[_s][i] + distance[i];
if (distance[i] < mindistance && !visited[i])
{
mindistance = distance[i];
nextnode = i;
}
}
//check if a better path exists through nextnode
visited[nextnode] = 1;
if (nextnode != _d)
for (i = 0; i < n; i++)
if (!visited[i])
{
if (mindistance + Graph[nextnode][i] < distance[i])
{
distance[i] = mindistance + Graph[nextnode][i];
parent[i] = nextnode;
edge[i] = edge[nextnode] + 1;
}
if (mindistance + Graph[nextnode][i] == distance[i])
{
if (edge[i] >= edge[nextnode] + 1)
{
parent[i] = nextnode;
edge[i] = edge[nextnode] + 1;
}
}
}
}
//print the path
for (i = 0; i < n; i++)
res[i] = 0;
i = nextnode;
while (i != _s)
{
res[i] = parent[i];
i = parent[i];
}
printf("%d", _s);
printf("->");
for (i = 0; i < n; i++)
{
if (res[i] != 0)
{
printf("%d", res[i]);
printf("->");
}
}
printf("%d", _d);
}
You have a couple of problems in the loop where you choose the next node to traverse. It is clearly incorrect to set
mindistance = Graph[_s][i] + distance[i];
on each iteration, since you need mindistance to track the minimum observed distance across iterations. Instead, before the loop you should set
mindistance = INFINITY;
While we're looking at this loop, observe also that you ignore the edge count criterion in selecting the next node to traverse. You need to use that criterion here, too, to ensure that you find a path that meets your criteria.
With those corrections, your program produces the expected output for me.
Do note, by the way, that this is still pretty straight-up Dijkstra. The trick is to recognize that you're implementing a two-component distance measure. The most-significant component is the path length (sum of edge weights), but the edge count is a secondary component by which ties are broken. The implementation therefore looks a little different, but if you factored out the distance comparison and setting to separate functions then you wouldn't be able to tell the remainder apart from standard Dijkstra (simple variation).
I have edited my code, but I still get the same wrong output:
#include<stdio.h>
#include<conio.h>
#define INFINITY 9999
#define n 5
#define s 0
#define d 4
void Dijkstra(int Graph[n][n], int _n,int _s, int _d);
int main()
{
int Graph[n][n]={{0,6,5,1,INFINITY},{6,0,3,INFINITY,INFINITY},{5,3,0,2,5},{1,INFINITY,2,0,6},{INFINITY,INFINITY,5,6,0}};
Dijkstra(Graph,n,s,d);
getchar();
return 0;
}
void Dijkstra(int Graph[n][n],int _n,int _s,int _d)
{
int temp[n][n],distance[n],parent[n],visited[n],mindistance,nextnode,j,i;
//parent[] stores the predecessor of each node
//edge[] stores the number of edges in the shortest path from the source
//create the cost matrix
for(i=0;i<_n;i++)
for(j=0;j<_n;j++)
temp[i][j]=Graph[i][j];
//initialize
for(i=0;i<_n;i++)
{
distance[i]=INFINITY;
parent[i]=_s;
visited[i]=0;
}
distance[_s]=0;
visited[_s]=1;
nextnode=_s;
while(visited[_d]==0)
{
mindistance=INFINITY;
//nextnode gives the node at minimum distance
for(i=0;i<_n;i++)
{
distance[i]=temp[nextnode][i];
if(distance[i]<mindistance && !visited[i])
{
mindistance=distance[i];
nextnode=i;
}
}
//check if a better path exists through nextnode
visited[nextnode]=1;
for(i=0;i<_n;i++)
if(!visited[i])
if(mindistance+temp[nextnode][i]<distance[i])
{
temp[nextnode][i]=mindistance+distance[i];
parent[i]=nextnode;
}
}
//print the path and distance of each node
i=_d;
printf("%d",i);
do
{
i=parent[i];
printf("<-%d",i);
}while(i!=_s);
}
Just a few small changes are needed to get the supposed output; I commented on the code below:
…
distance[_s]=0;
// visited[_s]=1; do not mark the initial node as visited - will select below
// nextnode=_s; no need here - will select as the node at minimum distance
while (!visited[_d])
{
mindistance=INFINITY;
//nextnode gives the node at minimum distance
for (i=0; i<_n; i++)
{
// distance[i]=temp[nextnode][i]; don't change tentative distance here
if (distance[i]<mindistance && !visited[i])
{
mindistance=distance[i];
nextnode=i;
}
}
//check if a better path exists through nextnode
visited[nextnode]=1;
for (i=0; i<_n; i++)
if (!visited[i])
if (mindistance+temp[nextnode][i]<distance[i])
{
// temp[nextnode][i]=mindistance+distance[i]; other way round
distance[i]=mindistance+temp[nextnode][i]; // smaller
parent[i]=nextnode;
}
}
…

Transform an array to another array by shifting value to adjacent element

I am given 2 arrays, Input and Output Array. The goal is to transform the input array to output array by performing shifting of 1 value in a given step to its adjacent element. Eg: Input array is [0,0,8,0,0] and Output array is [2,0,4,0,2]. Here 1st step would be [0,1,7,0,0] and 2nd step would be [0,1,6,1,0] and so on.
What can be the algorithm to do this efficiently? I was thinking of performing BFS but then we have to do BFS from each element and this can be exponential. Can anyone suggest solution for this problem?
I think you can do this simply by scanning in each direction tracking the cumulative value (in that direction) in the current array and the desired output array and pushing values along ahead of you as necessary:
scan from the left looking for first cell where
cumulative value > cumulative value in desired output
while that holds move 1 from that cell to the next cell to the right
scan from the right looking for first cell where
cumulative value > cumulative value in desired output
while that holds move 1 from that cell to the next cell to the left
For your example the steps would be:
FWD:
[0,0,8,0,0]
[0,0,7,1,0]
[0,0,6,2,0]
[0,0,6,1,1]
[0,0,6,0,2]
REV:
[0,1,5,0,2]
[0,2,4,0,2]
[1,1,4,0,2]
[2,0,4,0,2]
i think BFS could actually work.
notice that n*O(n+m) = O(n^2+nm) and therefore not exponential.
also you could use: Floyd-Warshall algorithm and Johnson’s algorithm, with a weight of 1 for a "flat" graph, or even connect the vertices in a new way by their actual distance and potentially save some iterations.
hope it helped :)
void transform(int[] in, int[] out, int size)
{
int[] state = in.clone();
report(state);
while (true)
{
int minPressure = 0;
int indexOfMinPressure = 0;
int maxPressure = 0;
int indexOfMaxPressure = 0;
int pressureSum = 0;
for (int index = 0; index < size - 1; ++index)
{
int lhsDiff = state[index] - out[index];
int rhsDiff = state[index + 1] - out[index + 1];
int pressure = lhsDiff - rhsDiff;
if (pressure < minPressure)
{
minPressure = pressure;
indexOfMinPressure = index;
}
if (pressure > maxPressure)
{
maxPressure = pressure;
indexOfMaxPressure = index;
}
pressureSum += pressure;
}
if (minPressure == 0 && maxPressure == 0)
{
break;
}
boolean shiftLeft;
if (Math.abs(minPressure) > Math.abs(maxPressure))
{
shiftLeft = true;
}
else if (Math.abs(minPressure) < Math.abs(maxPressure))
{
shiftLeft = false;
}
else
{
shiftLeft = (pressureSum < 0);
}
if (shiftLeft)
{
++state[indexOfMinPressure];
--state[indexOfMinPressure + 1];
}
else
{
--state[indexOfMaxPressure];
++state[indexOfMaxPressure + 1];
}
report(state);
}
}
A simple greedy algorithm will work and do the job in minimum number of steps. The function returns the total numbers of steps required for the task.
int shift(std::vector<int>& a,std::vector<int>& b){
int n = a.size();
int sum1=0,sum2=0;
for (int i = 0; i < n; ++i){
sum1+=a[i];
sum2+=b[i];
}
if (sum1!=sum2)
{
return -1;
}
int operations=0;
int j=0;
for (int i = 0; i < n;)
{
if (a[i]<b[i])
{
while(j<n and a[j]==0){
j++;
}
if(a[j]<b[i]-a[i]){
operations+=(j-i)*a[j];
a[i]+=a[j];
a[j]=0;
}else{
operations+=(j-i)*(b[i]-a[i]);
a[j]-=(b[i]-a[i]);
a[i]=b[i];
}
}else if (a[i]>b[i])
{
a[i+1]+=(a[i]-b[i]);
operations+=(a[i]-b[i]);
a[i]=b[i];
}else{
i++;
}
}
return operations;
}
Here -1 is a special value meaning that given array cannot be converted to desired one.
Time Complexity: O(n).

Optimizing a large if-else branch with binary search

So there is an if-else branch in my program with about 30 if-else statements. This part runs more than 100 times per second, so I saw it as an opportunity to optimize, and made it do binary search with a function pointer array (practically a balanced tree map) instead of doing linear if-else condition checks. But it ran slower about 70% of the previous speed.
I made a simple benchmark program to test the issue and it also gave similar result that the if-else part runs faster, both with and without compiler optimizations.
I also counted the number of comparisons done, and as expected the one doing binary search did about half number of comparisons than the simple if-else branch. But still it ran 20~30% slower.
I want to know where all my computing time is being wasted, and why the linear if-else runs faster than the logarithmic binary search?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
long long ifElseCount = 0;
long long binaryCount = 0;
int ifElseSearch(int i) {
++ifElseCount;
if (i == 0) {
return 0;
}
++ifElseCount;
if (i == 1) {
return 1;
}
++ifElseCount;
if (i == 2) {
return 2;
}
++ifElseCount;
if (i == 3) {
return 3;
}
++ifElseCount;
if (i == 4) {
return 4;
}
++ifElseCount;
if (i == 5) {
return 5;
}
++ifElseCount;
if (i == 6) {
return 6;
}
++ifElseCount;
if (i == 7) {
return 7;
}
++ifElseCount;
if (i == 8) {
return 8;
}
++ifElseCount;
if (i == 9) {
return 9;
}
}
int getZero(void) {
return 0;
}
int getOne(void) {
return 1;
}
int getTwo(void) {
return 2;
}
int getThree(void) {
return 3;
}
int getFour(void) {
return 4;
}
int getFive(void) {
return 5;
}
int getSix(void) {
return 6;
}
int getSeven(void) {
return 7;
}
int getEight(void) {
return 8;
}
int getNine(void) {
return 9;
}
struct pair {
int n;
int (*getN)(void);
};
struct pair zeroToNine[10] = {
{0, getZero},
{2, getTwo},
{4, getFour},
{6, getSix},
{8, getEight},
{9, getNine},
{7, getSeven},
{5, getFive},
{3, getThree},
{1, getOne},
};
int sortCompare(const void *p, const void *p2) {
if (((struct pair *)p)->n < ((struct pair *)p2)->n) {
return -1;
}
if (((struct pair *)p)->n > ((struct pair *)p2)->n) {
return 1;
}
return 0;
}
int searchCompare(const void *pKey, const void *pElem) {
++binaryCount;
if (*(int *)pKey < ((struct pair *)pElem)->n) {
return -1;
}
if (*(int *)pKey > ((struct pair *)pElem)->n) {
return 1;
}
return 0;
}
int binarySearch(int key) {
return ((struct pair *)bsearch(&key, zeroToNine, 10, sizeof(struct pair), searchCompare))->getN();
}
struct timer {
clock_t start;
clock_t end;
};
void startTimer(struct timer *timer) {
timer->start = clock();
}
void endTimer(struct timer *timer) {
timer->end = clock();
}
double getSecondsPassed(struct timer *timer) {
return (timer->end - timer->start) / (double)CLOCKS_PER_SEC;
}
int main(void) {
#define nTests 500000000
struct timer timer;
int i;
srand((unsigned)time(NULL));
printf("%d\n\n", rand());
for (i = 0; i < 10; ++i) {
printf("%d ", zeroToNine[i].n);
}
printf("\n");
qsort(zeroToNine, 10, sizeof(struct pair), sortCompare);
for (i = 0; i < 10; ++i) {
printf("%d ", zeroToNine[i].n);
}
printf("\n\n");
startTimer(&timer);
for (i = 0; i < nTests; ++i) {
ifElseSearch(rand() % 10);
}
endTimer(&timer);
printf("%f\n", getSecondsPassed(&timer));
startTimer(&timer);
for (i = 0; i < nTests; ++i) {
binarySearch(rand() % 10);
}
endTimer(&timer);
printf("%f\n", getSecondsPassed(&timer));
printf("\n%lli %lli\n", ifElseCount, binaryCount);
return EXIT_SUCCESS;
}
possible output:
78985494
0 2 4 6 8 9 7 5 3 1
0 1 2 3 4 5 6 7 8 9
12.218656
16.496393
2750030239 1449975849
You should look at the generated instructions to see (gcc -S source.c), but generally it comes down to these three:
1) N is too small.
If you only have a 8 different branches, you execute an average of 4 checks (assuming equally probable cases, otherwise it could be even faster).
If you make it a binary search, that is log(8) == 3 checks, but these checks are much more complex, resulting in an overall more code executed.
So, unless your N is in the hundreds, it probably doesn't make sense to do this. You could do some profiling to find the actual value for N.
2) Branch prediction is harder.
In case of a linear search, every condition is true in 1/N cases, meaning the compiler and branch predictor can assume no branching, and then recover only once. For a binary search, you likely end up flushing the pipeline once every layer. And for N < 1024, 1/log(N) chance of misprediction actually hurts the performance.
3) Pointers to functions are slow
When executing a pointer to a function you have to get it from memory, then you have to load your function into instruction cache, then execute the call instruction, the function setup and return. You can not inline functions called through a pointer, so that is several extra instructions, plus memory access, plus moving things in/out of the cache. It adds up pretty quickly.
All in all, this only makes sense for a large N, and you should always profile before applying these optimizations.
Use a switch statement.
Compilers are clever. They will produce the most efficient code for your particular values. They will even do a binary search (with inline code) if that is deemed more efficient.
And as a huge benefit, the code is readable, and doesn't require you to make changes in half a dozen places to add a new case.
PS. Obviously your code is a good learning experience. Now you've learned, so don't do it again :-)

Resources