Download Document

Document related concepts
no text concepts found
Transcript
Lecture 16: [C]
Data Structure
EEE2108: 공학프로그래밍
서강대학교 전자공학과 2012학년도 2학기
1. Basic Concepts
1.1 Allocation and deallocation of memory
int main()
{
int i, *pi;
if ( !(pi = (int *)malloc(sizeof(int))) )
{
fprintf(stderr, “Insufficient memory”);
exit(0);
}
*pi = 3;
printf(“%d\n”, *pi);
free(pi);
return 0;
}
2
1.2 Algorithm Specification
• Definition: An algorithm is a finite set of instructions that, if followed,
accomplishes a particular task. In addition, all algorithms must satisfy
the following criteria.
1)
2)
3)
4)
5)
•
Input. There are zero or more quantities that are externally supplied.
Output. At least one quantity is produced.
Definiteness. Each instruction is clear and unambiguous.
Finiteness. If we trace out the instructions of an algorithm, then for all
cases, the algorithm terminates after a finite number of steps.
Effectiveness. Every instruction must be basic enough to be carried
out, in principle, by a person using only pencil and paper. It is not
enough that each operation be definite as in 3); it also must be feasible.
In computational theory, one distinguishes between an algorithm and a program, the
latter of which does not have to satisfy the fourth condition. For example, we can think
of an operating system that continues in a wait loop until more jobs are entered.
3
Example
• [Selection sort] Suppose we must devise a program that
sorts a set of n ≥ 1 integers.
• STEP 1. From those integers that are currently unsorted,
find the smallest and place it next in the sorted list.
• STEP 2. Write it partially in C and partially in English
for (i=0; i<n; i++)
{
Examine list[i] to list[n-1] and suppose that
the smallest integer is at list[min];
Interchange list[i] and list[min];
}
4
#include <stdio.h>
#include <stdlib.h> // rand()
#define SWAP(x, y, t) ((t)=(x), (x)=(y), (y)=(t))
void sort(int list[], int n);
int main(void)
{
int i, list[100];
for (i=0; i<100; i++) list[i] = rand() % 1000;
sort(list, i);
for (i=0; i<100; i++) printf("%d\n", list[i]);
return 0;
}
void sort(int list[], int n)
{
int i, j, min, temp;
for (i=0; i<n-1; i++) {
min = i;
for (j=i+1; j<n; j++)
if (list[j]<list[min]) min = j;
SWAP(list[i], list[min], temp);
}
}
5
1.3 Performance Analysis
• One of the goals of this course to develop your skills for making
evaluative judgments about programs.
1)
2)
3)
4)
5)
6)
7)
•
Does the program meet the original specifications of the task?
Doest it work correctly?
Doest the program contain documentation that shows how to use it and
how it works?
Doest the program effectively use functions to create logical units?
Is the program’s code readable?
Does the program efficiently use primary and secondary storage?
Is the program’s running time acceptable for the task?
These criteria focus on performance evaluation, we can loosely
divide into two distinct fields: space complexity and time
complexity.
6
(1) Space Complexity
•
•
The amount of memory that it needs to run to completion.
Fixed space requirement
– Space requirements that do not depend on the number and size of the program’s
input and outputs.
– Include the instruction space, space for simple variables, fixed-size structured
variables (such as structs), and constants.
•
Variable space requirement
– The space needed by structured variables whose size depends on the particular
instance, I, for the problem being solved.
– Include the additional space required when a function uses recursion.
•
The total space requirement = fixed + variable
S(P) = c + SP(I)
– c: constant representing the fixed space requirements.
– SP(I) : the variable space requirement of a program P working on an instance I. It is
represented by SP(n) if n is the only instance characteristic of the instance I.
7
Example: summing a list of numbers
• Iterative function: Ssum(n) = 0
float sum(float list[], int n)
{
int i; float tempsum = 0;
for (i=0; i<n; i++) tempsum += list[i];
return tempsum;
}
• Recursive function: Srsum(n) = 12*n bytes
float rsum(float list[], int n)
{
if (n) return rsum(list, n-1) + list[n-1];
return 0;
}
Type
Name
Number of bytes
array pointer
list[ ]
4
integer
n
4
return address
4
Total per recursive call
12
8
(2) Time Complexity
• The amount of computer time that it needs to run to completion.
• The time, T(P) taken by a program, P,
T(P) = compile time + TP(I)
– The compile time is similar to the fixed space component.
– TP(I): program run (or execution) time
• Machine-dependent estimate of TP(I)
– Not easy task because it
– Letting n denote the instance characteristic, we might express
TP(n) = caADD(n) + csSUB(n) + clLDA(n) + cstSTA(n)
– where are constants that refer to the time needed to perform each
operation, and ADD, SUB, LDA, STA are number of additions,
subtractions, loads, and stores that are performed.
• Machine-independent estimate of TP(I)
– Program step
9
Program Step
• Definition: A program step is a syntactically or semantically
meaningful program segment whose execution time is independent of
the instance characteristics.
□
• Step count for summing
Frequency
float sum(float list[], int n)
{
int i;
float tempsum = 0;
for (i=0; i<n; i++)
tempsum += list[i];
return tempsum;
}
0
0
1
1
n+1
n
1
0
2n+3
10
•
Step count for recursive summing
float rsum(float list[], int n)
{
if (n)
return rsum(list, n-1)+list[n];
return 0;
}
Frequency
0
0
n+1
n
1
0
2n+2
• Step count for matrix addition
void add(int a[][MAX_SIZE])
{
int i, j;
for (i=0; i<rows; i++)
for (j=0; j<cols; j++)
c[i][j]=a[i][j]+b[i][j];
}
Frequency
0
0
0
rows+1
rows*(cols+1)
rows*cols
0
2rows*cols+2rows+1
11
1.4 Asymptotic Notation
• Definition: [Big “oh”] f(n) = O(g(n)) (read as “f of n is big oh g of n”) iff
(if and only if) there exist positive constants c and n0 such that f(n) ≤
cg(n) for all n, n ≥ n0.
• Example
3n+2 = O(n)
3n+2 ≤ 4n for all n ≥ 2
100n+6 = O(n)
100n+6 ≤ 101n for all n ≥ 10
10n2+4n+2 = O(n2)
10n2+4n+2 ≤ 11n2 for all n ≥ 5
6*2n+n2 = O(2n)
6*2n+n2 ≤ 7*2n for all n ≥ 4
O(1)
a computing time is constant
O(n)
linear
O(n2)
quadratic
O(n3)
cubic
O(2n)
Exponential
12
• The seven computing times
– O(1), O(log n), O(n), O(n log n), O(n2), O(n3), O(2n)
f ( n ) = am n m +  + a1n + a0 ,
• Theorem: If m
i
(
)
|
|
≤
f
n
a
n
∑ i
• Proof
then f(n) = O(nm).
i =0
≤n
m
m
i −m
|
|
a
n
∑ i
i =0
≤n
m
m
∑| a
i =0
i
|, for n ≥ 1
So, f(n) = O(nm).
□
13
1.5 Performance Measurement
•
•
•
We discuss on measuring time.
There are actually two different methods for timing events in C, by using C’s
standard library accessed through #include <time.h>
Method 1: clock()
– This function gives the amount of processor time, as the built-in type clock_t, that has
elapsed since the program began running.
•
Method 2: time()
– This function returns the time, measured in seconds, as the built-in type time_t.
Method 1
Method 2
Start timing
start = clock();
start = time(NULL);
Stop timing
stop = clock();
stop = time(NULL);
Type returned
clock_t
time_t
Result in
seconds
duration =
((double)(stop-start))
/CLOCKS_PER_SEC;
duration =
(double)
difftime(stop, start);
14
Example: timing program for worst-case sorting
• O(n) vs O(n2)
#include <stdio.h>
#define MAX_SIZE 1001
void sort(int list[], int n);
int main(void)
{
int i, n, step = 10;
int a[MAX_SIZE];
double duration;
clock_t start;
for (n=0; n<MAX_SIZE; n+=step) {
for (i=0; i<n; i++) a[i] = n-i;
start = clock();
sort(a, n);
duration = ((double)(clock()-start))/CLOCK_PER_SEC;
printf(“%d\t%f\n”, n, duration);
if (n==100) step = 100;
}
return 0;
}
15
2. Search
2.1 Sequential Search
• One way to search for a record with the specified key is to examine
the list of records in left-to-right or right-to-left order.
• Analysis: O(n)
// return the least i such that list[i] = x;
// return -1, if x is not found
int seqseach(int list[], int x, int n)
{
int i;
for (i=1; i<=n && list[i]!=x; i++) ;
if (i>n) return -1; /* not found */
return i;
}
16
2.2 Binary Search
 One way to search for a record with the specified key in an ordered,
