Linked List Example

Here's a program that illustrates using linked lists. It reads in records from a file into a linked list, then allows queries on the data:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* a student record */
typedef struct _student {
	int		id;		/* student id */
	char		name[100];	/* student name */
	float		gpa;		/* student's GPA */
} student;

/* linked list type with student record */
typedef struct _node {
	student		k;
	struct _node	*next;
} node, *list;

/* insert a student record into the list */
void insert_list (list *L, student k) {
	node	*p;

	/* get a free node */

	p = (node *) malloc (sizeof (node));

	/* put the record in it */

	p->k = k;

	/* link it to the first node in the list */

	p->next = *L;

	/* make the first node equal to this one
	 * (so now the former first, p->next, is the second)
	 */
	*L = p;
}

/* return pointer to student record corresponding to id */
student *search_list (list L, int id) {
	node	*p;

	/* chase down the list until id is found, or end is reached */

	for (p=L; p && p->k.id != id; p=p->next);

	/* if it's there, return a pointer to the record */

	if (p) 
		return &p->k;
	else

	/* otherwise, NULL */

		return NULL;
}

/* make an empty list */

void create_list (list *L) {
	*L = NULL;
}

/* main program */
int main () {
	list		C;	/* a class of students */
	student		s, *p;
	int		id;
	FILE		*f;

	/* open the List file if we can */

	f = fopen ("List", "r");
	if (!f) {
		perror ("List");
		exit (1);
	}

	/* make C an empty list */

	create_list (&C);

	/* read student records, inserting them into the list */

	for (;;) {
		fscanf (f, "%d %s %f\n", &s.id, s.name, &s.gpa);

		/* reached end of file?  get out of loop */

		if (feof (f)) break;
		insert_list (&C, s);
	}
	fclose (f);

	/* ask for IDs, print records */

	for (;;) {

		/* prompt for and read ID */

		printf ("Enter student ID, -1 to finish: ");
		scanf ("%d", &id);

		/* if they type -1, we're done */

		if (id == -1) break;

		/* find the student */

		p = search_list (C, id);

		/* not found? */

		if (!p)
			printf ("ID #%d not found!\n", id);
		else

		/* print the record */

			printf ("%d\t%s\t%0.2f\n", p->id, p->name, p->gpa);
	}
	exit (0);
}

Other List Operations

Some other list operations we might want to do are these: Let's look at some code to do insert_ordered:
/* insert k in order */
void insert_ordered_list (list *L, int k) {
	node	*p, *q, *r;

	/* get a new node, stick k in it */

	r = (node *) malloc (sizeof (node));
	r->k = k;
	r->next = NULL;
	if (!*L) 	
		*L = r;
	else if ((*L)->k > k) {
		r->next = *L;
		*L = r;
	} else {
		p = *L;
		q = p->next;
		while (q && q->k < k) {
			p = q;
			q = q->next;
		}
		p->next = r;
		r->next = q;
	}
}
Now we can write a simple program that will sort numbers simply by inserting them into the linked list in order.

We'll collaborate to write append and intersection in class. Also, think about how you would do "insertion without duplicates."

Stacks

Another data structure is the stack. A stack is a "last in, first out" (LIFO) data structure. Items can be inserted ("pushed") and deleted ("popped"), but only the most recently inserted element can be operated on. This element is called the "top" of the stack. Stacks come up a lot in computer science. For example, C functions use stacks to remember where to return.

Stack Examples

Here are the operations a stack ADT supports:

Stacks are often implemented with arrays. Here are some declarations and functions that will do stacks in arrays:
#define MAX_ELEMENTS	1000

typedef struct _stack {
	int	top;
	float	elements[MAX_ELEMENTS];
} stack;


/* make a stack */
void create_stack (stack *s) {
	s->top = MAX_ELEMENTS;
}

/* push (insert) an item into a stack */
void push_stack (stack *s, float k) {
	if (s->top == 0) {
		fprintf (stderr, "stack overflow!\n");
		exit (1);
	}
	s->elements[--(s->top)] = k;
}

/* pop (delete) top element and remove it */
float pop_stack (stack *s) {
	if (s->top == MAX_ELEMENTS) {
		fprintf (stderr, "stack underflow!\n");
		exit (1);
	}
	return s->elements[(s->top)++];
}

/* return true iff stack is empty */
int empty_stack (stack s) {
	return s.top == MAX_ELEMENTS;
}

/* return top of stack (without deleting) */
float top_stack (stack s) {
	if (!empty_stack (s))
		return s.elements[s.top];
	else {
		fprintf (stderr, "empty stack!\n");
		exit (1);
	}
}
This is the way the stacks underlying function calls and parameter passing are done. This is an efficient way to do stacks, but it only allows up to a certain number of elements; any more is a stack overflow. A more general way is to use linked list nodes:
typedef struct _node {
	float		k;
	struct _node	*next;
} node, *stack;

/* make a stack */
void create_stack (stack *S) {
	*S = NULL;
}

/* push k onto S */
void push_stack (stack *S, float k) {
	node	*p;

	/* get a new node */

	p = (node *) malloc (sizeof (node));

	/* the top of the stack is now the second element */

	p->next = *S;

	/* new top is k */

	p->k = k;

	/* p points to top */

	*S = p;
}

/* pop and return */
float pop_stack (stack *S) {
	node	*p;
	float	r;
	
	/* make sure there's something to pop */

	p = *S;
	if (!p) {
		fprintf (stderr, "stack underflow!\n");
		exit (1);
	}

	/* save value to return (before we free the storage) */

	r = p->k;

	/* "pop"; the next element is the top now */

	*S = p->next;

	/* free storage for previous top */

	free (p);

	/* return value we saved before */

	return r;
}

/* return true iff stack is empty */
int empty_stack (stack S) {

	/* true only if S is NULL (empty list) */

	return S == NULL;
}

/* return top of stack */
float top_stack (stack S) {
	if (!empty (S)) 
		return S->k;
	else {
		fprintf (stderr, "empty stack!\n");
		exit (1);
	}
}
One use of a stack is in the evaluation of postfix expressions. These are arithmetic expressions where the operators are specified after the operands. This is as opposed to infix expressions that you're used to, where operators are specified between operands.

For example, in postfix notation, "1 2 +" would be 1 + 2. (2 + 3) / (4 + 5) would be "2 3 4 5 + + /". Here is a C program using stacks that processes postfix expressions, with numbers and operators read from standard input, one per line.

int main () {
	char	input[100];
	float	a, b;
	stack	S;

	create_stack (&S);
	printf ("Enter expressions in postfix...");
	for (;;) {
		fgets (input, 100, stdin);
		if (feof (stdin)) break;
		switch (input[0]) {
			case '-':
				if (input[1] == 0) {
					a = pop_stack (&S);
					b = pop_stack (&S);
					push_stack (&S, b - a);
				} else {
					sscanf (input, "%f", &a);
					push_stack (&S, a);
				}
				break;
			case '+':
				a = pop_stack (&S);
				b = pop_stack (&S);
				push_stack (&S, a + b);
				break;
			case '*':
				a = pop_stack (&S);
				b = pop_stack (&S);
				push_stack (&S, a * b);
				break;
			case '/':
				a = pop_stack (&S);
				b = pop_stack (&S);
				push_stack (&S, b / a);
				break;
			case 't':
				printf ("%f\n", top_stack (&S));
				break;
			default:
				sscanf (input, "%f", &a);
				push_stack (&S, a);
				break;
		}
	}
	exit (0);
}
To use the program, run it, then type in postfix notation (one symbol per line). Type t to see the top of the stack (i.e., current result).