Data structure
Data structure
Contents Different Data Types Array String Record
Definition A data structure is a composite of
related data items stored under the same name.
User-defined data types
type
<type identifier> = <type declaration clause>;
<type identifier> = <type declaration clause>; …
Type declaration in Pascaltype List = array[1..10] of integer; StaffRec = record Name: string[30]; Salary: real; Age: integer end;var ScoreList, TestList: List; ExamList: array[1..10] of integer; Programmer, Clerk: StaffRec;
Ordinal data type An ordinal data type has a finite set of
values that can always be listed in order from the first to the last. predefined ordinal types include :integer, boolean and char.
Function ord() ord()
This function returns the ordinal value of an ordinal-type expression.
e.g. ord(‘A’) = 65ord(‘B’) = 66…
Function pred() pred()
This function returns the predecessor of the argument, i.e. the value listed before the argument if the values of that type are arranged in ascending order.
e.g. pred(‘B’) = A
Function succ()
succ()
This function returns the successor of the argument, i.e. the value listed after the argument if the values of that type are arranged in ascending order.
e.g. succ(‘A’) = B
Enumerated types An enumerated type includes in its
definition an exhaustive list of possible values for variables of that type.
Enumerated typestype
DayOfWeek = (Sunday, Monday, Tuesday, Wednesday, Thursday,Friday, Saturday);
MaritalStatus = (Single, Married, Divorce, Separated);
var
Today, Tomorrow, Yesterday: DayOfWeek;
MS: MaritalStatus;
Why use enumerated types?
Programmers may use using enumerated type values or codes. For example:
Using enumerated type (Today is of type DayOfWeek. See the defintion of DayOfWeek last week.)
if Today = Sunday then PayFactor := 2.0 else if Today = Saturday then PayFactor := 1.5 else PayFactor := 1.0
Using codes (Assume DayNum is an integer variable, and 0 represents Sunday, 1 represents Monday, etc.)
if DayNum = 0 then PayFactor := 2.0 else if DayNum = 6 then PayFactor := 1.5 else PayFactor := 1.0
enumerated types make the program considerably easier to read and understand
Subrange types A subrange type is an ordinal data type
whose values are a subset of another ordinal type (the host type).
type Letter = 'A'..'Z';var NextChar: Letter; {NextChar can only be a capital letter} InDay: 1..31;
{InDay can be any integer between 1 & 31 inclusive}
Arrays An array is a collection of identically
typed data items distinguished by their indices (or subscripts).
Declaring arrays in Pascal array[<subscript type>] of <element type>Example
const
NumEmp = 8;
type
EmpRange = 1..NumEmp;
EmpArray = array[EmpRange] of Boolean;
Day = (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);
WorkingDaySalary = array[Monday..Friday] of real;
Spreadsheet = array['A'..'Z', 1..100] of real;
How a one-dimensional array is stored in the memory type ArrayType = array[a..b] of SomeType;var AnArray: ArrayType;
Suppose that a and b are integer constants where a b, a variable of type SomeType occupies n bytes, and the variable AnArray is stored at address p. At what address is the element AnArray[i] stored?
p+(i-a)*n
How a multi-dimensional array is stored in the memory
type
TableType = array[1..3, 1..5] of integer;
var
M: TableType;
The variable M can be visualized as follows: 1 2 3 4 5
1 M[1,1] M[1,2] M[1,3] M[1,4] M[1,5] 2 M[2,1] M[2,2] M[2,3] M[2,4] M[2,5] 3 M[3,1] M[3,2] M[3,3] M[3,4] M[3,5]
If the array is stored in row major order, the elements are arranged in the memory as follows: M[1,1] M[1,2] M[1,3] M[1,4] M[1,5] M[2,1] … M[3,4] M[3,5]
row
How a multi-dimensional array is stored in the memory
If the array is stored in column major order, the elements are arranged in the memory as follows: M[1,1] M[2,1] M[3,1] M[1,2] M[2,2] M[3,2] … M[2,5] M[3,5]
column column
In Turbo Pascal, elements in a multi-dimensional array are arranged in row major order.
Multi-dimensional Arraytype
ArrayType = array[1.. M, 1.. N] of SomeType;
var LargeArray: ArrayType;
A variable of type SomeType occupies n bytes, and the variable LargeArray is stored at address p. At what address is the element LargeArray[R,C] stored? Consider the different cases for row-major order and column-major order.
p + ((R-1)*N + (C-1))*n {Row-major order}
p + ((C-1)*M + (R-1))*n {Column-major order}
Multi-dimensional Arraytype
ArrayType = array[L1.. U1, L2.. U2, …, Lm.. Um] of SomeTyp
e;
var
LargeArray: ArrayType;
Suppose that L1, L2, …, Lm and U1, U2, …, Um are integer constants where Li Ui (i = 1, 2, …, m). A variable of type SomeType occupies n bytes, and the variable LargeArray is stored at address p. At what address is the element LargeArray[x1, x2, …, xm] stored? Consider the different cases for row-major order and column-major order.
Strings A string is a sequence of characters. In Pascal, a string is enclosed in a pair of apostrophes. A string variable can contain up to 255 characters.
In the following example, the variable name can store up to 25 characters:
var name: string[25];
Storage of StringsIf a string variable consists of n characters, it occupies n + 1 bytes. Note: Turbo Pascal uses an extra byte to store the length of a string.
For example, if a string variable with maximum length 10 is declared, and the string ‘Johnny’ is stored in it, the contents in the variable may be as follows:
Length count string: 6 J o h n n y p S & < length
Storage of StringsIn some programming languages such as C, a null character (a character with ASCII code equal to 0) is appended to the end of the string.
NULL-terminated string: J o h n n y NULL q X *
This character marks the end of a string
Question: What are the advantages and disadvantages of storing strings in the above formats?
Pascal string functions copy(s, index, count)It returns a string which is a substring of s. The substring containing count characters starting with the indexth character in s.
concat(s1, s2, ...)It returns a string formed by concatenation of all the string parameters in the same order as listed in the parameter list.
Note: String concatenation can also be performed by using ‘+’.
e.g. Name := ‘David’ + ‘ ‘ + ‘Beckham’
Pascal string functionslength(s)It returns an integer which is the length of the string s.
pos(substr, s)It searches for substr within the string s and returns an integer value that is the index of the first character of substr within s. If substr is not found, it returns zero.
e.g.
pos('gram', 'Programming') returns 4,pos('call', 'Pascal') returns 0.
Pascal string procedures insert(source, s, index)It inserts the string source into the string s (a variable parameter) at the indexth position.
e.g. if s := ‘Honest Lincoln’
insert('Abe ', s, 8)
s will changed to ‘Honest Abe Lincoln’.
delete(s, index, count)It deletes count characters from the string s (a variable parameter) starting at the indexth position.
e.g if s := ‘Honest Abe Lincoln’
delete(s, 8, 4)
s will changed to ‘Honest Lincoln’.
Pascal string proceduresstr(x, s)It converts the numeric value x to a string and store it in the variable parameter s.
val(s, x, code)It converts the string s into a real or integer value x (the value of the result is affected by the data type of x).
If the conversion is successful, code is zero. Otherwise, the position of the first invalid character in the string s is stored in code.
Records A record is an ordered set of fields.
Usually the fields involved are related. We often a record to store details of a person, an object, etc. Actually, records are components of a database table.
Declaring records in Pascal record <field identifier 1>: <data type 1>; <field identifier 2>: <data type 2>; … <field identifier n>: <data type n>end;E.g.
type EmployeeInfo = record Name: string; {256 bytes} Sex: char; {1 byte} Age: integer; {2 bytes} Salary: real {6 bytes} end;var Programmer, Manager: EmployeeInfo; {265 bytes}
Accessing record fields
To access a field in a record, we write<record name>.<field name>
For example:writeln(Manager.Age);
readln(Programmer.salary);
Manager.Name := 'Mary Lee';
with..do statements
with <record name> do <statement>;
For examples, the logic of the following two statements are the same:
Not using with..do satatements if Programmer.Sex = 'M' then write('Mr') else write('Ms'); writeln(Programmer.Name); writeln(Programmer.Salary);
Using with..do satatements with Programmer do begin if Sex = 'M' then write('Mr') else write('Ms'); writeln(Name); writeln(Salary) end;
Array of records A record can only hold information about one item.
Commonly, we have to deal with a list of information on several items. This can be achieve by arrays of records.
E.g.const size = 100;type Str20 = string[20]; Str8 = string[8]; Info = record Name: Str20; Phone: Str8 end; InfoArray = array[1..size] of Info;var Directory: InfoArray;
Array of records
The following diagram illustrates Directory, the array of records: Name Phone
Directory[1] Directory[1].Name Directory[1].Phone Directory[2] Directory[2].Name Directory[2].Phone Directory[3] Directory[3].Name Directory[3].Phone
… … … Directory[100] Directory[100].Name Directory[100].Phone
We may see that arrays of records are very similar to tables in databases.
Variant record we can use variant records (also called
unions in some programming languages) to store data collections in which some fields are always the same (the fixed part) and some fields may be different (the variant part).
Declaring variant recordsrecord
<fixed field 1>: <type 1>;
<fixed field 2>: <type 2>;
…
<fixed field n>: <type n>;
case <tag field>: <tag type> of
<label 1>: (<variant field list 1>);
<label 2>: (<variant field list 2>);
…
<label k>: (<variant field list k>);
end;
Fixed part
Variant part
Variant records exampleconst StringLength = 20; { length of all strings except zipcode
} ZipStringSize = 5; { length of zipcode string } type IDRange = 1111..9999; StringType = string[StringLength]; ZipString = string[ZipStringSize]; Month = (January, February, March, April, May, June, July, August, September, October, November, December); Employee = record ID: IDRange; Name: StringType; Gender: char; NumDepend: integer; Rate, TotWages: real end; { Employee } Address = record Street, City, State: StringType; ZipCode: ZipString end; { Address }
Date = record ThisMonth: Month; Day: 1..31; Year: 1900..1999 end; { Date } MaritalStatus = (Married, Divorced, Single);
Variant records example Executive = record PayData: Employee; Home: Address; StartDate, BirthDate: Date; case MS: MaritalStatus of Married: (SpouseName: StringType; NumberKids: integer); Divorced: (DivorceDate: date); Single: (LivesAlone: boolean) end; { Executive }var boss: Executive;
Sets A set is an unordered list of elements
that are values of the same ordinal type (called the base type)
Declaring SetsSyntax
set of <base type>e.g.type Day = (Sun, Mon, Tue, Wed, Thu, Fri, Sat); CharSet = set of Char; Digits = set of 0..9; Days = set of Day;var GradeSet: CharSet; Codes: Digits; WorkingDaySet: Days;
Sets assignment statmente.g.
GradeSet := ['A'..'F'];
Codes := [0..9];
WorkingDaySet := [Mon..Fri];
N.B. Order and repetition of elements within the set are irrelevant. Therefore,
[1, 2, 3] = [3, 1, 2] = [2, 1, 2, 3, 1]
Empty set and universal set An empty set has no elements and is
denoted by [].
A universal set contains all the values in the base type for a particular type.
Set membership (in)Suppose that S is a set. In Mathematics, we write ‘x S’ to denote that ‘x is a member of S’. The expression in Pascal is
x in SThe left operand is of ordinal type, say T, and the right operand is a set whose base type is T. If x is a member of S, it returns true. Otherwise, it returns false. E.g.
ch in ['.', '?', ';', '!']
Set union The union of two sets is the set of elements
that are members of either or both sets. Mathematically, we write ‘A B’ as the union of A and B. In Pascal, we write
A + BE.g.
[1, 3, 4] + [1, 2, 4] is [1, 2, 3, 4] [1, 3] + [2, 4] is [1, 2, 3, 4] ['A', 'C', 'F'] + ['B', 'C', 'D', 'F'] is
['A', 'B', 'C', 'D', 'F'] ['A', 'C', 'F'] + ['A', 'C', 'D', 'F'] is ['A',
'C', 'D', 'F']
Set intersection The intersection of two sets is the set of ele
ments that are members of both sets. Mathematically, we write ‘A B’ as the union of A and B. In Pascal, we write
A * B E.g.
[1, 3, 4] * [1, 2, 4] is [1, 4] [1, 3] * [2, 4] is [] ['A', 'C', 'F'] * ['B', 'C', 'D', 'F'] is ['C',
'F'] ['A', 'C', 'F'] * ['A', 'C', 'D', 'F']
is ['A', 'C', 'F']
Set complement The complement of A in B is the set of elem
ents that are members of B but are not members of A. Mathematically, we write ‘B – A’ as the complement of A in B. In Pascal, we write
B - A E.g.
[1, 3, 4] - [1, 2, 4] is [3] [1, 3] - [2, 4] is [1, 3] ['A', 'C', 'F'] - ['B', 'C', 'D', 'F'] is ['A'] ['A', 'C', 'F'] - ['A', 'C', 'D', 'F'] is [] ['A', 'C', 'D', 'F'] - ['A', 'C', 'F'] is ['D']
Set equality and inequality Two sets are equal if and only if they have th
e same elements. To compare two sets, they must have the same base type.
E.g. [1, 3] = [1, 3] is true [1, 3] <> [2, 4] is true [1, 3] = [3, 1] is true [1, 3] = [1, 3, 1] is true
Subset and superset The set A is a subset of the set B if every elem
ent of A is also an element of B. We may also say that the set B is a superset of the set A. Mathematically, we write ‘A B’ as the
expression ‘A is a subset of B’. In Pascal, we write
A <= B Mathematically, we write ‘A B’ as the
expression ‘A is a superset of B’. In Pascal, we write
A >= B
Subset and supersetE.g. [1, 3] <= [1, 2, 3, 4] is true [1, 3] >= [1, 2, 3, 4] is false
[1, 3] <= [1, 3] is true [1, 3] >= [1, 3] is true
[1, 2, 3, 4] <= [1, 3] is false [1, 2, 3, 4] >= [1, 3] is true
[] <= [1, 3] is true [] >= [1, 3] is false [1, 3] <= [] is false [1, 3] >= [] is true
Text files A file is an element of data storage in a file s
ystem (e.g. disk, tape, directory, etc.). A text file is a file containing only printable
characters E.g. (such as letters from the English alphabet,
numbers, punctuation marks, a few symbols, etc.), end-of-line characters (<eoln>) and end-of-file characters (<eof>).
Manipulating text files Declaration text variables in Turbo Pascal
var
infile, outfile: text; Associating a text variable with an external text file
assign(f, s)
Opening an existing text file for reading
reset(f) Creating and opening a new file
rewrite(f)
Manipulating text files Reading data from a text file
read(<input file>, <variable list>);
readln(<input file>, <variable list>);
Writing data to a text file write(<output file>, <variable list>);
writeln(<output file>, <variable list>) ;
Closing a file close(f)
Binary files A binary file is a file containing arbitrary byt
es or words, as opposed to a text file containing only printable characters.
A component of a binary file is stored on disk using the same binary form as it has in main memory.
Declaring file variables The type declaration clause for a binary file is as follows:
file of <component type>E.g.
type NumberFile = file of integer; Book = record StockNum: integer; Author, Title: string[20]; Price: real; Quantity: integer end; BookFile = file of Book;var Numbers: NumberFile; Books: BookFile;
Manipulating binary files— Sequential access
Similar to text files assign(f, s) reset(f) rewrite(f) read(<input file>, <component type variable>) write(<output file>, <component type variable>);
close(f) eof(f)
Abstract Data Types An abstract data type is a combination of a
data type and procedures and functions for manipulating the data type.
The details of how to implement the data type and the procedures and functions are unknown to users of the abstract data type. Instead, users are only required to know how to use them.
Advantages of ADT It allows us to implement the client program and t
he ADT relatively independent of each other.
If we decide to change the implementation of an operator(procedure) in the ADT, we can do so without affecting the client program.
Finally, because the internal representation of a data type is hidden form its client program, we even change the internal representation at a later time without modifying the client program.
Pointers A pointer variable (pointer) is one
whose contents are a memory cell address. This means that we store the address of a particular data object in a pointer variable.
Declaring pointer type The type declaration clause for a pointer type is
^<data type>For example:
type RealPointer = ^real; NodePtr = ^node; node = record data: integer; next: NodePtr end;var px: RealPointer; pn: ^integer; head: NodePtr;
Pointer operations - New() The procedure new(<pointer>) allocates a memo
ry storage from the heap (a storage pool of available memory cells maintained by the computer) for a new data area,
and the address of this data is stored in pioneer variable <pointer>
Since allocation of memory is done during program execution when new is called, this is called dynamic allocation
Pointer operations - Dispose() The procedure dispose(<pointer>) returns the
memory cells in the data area whose address is stored <pointer> to the heap. These cells can be reallocated when procedure new is called.
After the procedure dispose(< pointer>) is done, <pointer> points to nothing. We say that the content of <pointer> is nil (a reserved word).
the dereferencing operator ^ The ^ (caret) is called the dereferencing operator.
Use <pointer>^ to reference the data area. the memory cell pointed to by px is expressed as
px^
It can be processed just like any other real variable. Note that the value of a pointer (i.e. an address) cannot be di
rectly displayed.
PointerExamples program PointerExamples; type IntPtr = ^integer; var i: integer; ip, iq: IntPtr;
At this stage, three memory locations are allocated for this program: i, ip and iq .
? ? ?
iq i ip heap
PointerExamples
? ?
iqi ip heap
?begin new(ip);
ip^ := 7; writeln(ip^); { Display: 7 }
iq := ip;
? ?
iqi ip heap
7
?
iqi ip heap
7
?
iqi ip heap
8
9
iqi ip heap
8
9
iqi ip heap
9
iq^ := 8; writeln(ip^, ' ', iq^); { Display: 8 8 }
i := 9; ip^ := i; writeln(ip^); { Display: 9 }
PointerExamples
9
iqi ip heap
9 ?
4
iqi ip heap
9 4
4 nil
iqi ip heap
9 4
Returned to the heap
new(iq); iq^ := 4; i := iq^; writeln(i); { Display: 4 } dispose(ip);
PointerExamples
4 nil
iqi ip heap
4?
4
iqi ip heap
? 4 ?
new(iq);
new(ip)end.
Allocated but cannot be referenced
PointerExamples
Note that there should always be at least one pointer variable points to a dynamically allocated storage. Remember to use dispose() to return a dynamically allocated storage which will not further be used to the heap. Otherwise, heap overflow may occur.
Lists A list is a data structure holding a
number of values which are usually accessed sequentially, working from the head to the tail.
Operations Let L be a list of objects of type ElementType,
x be an object of ElementType and p be an integer indicating a position in the list L.
The following are some examples of operations of lists: ListSize(L)
A function returning the number of elements in L.InsertAfter(x, p, L)
A function that inserts an object x after the pth position of L. It returns true if the insertion is successful and false otherwise.
InsertBefore(x, p, L)A function that inserts an object x before the pth position of L. It returns true if the insertion is successful and false otherwise.
DeleteItem(x, p, L)A function that deletes the object at the pth position of L. It returns if the deletion is successful and false otherwise.
OperationsLocateItem(x, L)
A function that returns the position of the first occurrence of x in L (and returns an appropriate value if x is not in L).
RetrieveItem(p, L)A function that returns the pth element in L. It will return an undefined value if there is no position p in L.
NextPos(p, L)A function that returns the position following the pth element in L.
PrevPos(p, L)A function that returns the position preceding the pth element in L.
LastPos(L)A function that returns the position of the last element in L.
FirstPos(L)A function that returns the position of the first element in L.
MakeNull(L)A procedure causing L to become an empty list.
PrintList(L)A procedure displaying the all the elements of L sequentially.
Array implementation Declaration — an example
const max = 5;
type
list = record
item: array[1..max] of ElementType;
size: integer
end;
var L: list;
Implementation of some operationsfunction ListSize(L: list): integer;begin ListSize := L.sizeend; function InsertAfter(x: ElementType; p: integer; var L: list): boolean;var i : integer;begin If (L.size >= max) or (p > L.size) or (p < 0) then InsertAfter := false else begin for i :=L.size downto p + 1 do L.item[i+1] := L.item[i]; L.item[p + 1] := x; L.size := L.size+1; InsertAfter := true endend;
Implementation of some operationsfunction RetrieveItem(p: integer; L: list): ElementType;begin if (p < 1) or (p > L.size) then writeln('RetrieveItem: Item not found!') else RetrieveItem := L.item[p]end; function NextPos(p: integer; L: list): integer;begin NextPos := p + 1end; function PrevPos(p: integer; L: list): integer;begin PrevPos := p - 1end;
Implementation of some operationsfunction LastPos(L: list): integer;
begin
LastPos := L.size
end;
function FirstPos(L: list): integer;
begin
FirstPos := 1
end;
Linked lists A linked list consists of a number of nodes.
Each node contains a pointer to the next node, thus forming a linear list.
1st node 2nd node 3rd node 4th node nil
linkdata
L
The contents of the memoryThe field data is used to store the data we want the linked list to store, and the field link is a pointer pointing to the next node. Actually, the address of the next node is stored in the field link.
…
0012 1234 …
1234 1st node
5678 …
1357 4th node
nil …
2468 3rd node
1357 …
5678 2nd node
2468 …
data
link
Linked lists Declaration Syntax of Linked lists
type
<Node Pointer Type> = ^<Node Type>;
<Node Type> = record
<Data Field 1>: <Data Type 1>;
<Data Field 2>: <Data Type 2>;
…
<Data Field n>: <Data Type n>;
<Pointer Field>: <Node Pointer Type>
end;
Linked lists DeclarationFor example, the linked list shown on the previous page can be declared as
follows:
type
NodePtr = ^NodeType;
NodeType = record
data: ElementType;
link: NodePtr
end;
var
L: NodePtr;
We can use a pointer (in this case, L) to the first node of a linked list to reference a linked list.
Implementation - ListSize function ListSize(L: NodePtr): integer;
var
n: integer;
q: NodePtr;
begin
q := L;
n := 0;
while q <> nil do begin
q := q^.link;
n := n + 1
end;
ListSize := n
end;
1st node 2nd node 3rd node 4th node nilL
function InsertAfter(x: ElementType; p: NodePtr; var L: NodePtr): boolean;
var q: NodePtr;begin if (p = nil) and (L <> nil) then InsertAfter := false else begin new(q); { Request for a new node from heap } q^.data := x; if L = nil then begin { L is a null linked list } L := q; L^.link := nil end else begin q^.link := p^.link; p^.link := q end; InsertAfter := true endend;
Implementation - InsertAfter
1st node 2nd node 3rd node 4th node nilL
p
q
new node nil
X
Implementation - DeleteItemfunction DeleteItem(p: NodePtr; var L: NodePtr): boolean;var temp: NodePtr;begin if p = nil then DeleteItem := false else if p = L then begin L := L^.link; dispose(p); DeleteItem := true end else begin temp := L; while (temp^.link <> p) and (temp^.link <> nil) do temp := temp^.link; if temp^.link = p then begin temp^.link := p^.link; dispose(p); DeleteItem := true end else DeleteItem := false endend;
1st node 2nd node 3rd node 4th node nilL
X
temp temp
p
function LocateItem(x: ElementType; L: NodePtr): NodePtr;
var
temp: NodePtr;
begin
temp := L;
while (temp <> nil) and (temp^.data <> x) do
temp := temp^.link;
LocateItem := temp
end;
Implementation - LocateItem
procedure PrintList(L: NodePtr);
var
temp: NodePtr;
begin
temp := L;
while temp <> nil do begin
writeln(temp^.data);
temp := temp^.link
end
end;
Implementation - PrintList
Implementation Notes Remember to assign nil to the link field of the
last node.
Take care to the pointers when inserting or deleting a node, especially at the beginning and the end of a linked list.
Do not use new unless you really want to add a new node to the linked list, and remember to use dispose whenever you want to remove a node from the linked list.
Using a header node I An empty list is no longer a nil pointer (i
nstead, a dummy node always exists at the front)
dummy node nil
L
Using a header node II The header node may point to the end
of the list (for convenient access to the front and the rear of a queue).
1st node 2nd node 3rd node nil
rear
L
front
Using a header node III The header node may be used to keep
the number of nodes within a list (however, the header must be updated during insertion and deletion).
3 1st node 2nd node 3rd node nil
L
Using a header node IV The header node may store the title for
the rest of the information stored in the list.
EngineParts E5 K2 C9 nil
L
Using a header node V The header node may indicate the
‘current’ node during list traversal (to facilitate searching, insertion, deletion or resume processing).
1st node 2nd node 3rd node nil
current
L
Circular linked lists A circular linked list is formed when the
pointer of the last node points to the first node. It overcomes the need to traverse the list from the first node to the last node every time when we need to process every node within the list.
1st node 2nd node 3rd node 4th node
L
Doubly linked lists To facilitate list traversal at the expense of
extra storage for links, an extra pointer can be added to each node to point to the previous node of the list. front 1st node
nil
2nd node 3rd node 4th node
nil
rear
prevnext
Circular Doubly Linked Lists
front rear1st node 2nd node 3rd node 4th node
Circular Doubly Linked Lists with header node
L
1st node 2nd node 3rd node 4th node
header node
Actually, we can have any form of linked lists, depending on the needs of the problem we are going to solve.
Stacks A stack is a data structure for storing
items which are to be accessed in last-in-first-out (LIFO) order.
Stack
Push: insert an item at the top.
Pop: delete an item from the top.
top We can think of a stack as a pile of trays at a fast food restaurant. After each tray is cleaned, it is put on the top of the existing pile one by one. Normally, the tray on the top is taken to be used first. Thus the last one in is the first one out.
Stack Applications Perhaps the most common use of stacks is to store subroutin
e arguments and return addresses. This is usually supported at the machine code level either directly by ‘jump to subroutine’ and ‘return from subroutine’ instructions or by auto-increment and auto-decrement addressing modes, or both.
A stack can also be used to evaluate an infix expression
Most common operations CreateStack(S) — to create a new (empty) stack S, Push(x, S) — to push a new item x onto the top of the stack S, an
d Pop(S) — to pop the top item off the stack S (and returns the pop
ped item).
StackTop(S) — returns the top item of the stack S (without popping),
IsEmptyStack(S) — returns whether the stack S is empty, IsFullStack(S) — returns whether the stack S is full.
Array implementation - CreateStack We may use an array to implement a stack, the first entry bei
ng the bottom. We need one variable (sometimes called the stack pointer) to keep track of the top position.
type
Stack = record
item: array[1..max] of ItemType;
top: 0..max
end;
procedure CreateStack(var S: Stack);
{ Initialises the stack S }
begin
S.top := 0;
end;
Array implementation – IsEmptyStack and IsFullStack
function IsEmptyStack(S: Stack): boolean;
{ Checks whether the stack S is empty }
begin
IsEmptyStack := (S.top = 0)
end;
function IsFullStack(S: Stack): boolean;
{ Checks whether the stack S is full }
begin
IsFullStack := (S.top = max)
end;
Array implementation - Pushprocedure Push(x: ItemType; var S: Stack);
{ Adds one item x at the top of the stack S }
begin
if IsFullStack(S) then
writeln('Cannot push: Stack is full!')
else
with S do begin
top := top + 1;
item[top] := x
end
end;
Array implementation - Popfunction Pop(var S: Stack): ItemType;
{ Returns the top item of the stack S and removes it }
begin
if IsEmptyStack(S) then
writeln('Cannot pop: Stack is empty!')
else
with S do begin
Pop := item[top];
top := top - 1
end
end;
Array implementation - StackTopfunction StackTop(S: Stack): ItemType;
{ Returns the top item of the stack S without popping the stack }
if IsEmptyStack(S) then
writeln('Stack is empty!')
else
StackTop := S.item[S.top]
end;
Linked list implementation One of the advantages of using a linked list to implement a st
ack is that storage is allocated only when necessary. The stack can grow or shrink. The maximum capacity of the stack is only limited by the amount of memory.
type
NodePtr = ^node;
node = record
item: ItemType;
next: NodePtr
end;
Stack = NodePtr;
Linked list implementation – CreateStack and IsEmptyStack
procedure CreateStack(var S: Stack);
{ Initialises the stack S }
begin
S := nil
end;
function IsEmptyStack(S: Stack): boolean;
{ Checks whether the stack S is empty }
begin
IsEmptyStack := (S = nil)
end;
Linked list implementation – Pushprocedure Push(x: ItemType; var S: Stack);
{ Adds one item x at the top of the stack S }
var
temp: NodePtr;
begin
new(temp);
temp^.item := x;
temp^.next := S;
S := temp
end;
Linked list implementation – Popfunction Pop(var S: Stack): ItemType;{ Returns the top item of the stack S and removes it }var temp: NodePtr;begin if IsEmptyStack(S) then writeln('Cannot pop: Stack is empty!') else begin Pop := S^.item; temp := S; S := S^.next; dispose(temp) endend;
Linked list implementation – StackTop
function StackTop(S: Stack): ItemType;
{ Returns the top item of the stack S without popping the stack }
begin
if IsEmptyStack(S) then
writeln('Stack is empty!')
else
StackTop := S^.item;
end;
Application of stacks –Infix, prefix and postfix notations An arithmetic expression may be written in differe
nt forms. Most programming languages (including Pascal) us
e infix notation. With infix notation, the operators are placed between their operands (e.g., 1 + 2, a * b).
The operators have their own precedence. If low-precedence operations are to be evaluated first, parentheses () are required.
Application of stacks –Infix, prefix and postfix notations
Some programming languages (such as LISP) use prefix notation. With prefix notation, the operators precedes their operands (e.g., + 1 2, * a b).
Application of stacks –Infix, prefix and postfix notations
Some other programming languages (such as FORTH) use postfix notation (also called Reverse Polish Notation). With postfix notation, the operators are preceded by their operands (e.g., 1 2 +, a b *).
With prefix and postfix notations, there is no precedence associated with operators. The order of evaluation solely depends on the order of the operators placed.
Postfix notation is well suited for stack-based computer architectures.
var x, x1, x2: token; S: Stack;begin CreateStack(S); x := NextToken(e); while x <> EndOfExpression do begin if x in OpSet then begin x2 := Pop(S); x1 := Pop(S); Push(x1 op(x) x2, S) end else Push(x, S) end; EvaulatePostfix := Pop(s)end;
Evaluating an expression in postfix notation using a stack
•e be an expression in postfix notation,•OpSet be the set of all operators (e.g., +, -, *, /),•NextToken(e) be a function which returns the next operand or operator to be processed, and•op(x) be the operation related to the operator x.
•e be an expression in postfix notation,•OpSet be the set of all operators (e.g., +, -, *, /),•NextToken(e) be a function which returns the next operand or operator to be processed, and•op(x) be the operation related to the operator x.
Postfix notation example
Token being processed Contents of stack at Line # Bottom Top
2 3 5 + 9 7 - * + 2 2 3 5 + 9 7 - * + 2 3 2 3 5 + 9 7 - * + 2 3 5 2 3 5 + 9 7 - * + 2 8 2 3 5 + 9 7 - * + 2 8 9 2 3 5 + 9 7 - * + 2 8 9 7 2 3 5 + 9 7 - * + 2 8 2 2 3 5 + 9 7 - * + 2 16 2 3 5 + 9 7 - * + 18
Take the following postfix expression as an example:
2 3 5 + 9 7 - * +
Hence, the result of the expression is 18.
Converting an infix expression to a postfix expression (1)
function Postfix(e: InfixExpression): PostfixExpression;
var
x, y: token; S: Stack; P: PostfixExpression;
begin
CreateStack(S);
InitializePostfixExpression(P);
Push(DummyOperator, S);
x := NextToken(e);
…
Converting an infix expression to a postfix expression (2)
while x <> EndOfExpression do begin if IsOperand(x) then ConcatenatePostfixExpression(P, x) else if x = ')' then repeat y := Pop(S); ConcatenatePostfixExpression(P, y) until y = '(' else begin while InStackPriority(StackTop(S)) >= InComingPriority(x) do begin y := Pop(S); ConcatenatePostfixExpression(P, y) end; Push(x, S) end; x := NextToken(e) end; …
Converting an infix expression to a postfix expression (3)
while not IsEmptyStack(S) do
ConcatenatePostfixExpression(P, Pop(S));
Postfix := P
end;
Assumption
In-stack priority (InStackPriority) and in-coming priority (InComingPriority) are shown below:
Operator In-stack priority In-coming priority *, / 2 2 +, - 1 1 ( 0 3 Dummy -1 -
Queues
A queue is a data structure for storing items which are to be accessed in first-in- first-out (FIFO) order.
front Queue rear
Dequeue: remove an item from the front.
Enqueue: insert an item at the rear.
Queue Application The operating system of a computer
maintains a queue for each resource to be accessed by multiple demands.
E.g. print queue, a queue of processes to ‘use’ the CPU, interrupt handler, etc.
Most common operationsCreateQueue(S) — to create a new (empty) queue Q,Enqueue(x, Q) — to insert a new item x at the rear of the queu
e Q, andDequeue(Q) — to remove the first item at the front of the queu
e Q (and returns the removed item).
QueueFront(Q) — returns the first item of the queue Q,QueueRear(Q) — returns the last item of the queue Q,IsEmptyQueue(Q) — returns whether the queue Q is empty,IsFullQueue(Q) — returns whether the queue Q is full.
Array implementation - not a very good
one We may use an array to implement a queue, the first entry being the front and the last entry being the rear. We need a variable to keep track of the rear position.
type Queue = record item: array[1..max] of ItemType; rear: 0..max end; procedure CreateQueue(var Q: Queue);{ Initialises the queue Q }begin Q.rear := 0;end;
Array implementation – IsEmptyQueue and IsFullQueue
function IsEmptyQueue(Q: Queue): boolean;
{ Checks whether the queue Q is empty }
begin
IsEmptyQueue := (Q.rear = 0)
end;
function IsFullQueue(Q: Queue): boolean;
{ Checks whether the queue Q is full }
begin
IsFullQueue := (Q.rear = max)
end;
Array implementation – Enqueue
procedure Enqueue(x: ItemType; var Q: Queue);
{ Adds one item x at the rear of the queue Q }
begin
if IsFullQueue(Q) then
writeln('Cannot enqueue: Queue is full!')
else
with Q do begin
rear := rear + 1;
item[rear] := x
end
end;
Array implementation – Dequeue
function Dequeue(var Q: Queue): ItemType;{ Returns the item at the front of the queue Q and removes it }var i: 0..max;begin if IsEmptyQueue (Q) then writeln('Cannot dequeue: Queue is empty!') else with Q do begin Dequeue := item[1]; for i := 2 to rear do item[i - 1] := item[i]; rear := rear - 1 endend;
Array implementation – QueueFront and QueueRear
function QueueFront(Q: Queue): ItemType;{ Returns the item at the front the queue Q }begin if IsEmptyQueue(Q) then writeln('Queue is empty!') else QueueFront := Q.item[1]end; function QueueRear(Q: Queue): ItemType;{ Returns the item at the rear the queue Q }begin if IsEmptyQueue(Q) then writeln('Queue is empty!') else QueueFront := Q.item[Q.rear]end;
Modified array implementation In order to be time efficient, shifting should be avoided. We can use one more variable to keep track of the front position.
Type
Queue = record
item: array[1..max] of ItemType;
front, rear: 0..max
end;
Modified array implementation -CreateQueue
procedure CreateQueue(var Q: Queue);
{ Initialises the queue Q }
begin
Q.front := 1;
Q.rear := 0;
end;
Modified array implementation – IsEmptyQueue and IsFullQueue
function IsEmptyQueue(Q: Queue): boolean;
{ Checks whether the queue Q is empty }
begin
IsEmptyQueue := (Q.rear=0) and (Q.front=1)
end;
function IsFullQueue(Q: Queue): boolean;
{ Checks whether the queue Q is full }
begin
IsFullQueue := ((Q.rear + 1 = Q.front) and (Q.rear <> 0)) or ((Q.front = 1) and (Q.rear = max))
end;
Modified array implementation – Enqueue
procedure Enqueue(x: ItemType; var Q: Queue);{ Adds one item x at the rear of the queue Q }begin if IsFullQueue(Q) then writeln('Cannot enqueue: Queue is full!') else with Q do begin if rear = max then rear := 1 else
rear := rear + 1; item[rear] := x; endend;
Modified array implementation – Dequeue
function Dequeue(var Q: Queue): ItemType;{ Returns the item at the front of the queue Q and removes it }var i: 0..max;begin if IsEmptyQueue (Q) then writeln('Cannot dequeue: Queue is empty!') else with Q do begin Dequeue := item[front]; if front = rear then begin front := 1; rear := 0; end else if front = max then front := 1 else front := front + 1; endend;
Linked list implementation type
NodePtr = ^node;
node = record
item: ItemType;
next: NodePtr
end;
Queue = record
front, rear: NodePtr
end;
Linked list implementation - CreateQueue
procedure CreateQueue(var Q: Queue);
{ Initialises the queue Q }
begin
Q.front := nil;
Q.rear := nil;
end;
Linked list implementation - IsEmptyQueue
function IsEmptyQueue(Q: Queue): boolean;
{ Checks whether the queue Q is empty }
begin
IsEmptyQueue := (Q.front = nil) and (Q.rear = nil)
end;
Linked list implementation - Enqueueprocedure Enqueue(x: ItemType; var Q: Queue);{ Adds one item x at the rear of the queue Q }var temp: NodePtr;begin new(temp); temp^.item := x; temp^.next := nil; if IsEmptyQueue(Q) then Q.front := temp else Q.rear^.next := temp Q.rear := temp end;
Linked list implementation - Dequeuefunction Dequeue(var Q: Queue): ItemType;{Returns the item at the front of the queue Q and removes it}var temp: NodePtr;begin if IsEmptyQueue(Q) then writeln('Queue is empty!') else begin Dequeue := Q.front^.item; temp := Q.front; Q.front := temp^.next; if Q.front = Q.rear then Q.rear := nil; dispose(temp) end;end.;
Linked list implementation - QueueFront
function QueueFront(Q: Queue): ItemType;
{ Returns the item at the front the queue Q }
begin
if IsEmptyQueue(Q) then
writeln('Queue is empty!')
else
QueueFront := Q.front^.item
end;
Linked list implementation - QueueRearfunction QueueRear(Q: Queue): ItemType;
{ Returns the item at the rear the queue Q }
begin
if IsEmptyQueue(Q) then
writeln('Queue is empty!')
else
QueueRear := Q.rear^.item
end;
TreesA tree is a data structure accessed beginning at the root node. Each node is either a leaf or an interior node. An interior node has one or more child nodes and is called the parent of its child nodes. A
B C D
F G
E
H
I J K
Trees A tree with no nodes is called a null tree. A tree which is not null has one and only one root. T
he root has no parent. Each of the other nodes has one parent only.
Each node in a tree can have any number of children. Those nodes which have no children are called leaves.
The nodes which have the same parent are called siblings.
Each child of any node can be considered as the root of the corresponding subtree.
Trees The degree of a node is the number of children (or subtrees) it has
got. If {Ni, Ni + 1, …, Nj} is a sequence of nodes in a tree such that
Nk is the parent of Nk + 1 for i k < j, the sequence is called the path from Ni to Nj.
Let p be the number of nodes in the path Ni to Nj. The length between Ni and Nj is (p – 1).
The depth of a leaf is the length between the root and that leaf.
The height of a tree is the maximum depth among the leaves. The ancestors of a node are those nodes (except itself) which appe
ar in the path from the root to itself. Suppose that a node has m ancestors. The level of that node is (m
+ 1).
Tree ExerciseA tree with n nodes has edges. Its height is . The root is . The leaves are .The path between E and K is .The path between C and K is .The ancestors of K are . The level of H is . The children of A are . The parent of H is .The siblings of C are .
A
B C D
F G
E
H
I J K
N-13AB, F, G, D, I, J, KE, H, KNot existA, E, H3B, C, D, EEB, D, E
Application of trees implementing directory structures in file
systems, search trees in artificial intelligence, syntax trees in programming languages,
etc.
Implementation of trees It is obvious to use pointers to implement a tree. we can define two pointer fields in a tree node: one for the first child (the leftmost one) and the other for the right sibling:
data item
first child
right sibling
Implementation of treesThus, the tree shown on the left can be implemented as
follows: nilA
nilB C
nilD
nilE
nilF
nil nilG
nilH
nil
Jnil
I
nil nil
K
A
B C D
F G
E
H
I J K
Binary trees A binary tree is a tree in which each node has at most two chil
dren (or subtrees). We often identify the children of a node as left child and right child.
A full binary tree has no nodes with only one child, i.e. each node has either no children or two children.
A perfect binary tree is a full binary tree in which all leaves have the same depth. A perfect binary tree of height h has 2h + 1 – 1 nodes, of which 2h are leaves.
Perfect binary treeFull binary tree
Pointer implementation of binary trees type
TreeNode = ^Node;
Node = record
item: ItemType;
LChild, RChild: NodePtr
end;
var
T: TreeNode;
Here LChild and RChild are pointers to the left child (or left subtree) and right child (or right subtree) respectively. T can be considered as a pointer to a binary tree.
There is no definite way to construct a binary tree.
data itemleft child right child
Binary tree traversals Inorder traversal
1. Traverse the left subtree of the root by inorder traversal2. Visit the root3. Traverse the right subtree of the root by inorder traversal
Preorder traversal 1. Visit the root2. Traverse the left subtree of the root by inorder traversal3. Traverse the right subtree of the root by inorder traversal
Postorder traversal 1. Traverse the left subtree of the root by inorder traversal2. Traverse the right subtree of the root by inorder traversal3. Visit the root
Binary tree traversals Exercise
Inorder traversal:
Preorder traversal:
Postorder traversal:
A
B C
D EF
G
B, F, A, D, C, G, E
A, B, F, C, D, E, G
F, B, D, G, E, C, A
Inorder traversal
procedure Inorder(T: TreeNode);
begin
if T <> nil then begin
Inorder(T^.LChild);
write(T^.item);
Inorder(T^.RChild)
end
end;
Preorder traversal
procedure Preorder(T: TreeNode);
begin
if T <> nil then begin
write(T^.item);
Inorder(T^.LChild);
Inorder(T^.RChild)
end
end;
Postorder traversal
procedure Postorder(T: TreeNode);
begin
if T <> nil then begin
Inorder(T^.LChild);
Inorder(T^.RChild);
write(T^.item)
end
end;
Array implementation of binary trees We can also use an array to implement a binary tree. Since there at most (2h + 1 – 1) nodes in a binary tree with height h (as in a perfect binary tree), the minimum number of entries in the array used for implementing the binary tree should be 2h + 1 – 1.
declaration:
type
BinTree = array[1..max] of ItemType;
var
T: BinTree;
Rules for storing the contents of the tree nodes
The root is stored at T[1]. If a node is stored at T[i], then its left child is stored at T[2 * i] and its right child is stored at T[2 * i + 1].
The unused entries must be cleared (or assigned appropriate values to indicate that those entries are not used).
Questions Given a node T[j] which is not the root. Where
is its parent stored?
Show how an array can be used to implement the binary tree
T[j div 2]
j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
T[j] A B C F D E G
Array implementation of Binary Trees Declaration
const
max = 15;
NULL = ' ';
type
ItemType = char;
BinTree = array[1..max] of ItemType;
var
T: BinTree;
i: integer;
Inorder traversal
procedure Inorder(T: BinTree; RootPos: integer);
begin
if (RootPos <= max) and (T[RootPos] <> NULL) then
begin
Inorder(T, RootPos * 2);
write(T[RootPos]);
Inorder(T, RootPos * 2 + 1)
end
end;
Preorder traversal
procedure Preorder(T: BinTree; RootPos: integer);
begin
if (RootPos <= max) and (T[RootPos] <> NULL) then
begin
write(T[RootPos]);
Preorder(T, RootPos * 2);
Preorder(T, RootPos * 2 + 1)
end
end;
Postorder traversal
procedure Postorder(T: BinTree; RootPos: integer);
begin
if (RootPos <= max) and (T[RootPos] <> NULL) then
begin
Postorder(T, RootPos * 2);
Postorder(T, RootPos * 2 + 1);
write(T[RootPos])
end
end;
Array implementation of Binary Trees main program
begin for i := 1 to max do T[i] := NULL; T[1] := 'A'; T[2] := 'B'; T[3] := 'C'; T[5] := 'F'; T[6] := 'D'; T[7] := 'E'; T[14] := 'G'; for i := 1 to max do write(T[i]); writeln; write('Inorder traversal: '); Inorder(T, 1); writeln; write('Preorder traversal: '); Preorder(T, 1); writeln; write('Postorder traversal: '); Postorder(T, 1); writeln;end.
A
B C
D EF
G
Binary Search Tree (BST) Trees are most often used to organize related data items to
facilitate an efficient search for and retrieval of an item.
F
C I
G KD
J
A binary search tree has the property that for any node
all data values less than that node’s data value are in its left subtree
all data values greater than that node’s data value are in its right subtree.
For this reason, searching a binary search is a relatively efficient process.
BST OperationsCreateTree(var Tree) — creates an empty BST
SizeOfTree(Tree) — returns the number of elements currently in the BST
TreeSearch(Tree,Target,var Success) — Searches a tree to find the Target
TreeInsert(var Tree,El,var Success) — Inserts item El into the BSTand set Success to True. If there is already an element with the same data field value as El, Success is set to False, and no insertion is performed
TreeDelete(var Tree,Target,var Success) — Deletes the element whose data value is Target and sets Success to True. If Target is not located, sets Success to False.
DisplayTree(Tree) — Display the tree elements in sequential order by data value.
BST implementation - declaration
type
NodePtr = ^TreeNode;
TreeNode = record
item: TreeElement;
Left, Right: NodePtr
end;
SearchTree = record
Root : NodePtr;
NumItems : integer;
end;
var
Tree: SearchTree;
BST implementation - CreateTreeProcedure CreateTree(var Tree:SearchTree);
begin
Tree.Root := nil;
Tree.NumItems := 0;
end;
2
BST implementation – TreeInsert example
Assume the input sequence is
4, 1, 7, 5, 3, 2, 6
6
3
1
5
7
4
BST implementation - TreeInsertProcedure DoInsert(var Parent:NodePtr;El :TreeElement;var Success:Boolean);begin if Parent = nil then begin new(Parent); Parent^.Item := El; Parent^.Left := nil; Parent^.Right := nil; Success := True end else if El = Parent^.item then Success := False else if El < Parent^.item then DoInsert(Parent^.left, El, Success) else DoInsert(Parent^.right, El, Success)end;
Procedure TreeInsert(var Tree:SearchTree;El :TreeElement;var Success:Boolean);begin DoInsert(Tree.root, El, Success); if Success then Tree.NumItems := Tree.NumItems + 1end;
BST implementation - TreeSearchProcedure DoSearch(Parent:NodePtr; Target :TreeElement;
var Success:Boolean);begin if Parent = nil then Success := False else if Target = Parent^.item then Success := True else if Target < Parent^.item then DoSearch(Parent^.left, Target, Success) else DoSearch(Parent^.right, Target, Success)end;
Procedure TreeSearch(Tree:SearchTree; Target :TreeElement; var Success:Boolean);
begin DoSearch(Tree.root, Target, Success)end;
BST implementation – DisplayTreeProcedure Display(Root:NodePtr);
begin
if Root <> nil then begin
Traverse(Root^.Left);
writeln(Root^.Item);
Traverse(Root^.Right)
end
end;
Procedure DisplayTree(Tree:SearchTree);
begin
Traverse(Tree.root)
end;