sequential list
 Analysis: O(log n)
// find x in list[0] <= list[1] <= ... <= list[n-1]
int binsearch(int list[], int x, int n)
{
int min, max, mid;
min = 0;
max = n - 1;
while (min <= max) {
mid = (min+max)/2;
if (x < list[mid]) max = mid - 1;
else if (x > list[mid]) min = mid + 1;
else return mid; /* found match */
}
return -1; /* not found */
}
17
3. Sort
3.1 Bubble Sort
• Bubble sort is a simple sorting algorithm. Although the algorithm is
simple, it is not efficient for sorting large lists; other algorithms are
better.
• Analysis: O(n2)
void bubble_sort(int list[], int n)
{
int i, j;
for (i=0; i<n; i++) {
for (j=n-1; j>i; j--)
if (list[j-1]>list[j])
swap(&list[j-1], &list[j]);
}
}
18
3.2 Insertion Sort
• Algorithm
– Every repetition of insertion sort removes an element from the
input data, inserting it into the correct position in the already-sorted
list, until no input elements remain.
– In each iteration the first remaining entry of the input is removed,
inserted into the result at the correct position, thus extending the
result:
becomes
19
void insertion_sort(int list[], int n)
{
int i, j;
int temp;
}
for (j=1; j<n; j++) {
temp = list[j];
i = j-1;
while (i>=0 && list[i]>temp) {
list[i+1] = list[i];
i--;
}
j
[0] [1]
list[i+1] = temp;
}
5
4
• Analysis: O(n2)
n −1
O ( ∑ (i + 1)) = O ( n 2 )
i =1
[2]
[3]
[4]
3
2
1
1
4
5
3
2
1
2
3
4
5
2
1
3
2
3
4
5
1
4
1
2
3
4
5
20
3.3 Quick Sort
• Quick sort is a sorting algorithm developed by Tony Hoare that, on
average.
• Analysis: In the average O(n log n) and in the worst case, O(n2).
– Quick sort is often faster in practice than other O(n log n) algorithms.
• Algorithm
– The steps are:
① When n<=1, the list is sorted.
When n>1, select a pivot element from out of n elements.
② Partition the n elements into 3 segments left, middle, and right.
The middle segment contains only the pivot element.
All elements in the left segments are <= pivot.
All elements in the right segments are >= pivot.
③ Sort left and right segments recursively.
– Answer is sorted left segment, followed by middle segment followed by
sorted right segment.
21
Example
• list
① Select a pivot as list[left] = 6
② Partition
③ Sort left and right segment recursively.
22
Choice of pivot
• Left most element
– Pivot is left most element in list that is to be sorted.
– When sorting list[6:20], use list[6] as the pivot.
• Random selection
– Randomly select any one such that left <= pivot <= right
– When sorting list[6:20], generate a random number r in the range
[6, 20]. Use list[r] as the pivot.
• Median-of-three rule
– Select the one with median key as the pivot.
– When sorting list[6:20], examine list[6], list[(6+20)/2], and list[20].
Select the element with median (i.e., middle) key.
– If list[6] = 30, list[13] = 2, and list[20] = 10, list[20] becomes the
pivot.
– If list[6] = 3, list[13] = 2, and list[20] = 10, list[6] becomes the pivot.
23
C code (version 1) [2]
void quick_sort(int list[], int left, int right)
{
int pivot, i, j;
if (left < right) {
// select a pivot
pivot = list[left];
// partition
i = left;
j = right+1;
do {
do i++; while (list[i] < pivot);
do j--; while (list[j] > pivot);
if (i < j) swap(&list[i], &list[j]);
} while (i < j);
swap(&list[left], &list[j]);
// sort left and right segments recursively
quick_sort(list, left, j-1);
quick_sort(list, j+1, right);
}
}
24
C code (version 2) [1]
void quick_sort(int list[], int left, int right)
{
int pivot, i, mid;
if (left < right) {
// pivot is midpoint; move to left side
swap(&list[left], &list[(left+right)/2]);
pivot = list[mid=left];
// partition
// left side < pivot (left+1 to mid),
// right side >= pivot (mid+1 to right)
for (i=left+1; i<=right; i++) {
if (list[i] < pivot)
swap(&list[i], &list[++mid]);
}
// resotre pivot position
swap(&list[left], &list[mid]);
if (mid > left) quick_sort(list, left, mid-1);
if (mid < right) quick_sort(list, mid+1, right);
}
}
25
Average Complexity
• If T(n) is the time taken to sort a list of n records, then
when the list splits roughly into two equal parts each time
a record is positioned correctly, we have
T ( n ) ≤ cn + 2T ( n / 2), for some constant c
≤ cn + 2( cn / 2 + 2T ( n / 4)) = 2cn + 4T ( n / 4)
≤ ...
≤ cn log2 n + nT (1) = O ( n log n )
26
3.4 Comparison of Sort Methods
Name
Best
Average
Worst
Bubble sort
n
n2
n2
Insertion sort
n
n2
n2
Quick sort
n log n
n log n
n2
Merge sort
n log n
n log n
n log n
Heap sort
n log n
n log n
n log n
27
4. Linked Lists
4.1 Basic
• Definition: A dynamic data structure that consists of a sequence of
records where each element contains a link to the next record in the
sequence.
• Each record of a linked list is often called an element or node,
– Each node has a payload and a reference (i.e., a link) to the next and/or
previous node in the sequence.
• The start (head) of the list is maintained in a sequence variable.
• End of the list is indicated by NULL (sentinel).
28
Linked Lists Types
• Linear and circular lists
– If the last node points to the first node of the list, the list is said to
be circular or circularly linked; otherwise it is said to be open or
linear.
29
• Singly, doubly, and multiply linked lists
– Singly linked lists: Each node haa a data field as well as a next
field, which points to the next node in the linked list.
– Doubly linked list: Each node contains, besides the next-node link,
a second link field pointing to the previous node in the sequence.
The two links may be called forward(s) and backwards, or next
and prev(ious).
– Multiply linked list: Each node contains two or more link fields,
each field being used to connect the same set of data records in a
different order (e.g., by name, by department, by date of birth,
etc.).
30
Linked lists vs. Arrays
• A dynamic array is a data structure that allocates all
elements contiguously in memory, and keeps a count of
the current number of elements.
Name
Linked lists
Dynamic Array
Size
Dynamic
Fixed
Indexing
O(n)
O(1)
at beginning
O(1)
O(n)
at end
O(1)
O(1)
in middle
search time + O(1) O(n)
Insert/Delete
31
4.2 Singly Linked List
• Operations
– insert
– delete
32
create_node()
/* data strcture */
typedef struct node {
int data;
struct node *next;
} node_type;
// payload
/* creating new node */
node_type *create_node(int data)
{
node_type *node =
(node_type *)malloc(sizeof(node_type));
if (node!=NULL) {
node->data = data;
node->next = NULL;
}
return node;
}
33
insert_node()
/* insert after node */
node_type *insert_node(node_type **head,
node_type *node, int data)
{
node_type *newnode;
newnode = create_node(data);
if (newnode) {
/* insert newnode after node */
if (node) {
newnode->next = node->next;
node->next = newnode;
}
/* NULL list */
else {
newnode->next = NULL;
(*head) = newnode;
}
}
return newnode;
}
34
delete_node()
int delete_node(node_type **head, node_type *delnode)
{
node_type *current = (*head);
if (!(*head)) return -1;
if (*head == delnode) { /* the node is the head */
*head = delnode->next;
free(delnode);
return 0;
}
else {
/* find the node to be deleted */
while (current->next && current->next!=delnode)
current = current->next;
if (current->next) {
current->next = delnode->next;
free(delnode);
return 0;
}
}
return -1;
}
35
main()
int main()
{
int i;
node_type *head = NULL;
node_type *current, *delnode;
/* creat linked lists */
current = head;
for (i=0; i<10; i++) {
current = insert_node(&head, current, i);
if (i==3) delnode = current;
}
/* delete */
delete_node(&head, head);
delete_node(&head, delnode);
/* iterating */
for (current=head; current!=NULL; current=current->next)
printf("%d\n", current->data);
return 0;
}
36
4.3 Doubly linked lists
• If ptr points to any node in a doubly linked list, then:
ptr = ptr->prev->next = ptr->next->prev
• Operations
37
create_node()
/* data strcture */
typedef struct node {
int data;
struct node *next;
struct node *prev;
} node_type;
// payload
/* creating new node */
node_type *create_node(int data)
{
node_type *node =
(node_type *)malloc(sizeof(node_type));
if (node!=NULL) {
node->data = data;
node->next = NULL;
node->prev = NULL;
}
return node;
}
38
insert_node()
node_type *insert_node(node_type **head, node_type **tail,
node_type *node, int data)
{
node_type *newnode;
newnode = create_node(data);
if (newnode) {
/* insert newnode after node */
if (node) {
newnode->prev = node;
newnode->next = node->next;
if (node->next == NULL) (*tail) = newnode;
else node->next->prev = newnode;
node->next = newnode;
}
/* NULL list */
else {
newnode->prev = NULL;
newnode->next = NULL;
(*head) = newnode;
(*tail) = newnode;
}
}
return newnode;
}
39
delete_node()
int delete_node(node_type **head, node_type **tail, node_type *delnode)
{
node_type *current = (*head);
if (!(*head) || !(delnode)) return -1;
/* the node to be deleted is the head */
if (*head == delnode) {
*head = delnode->next;
delnode->next->prev = NULL;
}
else if (*tail == delnode) {
*tail = delnode->prev;
delnode->prev->next = NULL;
}
else {
delnode->prev->next = delnode->next;
delnode->next->prev = delnode->prev;
}
free(delnode);
return 0;
}
40
main()
int main()
{
int i;
node_type *head = NULL;
node_type *tail = NULL;
node_type *current, *delnode;
/* creat linked lists */
current = head;
for (i=0; i<10; i++) {
current = insert_node(&head, &tail, current, i);
if (i==3) delnode = current;
}
/* delete */
delete_node(&head, &tail, head);
delete_node(&head, &tail, tail);
delete_node(&head, &tail, delnode);
/* iterating */
for (current=head; current!=NULL; current=current->next)
printf("%d\n", current->data);
for (current=tail; current!=NULL; current=current->prev)
printf("%d\n", current->data);
return 0;
}
41
5. Stacks and Queues
5.1 Stack
• A stack is a last in, first out (LIFO) abstract data type and data
structure.
• A stack is characterized by only three fundamental operations: push,
pop and stack top.
– The push operation adds a new item to the top of the stack, or initializes
the stack if it is empty. If the stack is full and does not contain enough
space to accept the given item, the stack is then considered to be in an
overflow state.
– The pop operation removes an item from the top of the stack. If the stack
is empty then it goes into underflow state.
– The stack top operation gets the data from the top-most position and
returns it to the user without deleting it. The same underflow state can
also occur in stack top operation if stack is empty.
•
A stack can be easily implemented either through an array or a linked
list.
42
Implementation: Array
•
The array implementation aims to create an array where
the first element (usually at the zero-offset) is the bottom.
That is, array[0] is the first element pushed onto the stack
and the last element popped off.
43
typedef struct {
int size;
int items[STACKSIZE];
} STACK;
void push(STACK *ps, int x)
{
if (ps->size == STACKSIZE) {
fputs("Error: stack overflow\n", stderr);
exit(0);
}
else
ps->items[ps->size++] = x;
}
int pop(STACK *ps)
{
if (ps->size == 0){
fputs("Error: stack underflow\n", stderr);
exit(0);
}
else
return ps->items[--ps->size];
}
44
#include <stdio.h>
#include <stdlib.h>
#define STACKSIZE
...
10
int main()
{
STACK list;
int i;
list.size = 0;
for (i=0; i<STACKSIZE; i++)
push(&list, i);
for (i=0; i<STACKSIZE; i++)
printf("%d\n", pop(&list));
return 0;
}
45
Implementation: Linked List
• The linked-list implementation is equally simple and
straightforward. In fact, a simple singly linked list is
sufficient to implement a stack.
46
typedef struct stack {
int data;
struct stack *next;
} STACK;
void push(STACK **head, int value)
{
/* create a new node */
STACK *node = malloc(sizeof(STACK));
if (node == NULL){
fputs("Error: no space available for node\n", stderr);
exit(0);
}
else {
/* initialize node */
node->data = value;
node->next = !(*head) ? NULL : *head;
*head = node;
}
}
47
int pop(STACK **head)
{
if (!(*head)) { /* stack is empty */
fputs("Error: stack underflow\n", stderr);
exit(0);
} else {
/* pop a node */
STACK *top = *head;
int value = top->data;
*head = top->next;
free(top);
return value;
}
}
int main()
{
STACK *head = NULL;
int i;
for (i=0; i<10; i++) push(&head, i);
for (i=0; i<10; i++) printf("%d\n", pop(&head));
return 0;
}
48
5.2 Queue
• A queue is a first in, first out (FIFO) abstract data type and
data structure.
• A queue is characterized by two fundamental operations:
enqueue and dequeue.
•
Queue overflow results from trying to add an element
onto a full queue and queue underflow happens when
trying to remove an element from an empty queue.
•
A queue can be easily implemented either through an
array or a linked list.
49
Implementation: Linked List
struct queue_node
{
int data;
struct queue_node *next;
};
struct queue
{
struct queue_node *first;
struct queue_node *last;
};
50
int enqueue(struct queue *q, const int value)
{
struct queue_node *node =
(struct queue_node *)malloc(sizeof(struct queue_node));
if (node == NULL) {
errno = ENOMEM;
return 1;
}
node->data = value;
if (q->first == NULL)
q->first = q->last = node;
else {
q->last->next = node;
q->last = node;
}
node->next = NULL;
return 0;
}
51
int dequeue(struct queue *q, int *value)
{
struct queue_node *tmp;
if (!q->first) {
*value = 0;
return 1;
}
*value = q->first->data;
tmp = q->first;
if (q->first == q->last)
q->first = q->last = NULL;
else
q->first = q->first->next;
free(tmp);
return 0;
}
52
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
...
void init_queue(struct queue *q)
{
q->first = q->last = NULL;
}
int queue_empty(const struct queue *q)
{
return q->first == NULL;
}
int main(void)
{
int i, data;
struct queue list;
init_queue(&list);
for (i=0; i<10; i++) enqueue(&list, i);
for (i=0; !queue_empty(&list); i++) {
dequeue(&list, &data);
printf("%d: %d\n", i, data);
}
return 0;
}
53
Summary
• Performance Analysis: Space Complexity, Time
Complexity
• Search: Sequential Search, Binary Search
• Sort: Bubble, Insertion, Quick
• Linked Lists: Singly Linked List, Doubly linked lists
• Stacks and Queues
54
Related documents