You are on page 1of 258

Machine Translated by Google

TOMSK STATE UNIVERSITY


INSTITUTE OF APPLIED MATHEMATICS
AND COMPUTER SCIENCES

Yu. L. Kostyuk

LECTURES ON THE BASICS


PROGRAMMING
Tutorial

publishing house

2019
Machine Translated by Google

UDC 510.5
BBC 22.18.73

Kostyuk Yu. L. Lectures on the basics of programming: a textbook. -


Tomsk: Publishing House 2018. - 258 p., ill.

ISBN

The textbook corresponds to the program of the initial course in programming for
university specialties, focused on training specialists in the field of informatics and
computer technology. The book outlines testing methods, labor input studies, and proofs
of properties of algorithms. Simple algorithms from the most important classes are given
and studied: calculation of recurrent sequences; sorting and searching; recursive
calculations, set and graph algorithms, and simple linear algebra algorithms. Particular
attention is paid to the analysis of the effectiveness of algorithms. In the first part of the
lectures, algorithms and programs are written in Pascal, and in the second part of the
lectures, in C. The main elements of these languages are also briefly described. The
manual does not aim to study the whole variety of algorithms; it is only the first step
towards a more thorough and detailed study of them.

For students of relevant specialties, as well as specialists and teachers of computer


science who want to start a systematic study of programming
niya.

UDC 510.5
BBC 22.18.73

Reviewers:
Head of the Department of Automated Control Systems, Tomsk State
University of Control Systems and Radioelectronics, Doctor of Technical
Sciences, Professor A. M. Korikov;
Professor of the Department of Programming, Tomsk State
University, Doctor of Technical Sciences A. Yu. Matrosova

ISBN © Yu. L. Kostyuk, 2019


Machine Translated by Google

Foreword

The most important thing in the profession of a programmer is the ability to create
good programs. Everything else (knowledge of a programming language, translator,
operating system, ability to work quickly at a computer) is necessary only insofar as it
helps to write good algorithms and thereby develop good programs.

Unfortunately, most programming textbooks pay little attention to algorithmization


- the science of algorithms. This book not only discusses and investigates the most
important classes of simple algorithms, but also sets out methods for testing and
proving properties
programs. The book consists of lectures given to first-year students of the Institute of
Applied Mathematics and Computer Science of TSU. In parallel with the lectures,
practical exercises on
writing and executing computer programs.
For the successful development of the initial university programming course,
preliminary knowledge of mathematics and computer science in the volume of high
school is required. It is highly desirable not just to be familiar with any programming
language, but to be able to write
to write programs in this language for solving simple problems and to have the skills to
debug such programs on a computer. The book uses the Pascal and C languages, or
rather their basic subsets, which are sufficient for writing most algorithms. They include
the following basic elements
you languages like:
1) integer, real character and logical data types;
2) one- and two-dimensional arrays, character strings, list structs
tours;
3) assignments, expressions, arithmetic and logical operations;
4) conditional statements if and case (switch), comparison operations;
5) while and for loops ;
6) procedures and functions, their description, call, parameter substitution;
7) standard procedures and functions.
In the first part of the lectures, the programs are written in Pascal, and in the
second part of the lectures, in C. The elements are also briefly described.
Machine Translated by Google

four
Foreword

languages that are further used in programs. At the same time, object-oriented language
tools are not considered at all, which are necessary only when creating large and complex
programs, and which only interfere at the initial stage of learning programming.

When it is required to take into account the features of a particular variant of the
Pascal language, the algorithms are presented based on Turbo Pascal® translators.
or Delphi® by Borland, or freeware translators Free Pascal or Lazarus. Unlike the Pascal
language, the C language (and also C++) is more strictly standardized, so any of the
available translators can be used to implement the programs in question on a computer.

Each lecture is equipped with control questions and tasks for independent work. The
tasks are recommended to the reader to implement on a computer, develop tests for
them and conduct testing.
Feedback, comments and suggestions on the book, please send to the following
address:
634050, Tomsk, Lenin Ave., 36, TSU, Institute of Applied Mathematics
ki and computer science.
E-mail: kostyukÿyÿl@sibmail.com
Machine Translated by Google

Lecture 1
Algorithms and programs. Testing.
Analytical Verification
1.1. Algorithms and programs
A person encounters algorithms everywhere in his daily life: any somewhat
complex action that can be divided into sequentially performed stages is an algorithm.
In everyday life, an intuitive understanding of the algorithm is quite enough: a person,
guided by common sense and his experience, clarifies the details along the way and,
in the end, gets the intended result.

In mathematics, a more rigorous definition of an algorithm is required. The


concept of an algorithm began to take shape since the time of Euclid (about 300
BC), but it was only in the 1930s that the mathematical theory of algorithms appeared.
According to this theory, an algorithm is understood as a set of rules that determine
the procedure for solving any problem from a certain set of problems. This implies
that there is some executor who performs the actions of the algorithm for a specific
version of the problem in accordance with the rules specified in the algorithm.
Algorithm
together with the performer can be represented as a device, at the input of which
to which some input data is supplied, and at the output, in the process of executing
the algorithm, output data is formed, see Fig. 1.1.

Input Algorithm and Weekends


data executor data

Rice. 1.1

The concept of an algorithm is associated with its main


properties: 1) mass character - the ability of an algorithm to solve any problem due to
given set of tasks;
2) finality - the ability of the algorithm to stop after
obtaining a solution to the problem;
Machine Translated by Google

6 Lecture 1

3) the presence of an internal structure, understood as a set of separate


rules (actions) and the order of their implementation;
4) the existence of an executor who understands all the individual actions
in the algorithm and implements them in the order prescribed by the structure
of the algorithm.
The property of mass character means that any variants of the input data
belonging to a certain set can come to the input of the algorithm, and for any
variant the algorithm is able to obtain the result corresponding to them - the
variant of the output data. It is precisely because of the mass property that
once created an algorithm remains necessary as long as there is a need to
solve this class of problems for various input data.

If the executor of the algorithm is a person, then the algorithm may not be
specified very strictly and even not quite completely: a person can invent what
is missing in his description. If the executor is an automatic device, such as a
computer, then the algorithm must be determined absolutely precisely - the
machine cannot guess what the person meant when writing the algorithm if
he omitted something. Algorythms can be defined and written in many ways,
in particular using natural (Russian or English) language. However, it is difficult
to make such a record unambiguous for a human and almost impossible for a
computer. Therefore, to write algorithms, artificial programming languages
have been invented in which all actions are defined absolutely completely and
unambiguously.

Since the computer understands only its own internal (machine) language,
the algorithm must be written in machine language (then its
called a machine program). Creation of a machine program -
complex and painstaking work, since machine language instructions perform
simple actions and are encoded by numbers. Therefore, to implement even a
simple algorithm, a large computer program is required. The invention of
programming languages in the late 1950s finally freed programmers from
digging into machine instructions and led to a real revolution in programming.
Modern programming languages combine rigor and absolute unambiguity with
human comprehensibility. Their practical application became possible
only when special translator programs were created that translate algorithms,
write
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 7

sleigh in a programming language, into machine programs. It is clear that the


translator program itself must be written in machine language. When using a
programming language, the computer implements the execution of the
algorithm in two stages. First, the computer, executing the translator program,
translates the algorithm written in the programming language into a machine
program. Such a translator program is also called a compiler, and the translation
process is called translation or compilation.
At the second stage, the computer executes the translated machine program;
during its execution, input data is received at the input of the program, and output
data is formed at the output (the result of calculations according to the algorithm).
A machine program can be executed multiple times for different inputs without
retranslation.
The text of the algorithm in the programming language is called the source
module, and the translated program in machine language is called the executable
module. The entire process of translation and execution of the program is shown
in Fig. 1.2.

Input data

Source Performing Output


module
Translator module

A computer

Rice. 1.2

An algorithm in a programming language is called a program if it is written in


such a way that a computer using a compiler can translate it into a machine
program. The program must contain data descriptions, standard headers, and
other required elements, without which translation is impossible. At the same
time, it is easier for a person to understand the algorithm without such secondary
elements.
Machine Translated by Google

eight
Lecture 1

(which are easy to extend), so the programs in the text of this book are
written, as a rule, without descriptions.
It is necessary to distinguish between the concepts of an algorithm and a
program. The algorithm can be written in any way and is not fully defined, as
long as a person is able to understand it. The program must be “understood”
by the computer (or translator). It is said that the algorithm is implemented as
some kind of program. Therefore, one algorithm can correspond to several
different programs (even written in the same programming language).

1.2. Testing
The computer, when executing a translator program, acts according to
strictly formal rules: it is not able to understand the programmer's intention
and understand the idea of the algorithm underlying the analyzed program. It
follows from this that the program being translated should not contain any
syntactic error, i.e. the program must strictly comply with the rules of the
programming language. But since the program is written by a person (and it
is human to make mistakes!), Syntax errors are almost always present.
During the translation process, the translator detects such errors and issues
messages about them. The programmer must analyze the translator's
messages and correct errors. It should be noted that the repair process is not
always easy, especially since reported
Translations of a translator refer to that section of the program text in which
a syntax error was encountered, but not to the original cause of the error.
And even if the program was broadcast successfully, this does not mean at
all that it is flawless: it may well contain semantic, i.e. semantic errors.

The computer is an ideal executor of algorithms: what


a modern computer is completed in one second, a person may take several
years. However, in order for a program to be used for its intended purpose, it
must be error-free: when it is executed, for any valid input, it always

should give correct output. Unfortunately, as practice shows, no supergenius


programmer can write a large enough program at once without a single
semantic error.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 9

Thus, to be sure of the correctness of the program, it is necessary to test it


(testing) on various input data. To do this, you need to prepare a test (a variant of the
input data) and
submit them to the input of the executable program. After executing the
program, it is necessary to compare the resulting output data with the expected ones .
output data.

Consider two examples of simple Pascal programs.

Example 1.1. A program for calculating the sum of two numbers.

programex1; var {program name}


a,b,c: integer;{declaration of variables}
begin {program start}
read(a,b); {input of variables a,b}
c := a + b; {addition a,b and assignment c}
writeln('c=',c); {output inscription and result from}
end. {end of program}

In Pascal, when writing a program, reserved service words are used. The first
line is the word "program", then the name of the program. The second line contains
a description of the variables, beginning with the word "var". The type of these
variables is integer (integer), they can have a value from -2147483648 to
+2147483647, they occupy 4 bytes in memory. Curly brackets contain comments
that do not affect
program broadcast. After the word "begin" the program statements are written, they
perform the following actions:
1) the read statement requires entering (from the keyboard) a numerical
value for the variables a and b with a space between them, and at the end -
pressing the "enter" key;
2) an addition operation is applied to the values of the variables a and b , and its
result is assigned to the variable c;
3) the writeln operator displays (in the output window) the inscription 'c=', the
numerical value of the variable c, i.e. the result of the addition, after which it produces
a line feed of the output text.
The word "end" with a dot ends the text of the program.
Input example: 39 -14
output: s=25
End of example.
Machine Translated by Google

ten Lecture 1

Example 1.2. The program for calculating the roots of a quadratic equation
2
aÿx +bÿx+c=0:

program ex2; var {program name}


a,b,c,d,x1,x2: real;{declaration of variables}
begin {program start}
read(a,b,c);
{input variables a,b,c}
d := b*b - 4*a*c; x1 := (-b calculation}
{discriminant
- sqrt(d))/(2*a); {calculation}
x2 := (-b + sqrt(d))/(2*a); {roots x1, x2}
writeln('x1=',x1:7:3,' x2=',x2:7:3);
{output label and output output}
end. {end of program}

All variables are declared with the real type , and can have a positive or negative
value up to approximately 1038. Unlike the integer type, the real type has an
approximate value, about 10 correct decimal digits.

The program performs the following actions:


1) the read statement requires the input of numerical values for the variables
a, b and c separated by spaces (the variables set the coefficients of the quadratic
equation);
2) the discriminant is calculated, the result is assigned to the variable d
(the asterisk denotes the multiplication operation);
3) two roots of the equation are calculated, their values are assigned to the
variables x1 and x2 , respectively (the slash denotes the division operation, and the
sqrt function is the calculation of the square root);
4) the writeln statement displays the inscription 'x1=', then the numeric value of
the variable x1, the inscription 'x2=', the numeric
each,
value
3 ofofwhich
the variable
are after
x2the
7 characters
decimal
point). Input example: 1 -3.5 -1

output: x1= -0.500 x2= 4.000


End of example.

In the considered examples, only one test is given, although in reality this may
not be enough. So, in example 1.2, the value
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification eleven

variable d may turn out to be negative, and then the calculation of the sqrt
function will crash, the program will not complete and will not display any
result. Therefore, for full-fledged testing, it is necessary to set tests, including
those to detect such situations.

We can hypothetically imagine that we were able to test the program


by executing it as many times for various data as there are in total their
possible variants. Such testing is called exhaustive testing. If in all cases
the program gave the correct result, then it can be hoped that in the future,
for any particular variant of the input data, the program will produce the
correct result.
However, in most cases exhaustive testing is physically impossible.
Consider the program in Example 1.1 (addition of two integers). The range
for each number is from -2147483648 to +2147483647, for a total of 232
. The number of all possible tests, i.e. times .
64
personal pairs of terms is equal to 2 Even if you perform 109 tests per second
(a billion!), It will take more than three thousand years to fully test! If,
however, the program is not tested on all variants of the input data, then
there is no guarantee that the program will produce the correct result for an
arbitrarily chosen variant: the program may make a mistake just for this
(unverified!) variant. Moreover, there are some errors that can go unnoticed
even after exhaustive testing, such as missing an assignment for a variable
whose value is being used. On the first run, such a program may produce
one result, and on the second run, with the same input data, a different
result, since the unassigned value of the variable may have (accidentally!)
a different value on the second run of the program. Based on such
reasoning, E. Dijkstra formulated the law: “Testing can prove the presence
of errors in the program,

but never their absence.


Fortunately, in practice the situation is not so bleak. There is an
engineering discipline called programming technology that provides
guidance on how to design and how to test programs so that they are
reliable (unfortunately, a bug-free program is an unattainable ideal in most
cases). A program can be considered reliable if, when executed, the
probability of obtaining an erroneous result is so small that it can
Machine Translated by Google

12 Lecture 1

neglect. Different practical applications require different


reliability of programs: it is enough to compare the requirements for the
program for drawing illustrations and the requirements for the program for
controlling a nuclear reactor. It is important to learn how to select such tests
so that they can be used to identify (and subsequently correct) as many
errors in the program as possible. Many novice programmers sincerely
believe that tests should demonstrate the correct operation of the program.
This encourages them to use simple tests that run correctly right away. In
fact, the task of testing is that tests should reveal as many errors as
possible! If the test is not able to detect a single possible error, then it
simply
useless. The art of testing is to find as many bugs in a program as possible
with a small number of tests.
Black box testing : when creating tests, the internal structure of the
program is not taken into account. In this method, tests can be created by
a person who does not know how the program is written. It is only necessary
to know what problem the program solves, what kind of data is fed to the
input of the program, in what format, and what kind of data and in what
format are output. White box testing: tests are created based on the
internal structure of the program, so that all components of the program
are executed in different tests.

For more reliable testing, both of these methods should be used. Once
an incorrect result is found in any test, the cause of the error must be found
and corrected. This process is called debugging. Finding an error can be
tricky, because you need to find exactly the action in the program that
caused the error. Step-by -step testing is useful for finding a bug - executing
the program in interactive mode, tracking the intermediate values of
variables. This is possible for tests where the number of steps is not too
large.

1.3. Analytical Verification


To create sufficiently reliable testing programs, it is not enough;
therefore, E. Dijkstroy and a number of other great programmers laid the
foundations for analytical verification of programs, i.e., analy-
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 13

of the theoretical study (proof) of the properties of programs. In this case, the program is
tested not for one specific set of input values, as in normal testing, but for a set of sets of
input values that satisfy some logical relation.

The idea of the proof is as follows. Any program in the course of its execution uses and
changes the values of variables. For such variables, there are two logical conditions called
precondition and postcondition. It is assumed that the precondition must be true before the
program is executed, and the postcondition must be true after the program is executed .

completion.

The proof is in two parts:


1) proof of termination, i.e. proof that for any values of variables at the input of the
program, it will someday necessarily finish its execution;

2) proof of the truth of the postcondition after the completion of the program under the
assumption of the truth of the precondition before the execution of the program.

Back in the 60s of the twentieth century, it was proved that any algorithm can be written
using only the following actions (operators) in various combinations:

- assignment, arithmetic operations,


- sequencing,
- branching ( if-then-else construct),
- loop (the while- do construct).
To carry out the proof, it is necessary to consider the rules for constructing pre- and
postconditions of the enumerated types of structural elements of programs and programming
language operators.
Assignment. If there is some condition P(x), in the record of which
variable x is included, then
{P(w)} x:=w {P(x)}

where P(w) is the same condition as P(x), with each occurrence of x replaced by the right
side of the assignment (formula) w.
After performing all the actions in the formula w , the result of the calculations
is assigned to the variable x.
Unlike conventional mathematical notation, formulas are written in a line in programs. If
the formula is long, then you can make transfers,
Machine Translated by Google

fourteen
Lecture 1

but without duplication of operation signs. Variable names can consist of letters and/or
numbers, but must always begin with a letter. Constants
can be integer or real, in the second case, their record must contain a dot separating
the integer part from the fractional part. The type of variables is determined by their
description.
Arithmetic operations are denoted by signs (+, -, *, /) or words (div, mod). Unlike
normal mathematical notation, the multiplication sign "*" cannot be omitted. If both
integer arguments are involved in the operations (+, -, *), then the result type will also
be integer. If at least one of the arguments is real, the result will be real. The result of
the division operation "/" is always real. The div operation calculates the quotient of
the division of integers with the remainder discarded, and the mod operation

remainder after division of integers.

Pascal defines a large set of standard mathematical functions. Function


arguments are written after the function name in parentheses, the result of most
functions is real, but there are exceptions. Thus, the result of the function "abs" (absolute
value) is the same as the result of the argument (integer or real).

The order of calculations in the formula is from left to right, but taking into account
the priorities of operations. Parentheses change the order of evaluation: the operations
inside the parentheses are evaluated first.
Sequence of operators. If A and B are operators for which the following
conditions are satisfied (see Fig. 1.3):
{P} A {Q} and {Q} B {R}

A B

Rice. 1.3

then

{P} A; In {R},
i.e. a postcondition for A is a precondition for B.
A sequence can consist of any number of statements. If the sequence is taken
into “operator brackets” begin and end, then it can be considered as one operator.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification fifteen

Branching (conditional statement). First, condition E is checked. If it is


true, then operator A is executed; otherwise, operator B is executed, see Fig.
1.4.

Rice. 1.4

If A and B are operators for which the conditions


{P, E} A {Q} and {P, not E} B {Q}
then

{P} if E then A else B {Q}


If, in addition, B is an empty operator, then:
{P} if E then A {Q}

Loop (with condition). First, condition E is checked. If it is true, then


statement A is executed and a transition to checking condition E is made,
otherwise nothing else is done, see Fig. 1.5.

Rice. 1.5

If A is an operator for which the condition is satisfied


{P, E} A { P }
then

{P} while E do A { P, not E }


Machine Translated by Google

16 Lecture 1

The condition P here does not change during the execution of the loop, it
is called the loop invariant. The loop ends when condition E becomes false, so
if the variables in condition E are not changed in statement A , then the loop
will repeat indefinitely!
The correctness of the considered relations follows from the meaning of
the Pascal language operators. Their application is useful in deriving pre- and
postconditions for the considered operators.
The proof of termination is often obvious if, for example, the program
consists of a sequence of assignments or a for loop. If the variables in the
while loop 's condition change in complex ways, then proving termination can
be tricky. In any case, it is necessary to determine under what condition the
loop will end - this condition will be part of the postcondition.

The proof of the truth of the postcondition after the end of the program, as
a rule, is far from obvious. To carry out such a proof, the following special
methods are used:
1) sequential enumeration of actions to be performed, used for a sequence
of statements in a program, when changes in logical relationships are tracked
in the course of execution of statements;
2) enumeration of variants, used for branching in the program, when the
entire range of values of the variables included in the precondition is divided
into subdomains and the proof is carried out separately for each subdomain;

3) the method of mathematical induction, applied to cycles, this


the method is standard for mathematical proofs;
4) invariant, also applies to cycles when from the precondition and
postconditions, a common, unchanging part in the cycle is allocated;
5) the method of equivalents is used when there are two different programs
with the same pre- and postconditions, and to prove the correctness of the
second program, they prove the correctness of the first and the equivalence of
the first and second programs;
6) abstraction, used in the proof of complex programs, when it is impossible
to carry out the proof at once for the entire program
face.

Let us consider the application of various methods of proof with a few


simple examples.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 17

Example 1.3. Let the precondition x=a, y=b be satisfied for numerical
variables x,y , where a, b are some numerical values. In a condition record, a
comma implies a logical connective "and". It is required to prove that after the
execution of the program:
z:=y; y:=x; x:=z;

the postcondition x=b, y=a will be true.

Proof. The end is obvious. proof of truth


we will carry out the postconditions by sequential enumeration of the actions to be
performed. After the first assignment, the current condition is: x=a, y=b, z=b, after
the second: x=a, y=a, z=b, after the third: x=b, y=a, z=b, which is needed to be
proven.
End of example.
Example 1.4. Let the precondition x=a, y=b be satisfied for numerical
variables x,y , where a, b are some numerical values. It is required to prove that
after the execution of the program
if x<y then
beginz:=y; y:=x; x:=zend;

the postcondition xÿy will be true.


Proof. The end is obvious. We will carry out the proof
listing two options:
1) xÿy; the condition of the if statement will be false, so no further actions in
the program will be performed; from this condition and the precondition follows
the validity of the postcondition;
2) x<y; the condition of the if statement will be true, so assignments will be
performed in the program, as a result of which, as shown in Example 2.1, the
variables x and y will exchange their values, which will lead to the change of the
x<y condition to the opposite, i.e. to xÿy.
End of example.

Proof is a powerful tool for finding errors in a program. Let us assume that
the program (1.3) is written as two assignments: x:=y; y:=x. Then after the first
assignment: x=b, y=b, nothing has changed after the second one: x=b, y=b. The
last condition does not coincide with the required postcondition, i.e. there is an
error in the program!
Machine Translated by Google

eighteen
Lecture 1

The program in Example 1-2, which calculates the roots of a quadratic equation,
has a serious flaw. If the three numbers introduced define the coefficients of an equation
that has no real roots, then its discriminant will be negative. Then, when calculating the
square root of a negative value, an emergency termination of the calculations will occur.

Example 1.5. The program for calculating the roots of a quadratic equation
2
aÿx +bÿx+c=0 with discriminant check.

programex3; var {program name}


a,b,c,d,x1,x2: real;{declaration of variables}
begin {program start}
read(a,b,c);
{input variables a,b,c}
d := b*b - 4*a*c; {check calculation}
{discriminant
if d>=0 then begin that dÿ0}
x1 := (-b - sqrt(d))/(2*a); {calculation}
x2 := (-b + sqrt(d))/(2*a); {roots x1, x2}
writeln('x1=',x1:7:3,' x2=',x2:7:3);
{output label and output output}
end
else writeln('error!'); {if d<0 then error}
end. {end of program}

Proof. The discriminant is calculated according to a formula known from


algebra, after which the condition d>=0 is checked in the if statement . In this
case, two options are possible:
1) the condition d>=0 is true, then the roots are calculated using known formulas
and the result is displayed;
2) otherwise, the writeln statement outputs the text "error!".
White box testing .
Test example 1. (condition d>=0 is false)
Input: 1 -2 3
output: error!
Test example 2. (condition d>=0 is true)
Input: 2 -10 12
output: x1= 2.000 x2= 3.000
End of example.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 19

Method of mathematical induction. The method allows one to prove a


statement depending on the induction parameter, i.e., some integer variable.
The assertion is proved for all parameter values greater than or equal to some
initial value.
The proof by mathematical induction contains three steps.
pa: 1) basis, 2) assumption, 3) inductive conclusion.
At the basis stage, one checks that the assertion P(n) being proved with
respect to the induction parameter n is valid for n = n0.
At the second stage, the assumption is made that the statement P(n) is true
for all values of n not greater than some k, k ÿ n ÿ n0.

Finally, at the stage of inductive inference, we prove that P(n) will be valid
for n = k + 1 > n0. This implies that the statement P(n) is true for any n ÿ n0.

Let us show this for n = M such that M > n0. First we set k = n0. According
to the third stage of the proof, the statement will be true for n = n0 + 1. Applying
the third stage of the proof again for n = n0 + 1, we obtain the validity of the
statement P(n) for n = n0 + 2, etc., until we reach n = M.

The method of mathematical induction is widely used in mathematics to


prove a wide variety of logical statements. Let's look at a few different examples
of its use.
Example 1.6. Let us prove that to calculate the sum of squares of numbers
from 1 to n
S n = 12 + 22 + … + n 2

the formula is valid


2
3 n2 ÿ ÿ 3nn
sn ÿ . (*)
6

Proof.
Basis. For n = 0 the sum Sn = 0, on the other hand, for n = 0 formula (*) is
true.
Assumption. Let formula (*) be true for n ÿ 0.
inductive inference. For n + 1, by formula (*) we obtain
Machine Translated by Google

twenty
Lecture 1

32 3 2
2nnnÿ ÿ 3 2 2( n1)ÿ 3( ÿ 6 n
ÿ ÿ ÿ1) (n
sn ÿ one
ÿ
ÿ ÿ (ÿn one)
one)
.
6
End of example.
Let us illustrate the application of the method of mathematical induction on
the example of a program for calculating factorial.
Example 1.7. Let the precondition nÿ1 be satisfied. It is required to prove
that after executing the program

f:=1; i:=2;
while i<=n do
begin f:=f*i;
i:=i+1
end;

the postcondition will be true:


f=1*2*...*n, i=n+1.

Proof. Termination is obvious, since the variable i is assigned 2 before


the loop is executed, and each time the loop is executed, it is increased by
1. It is easy to prove by the method of mathematical induction that after n-1
executions of the loop, the variable i will be equal to n + 1
and the condition in the loop header will become false. Postcondition validity
also prove by this method.

Basis. If n=1, then f=1, i=2, since the loop will never be executed. Thus, the
basis is valid.
Assumption. We assume that for n=k, kÿ1 ,
condition f=1*2*...*k, i=k+1.
inductive inference. The whole difference between executing the
program for n=k+1 and executing it for n=k is that at first the program will
be executed in exactly the same way as with n=k, and then the loop will be
executed one more time for i=k+1 . Since, by assumption, before the last
execution of the loop f=1*2*...*k, then after the last execution of the loop
(after the execution of the operators f:=f*i; i:=i+1) it will be true:
f=1*2*...*k*(k+1)=1*2*...*n, i=k+2=M+1,
Q.E.D.
End of example.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 21

invariant method. The method of proof using the invariant is as follows. Let the precondition
be written as (P, Q) and the postcondition as (S, Q), i.e., they have a common part Q called the
invariant
volume.

An invariant is a logical condition that is true both before


algorithm, and after its completion.
Proof for the loop:
1. Prove the termination of the cycle.
2. Check that the condition is true before the loop is executed.
3. Check that the condition is true when the loop is executed once
la.

Then, by mathematical induction, it follows that the condition will be


true after the loop ends.
Consider the application of the invariant in the previous example.
Example 1.8. Let the precondition nÿ1 be satisfied. It is required to prove
that after executing the program

f:=1; i:=2;
while i<=n do
begin f:=f*i;
i:=i+1
end;

the postcondition will be true:

f=1*2*...*n, i=n+1.

Proof. Termination is proved in Example 1.7. After performing the assignments in the first line
of the program, the precondition for the loop operator is: n=ÿ1, i=2, f=1. We write the last equality in
the form:
f=1*...*(i-1). We write the postcondition as follows: nÿ1, i=n+1, f=1*...*(i-1). The validity of the equality
i=n+1 follows from the fact that after the end of the execution of the loop i>n, but since the variable i
increases by 1 each time the loop is executed, it cannot be greater than

than n+1. The equality f=1*...*(i-1) is an invariant here. Indeed, by directly checking the statements
in the program, we are convinced that the execution of two actions in the loop body changes the
variables f and i, but leaves the invariant valid. End of example.
Machine Translated by Google

22 Lecture 1

The most important thing in proving by the invariant method is the ability to
formulate the condition in the form of an invariant, which requires some skill. At
the same time, the use of an invariant usually simplifies the proof of the program.

Tests for the program in the previous two examples. Black box tests :

- minimum possible n=0;


- 1 more than the minimum possible n, n=1;
- the value of n, somewhat larger, for example, n=5.
White box tests :
- such n that the loop is never executed, n=1;
- such n that the loop is executed 1 time, n=2;
- the value of n, somewhat larger, for example, n=5.
Those. all tests: n=0, n=1, n=2, n=5.
At the output, respectively: f=1, f=1, f=2, f=120.

Method of equivalents. If two programs change all the variables from


the set x1, . . . , xn in the same way, then these two
withprograms
respect toare
this
equivalent
set of variables.

Then the proof for one of the programs with respect to this set of variables is
also valid for the other program. In this case, programs can perform various actions
on other variables that are not included in the set x1, . . . ,
xn.

Example 1.9. Equivalence of loop operators. Let S be an operator that does


not change the variable i. Then, if the expressions a and b do not contain function
calls with side effects and variables that change inside the cycle, then the cycles

for i:=a to b do S
and

i:=a; while i<=b do begin S; i:=i+1 end

according to the rules of the Pascal language:

Precondition for both loops: i=a, a<=b+1.


After the while loop completes: i=b+1.
After the completion of the do loop , according to the rules of the Pascal language, the value
of i is not defined, but in the postcondition it should be considered equal to b + 1.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 23

Similarly, the cycles are equivalent:

for i:=b downto a do S

and

i:=b; while i>=a do begin S; i:=i-1 end

End of example.

The considered example is a special case of the equivalence of the loop operators while and
for. Let us consider a number of other cases of the equivalence
laziness.

Example 1.10. Equivalence of compound statements that differ in the order in which they are
executed. If S1 and S2 are statements that use different variables, then the sequences

S1; S2 and S2; S1

End of example.

Example 1.11. Equivalence of if and case conditional statements. Ek


vivalent operators

case i of a1:S1; a2:S2 else S3 end


and

if i=a1 then S1 else if i=a2 then S2 else S3

End of example.

Arrays in Pascal. An array is a numbered collection of variables of the same type. An array
description specifies the type and bounds for the numbers of its elements. For example, description:

varM: array[1..100]of real;

defines a one-dimensional array of real type variables numbered from 1 to 100. Any of the array
elements can be used in the same way as a simple real type variable, but for this you need to
specify the index - the number of this element, for example:

M[5], M[i], M[i+5].

the value of the index or index expression must be of integer type and be at least 1 and at most
100.
Machine Translated by Google

24 Lecture 1

An array can be two-dimensional or even larger. For example, a description:

var M2: array[1..10,0..49]of integer;

defines a two-dimensional array of integer type variables, numbered from 1 to 10 in


the first dimension and from 0 to 49 in the second dimension. There are 500 elements
in the M2 array in total. Examples of indexing elements of array M2:

M2[5,0], M2[i,j], M2[i+5,j-1].

The considered examples define static arrays, their sizes are given by constants in
the description and cannot be changed during calculations.
by program.

Example 1.12. A program to input an array of 10 elements and output them.


program ex4; {program name}
var i: integer; M:
{declaration of variables}
array[1..10] of integer;
begin {program start}
for i:=1 to 10 do {input of array elements M}
read(M[i]);
for i:=10 downto 1 do {output elements}
write(M[i],' '); writeln; {array M in reverse order}
end. {jump to new output line}
{end of program}

Input example: 1 3 21 4 -9 10 125 33 31 2


output: 2 31 33 125 10 -9 4 21 3 1
End of example.

Example 1.13. Let us prove that the program


min:=A[1];
for i:=2 to n do
if min>A[i] then min:=A[i]

calculates the minimum value in the integer array A among elements from 1st to nth.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 25

Proof. Using the equivalence of for and while loops, we can write a
precondition: i=2 and a postcondition: i=n+1. Then the loop invariant will be as
follows:
min= min{A[1],...,A[i-1]},

where the min function denotes the minimum value from the set of arguments.
Black box testing :

- minimum n=1, array A: A[1]=10;


- n is 1 more than the minimum, n=2, two variants of array A:
1) A[1]=10, A[2]=5; {desc}
2) A[1]=5, A[2]=10; {ascending}
- n is slightly larger, for example, n=4, array A:
1) A[1]=10,A[2]=5,A[3]=1,A[4]=-2; {desc}
2) A[1]=-5,A[2]=0,A[3]=1,A[4]=7; {ascending}
3) A[1]=5,A[2]=5,A[3]=5,A[4]=5; {element equality}
White box testing :
- such n that the loop is never executed, n=1;
- n=2, so that the loop is executed 1 time, two variants of array A:
1) an array A such that the condition min>A[i] is true;
2) an array A such that the condition min>A[i] is false;
- n is slightly larger, for example, n=4, two variants of array A:
1) the min>A[i] condition was always true, decreasing in the array; 2) the
min>A[i] condition was always false, increasing in the array.
Here, the tests for both methods can be almost the same.
End of example.

Example 1.14. Compare the program


min:=A[1]; k:=1;
for i:=2 to n do
if min>A[i] then begin
min:=A[i]; k:=i end

with the program from example 1.13. These programs are equivalent with respect
to the variables min and i. As for the variable k, it is easy to prove that the following
invariant holds for it:

k= argmin{A[1],...,A[i-1]},
Machine Translated by Google

26 Lecture 1

where the function argmin denotes the number of the minimum value among
the arguments written in brackets.
The tests for this example can be the same as for example 1.13.
End of example.

The method of equivalents is useful both for proving programs and for developing
them. An experienced programmer, analyzing a problem, tries, first of all, to adapt
the algorithms and programs known to him for solving it, making the necessary
changes to them. If he succeeds, then the new program will be equivalent to the old
program with respect to some variables. This method speeds up the development of
the new program itself, simplifies its proof, and increases its reliability (because the
old program has been tested and investigated before!). But for this, the programmer
must know not only a large number of typical programs, but also the methods of their
proof.

abstraction method. It is used to prove complex programs. To do this, individual


parts of the entire program are presented as
separate programs with their own inputs and outputs, and for them are given
their pre- and post-conditions. Each such separate program is proved independently
(by abstracting) from other parts. In turn, the entire program as a whole is proved
independently of its separate parts, under the assumption that all these separate
parts are correct.

Example 1.15. Calculation of all prime numbers in the range from 2 to n:


for i:=2 to n do
beginj:=2; p:=0;
while (j<i)and(p=0) do
if i mod j = 0 then
p:=1
else j:=j+1;
if p=0 then writeln(i)
end;

The outer loop iterates over all numbers between 2 and n, while the inner loop
checks each number i to see if it is evenly divisible by any of the numbers less than
i.
Proof for outer loop.
Precondition for outer loop: i=2.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 27

Postcondition: i=n+1.
Invariant: “all prime numbers from 2 to i-1 have been computed.
Proof for the inner loop.
Precondition for inner loop: j=2,p=0.
Postcondition: j=n,p=0, then the number i is prime,
or: j<n,p=1, then the number i is not prime.
Invariant: "number i is not divisible by numbers from 2 to j-1
and: (the number is not divisible by j,p=0)
or: (number is divisible by j,p=1)”.
End of example.

The abstraction method is also used in the development of complex programs.


In this case, it is called the staged development method or
top-down method and is implemented as follows: - first, a
general program is created (at the first level), inside which
a swarm of individual operators are second-level programs;
- then programs of the second level are created, inside which a separate
nye operators are programs of the third level, and so on.
The greatest positive effect is achieved if previously created programs can be
used as individual operators. For convenience, they can be arranged in the form of
procedures or functions, as standard programs. At the same time, such standard
programs often have to be modified, adjusted to specific conditions.

Thus, to successfully develop complex programs, a programmer must:

1) know and understand a large number of standard programs;


2) be able to modify standard programs;
3) be able to assemble from standard programs, as from "bricks", more
complex programs.

In practice, analytical verification does not replace, but complements computer


testing. There are a number of reasons why one cannot confine oneself to an
analytical proof.
First, it is very difficult to take into account all the limitations associated with a
particular representation of data (especially integers and real numbers) in a computer.
Machine Translated by Google

28 Lecture 1

Secondly, when executing a real program on a computer, it is difficult to


take into account in advance all the nuances of the interaction of the program
with standard programs and the operating system.
Thirdly, it is possible to formulate such postconditions that do not
completely determine the properties of the program.
And finally, there can be errors in the proof!
Therefore, only a combination of testing and analytical proof can ensure
that the program is sufficiently reliable. In fact, the greatest practical benefit
of a proof is that it forces the programmer to write programs that are clear and
easy to understand, which in principle will have fewer bugs than writing
programs the way it works.

In addition, when testing a program using the white box method, it is very
effective to insert into it operators for checking pre- and postconditions,
invariants. Such operators, when a condition is violated, should display a text
message that will help you find the error. Subsequently, during the operation
of the finished program, these operators can be commented out.

1.4. Labor intensity analysis


Any program for its execution requires two main resources - space and
time. Space is measured by the amount of memory
ty for input and output data, variables within the program, as well as the
memory needed to accommodate the program itself. The memory for input
and output data is determined by the conditions of the problem. The memory
for a program is determined by its length and translator and does not depend
on the size of the data. It is usually much smaller than the data memory if the
data is placed in arrays. At the same time, the volume
The amount of memory for accommodating internal variables in a program
may differ for different programs solving the same problem. A program that
requires less memory is more memory efficient .
The most important resource is time. In most cases, saving time is more
important than saving memory. The running time of a program is determined
by the number of actions performed in it and their complexity, as well as the
time it takes to perform individual elementary actions on a given computer.
However, when analyzing programs, it is only important to know how many times
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 29

one program will run faster than another given the same input on the same computer.
Therefore, for each program, it is necessary to calculate the complexity , which is a
function of the dependence of the number of elementary actions on the size of the
input data, for example, on the size of the array n. In this case, it is sufficient to
estimate the labor intensity at
n ÿÿ.

It often happens that the number of elementary actions in a program depends


not only on n, but also on the concrete values of the elements and on their mutual
arrangement. Then the labor input is estimated at the worst, when the real labor input
is no more than the labor input at the worst. Sometimes labor input is estimated as
the best or labor input as average. Thus, the program in Example 1-13, which
calculates the minimum value among n
elements of the array, has the complexity:

T(n) = A n + B,
since the number of loop executions is directly proportional to n, and the number of
actions inside the loop does not exceed some constant A. As n ÿÿ, B can be neglected.
The real (physical) execution time of the program depends not only on the speed of
the computer, but also on what programming language the program is written,
on what translator it is translated. Therefore, the form of the labor intensity function is
written up to a constant - a factor in front of the function, using the O-large notation,
and then one speaks of the order of growth of the labor intensity function. Therefore,
for the complexity of the program from Example 1.13, instead of T(n) = A n + B , it
suffices to write O(n), here the order of complexity is linear.

Example 1.16. Calculation of the sum S of elements of a square matrix D


size n x n:
S:=0;
for i:=1 to n do
for j:=1 to n do
S:=S+D[i,j];
2
Here the complexity T(n) = A n + B n + C, since the number of
loops over i – n times, and for each execution of the loop over j , the
Machine Translated by Google

thirty Lecture 1

2
n times , i.e. total - n once. As n ÿÿ, the quantities B and C can be
2
brecht. The order of complexity is O(n ) is quadratic.
End of example.

Example 1.17. The complexity of the program from example 1.15 (calculating all prime
numbers in the range from 2 to n) in the worst:
2
T(n) = A n + B n + C,

since the total number of executions of the inner loop does not exceed
amounts:
2
1+2+3+ . . . + (n ÿ 2) = (n ÿ 1) (n ÿ 2) / 2 ÿ n /2.
2
Thus, the order of complexity is O(n ) is quadratic.
End of example.

The program from Example 1.15 can be noticeably sped up if, in the inner loop, we
check the divisibility of the number i not by all numbers less than i, but only by numbers less
than or equal to i , since if the number i is divisible
by j, then it is also divisible by i/j.

Example 1.18. Faster program for calculating all prime numbers in the range from 2 to
n:

for i:=2 to n do
beginj:=2; p:=0; x=sqrt(i);
while (j<=x)and(p=0) do
if i mod j = 0 then p:=1

else j:=j+1;
if p=0 then writeln(i)
end;

The complexity of the program in the worst:


3/2
T(n) = A n + B n + C,

since the total number of executions of the inner loop does not exceed:

1 ÿ 2 ÿ 3 ÿ...ÿ n ÿ nÿ n .

Thus, the order of complexity is O(n 3/2).


End of example.
Machine Translated by Google

Algorithms and programs. Testing. Analytical Verification 31

When comparing two algorithms (or programs that implement them) with
different speed (or required memory) functions, the best algorithm should be
considered the one whose function grows more slowly. ) is quadratic
2
For example, O(n) is linear complexity, better than O(n
laboriousness. So, with an increase in n by 10 times, the running time of an
algorithm with linear complexity will increase by 10 times, an algorithm with complexity
O(n 3/2) - 31 times, and the algorithm with quadratic complexity - 100 times!
If the algorithms have the same order of complexity , then the algorithm is
better, in which the factor A in front of the complexity function is less.

Questions and tasks


1. What properties should an algorithm have? What is the meaning of these properties?
2. What properties does an algorithm have if it has no input data?
3. Why is a special programming language needed?
4. What is a syntax error and what is a semantic error in a program? Like
when do they show up?
5. What is the purpose of testing?
6. Why is it impossible, as a rule, exhaustive testing?
7. Give an example of an algorithm for which exhaustive testing is possible.
8. Why can't testing prove the absence of errors in the program?
9. Write a program that inputs numbers a and b and calculates x according to the equation:
aÿx + b = 0. Give examples of black and white box tests for it. When will the program fail
to run?
10. The program raises the integer n to the integer power m and returns the result, which is
assigned to a variable of type integer. Design black box tests. What will be the result for
n = 8 and m = 40?
11. How to step through debugging using any Pascal translator?
12. Write a program that enters three real numbers, checks if there can be a triangle with
sides equal to these numbers. If not, then it displays a message about this and asks to
enter the data again. If it can, it calculates and prints the area of the triangle. Develop
black box and white box tests, test the program on the combined

fear.

13. How to select white box tests for a sequence of statements,


conditional statement and loop?
14. Prove by mathematical induction that 1 + 2 + 15. Prove by … + n = n (n + 1)/
mathematical induction that 1 + 3 + … 2 . + (2n - 1) =2 n.
Machine Translated by Google

32 Lecture 1

16. What is the proof of program properties? What does it mean to prove the termination of a
program?
17. What are precondition and postcondition, how to set them?
18. What is the structure of programs whose properties are proved using a sequential enumeration
of the actions to be performed?
19. What is the structure of the programs, when proving the properties of which we use
Is there a list of options?
20. What is the structure of programs whose properties are proved using the method of
mathematical induction?
21. What is the structure of programs whose properties are proved using the invariant method?
22. What is the structure of programs whose properties are proved using the abstraction
method?

23. What is the proof by the method of equivalents?


24. Write a program that calculates the maximum value from three input numbers. Develop black
and white box tests for it. Prove right
vigor of the program.
25. Write a non-array program that inputs n, then n integers, and prints out the two largest inputs.
Develop black and white box tests for it. Prove the correctness of the program. What is the
complexity of the program?

26. An integer two-dimensional array of n rows and m columns is given. Write a program to
calculate the sums of its elements in all columns. Prove its correctness and derive the
complexity.
27. The program from example 1.18 can be further accelerated if, in the inner loop, the divisibility
of the number i is checked not in a row by all numbers that are not greater than ÿi, but only by
previously calculated prime numbers not greater than ÿi. Derive the labor input formula, taking
into account that for large n the number of prime numbers not greater than n tends to n/ln n.
Machine Translated by Google

Lecture 2
Recurrent Algorithms

2.1. Recurrent sequences and limits

Definition. A numerical sequence {xk} is called recurrent of rank p if

ÿ xa kk
ÿ
, k ÿ
0,1, ..., p ÿ

one,

ÿ (*)
kk ÿ ( ,
xfkxÿ
ÿ

one
,x k ÿ

2 ,..., xkp _
ÿ ), kppÿ ÿ , one, ...

where a0, a1, …, ap – 1 are constants and f is a function.


The definition of a recurrent sequence is itself an algorithm that simply needs to
be "rewritten" in Pascal. This algorithm consists of assigning initial values to the
elements x0, x1, …, xp – 1 and looping the calculation of subsequent elements xk .
The proof of the correctness of the algorithm is based on the method of mathematical
induction or invariant. Researching the efficiency of the algorithm is also easy. If in
what follows it is necessary to use all elements of the sequence {x0, . . ., xn}, then
they must be stored in an array of size n + 1. If not all elements are needed, but only
p elements, then it is sufficient to use an array of size p. The complexity is determined
by the number of executions of operators in a cycle and is linear if the capacity of
calculating the function f is a constant.

Example 2.1. The sum of any elements {a1, . . ., an} can be represented as a
recurrent sequence of rank 1:
ÿS 0,
ÿ

0
ÿ
ÿ
SS ai ÿ ÿ ii 1 ÿ

i,
ÿ
12, ..., . n

The amount is calculated by the program:

S:=0;
for i:=1 to n do
S:=S + a(i);
Here a(i) is the formula for calculating the i-th element of the sum.
End of example.
Machine Translated by Google

34 Lecture 2

In some cases, a recurrent sequence may have a limit as n ÿ ÿ. Example 2.2. Infinite
sums (series) are given by the relations:

ÿSf ,one
ÿ
one

ÿ
ÿ SS fk
ÿ ÿ kk 1 ÿ

k , ÿ
2, 3, ...

in which the terms f k tend to zero as k ÿ ÿ. In this case, the terms themselves can also be
specified as a recurrent sequence:

ÿ faone
ÿ
,
ÿ
ÿ fpkfk
k ( , ), k
ÿ
ÿ

one
ÿ
2, 3, ...

Calculation of the limit of such a sequence with a given accuracy


eps can be done with the following program:
S:=a; f:=a; k:=2;
while abs(f)>=eps do begin

f:= p(k, f);


S:=S+f;
k:=k+1
end

Here the invariant is as follows: f = f k-1, S = Sk-1.


The complexity of the program, i.e. the number k of cycle executions is
determined from the formula | f k, |ÿwhere
ÿ ÿ is the specified accuracy of calculation by
sequences.
End of example.

Example 2.3. The approximate value of the sin x function can be calculated
using the sum
35 2k1 ÿ

xx ÿ ÿ ÿ ÿ k x
sin xxÿ
ÿ

,
one

...( one)
3! 5! (2 k1)!
ÿ

for which the recurrence relations in Example 2.2 are valid. By dividing the expression for
the kth term of the sum:
k
x 2k1 ÿ

fk
ÿ

one
ÿÿ
( one)
(2k1)!
ÿ

to the expression for the (k–1)th term of the sum:


Machine Translated by Google

Recurrent Algorithms 35

2 k3 ÿ

k 2
ÿ x
fk ÿ
ÿÿ
( one) ,
(2k3)!
one

we obtain a recurrence relation for the elements of the sum:


ÿ fx
one
ÿ
,
x2
ÿ

ÿ
ffk ÿÿ
k , k ÿ
2, 3, ...
(2kk
ÿ

one

ÿ 1)(2 2)
ÿ ÿ

The program for calculating the sum with a given accuracy eps:
S:=x; f:=x; k:=2;
while abs(f)>=eps do begin

f:=-f*x*x/((2*k-1)*(2*k-2));
S:=S+f;
k:=k+1
end

The termination of the program follows from the fact that fk ÿ 0 as k ÿ ÿ. Here the
invariant is the same as in example 2.2:

f = fk -1, S = Sk-1,
The complexity of the algorithm (the number k of cycle executions) can be
determine from the formula:
x 2k1
ÿ

fk ÿ
ÿÿ ,
(2k1)! ÿ

where ÿ is the specified accuracy.

End of example.

For a sum with alternating terms, as in Example 2.3, the calculated value of the
sum will differ from the true value of the limit by no more than the absolute value of
the first discarded term. If all the terms have the same sign, then to determine how
many terms need to be summed, one has to derive the formula of the so-called "residual
term of the sum." More

Moreover, a sum containing terms of the same sign may not even have a limit (i.e. be
infinite). Here are some more examples of recurrent sequences,

having a limit.
Machine Translated by Google

36 Lecture 2

Example 2.4. Consider the recurrent sequence for calculating the square root of a
given number, which was already known to Heron:

ÿ s0 ÿ
one,

ÿ a ÿ
ÿ

ÿ
sk ÿ
one

sk ÿ , k 1, 2, ...
sk ÿÿ ÿ
ÿ ÿ

2
ÿ

one

ÿ
ÿ

ÿ ÿ ÿ

one

Let's prove that lim sk a ÿ


for a ÿ 0.

Proof. Let zheniya. Then sÿaÿe


kk , where ek is the error of the kth approx.

one
ÿ a ÿ one ek2
a ae .
ÿ

sk ÿ ÿ
ae kÿ ÿ ÿ
ÿÿÿ one

ÿÿ
k
2 2
ÿ

aeÿ aeÿ
one

ÿ ÿ

ÿ k ÿ

one
ÿ k ÿ

one

Let's calculate e1:


2
one a ÿ

one)

1 (1
2ÿ ÿ ÿ ÿ ÿa ÿ
es ÿ one aÿ a ÿ 0,
2
and then
2
one ek ÿ

one
one

ek ÿÿ
ÿ 0, ek ÿÿek ÿ

one , k ÿ
2, ...
2 ae ÿ k ÿ

one
2

The resulting inequalities assert that, starting from e1, all ek are nonnegative and each
subsequent error is less than the previous one by at least a factor of 2. That is, e1/e2 ÿ 2,
k-1
e1/e3 ÿ 4, …, e1/ek ÿ 2 , where
one ÿ a
k log 2
.
2ÿ
Therefore, lim ÿ e
0k . Moreover, starting from s1 , all sk are decreasing.
k ÿÿ

Calculating the limit of a sequence starting from s1 , with a given


eps precision is written as a program:

s:=(1+a)/2; e:=eps;
while e>=eps do begin
s1:=(s+a/s)/2;
e:=s-s1; s:=s1
end
Machine Translated by Google

Recurrent Algorithms 37

The complexity of the program, i.e. the number of iterations of the loop, has
order O(log (1+a)/ÿ).
End of example.
Example 2.5. Let a continuous function y = f (x) be given on the interval [a, b] ,
and the values of the function at the ends of the interval f (a) and f (b) have different
signs. Then, on this interval, the function has at least one root x0 such that f (x0) = 0.
The problem is to calculate the root of the function with a given accuracy ÿ / 2 i.e. it is
required to find such z that ,
the following inequality holds:

| x0 – z| ÿ ÿ / 2 .
The dichotomy method for solving this problem is that each
At the next step, the interval is divided in half and one of the half intervals where the
root can be located is determined. One can construct the following recursive
sequence of pairs of numbers {ui , vi}:
ÿ uavb
ÿ ÿ
0 , 0 ,
ÿ

ÿ u
1iÿ
ÿ
UV
ii ÿ,
1 ÿ ÿ (uv ii 2, for sign ( ) sign ( ( fuv fu )/ i ÿ i
ÿ i )/ 2),

u ÿ ÿ ( uv ii v iÿ v i when sign ( ) sign (fu( fuv ÿ


ÿ
ÿ ÿ
ÿ i1 ÿ )/ 2, one , i i i )/ 2),

which converges to the desired root. The program calculates its limit x with the given
precision eps/2:

u:=a; v:=b; x:=(u+v)/2;


while (vu)>=eps do begin if
f(u)*f(x)<=0 then v:=x

else u:=x;
x:=(u+v)/2
end

The complexity of the program, i.e. the number of cycle executions is determined
by the formula:
ÿ ba
ÿ

ÿ
k ÿ
log ÿ .
2 ÿ

ÿ
ÿ ÿ

End of example.
Machine Translated by Google

38 Lecture 2

2.2. Other recurrent algorithms

Example 2.6. A polynomial in x of degree n can be represented as Horner's


formula:
n n
... axa (...(an x an )xÿ ...
ÿ

one

Pn (x) axax
ÿ
n ÿ n ÿ

one
ÿÿÿÿ10 ÿ

one
ÿÿÿ a )xa
one 0 ,

where an, an–1, …, a1, a0 are the coefficients of the polynomial.

From this formula, one can obtain the following recurrence relation for computing
a polynomial for a given value of x and coefficient
elements a0, a1, …, an :

ÿ Pa0 ÿ
n ,
ÿ
PP xa ÿi
ÿ i
ÿ
ÿ

one niÿ , i ÿ
12, ..., . n

In turn, calculations by the recurrence relation can be


execute the program:

P:=a[n];
for i:=1 to n do
P:=P*x+a[ni];

in which the coefficients of the polynomial are given as elements of the array a.
End of example.

The polynomial formula in Example 2.6 can be interpreted as a representation


nonnegative integer P(x) in a number system with integer base x. Then the program
in example 2.6 allows you to calculate the value of a number given the base x and a
set of digits in the record of the number, given as elements of the array a.

Example 2.7. From the formula in Example 2.6, you can get another recur
an annuity relation for calculating the digits a0, a1, …, an of a nonnegative integer
Pn for a given value of the base x:
ÿ ai P ÿ

ni
ÿ
mod x ,
ÿ
ÿPP xi div , 0,1, ..., ,n P 0,
ÿ ÿ
ÿ
ni 1 ÿÿ

ni
ÿ

ni ÿ

where the mod operation is the calculation of the remainder of the division, and div
is the division with the remainder discarded. This assumes that the number of digits
Machine Translated by Google

Recurrent Algorithms 39

n in the representation of the number Pn is not known in advance. Calculations


by the recurrence relation can be performed by the program:
i:=0;
while P>0 do
begin
a[i]:=P mod x;
P:=P div x;
i:=i+1
end;
n:=i-1;

After the loop is completed, the most recently calculated non-zero


element of the array is a[i-1] (the highest digit of the number), the variable n
contains the number of this element in array a.
End of example.
Example 2.8. The Fibonacci numbers are given by the following
recurrent sequence of rank 2:
ÿ 0,0 f ÿ

one
ÿ
one,

ÿ
ÿ fffkÿ ÿ kk 1 2, 3, ...,
ÿ
ÿ

k ÿ, 2

the nth element of which is calculated by the following program:


f1:=0; f2:=1;
for k:=2 to n do begin
f3:=f2+f1;

f1:=f2; f2:=f3
end

Let us prove that the De Moivre formula is valid for this recurrent sequence:

ÿÿ n nÿ
one
ÿ
1ÿ 5ÿ ÿÿ 5ÿ one

.
ÿ

f n
ÿ ÿ

ÿÿ
ÿ ÿ ÿ

(*)
5 ÿ ÿ
2 2 ÿ ÿ

ÿ
ÿÿ

ÿ ÿ ÿ ÿ ÿÿ

Proof.
Basis. For n = 0 and n = 1, we check by a simple substitution into the
formula (*).
Assumption. Let formula (*) be true for n ÿ 0.
Machine Translated by Google

40 Lecture 2

Conclusion. For n + 1, we obtain by the formulas: ÿ ÿ ÿ 1 ÿ

n n n n
ÿÿÿÿÿ5
ÿ ÿ

one one

ÿÿ ÿ ÿÿ
one ÿ ÿ ÿÿ ÿÿ ÿÿ ÿ5 ÿ1 ÿÿ ÿÿ 5ÿ ÿÿ ÿÿ 5 ÿ ÿÿ ÿ one
ÿ
ÿ1ÿ
ÿ ÿ ÿÿ ÿÿ ÿÿ 5ÿ 1ÿ ÿ ÿ ÿ ÿ ÿ 5 ÿ ÿÿ ÿ

f n1
ÿ
ÿ ÿÿÿÿ ÿ

2 2 ÿ 5ÿ ÿÿ ÿÿ ÿÿ
ÿÿÿÿ 2 2
ÿÿÿ ÿÿÿ

_ ÿ1n _ ÿ1n

one 1ÿ 5 one 5 ÿ
ÿ
,
2 2
ÿÿÿ

given that:
2
ÿÿÿ one 5 ÿ 1 5
ÿÿ ÿ1ÿÿ .
2 2
ÿÿÿ

Number ÿ ÿ lim ( ff k k ÿ1 fork/ )ÿ (1


ÿ 5) / 2 1.61803398875,
ÿ included in the
ÿÿ

mula (*), is called the golden ratio. Note that it is expedient to apply this
formula for sufficiently large n. In this case, calculations must be performed on
real ones, i.e. approximate numbers, and round the result to the nearest
integer. Since the absolute value of the second term in the formula (*) is less
than 0.5, it can be neglected. End of example. Example 2.9. The Euclidean
algorithm that calculates the greatest common divisor gcd(a, b) of two
integers a ÿ 0, b ÿ 0 is based on the following invariant relations:

1) GCD(a, 0) = a;
2) gcd(a, b) = gcd(b, a);
3) gcd(a, b) = gcd(a mod b, b), a ÿ b > 0.
The first two relations are obvious. Let's prove the third. First, note that
the operation r = a mod b is equivalent to repeatedly subtracting b from a until
0 ÿ r < b. Therefore, it suffices to prove that

gcd(a, b) = gcd(a – b, b), a ÿ b > 0.


Let the opposite statement be true, namely: gcd(a, b) =
d, gcd(a – b, b) = c, and c ÿ d.
Machine Translated by Google

Recurrent Algorithms 41

abÿ

a b a-b b
But then ÿÿ
, wherein , as well as - both are intact, while
c c c c c
a
me like can be integer only if c = d. It's a contradiction
c
and proves the third relation.
From these relations, one can construct such a sequence of pairs
{xi , yi} such that gcd(xi , yi) = gcd(a, b), max(xi , yi) > max(xi + 1, yi + 1):

ÿ xayb
0
ÿ
, 0
ÿ
,
ÿ

ÿ xi1ÿ
ÿ
xyymod
iii 1 , ÿ
ÿ
y , for i xyii ÿ ÿ 0,
ÿ

ÿ yyxx
1ÿ
ÿ
iiii mod , ÿ one
ÿ
x , for i yxii ÿ ÿ 0.

The program calculates the sequence {xi , yi} as long as its


you can continue (as long as both x and y are positive):

x:=a; y:=b;
while (x>0)and(y>0) do
if x>=y then x:=x mod y
else y:=y mod x;
if x>0 then no:=x
elsenod:=y

The complexity of the program, i.e. the number of executions of the loop is determined by
the numbers a and b. In this case, the worst case will be if for you
fulfillment of the conditions:
fk ÿ max(a, b) > fk – 1,
where fk , fk – 1 are the numbers of the Fibonacci sequence, when the
operation fk mod fk – 1 is equivalent to the operation fk – fk – 1, and, in addition,
Livo:

max(a, b) = fk , min(a, b) = fk – 1 .
Then k (number of iterations of the loop) will be the maximum possible. Neglecting the
second term in De Moivre's formula and taking the logarithm
left and right sides, we get:

k ÿ flog
ÿ log
ÿ2 log
( 5k()5 ) ÿ
ÿ

2 fk ,

k ÿ ab ÿ ÿ, ab
max( ) log
2
2 log ( 5 max( , )) 1.42 log
ÿ

,
ÿ

where ÿ ÿ (1ÿ 5)/ 2 .


Machine Translated by Google

42 Lecture 2

Thus, the complexity (in the worst case) of Euclid's algorithm has
the order is O(log max(a, b)).
End of example.
Example 2.10. A program to quickly raise a number x to a positive integer power
p. The result is the number z:

z:=1; s:=p; y:=x;


while s>0 do
begin if odd(s)
then begin
s:=s-1; z:=z*y
end;
s:=sdiv 2; y:=y*y
end;

The odd function returns "true" if its argument is an odd integer, and "false"
otherwise. For greater efficiency, this function can be replaced by an expression
consisting of
bitwise operation and on the binary representation of integers and comparing the
result with the number 1:
(s and 1)=1.
The operation s div 2 can be replaced by a bitwise shift operation
th representation of s to the right by 1 bit:
s:=s shr 1;

The termination of the program follows from the fact that before the
execution of the cycle sÿ1, and at each execution of the cycle s decreases (but
remains non-negative, i.e. s=0 after the end of the cycle).
Loop precondition: z=1, s=p, y=x; postcondition: s=0.
s = x p
Invariant: z*y . The correctness of the invariant follows from
wearing:

s s-1 s/2
z*y = (z*y)*y = z*(y*y) ,

and from pre- and postcondition relations.


It follows from the correctness of the invariant and postcondition that after
cycle z = x p.

Let's count the number of executions of the loop, taking into account that
one or two multiplications z*y and y*y are performed at each execution. At every
Machine Translated by Google

Recurrent Algorithms 43

executing the loop, the number s is reduced by at least 2 times. So if initially


executed:
2k ÿ s = p < 2 k+1
,
then after k steps s=1, and after k+1 steps the loop will end. Thus, the total
number of multiplications will not exceed 2(1 + ÿlog2 pÿ), and, on the whole,
the order of complexity is O(log p).
So, when raised to a power of 1000, the loop will be executed 11 times, and the number
The number of multiplications will be less than 22.

End of example.

Questions and tasks

1. Prove the correctness of the program that calculates the sum of the expansion of the function
sin x, by the invariant method.
2. Approximate value of the cos x function:
24xx _ 2n
n x
cos 1 ÿ ÿ ÿ x ... ÿ ÿ ( one) .
2! four!
(2)!n
Write down the recursive relation for the elements of the sum and the program for calculating cos
x for a given value of x with a given accuracy ÿ.
3. Write a program that enters a value for x and a given precision ÿ and you

calculates x using Heron's formula, counting how many iterations will be performed in the loop and
an estimate of the number of iterations in the worst case. Check the operation of the program on
tests for x equal to respectively: 0.000001, 0.0001, 0.01, 100, 10000, 1000000 for different values
of ÿ.
4. Write and debug a program that calculates the square root of a number using the Heron formula
and (for comparison) using the standard sqrt function. Determine (using a counter in a loop) how
many terms of the sequence must be calculated in order to achieve the relative accuracy of
calculating the square root of 10–6 from the numbers 0.0001, 0.01, 0.5, 2, 100, 104
, 106.
5. Write a program that introduces the boundaries of the interval a, b and the given accuracy 2
3 – 7x
ÿ, and calculates the root of the function y = x + 9x – 8. Typing times
personal boundaries, calculate all the roots of this function.
6. Write a program that introduces the boundaries of the interval a, b and the specified accuracy 3
ÿ, and calculates the root of the function y = x – 10 x + 35 x 2 – 40 x + 24
four
.
Using this program, find the roots of the function on the intervals: [0.5, 1.5], [1.5, 2.5], [2.5, 3.5],
[3.5, 4.5].
Machine Translated by Google

44 Lecture 2

7. Write a program that enters the base of the number system, and then an integer written in this
system. After that, it enters the base of the second number system, calculates and displays the
representation of the number in the second number system. Come up with principles for
representing numbers with a base greater than 10. Develop and justify tests for this program, test
it for numbers with bases: 2, 8, 10, 16.

8. Prove the correctness of the program that calculates the Fibonacci numbers using the inva method
riants.
9. Write a program that calculates the nth Fibonacci number from the recurrent sequence and from
the truncated Moivre formula without the second term. Choosing n, find out from which number,
using the truncated Moivre formula, you can calculate the Fibonacci numbers with an accuracy of
0.5. With an accuracy of 0.001?
10. Write a program that calculates the GCD for two input integers. Develop and justify tests for it,
conduct testing and debugging. Provide a counter to calculate the number of loop executions.
For various input numbers, compare the value of the counter with the estimate of the complexity
of the Euclid algorithm.
11. Write a program that enters a number x ( double type), a positive integer
p
p and calculates x by multiplying the number x by itself (p - 1) times, and also accelerated
algorithm. Test the program for various x: 2, 1.1, 1.01, 1.0001, and various p: 10, 100, 1000.
Compare the results (output with a relative accuracy of 15 digits) obtained by these two methods.
Machine Translated by Google

Lecture 3
Search and sort

3.1. Array search

The task of the search is as follows. Let an array A of n elements, numbered from 1 to n ,
and some value p (search) be given. It is required to find a number i such that: A[i]=p.

Search results might look like this:


1) there is a unique element with number i for which A[i]=p;

2) there is not a single element equal to p in the array A , i.e.


A[i]ÿp for any i=1,2,...,n;
3) in array A there are several elements with numbers i1,i2,... such that A[i1]=p, A[i2]=p,...
etc.
Most often, when searching, you want to find any element equal to p.
Example 3.1. Search in an unordered array. To search, the algorithm compares the
elements of the array, starting with the 1st, with the value p until a match is found or until the

all elements:
i:=0; k:=1; while
k<=n do
if A[k]=p then begin
i:=k; k:=n+1 end

else k:=k+1;

The result of the algorithm: if i > 0, then A[i] = p, if i=0, then


there is no element with value p in the array.
Loop invariant: 1) if i=0,
then there are no elements of the array {A[1], ..., A[k-1]}
none equal to p,
2) if i>0, then A[i]=p, k=n+1.
Labor input in the worst: O(n).
End of example.
Machine Translated by Google

46 Lecture 3

Example 3.2. Search in an ordered array. Let the array be ordered in non-
decreasing order:
A[1]ÿ A[2]ÿ ...ÿA[n].
Let's define the region of "suspicious" elements in the array in this way: if an
element with the value p exists in the array, then it exists inside this region. Let us
denote the number of the initial element of the region by the variable b, and the number
of the final element by the variable e. Before the start of the algorithm b=1,e=n.

The search is called dichotomous, because at each step of the algorithm, the area
of suspicious elements is halved. To do this, the number of the element with in the
middle of the area is calculated using the formula:
c=(b+e)div 2.
Two cases are possible:
1) A[c]<p. Then the required element can be among:
A[c+1],...,A[e];
2) A[c]ÿp. The element you are looking for can be among:
A[b],...,A[c].
Note that the area of suspicious elements is halved, even if there were only 2
elements in it.

b:=1; e:=n; while {one}


b<e do {2}
begin {3}
c:=(b+e)div 2; if {four}

A[c]<p then b:=c+1 else e:=c {5}


{6}
end; {7}
if A[b]=p then i:=b else i:=0 {8}

The search continues until the area of suspicious elements


will not decrease to one element. After that, it remains to check whether this single
element matches the value of p. Loop precondition: b=1, e=n.

Postcondition: b=e.
Invariant: the required element (if it exists) is among:
A[b], ...,A[e].
Let us calculate the complexity of the algorithm. Let:
2m - 1 < k ÿ 2 m , (*)
Machine Translated by Google

Search and sort 47

where k is the size of the suspicious area.


First , k = n. For even k , the size of the region is halved,
and for odd - by two with rounding, the inequality (*) is preserved.
After m = log2 n steps, the size of the suspicious area is k = 1.
The total number of comparisons C in the worst case: C = log2 n + 1, taking into
account the comparison after the loop, and in general the order of complexity is O(log n).
In an ordered array, elements equal to the search value are arranged in a row.
The program looks for the number of the initial element of such a group, since in both
cases this element remains in the area.
To find the number of the last element in this group, the program
should be changed in lines 4-6:
c:=(b+e+1)div 2; if {four}

A[c]<=p then b:=c else e:=c-1 {5}


{6}

Note that by changing only lines 5-6, but not changing line 4, we
we get a looping algorithm!
End of example.

Example 3.3. Removing Duplicate Items in Ordered


array. In such an array, elements with the same value are located in a row. From
the original array A , it is required to form such an array C, which contains all the
values from the array A, but without repetitions. The algorithm scans sequentially all
the elements from array A, while copying the first element from each group of
identical ones to array C :

m:=1; C[1]:=A[1]; for i:=2


to n do
if C[m]<A[i] then begin
m:=m+1; C[m]:=A[i]end;

The number of elements copied into array C is counted in the variable m.

Loop invariant: among the elements {C[1],…,C[m]} there are all


which are among {A[1],…,A[i-1]}, but without repetitions.
Obviously, the complexity of the algorithm is O(n).
End of example.
Machine Translated by Google

48 Lecture 3

Example 3.4. In an arbitrary array A of n elements, it is required to determine the beginning


bm and the end em of an ordered (non-decreasing) segment of maximum length. Any ordered
segment begins either with the first element, or with such that the previous element is strictly greater
than it. The end element of the segment is either the last element in the array or the element
following it is strictly less than it. Such a segment may contain at least one element. In the algorithm,
the variable b fixes the beginning of the next viewed segment:

b:=1; bm:=1; em:=1; for i:=1 to


n do
if (i=n)or(A[i]>A[i+1]) then begin {end of next
segment found} if (ib)>(em-bm) then begin

bm:=b; em:=i end;


b:=i+1

end;

Loop invariant: among the elements {A[1],…,A[b]} there is a negation


String of maximum length from element A[bm] to element A[em].
Obviously, the complexity of the algorithm is O(n).
End of example.

3.2. Simple sorting algorithms

The task of sorting (ordering) an array is one of the most


important, so various algorithms have been devised to solve it.
Example 3.5. The simplest sorting algorithm, in which only
one loop and one conditional statement:
i:=1;
while i<n do
if X[i]<=X[i+1] then i:=i+1
else begin
z:=X[i]; X[i]:=X[i+1]; X[i+1]:=z;
i:=1
end;
Machine Translated by Google

Search and sort 49

Loop invariant: {X[1]ÿX[2]ÿ. . . ÿX[i]},


"The set of values in array X remains unchanged."
Since the loop termination condition is i=n, after the loop is completed, the
array will become completely ordered. However, the termination of the algorithm
is not obvious.
Let's define inversion as follows: if X[i]>X[j] for i<j
then there is an inversion for that pair of array elements.
Let us calculate the maximum possible number of inversions for different j:
if j=2, then 1 inversion, if j=3, then 2 inversions,

. . .
if j=n, then n – 1 inversions.
Total inversions: 1 + 2 + + (n . . –. 1) = n (n – 1)/2.
Since when an inversion is detected (the condition X[i]<=X[i+1] is false) in
the algorithm after the exchange of values, the number of inversions decreases
by 1, sooner or later all inversions will disappear. But then, while the loop is
running, the variable i will increase at each step and, in the end, will become
equal to n, after which the loop will end.
An inversion for some i can be detected if:
X[1]ÿX[2]ÿ ... ÿX[i], X[i]>X[i+1]. (*)

Therefore, to eliminate one inversion, it will take i cycle steps to get from the
1st element of the array to the i-th one. In the worst case, when condition (*) is
satisfied, the following is also true: X[1]>X[i+1]. Then, for the condition to be
fulfilled:
X[1]ÿX[2]ÿ ... ÿX[i+1], (**)

the loop must be executed R(i) times:

R(i) = 1 + 2 + . . . + i = i (i + 1)/2.

Then, to eliminate all n (n - 1)/2 inversions, the loop must be executed T(i) times:

3
T(i) = R(1) + R(2) + . . . + R(n – 1) ÿ n /6.
3
Thus, the complexity in the worst case is of the order O(n ).
Machine Translated by Google

fifty Lecture 3

Note that the complexity is at its best when the array is already completely
ordered, has order O(n). End of
example.

Example 3.6. Improvement of the simplest sorting algorithm:


i:=1;
while i<n do
if X[i]<=X[i+1] then i:=i+1
else begin
z:=X[i]; X[i]:=X[i+1];X[i+1]:=z;
if i>1 then i:=i-1;{instead of: i:=1}
end;

In this case, the cycle invariant remains the same, but in the worst
case, the elimination of each inversion will require exactly two cycle steps,
and there will be n (n – 1) steps in total. Thus, labor intensity at its worst
2
will have order O(n ).
After changing the algorithm, the worst-case complexity decreased by
about n/6 times. Thus, an array of 300 elements will be processed 50 times
faster, and an array of 30,000 elements will be processed 5,000 times faster!
End of example.
Example 3.7. Exchange sorting algorithm. It can be considered
further improvement of the simplest sorting algorithm:
for i:=1 to n-1 do
beginj:=i; while
(j>0)and(X[j]>X[j+1]) do
begin
z:= X[j+1]; X[j+1]:=X[j]; X[j]:=z;
j:=j-1end;

end;

Since it uses two nested loops, to prove it, you should use the abstraction
method, analyzing the inner and outer loops separately.

Inner loop invariant:


{X[1] ÿ X[2] ÿ . . . ÿX[j]},
Machine Translated by Google

Search and sort 51

{X[j+1] ÿ X[j+2] ÿ . . . ÿX[i]},


"The set of values in array X remains unchanged"
Outer loop invariant:
{X[1] ÿ X[2] ÿ . . . ÿX[i]}.
The complexity of the algorithm. The total number of executions of
the inner loop in the worst case:
1+2+ . . . + (n - 1) = n (n - 1)/2,
2
Those. complexity in the worst O(n ).
If the array is already ordered, then the inner loop will never be
executed, and then the complexity (in the best!) will be on the order of O(n).
End of example.
Example 3.8. Insertion sort algorithm. This algorithm, in turn, is an improvement
on the exchange sort algorithm. In it, instead of three assignments, only one is used
in the inner loop. In this case, the elements of the array are shifted to the right, making
room for the inserted element:

for i:=1 to n-1 do


beginj:=i; z:= X[i+1];
while (j>0)and(X[j]>z) do
beginX[j+1]:=X[j]; j:=j-1end;
X[j+1]:=z;
end;

Proof: abstraction method.


Inner loop invariant:
{X[1] ÿ X[2] ÿ z = . . . ÿX[j]},
X[i+1], {z ÿ X[j+2] ÿ "The set of . . . ÿX[i]},
values in array X and z remain unchanged"
Outer loop invariant:
{X[1] ÿ X[2] ÿ ÿ X[i]}. . . .
The complexity in the worst case is the same as in the previous algorithm,
2
O(n ). In this case, the insertion sort algorithm is faster than
due to fewer actions in the inner loop. Those. for him
2
multiplier before n less.

The complexity in the best case is of the order O(n).


End of example.
Machine Translated by Google

52 Lecture 3

Example 3.9. Selection sort algorithm. In the algorithm, in the inner loop,
the minimal element is selected from the elements in the range from the jth
to the nth, after which it is exchanged with the jth element.
{precondition: j=1} for
j:=1 to n-1 do begin k:=j;
{precondition: i=j+1, k=j}
for i:=j+1 to n do
if A[k]>A[i] then k:=i;
{postcondition: i=n+1}
{invariant: k = argmin(A[j],...,A[i-1])}
z:=A[j]; A[j]:=A[k]; A[k]:=z
end;
{postcondition: j=n}

Outer loop invariant:

{A[1]ÿA[2]ÿ,...ÿA[j-1]},A[j-1]ÿmin{A[j],...,A[n]}

Postcondition: A[1]ÿA[2]ÿ,...ÿA[n].
Labor intensity. Total number of executions of the inner loop:
1+2+ . . . + (n - 1) = n (n - 1)/2.
2
Those. complexity O(n ) for both the worst and the best.
End of example.

Example 3.10. Bubble sort algorithm:


for i:=n downto 2 do
for j:=1 to i-1 do
if X[j]>X[j+1] then
begin
z:=X[j]; X[j]:=X[j+1];
X[j+1]:=z
end;

After the next execution of the inner loop, the largest among the first i elements
will be in the i-th place in the array. The total number of executions of the inner loop
2
is n (n - 1)/2, Labor intensity: O(n ).
End of example.
Machine Translated by Google

Search and sort 53

indirect ordering. It is set by an additional array, while the elements of the


ordered array are not rearranged, but remain in their places. For indirect ordering of
the array A , such an additional array In of n elements is specified, called the index
array,
what is being done:

A[In[1]]ÿ A[In[2]]ÿ ... ÿA[In[n]].

The element In[i] means that if the array A is sorted, then the element A[In[i]]
will be located at the i-th place.
Modification of the sorting algorithm for indirect ordering:
1) first, the elements of the array In must be assigned initial values:
In[1]=1, In[2]=2, . . ., In[n]=n;

2) everywhere in the program where the array element A[j] is used in the opera
comparison radio, replace A[j] with A[In[j]];
3) everywhere in the program where the array element A[j] is used in
the assignment, replace A[j] with In[j].
Any sorting algorithm can be used for modification.

Example 3.11. Modification of the simplest sorting algorithm for


indirect ordering of array A:

for i:=1 to n In[i]:=i;


i:=1;
while i<n do
if A[In[i]]<=A[In[i+1]]
then i:=i+1
else begin
z:=In[i]; In[i]:= In[i+1]; In[i+1]:=z;
i:=1
end;

Comparisons are made using the In array, exchanged


also the elements of the array In.

End of example.
Example 3.12. Modification of dichotomous search in indirect mention
row array A:
Machine Translated by Google

54 Lecture 3

b:=1; e:=n;
while b<e do
begin
c:=(b+e)div 2; if
A[In[c]]<p then b:=c+1 else e:=c

end;
if A[In[b]]=p then i:=b else i:=0
End of example.

3.3. Dynamic arrays and random numbers

Arrays in a program can be static when the index boundaries in the


array description are given by constants. At the same time, at the same time
memory is allocated to accommodate array elements.
In the description of variables - dynamic arrays, the boundaries of indices
are not specified, and no memory is allocated to allocate array elements.
Memory allocation for dynamic arrays is performed during program execution at the
moment of calling the special procedure SetLength.

Example 3.13. Description of variables A, B - one-dimensional dynamic


arrays and variable C - a two-dimensional dynamic array:

var A, B: array of integer;


C: array of array of integer;

Memory allocation for these arrays:

n:=100;
SetLength(A,n); SetLength(B,n);
SetLength(C,10,15);
The numbering of elements in a dynamic array starts from zero:
A[0],A[1],. . ., A[n-1];
C[0,0],C[0,1],. . .,C[0,14],
. . .
C[9,0],C[9,1],. . .,C[9,14].
Machine Translated by Google

Search and sort 55

After the arrays are no longer used in the program, you can free the memory:

SetLength(A,0);SetLength(B,0);
SetLength(C,0,0);
End of example.
Example 3.14. Dynamic array program:

programex14;
var X: array of integer;
n, i, z: integer;
begin readln(n); SetLength(X,n); for i:=0 do n-1
do read(X[i]);
i:=0;
while i<n-1 do
if X[i]<=X[i+1] then i:=i+1
else begin z:=X[i]; X[i]:=X[i+1];
X[i+1]:=z; i:=1
end;
for i:=0 do n-1 do write(X[i],' ');
writeln; SetLength(X,0); end.

First, the program enters the number n of elements in the array X and
allocates memory for it. After that, in a loop enters the values of the elements
array X, from number zero to n-1 inclusive. Then the array X is sorted by
the simplest algorithm and output in a loop. In the output, a space is printed
after the value of each element to separate one number from another. At
the end, the memory allocated to the array X is freed.
End of example.
Remapping standard input and output. When executing a program, instead of
standard keyboard input and screen output, you can do input from a file and output
to a file. To do this, you need to create an input file and write the input data into it as
if it were entered from the keyboard (including all spaces and newlines in the input).
This file must be written to the same folder where the executable program is written,
in which the input of such data is provided.
Machine Translated by Google

56 Lecture 3

Let, for example, prog.exe be an executable program (after translation),


the input file is in.txt, then the call from the command line will be as follows:

. . . >prog <in.txt >out.txt

An in.txt file can be created using Notepad or Far. The out.txt file is created
when prog.exe is executed and can then be viewed in Notepad or Far.

Using files for input and output is especially useful when testing a program,
because after discovering and correcting any bug in it, it is necessary to retest it on
the same tests on which it was tested earlier.

Another way to reassign standard input and output is to write a call to standard
procedures at the beginning of the program: reset to reassign input and rewrite to
reassign output. For example, to redirect input and output to in.txt and out.txt files ,

responsible:

reset(input,'in.txt');
rewrite(output,'out.txt');

Random number generation. If a large amount of data is required to test a


program, then instead of manually preparing this data, you can write a program to
generate it. You can use the standard random function for this.

Example 3.15. Program to create an in.txt file containing


number n, and then - n random numbers:

program ex6;
varn,d,r,i: integer;
begin rewrite(output,'in.txt');
readln(n,d,r); {d - range of numbers}
randseed:=r; {r - initial random number}
writeln(n);
for i:=0 to n-1 do
write(random(d):5);
writeln;
end.
Machine Translated by Google

Search and sort 57

First, values are entered for the variables n, d, r. Prior to generation, the
standard randseed variable is initialized to r. The entered value n is output to the
output file , followed by the line feed of the output. After that, random integers from
the range from 0 to d-1 inclusive are generated in the loop and written to the file, 5
character positions are allocated for each number.

In the future, the in.txt file can be used as input for testing the number sorting
program.
End of example.
Although the numbers generated by the random function are called random,
they only simulate randomness. A computer is a deterministic system; in principle, it
does not allow any accidents.
The standard random function generates numbers according to the recursive
formula:
xi+1 = (aÿxi + c) mod m,

where a, c, m are special constants (integers), xi , xi+1 are the previous and
next elements of the random sequence, their value is stored in the standard
randseed variable.
When the random(d) function is called, the final result of f is calculated by the
formula:
f = (xi+1 / m) mod d,

where the division is made with the fractional part discarded.


If you want to generate different random sequences, then before generating
each sequence, you must set different initial values for the randseed variable.

Questions and tasks

1. Carry out a complete proof by the method of the invariant of the program for searching for an
element in an unordered array. Prove that if the array has several elements equal to p, then
the program will calculate the smallest number among them.
2. Change the program for finding an element in an unordered array so that in
if the array has several elements equal to p, then the program you
counted the largest number among them. Carry out a complete proof of this fact.
Machine Translated by Google

58 Lecture 3

3. Carry out a complete proof by the invariant method of the program for the dichotomous search
for an element in an ordered array. Prove that if the array has several elements equal to p,
then the program will calculate the smallest number among
them.

4. Change the program for dichotomous search for an element in an ordered array so that it
searches for the element equal to p with the largest number. Carry out a complete proof of this
fact.
5. Carry out a complete proof by the method of the invariant of the program for removing repetitions
floating values of elements in an ordered array.
6. Carry out a complete proof by the method of the invariant of the program for searching for the
beginning and end of an ordered segment of maximum length in an array.
7. Write a program that inputs the number n, then allocates memory for the n elements of array A,
and inputs n elements into array A. After that, it searches for the beginning and end of the
ordered segment of maximum length, and then prints its beginning and end. Create tests for
the program using the black and white box method and conduct testing.

8. Carry out a complete proof by the program invariant method of the improved simple sorting
algorithm. What makes it work faster?
9. Carry out a complete proof by the method of the invariant of the exchange sort program
rovki.
10. Carry out a complete proof by the method of the sorting program invariant
inserts.

11. Carry out a complete proof by the method of the invariant of the sorting program.
boron.
12. Carry out a complete proof by the invariant method of the bubble program
sorting.
13. Write a program that inputs the number n, then allocates memory for the n elements of array
A, and inserts n elements into array A. After that, it sorts it with selection sort, and then outputs
its elements. Create tests for the program using the black and white box method and conduct
testing.
14. Write a program that inputs the number n, then allocates memory for the n elements of array
A , and inputs n elements into array A. After that, indirectly ordering
Eats it with an insertion sort algorithm, and then puts its elements in order
in pure form. Create tests for the program using the black and white box method and
conduct testing.
15. Write a program that inputs the number n, then allocates memory for the n elements of array
A, and inserts n elements into array A. Then indirectly sorts it with a bubble sort algorithm,
inputs the search value p, performs a dichotomous search, then outputs the number of the
element equal to p in the array
Machine Translated by Google

Search and sort 59

ve A. Create tests for the program using the black and white box method and conduct
testing.
16. Write a program that inputs the numbers n, d, r and the character string S with the name for
a file, then redirects standard output to that file. After that, it outputs the number n to the file,
generates and writes to the file n random numbers from the range from –d to +d for a given
initial value of r. Create tests for the black-and-white-box program and conduct testing.

17. Write a program that inputs a character string S with the name of the input file (which was
written by the program from the previous task), then reassigns standard input to this file.
Reads the number n from the file , allocates memory for n
elements of array A and inserts n elements into array A. After that, it indirectly orders it with
selection sort and then outputs its elements in ordered form without repetition. Create tests
for the program using the black and white box method and conduct testing.
Machine Translated by Google

Lecture 4
recursion

4.1. Procedures and functions

Procedures and functions allow you to create such separate algorithms,


from which, as building blocks, you can subsequently construct large programs.

A description of a procedure or function is an algorithm that can be


executed in the future. Arguments (parameters) can be defined in the
description, they are called formal.
A procedure or function call can be made in a program or another procedure
or function when actual parameters are substituted for formal parameters. Their
types must match!

The function call is executed inside the evaluated expression, the result
of the function (return value) is used when evaluating this expression.

The procedure call is written as a separate statement.


Parameter substitution on call. When calling a procedure or
function, the following options for parameter substitution are possible:
1) substitution by value, when a formal parameter is called, a calculated
expression (in particular, a constant or a variable name) can be used instead
of a formal parameter ; such a parameter cannot be assigned a new value
within the procedure algorithm;
2) by reference to the name of a variable (then in the description, before
the parameter name, var is written ), when called, only the name of a variable,
in particular, an array, can be used instead of a formal parameter, such a
parameter can be assigned a new value inside the procedure algorithm;
Formal parameters - it is expedient to describe arrays without specifying
boundaries, so that when calling, it is possible to substitute arrays with different
personal number of elements.
Machine Translated by Google

recursion 61

Example 4.1. A program with a procedure that calculates the minimum


the value in the array.

programex1;
procedure pmin(var X: array of integer;
n: integer varr: integer);
var i: integer; {i - local variable}
beginr:=X[0];
for i:=1 to n-1 do
if r>X[i] then r:=X[i];
end; {end of procedure description}
var A: array of integer;
n,i,min: integer;
begin readln(n); SetLength(A,n);
for i:=0 to n-1 do read(A[i]);
pmin(A,n,min); {procedure call}
writeln('min=',min);
SetLength(A,0);
end.

The pmin procedure is described with three formal parameters: X is an


array without boundaries, it will be substituted by reference, n is an integer, under
setting by value, r is an integer variable, substitution by reference. Inside the
procedure algorithm, a local variable i is declared; it acts only inside the procedure.
In the algorithm of the procedure, the variable r is assigned the minimum value
among the elements of the array X.
The following describes the variables that operate in the main program,
whereby the variable i has nothing to do with the same variable declared inside
the procedure, and the variable n has nothing to do with the same variable, the
procedure parameter.
The program execution begins by entering a number for n , the size of the array.
Next , memory is allocated for the array A – n integer elements. After that, n values
for the array A are entered in the loop. Then the pmin procedure is called with
parameter substitution: instead of X
A is substituted , instead of n - n, instead of r - min. The result of the call
to the procedure pmin is the minimum value calculated in the procedure in
the array A, written to the variable min. At the end of the program output
Machine Translated by Google

62 Lecture 4

the inscription (min=) and the value of the min variable are displayed. In addition,
the memory allocated to array A is freed.
End of example.
Example 4.2. Program with a function that calculates the minimum value
array.

programex8;
function pmin(varX: array of integer;
n: integer): integer;
var i,r: integer; {i,r – local variables}
beginr:=X[0];
for i:=1 to n-1 do
if r>X[i] then r:=X[i];
pmin:=r;
end; {end of function description}
var A: array of integer;
n,i,min: integer;
begin readln(n); SetLength(A,n);
for i:=0 to n-1 do read(A[i]);
min:=pmin(A,n); {function call}
writeln('min=',min);
end.

Unlike the previous program, a function is described here, not a


procedure. There is no third parameter in the function description, but the
type of the value returned by the function ( integer type) is specified . Inside
the function algorithm, two local variables are declared - i and r. The
minimum value obtained in the variable r among the elements of the array
X at the very end of the declaration is assigned to the function name as the return val
A function call differs from a procedure call in that the function call is
executed on the right side of the assignment, in which the variable min
gets the result of the calculation - the minimum value in array A.
End of example.
Machine Translated by Google

recursion 63

4.2. Recursive Algorithms

A recursive algorithm is written as a procedure or function that calls itself. In


particular, calculations on a recurrent sequence can be written as a recursive
algorithm.

Example 4.3. Factorial is a recurrent sequence of rank 1:

ÿ 0! one,
ÿ

ÿ
ÿ
!
nnn ÿ ÿ ÿ ( one)!
n ÿ
12, ...

function fact(n:integer): integer;


begin
if n<=0 then fact:=1
else fact:=fact(n-1)*n
end; {function calls itself!}

Proof.
Basis of induction. For n = 0, the fact function will calculate the result 0.
Assumption. Let the fact function calculate the correct result n! for n
= k, k ÿ 0. Induction. We make sure that for n = k + 1 the fact function will
calculate re
result equal to (n – 1)! n = n!.
Let's trace the calculation process when calling:
d:=fact(3);

To compute fact(3), the algorithm first computes fact(2), which computes fact(1),
then computes fact (0). This can be represented as a “deeper” calculation, as in the
left table in Fig. 4.1. Finally, when fact(0) is computed , the result of the algorithm is
1, then when fact(1) is computed, fact(2) is computed, and finally fact(3) is
computed. This process - returning from recursion - is presented in the right table in
Fig. 4.1.

The recursion depth is the number of repeated calls to a function or


procedure before exiting.
In this example, the recursion depth is 4, but in the general case when
fact(n) is called, it is n+1.
Machine Translated by Google

64 Lecture 4

fact(n); fact(n);
3 3 6
2 2 2
one one one

0 0 one

Rice. 4.1

End of example.

Each time a procedure or function is called, memory is allocated for the


arguments and for all internal variables declared within the procedure or function.
Therefore, the maximum recursion depth
determines the maximum amount of allocated memory that must be taken into
account when analyzing recursive procedures and functions. In this case, special
attention should be paid to how the methods of substitution of formal parameters are
described - by value or by reference. When substituting by value, memory is allocated
on the stack for the value of this parameter at the time of the call (if the parameter is
an array, then memory is allocated for all elements of the array, i.e., a copy is created,
and all actions with the array inside the procedure or function will be performed with
this copy). When substituting by reference, memory is allocated for the address in
memory where the value of the actual parameter is located (for example, no copy is
created for an array, all actions with the array will be performed with the array
substituted when the function is called).

Example 4.4. Task "Towers of Hanoi". There are three pegs, marked with letters:
a,b,c. On a peg a there are n discs strung in the form of a tower, like in a children's
pyramid.
Task: Move the entire tower to peg c, shifting one disk at a time so that any larger disk
does not lie on the smaller disk for any of the pegs.

Proof.
Basis. Number of disks n=1. Move the disc from peg a to
peg c.
Machine Translated by Google

recursion 65

Assumption. Let the algorithm be able to move towers from n=kÿ1 disks.

Induction. Let n=k+1ÿ2. Shifting a tower of k discs


(top) from peg a to peg b, then one lower disk from peg a to peg c , and
finally a tower of k disks from peg b
on a peg c. Problem solved!
On fig. 4.2 shows this process:
1) initial position - k + 1 disk on peg a;
2) after placing k discs on peg b;
3) after shifting the lower disk to the peg c;
4) after shifting k discs from peg b to peg c, i.e.
final position.

one

four

Rice. 4.2
Machine Translated by Google

66 Lecture 4

The algorithm for solving the game can be written down by repeating the proof
process. In the procedure, the process of shifting one disc is written as outputting the
number of the peg where the disc lay and the number of the peg on which the disc
was placed.
If, when carrying out the proof, we take into account that the lower disk can be
moved if and only if all the upper disks are transferred in the form of a tower to an
intermediate peg, then it follows that the algorithm implements the minimum possible
number of rearrangements!

procedure hanoi(n,a,b,c:integer);
begin
if n=1 then writeln(a,'>',c)
else begin
hanoi(n-1,a,c,b);
writeln(a,'>',c);
hanoi(n-1,b,a,c);
end;
end;

Example call for 5 disks: hanoi(5,1,2,3);

Let us denote the number of rearrangements of n disks as a function of H(n).


Recursive relation for the function H(n):
ÿ 1, n ÿ
one,

H n( ) ÿ
-
2H
( ÿn ÿ ÿ eleven, n ÿ
2, 3, ...,

From it we get:
n-1 n-2 n
H(n) = 2H(n – 1) + 1 = 2 2H(n – 2) + 2 + 1 = 2 +2 + …+2+1=2 - one.

Thus, the complexity of the algorithm is O(2n ), and this is the minimum
possible complexity of solving the problem. Recursion depth: n.
If it takes 1 second to shift one disk, then with n = 64 all the work can be
completed in 264 s ÿ 580 billion years.
End of example.

Example 4.5. Recursively calculating the minimum value among


array elements A[0],...,A[n]:
Machine Translated by Google

recursion 67

function fmin(var A: array of integer,


n:integer):integer;
varmi:integer; begin if n=0 then fmin:=A[0] else
begin mi:=fmin(A,n-1); if A[n]>=mi then fmin:=mi
else fmin:=A[n] end

end;

Proof. Basis. n=0.


The result is A[0]. Assumption. Let
n=kÿ0, the result of calculations is the minimum
value among {A[0],...,A[k]}.
Induction. If n=k+1ÿ1, then the result is the smallest of the minimum
th value among {A[0],...,A[n-1]} and A[0].
It is easy to see that the complexity (the number of recursive calls) and the
recursion depth are equal to n. End of example.

Example 4.6. Recursive algorithm for dichotomous search in an ordered array:

function seek(var A: array of integer; p,b,e:integer):integer;


var c:integer; begin if b=e then

if A[b]=p then seek:=b else seek:=-1 else begin


c:=(b+e)div 2; if A[c]<p then seek:=seek(A,p,c+1,e) else
seek:=seek(A,p,b,c) end

end;
Machine Translated by Google

68 Lecture 4

Function parameters: A – array to be searched, p – search value, b,e – beginning


and end of the area in the array where the number of the array element is searched
for, having a value equal to p. The return value is the array element number if the
search is successful, and -1 if there is no such element in the array. Call to find the
value d in an array X of n elements, numbered from 0 to n-1:

i:=seek(X,d,0,n-1);

It is easy to see that the complexity (number of recursive calls)


and recursion depth are ÿlog2 nÿ . End
of example.

Example 4.7. Recursive calculation of Fibonacci numbers:


fib(n:integer):integer;
begin
if n=0 then fib:=0
else if n=1 then fib:=1
fib:=fib(n-1)+fib(n-2)
end;

Let's calculate the complexity as the number of recursive calls Rn:


ÿ RR ÿ
one,
ÿ
one,
01
ÿ
RRR ÿ ÿ ÿ n ÿ
2, 3, ...
ÿ n n ÿ

one n 2 ÿ
one,

The Rn values grow faster than the Fibonacci numbers Fn:


Rn : 1, 1, 3, 5, 9, 15, 25, 41, 67, . . .

Fn: 0, 1, 1, 2, 3, 5, 8, 13, 21, . . .

It is easy to prove by induction that: Rn = 2Fn+1 – 1. In this


case, the recursion depth is equal to n.
Thus, such a recursive algorithm for computing numbers
Fibonacci is extremely inefficient!
The reason for the inefficiency is that the calculation of fib(n-2) does not use
the Fibonacci number previously calculated when calling fib(n-1) . The situation can
be corrected by storing all previously calculated Fibonacci numbers in an array. A
similar method in programming received
name "dynamic programming method":
Machine Translated by Google

recursion 69

fib(n:integer):integer;
begin
if n=0 then begin F[0]:=0; fib:=0 end
else if n=1 then
begin F[1]:=1; fib:=1 end
else if F[n]>0 then fib:=F[n] end
else begin
F[n]:=fib(n-1)+fib(n-2); fib:=F[n];
end;
end;

Fibonacci numbers are stored in array F with numbering from 0 to n.


Before calling, the elements of this array must be set to zero:
for i:=0 to n do F[i]:=0;
x:=fib(n);

This version of the algorithm has a complexity and a recursion depth of O(n),
since each of the Fibonacci numbers is calculated only once, but its complexity is
several times greater than when calculating the numbers
Fibonacci in a cycle.
End of example.

Any algorithm that computes a recurrence relation can be


implement it recursively. However, looking at the last two examples, we can conclude
that if a recurrent sequence can be computed with a loop, then it should be computed
with a loop, not recursion!

4.3. Merge sort algorithm

Example 4.8. The problem of merging two ordered arrays A (n1


elements) and B (n2 elements) into an ordered array C. This problem can be solved
by copying the contents of arrays A and B into array C (n1+n2
elements) and ordering array C. However, a more efficient non-recursive solution is
possible.
In the algorithm, the input arrays A and B are scanned in parallel in
such a way that the next element, the smallest of the remaining ones, is rewritten
Machine Translated by Google

70 Lecture 4

array C. The first loop is executed until one of the input arrays has no unscanned
elements. If, after the end of the first cycle, some of the elements of array A remain
unscanned, then in the second cycle the remaining elements will be copied to array
C, and if the elements of array B remain unscanned , then the elements remaining
there are copied to C by the third cycle.

i1:=1; i2:=1; j:=1; while


(i1<=n1)and(i2<=n2) do begin if A[i1]<=B[i2] then
begin C[j]:=A[i1]; i1:=i1+1 end

else begin C[j]:=B[i2]; i2:=i2+1 end;


j:=j+1
end;
while i1<=n1 do begin
C[j]:=A[i1]; i1:=i1+1; j:=j+1
end;
while i2<=n2 do begin
C[j]:=B[i2]; i2:=i2+1; j:=j+1
end;

Loop invariant (for each of the three loops):


1) in the array C , the set of elements {C[1], . . ., C[j-1]} matches all
elements from the array A part: {A[1],...,A[i1-1]} and the array B part:
{B[1],... ,B[i2-1]};
2) C[1] <= C[2] <= <= C[j-1];...
3) C[j-1]<=A[i1] for i1 <= n1,
C[j-1] <= B[i2] for i2<=n2.
The complexity of the entire algorithm is O(n).
End of example.

The merge algorithm is of great independent importance. It can be


apply to construct an efficient sorting algorithm.
Example 4.9. Merge algorithm as a procedure. It is assumed that two
adjacent segments will merge from array X to array Y. The parameters in
the procedure have the following meaning:
b1 is the beginning of the 1st
segment, e1 is the end of the 1st segment,
Machine Translated by Google

recursion 71

e2 is the end of the 2nd segment.


An ordered segment is formed in the Y array :
b1 is the beginning of the

segment, e2 is the end of the segment.


procedure S(var X,Y: array of integer;
b1,e1,e2:integer);
var i1, i2, j:integer;
i1:=b1; i2:=e1+1; j:=b1;
while (i1<=e1)and(i2<=e2) do begin if X[i1]<=X[i2]
then begin Y[j]:=X[i1]; i1:=i1+1 end

else begin
Y[j]:=X[i2]; i2:=i2+1 end;

j:=j+1
end;
while i1<=e1 do begin
Y[j]:=X[i1]; i1:=i1+1; j:=j+1
end;
while i2<=e2 do begin
Y[j]:=X[i2]; i2:=i2+1; j:=j+1
end;
end;
End of example.
Example 4.10. Merge sort algorithm:
procedure sort(var X,Y: array of integer;
b,e:integer);
var c,i:integer;
begin
if b<e then begin
c:=(b+e)div 2;
sort(X,Y,b,c);
sort(X,Y,c+1,e);
S(X,Y,b,c,e);
for i:=b to e do X[i]:=Y[i]
end
end;
Machine Translated by Google

72 Lecture 4

The sort procedure uses procedure S from Example 4-9. The procedure sorts the
segment of the array X with element numbers from b to e. Array Y is auxiliary . Calling
the sort procedure for arrays A and B
of n elements:

sort(A, B, 0, n-1);

Proof.
Basis. The size n of the ordered segment in the array X is equal to 1. Al
the burn does nothing.
Assumption. Let the algorithm be able to arrange segments in mass
array X of size from 1 to n = k ÿ 1 elements.
Induction. Let a segment in array X have size n = k + 1 ÿ 2 elements. Then the
algorithm does the following:
1) divides the segment into two equal (or almost equal) parts;
2) each of the parts (no more than k elements in size) is recursively mentioned
line up;
3) merges two ordered parts into one segment of the Y array;

4) copies the segment of the Y array to the X array in place of the original segment
ka, obtaining an ordered segment of length n = k + 1 in the array X. If the
size n of the ordered array
m-1 m
2 <nÿ2 ,
then after no more than m consecutive divisions, the size of the array fragment
will become equal to 1. That is, the recursion depth will also not exceed m, i.e.
ÿlog2 nÿ , which is much less than the size of the array X.
Recurrent relation for labor input T(n):
ÿ T (eleven,
ÿ

ÿ
ÿ
T (n/ T2)n ( ) 2
ÿ
ÿ cn n , ÿ
2, 3, ...,

where c is a constant.
Assuming 2m – 1 < n ÿ 2m, we get:
2
T (n) = 2 T (n/2) + cn = 2 T (n/4) + cn + cn = … ÿ cnm = cn ÿlog2 nÿ

Recursive relation for the number of recursive calls Rn


m
sort procedures for n = 2 :
Machine Translated by Google

recursion 73

ÿ R ÿ
one,
one

ÿ 2
RR ÿ
2 n ÿ
2, 2 , ...
ÿ n nÿ/2 one,

From here we get:

Rn = 2Rn/ 2 + 1 = 4Rn/ 4 + 2 + 1 = . . . = 2ÿ2m – 1 ÿ 2n.

Thus, the number of all recursive calls is much less than the number of other actions
during the execution of the algorithm (comparisons and copies).

In general, the complexity T (n) is of the order: O(n log n).


End of example.

Table 4.1 shows the values of such functions as ÿlog2 nÿ , n ÿlog2 nÿ, n
3/2 2 3
,n , n , 2n , which characterize the complexity of a number of types
output algorithms.

Table 4.1
3/2 2 3 n
n ÿlog2 nÿ n ÿlog 2 nÿ n n n 2

four 2 eight eight 16 64 16

ten four 40 31 100 1000 1024

twenty 5 100 100 400 8000 ÿ106

40 6 240 280 1600 64000 ÿ1012

100 7 700 1000 10000 106 ÿ1030

1000 ten 10000 31000 106 109 ÿ10300

106 twenty 20ÿ106 109 1012 1018 ÿ10300000

The superiority of the more efficient algorithm is especially evident for large values of n.
Thus, to sort an array of a million elements, the simplest sort will perform (in the worst case)
about 166ÿ1015
3 2
cycles (n /6), bubble sort – about 5ÿ1011 cycles (n /2), and litter
merging is about 40ÿ106 cycles ( 2n ÿlog2 nÿ ). If the computer performs 109 cycles per
second, which corresponds to a speed of about 5 billion operations per second, then the
operating time of these programs will be respectively: 5 years, 8 minutes and 0.04 seconds.
Machine Translated by Google

74 Lecture 4

n
If the algorithm has exponential complexity, for example, O(2
), then its running time grows catastrophically fast with an increase in n , and
no increase in computer speed will help. So, for n = 100, 2100 ÿ1030
, when performing 109 actions in 1 second, more than 40
trillion years will be required, and with 1015 actions in 1 second, more
more than 40 million years.

Questions and tasks

1. Write a program that describes a function with parameters n and A that calculates in a loop
the sum of n elements of array A. The program enters the number n, allocates memory for
n elements of array A , and enters n elements into array A. After that, by calling the function,
calculates and displays the sum of the elements of array A. Create tests for the program
using the black and white box method and conduct testing.
2. Write a program that describes a recursive function with parameters n and A, which calculates
the sum of n elements of the array A. Prove the correctness of the function by mathematical
induction. The program enters the number n, allocates memory for n
elements of array A and enters n elements into array A. After that, by calling the function,
calculates and displays the sum of the elements of array A. Create tests for the program
using the black and white box method and conduct testing.
3. How should the recursive algorithm be written in order to guarantee it for
veracity?
4. Write in recursive form the algorithm for calculating the greatest common divisor
two non-negative numbers and prove its correctness.
5. Write and debug a program that enters the number of discs to be moved in the Towers of
Hanoi problem and calls a recursive procedure that prints messages about each disc shift.
At the end, it prints the number of shifts counted in the function, as well as the number of
shifts calculated by the formula. Create tests for the program using the black box method
and conduct those
erasing.
6. Prove by the invariant method the correctness of the recursive function of the dichotomous
th search for an element in an ordered array. Prove that if the array has several elements
equal to p, then the program will calculate the smallest number among
them.

7. On the basis of the recurrent relation on the number of calls Rn of the recursive function for
calculating Fibonacci numbers, prove the relation by mathematical induction: Rn = 2Fn+1
– 1, where Fn is the Fibonacci number.
Machine Translated by Google

recursion 75

8. Prove by mathematical induction the correctness of the recursive function for calculating
Fibonacci numbers using the "dynamic programming" method. Prove that the number of calls
to this recursive function when calculating the nth Fibonacci number is at most n.

9. Prove by the invariant method the correctness of the procedure for merging two adjacent
ordered parts of the array X into one ordered part of the array Y.

10. Change the recursive merge sort procedure so that instead of calling the merge procedure
inside it, actions that implement such a merge are inserted
nie.

11. Write and debug a program that enters the number n, allocates memory for n
elements of array A and inserts n elements into arrays A and C. After that, it sorts array A by
calling the merge sort procedure, and also sorts array C by calling the insertion sort procedure,
after which it compares arrays A and C with each other. Create tests for the program using
the black method box and test.

12. Write and debug a program that inputs the value n, generates n random integers from the
range from -1000 to 1000, stores them in an array (dynamic memory is allocated for the
array), and sorts them one by one using several sorting algorithms (simple sorting, exchange
sorting). sort, bubble sort, merge sort). After executing the next sorting algorithm, check
whether this algorithm worked correctly. In addition, inside each algorithm, provide counters
that count the number of executions of the innermost loop. After completing all calculations
for various n (10, 100, 1000, 10000), print the values of these counters and draw conclusions
about which

of the implemented algorithms is worse, which ones are better.


13. Given an array of n random numbers. The simplest ordering sort is /12 iterations of the loop,
3
which array will execute approximately n bubble sort /2 iterations of the loop, and
2
typing - n merge sort is 2ÿnÿlog2 n repetitions
cycles. If the speed of the computer is 109 cycles per second, how long will it take for these
algorithms to sort an array of 100,000 elements? How will the running time of the algorithms
change if the speed of the computer increases by 10 times and the size of the array increases
by 10 times?
Machine Translated by Google

Lecture 5
List Structures

5.1. List Algorithms

If in a task it is necessary to repeatedly delete or insert individual elements


of an array, then it is more efficient and convenient to use not arrays, but list
structures (which, for brevity, are simply called lists). In a list, all elements of the
list are connected in a sequence so that it is possible to move from one element
in one step to the next one. Each list item has two parts: the content and the
pointer. Since the content is problem-specific and can be of any type, the list item
is generally represented by a data structure called a record . The record contains
several fields, the types of which can be different. Each field of the record is
designated by a separate name. An example of describing data types for list items:

typepel=^elem;
elem=record s:integer; p:pel end;
In this description, two types are described.
The pel type is described as a pointer to the elem type, i.e. list element.
The elem type is the element type of the list. It is defined as a structure of
two fields: the s field is the content, here the content type is integer; the field p
is a pointer that points to another element of the list, the type of the field p is
pel.

A type declaration does not create any variables or other objects in the
program, but only defines the types of variables that can be further described, for
example:
var p1,p2,p3: pel;
Only pointers are described here, there are no list elements themselves yet.
List elements are created when memory is allocated using the new operator, for
example:
new(p1); new(p2);
Machine Translated by Google

List Structures 77

There are two list elements created here: the list element pointed to by
takes the pointer p1, and the element of the list pointed to by the pointer p2.
Examples of creating two lists from the created elements are presented
us in fig. 5.1 in graphic form.

5 ten 5 ten

p1 p2 p1 p2

Rice. 5.1

p1^.s:=5; p1^.p:=p2;

The first element of the list contains 5, the pointer points to the 2nd element.
Here p1^.s is the field s of the list element pointed to by
pointer p1, and p1^.p is the p field of the list element pointed to by
pointer p1.
p2^.s:=10; p2^.p:=nil;
In the second element of the list, the content is 10, the pointer is equal to
nil (a special empty value, in the figure a crossed out rectangle, means the end
of the list). The result is a simple linear list. Pointer p1 points to the beginning of
the list, pointer p2 points to the end of the list. If a
replace the last assignment with:

p2^.p:=p1;

then in the second element of the list, the pointer will point to the initial element
of the list. The result is a cyclic list in which from the last element you can go to
the first element by reference.
The list can contain an arbitrary number of elements, even none, in which
case the pointer to the list is nil.
If any element of the list is no longer needed, it can be removed using the
standard dispose procedure, the argument of which is a pointer variable that
refers to the list element to be removed, for example:
dispose(p1);

After deletion, access to the element becomes impossible, and the


the body is set to nil.
Machine Translated by Google

78 Lecture 5

Example 5.1. A program for entering a set of integers from the keyboard
and forming a sequential list from them. The end of the input is determined
by the entered number 0. The pointer p1 points to the first, and the pointer
p2 points to the last element of the list.
typepel=^elem;
elem=record s:integer; p:pel end;
var p1,p2,p3:pel;
s: integer;
p1:=nil;
p2:=nil;
read(s);
while s<>0 do
begin new(p3); if
p1=nil then p1:=p3 {for the 1st
element of the list}
else p2^.p:=p3;{ for the 2nd, etc. list items}
p2:=p3; p2^.s:=s;
read(s)
end;
p2^.p:=nil;
...
end.

In the loop, the next new element of the list is appended to the end
list.

End of example.
The following example shows how to view all elements
sequential list.

Example 5.2. The program for calculating the sum of the contents of the
elements of the linear list created in example 5.1.
p3:=p1; sum:=0;
while p3<>nil do begin
sum:=sum+p3^.s;
p3:=p3^.p

end;
Machine Translated by Google

List Structures 79

The pointer p3 loops through all the elements of the list. Operator
p3:=p3^.p advances p3 to the next element in the list.
End of example.

Example 5.3. A program to remove all elements of a linear list,


created in example 5.1.

while p1<>nil do begin


p3:=p1^.p
dispose(p1);
p1:=p3

end;
p2:=nil;

operator dispose(p1); removes the element of the list pointed to by p1.

End of example.

In examples 5.1 - 5.3 the list can be empty, then both pointers
p1 and p2 will be nil.
In a number of tasks, such special data structures as queues and stacks (stores)
are used. Each of these structures contains a set of homogeneous elements that can
be added to and removed from the set. A queue is a set of data that works according
to the principle: first in, first out. The stack works on the principle: last in, first out. If
the problem knows in advance the maximum

a certain number of elements of the queue or stack, then they can be implemented in
in the form of arrays, and if unknown, then in the form of lists.

Example 5.4. Programs that perform actions on the stack and queue. A list that
implements a stack or queue also has two pointers: p1, which points to the beginning
of the list, and p2 , which points to the end of the list.
Adding an element with value X to the queue (at the end of the list):

new(p3); p3^.s:=X; p3^.p:=nil;


if p2=nil then p1:=p3 {if list was empty}
else p2^.p:=p3; {otherwise - not empty}
p2:=p3;
Machine Translated by Google

80 Lecture 5

Adding an element with value X to the stack (at the beginning of the list):

new(p3); p3^.s:=X; p3^.p:=p1; p1:=p3;


if p2=nil then p2:=p3; {if the list was empty}
Removing an element from the queue or stack and copying its value to the
variable X (removing from the beginning of the list):

if p1<>nil then begin {if the list is not empty}


p3:=p1; X:=p1^.s; p1:=p1^.p;
dispose(p3)
end;
if p1=nil then p2:=nil; {if list is empty}
End of example.

5.2. List Search

Example 5.5. The program for finding the element of the list with the value S0 in
linear list. The pointer p1 is the beginning of the list:
p3:=p1;
while (p3<>nil)and(p3^.s<>S0) do
p3:=p3^.p;

Search result: pointer p3 points to the search element or path


veins nil if there is no such element.
Loop invariant (pointer p3 points to i-th element):

"among the elements of the list with numbers 1, ..., i - 1, the content of none
of them is equal to S0."

Postcondition: p3=nil or p3^.s=S0.

Labor input O(n). If the search fails, the loop will run
exactly n times, and if successful, may end sooner.
End of example.

Example 5.6. The program for finding the list element with the value S0 in an
ascending sorted list:
Machine Translated by Google

List Structures 81

p3:=p1;
while (p3<>nil)and(p3^.s<S0) do
p3:=p3^.p;
if (p3<>nil)and(p3^.s>S0) then
p3:=nil;

The loop invariant here is the same as in Example 5.5.


Loop postcondition: p3=nil or p3^.sÿS0.

Labor input O(n). In contrast to Example 5.5, the loop can end earlier on both a
successful and an unsuccessful search.
End of example.

Example 5.7. Inserting an element with value S0 into sorted by


ascending list. The pointer p1 is the beginning of the list, p2 is the end of the list:

new(p3); p3^.s:=S0;
p4:=nil; p5:=p1;
while (p5<>nil)and(p5^.s<S0) do
p4:=p5; p5:=p5^.p end;
if p4=nil then p1:=p3{insert at the beginning of the list}
else p4^.p:=p3;{insert into list between p4 and p5}
p3^.p:=p5; if
p5=nil then p2:=p3;{insert at end of list}

First, the loop looks for a place to insert a new element.


(with pointer p3). Loop postcondition options:
1) p5=nil, p4=nil - the list was empty, created from a new element
that;

2) p5=nil, p4ÿnil – the entire list has been scanned, p4 points to


the last element of the list, the new element is appended to the end of the list;
3) p5ÿnil, p5^.sÿS0, p4=nil – the loop has never been executed, new
the element is appended to the beginning of the list;
4) p5ÿnil, p5^.sÿS0, p4ÿnil – a new element is inserted between the elements
of the list with pointers p4 and p5.
End of example.

Example 5.9. Removing an element with value S0 from sorted by


ascending list. The pointer p1 is the beginning of the list, p2 is the end of the list:
Machine Translated by Google

82 Lecture 5

p4:=nil; p5:=p1;
while (p5<>nil)and(p5^.s<S0) do
p4:=p5; p5:=p5^.p end;
if (p5<>nil)and(p5^.s=S0) then begin
{p5 points to the element to be removed}
if p4<>nil then p4^.p:=p5^.p
else p1:= p5^.p; {element to be removed 1st}
if p5^.p=nil then p2:=p4;
{element to be removed last}
dispose(p5);
end;

First, the loop searches for the element to be removed. Loop postcondition
options:
1) p5=nil – the list does not contain the element to be removed;
2) p5ÿnil, p5^.sÿS0, p4=nil – the loop has never been executed, the removed
element is the 1st one;
3) p5ÿnil, p5^.sÿS0, p4ÿnil – the element to be removed is between
the elements of the list with pointers p4 and p5.
End of example.

The above programs perform the search using a loop. With friend
On the other hand, recursive search algorithms are usually simpler.

Example 5.10. Recursively searching for the value S0 in an unordered linear


list:

function flist(p:pel,S0:integer):pel;
begin
if p=nil then flist:=nil
else if p^.s=S0 then flist:=p
flist:=flist(p^.p,S0)
end;

Exit from recursion: either the returned pointer points to an element with content
equal to S0, or it is equal to nil, then the desired value is not in the list. Call example:

p3:=flist(p1,d);
End of example.
Machine Translated by Google

List Structures 83

Example 5.11. Recursively searching for the value S0 in an ordered linear list:

function flist2(p:pel,S0:integer):pel;
if p=nil then flist2:=nil
else if p^.s=S0 then flist2:=p
else if p^.s>S0 then flist2:=nil
else flist2:=flist(p^.p,S0)
end;
The difference from the previous version is that here an additional case
of exiting the recursion with a search result of nil is possible, when the
content of the next element of the list is greater than S0.
End of example.

5.3. List sorting

Example 5.12. The list bubble sort program is built on


analogy with sorting an array. Here the pointer p1 is the beginning of the list.

p3:=nil;
while p3<>p1 do begin
p4:=p1; p5:=p1^.p;
while p5<>p3 do begin if
p4^.s>p5^.s then begin
z:=p4^.s; p4^.s:=p5^.s; p5^.s:=z
end;
p4:=p5; p5:=p5^.p
end;
p3:=p4
end;
Pointer p3 is moved backwards when executing the outer loop.
on the other side, from the end of the list to the beginning, similar to how the
outer loop parameter changes in sorting for an array. Pointers p4 and p5
move through the list from beginning to end, with their help two adjacent elements of
the list are compared and, if necessary, they are exchanged
content.
Machine Translated by Google

84 Lecture 5

Conventionally, we can assume that the elements of the list are renumbered,
their number is equal to n, and that in the outer loop the elements are numbered
by the "variable" i, and in the inner loop - by the "variable" j .
Inner loop invariant:
1) pointer p4 points to the j-th element of the list;
2) pointer p5 points to the (j + 1)th element of the list;
3) the j-th element of the list has the maximum content (field s)
among the elements of the list from the 1st to the j-th;
4) the set of values (fields s) of all elements of the list remains unchanged
nym.

Outer loop invariant:


1) pointer p3 points to the (i + 1)th element of the list;
2) the elements of the list from (i + 1)th to nth are ordered;
3) any element of the list from the 1st to the i-th has content (field s) not
greater than the contents of the list elements from (i + 1)th to last.
Thus, after the end of the inner loop, the pointer p4 points to the i-th element
of the list, and after the assignment p3:=p4 , the pointer p3
will move to the left in the list.
2
The complexity of the algorithm is O(n ), the same as sorting for mass
siva.

End of example.
Example 5.13. Merging two ordered lists. Pointers p1 and p2 point to the
beginning of the two input lists, and the output ordered list is built from the
elements of these lists, to which the pointer p3 points.

procedure slist(var p1,p2,p3:pel);


var p4:pel;
begin
if p1^.s<=p2^.s then begin
p3:=p1; p4:=p1; p1:=p1^.p end
else begin p3:=p2; p4:=p2; p2:=p2^.p end;
{smallest element moved to output list}
while (p1<>nil)and(p2<>nil) do
{loop until both input lists are empty}
if p1^.s<=p2^.s then
begin p4^.p:=p1; p4:=p1; p1:=p1^.p end
Machine Translated by Google

List Structures 85

else begin p4^.p:=p2; p4:=p2; p2:=p2^.p end;


{in the loop, the smallest element is in the output list}
if p1<>nil then p4^.p:=p1 else p4^.p:=p2;
{the rest of one input list appends to the end}
p1:=nil; p2:=nil
end;

First, before the loop, the smallest element is moved into the output list and
pointed to by p3. Pointers p1 and p2 are moved through the input lists during the
loop, and after moving all elements to the output list, they are assigned nil. The local
pointer p4 points to the last element of the output list being created.

The peculiarity of this algorithm is that no memory is allocated for the


elements of the new list, the output list is created from the elements of the input
lists.

The complexity of the algorithm is O(n), the same as merge for arrays.
End of example.
Example 5.14. List merge sort. The pointer p points to the beginning of the list,
the variable n must contain the number of elements
list.

procedure sortlist(var p:pel;n:integer);


var p1,p2:pel;
k,i:integer;
begin
if n>1 then begin
k:=n div 2; p1:=p;
for i:=1 to k-1 do p1:=p1^.p;
p2:=p1^.p; p1^.p:=nil; p1:=p;
{list divided into two almost identical parts}
sortlist(p1,k);
sortlist(p2,nk);
{both parts of the list recursively sorted}
slist(p1,p2,p)
{both parts of the list are merged}
end
end;
Machine Translated by Google

86 Lecture 5

The algorithm is similar to merge sort for an array. The difference is that
first, after calculating the length of the first of the merged lists, the original list is
"cut" in two. In addition, after separately recursively sorting each of the two parts
of the list and merging them into a common ordered list by the slist procedure,
there is no need for any copying here.

The complexity of the algorithm is the same as in merge sort for an array,
i.e. O(n log n).
Compute the length of the list pointed to by p1, and you
call of the sort procedure:

p3:=p1; n:=0;
while p3<>nil do begin
n:=n+1; p3:=p3^.p end;
sortlist(p1,n);
End of example.

Questions and tasks

1. Write and debug a program that enters the number n and enters n numbers, placing them
sequentially in the created list elements, as in a queue. After that by
therefore extracts and removes elements from the beginning of the list, summing their
values, and calculating the minimum and maximum values. At the end displays the sum,
minimum and maximum value. Create tests for the program using the black and white box
method and conduct testing.
2. Write and debug a program that enters the number n and enters n numbers, placing them in
the generated list elements so that the list is ordered. Finally, prints the values in sequence
from the list. Create tests for the program using the black and white box method and conduct
testing.
3. Carry out a complete proof by the program invariant method (containing
loop) searching for an element in an unordered list.
4. Carry out a complete proof by the program invariant method (containing
loop) to find an element in an ordered list.
5. Carry out a complete proof by the method of mathematical induction recursive
programs to find an element in an unordered list.
6. Carry out a complete proof by the method of mathematical induction recursive
programs to find an element in an ordered list.
Machine Translated by Google

List Structures 87

7. Write a program that removes items with duplicate values from an ordered list. Carry out
its full proof by the invariant method.
8. Carry out a complete proof by the invariant method of the bubble sort program for lists.

9. Carry out a complete proof by the invariant method of the bubble sort program for lists.

10. Carry out a complete proof by the method of the invariant of the merge program in order
lists.

11. Carry out a complete proof by the invariant method of the merge sort program
sorted lists.
12. Write and debug a program that enters the number n and enters n numbers, placing
them sequentially in the created list elements, as in a queue. It then bubble sorts the
list and outputs the values from the list sequentially. Create tests for the program
using the black and white box method and conduct testing.

13. Write and debug a program that enters the number n and enters n numbers, placing
them sequentially in the created list elements, as in a queue. It then orders the list by
calling the merge sort routine and prints the values in sequence from the list. Create
tests for the program using the black and white box method and conduct testing.
Machine Translated by Google

Lecture 6
Backtracking

6.1. Combinatorial Algorithms

There are a number of problems that are very difficult to solve without recursion.
These include, in particular, combinatorial problems in which a solution is sought
that consists of a combination of values of some variables. As an example, consider
the problem of finding all permutations of natural numbers from 1 to n. We will solve
the problem as follows:
1) as the first number, choose any of the numbers 1, ..., n;
2) as the second number, choose any of the numbers 1, ..., n, except for the
number that was chosen first;
3) as the third number, select one of the numbers that is not selected per
you or the second, etc.
We continue this process until we choose the last, nth number for permutation.
Thus, n options are selected for the first number, 1 option for the second, and so on,
and 1 option for the last, nth number. In total, it turns out n (n - 1) ... 2ÿ1 = n!
permutation options. In order to obtain choosing
all possiblethepermutations,
kth number, itatiseach
necessary
stage of
to
sequentially go through all admissible numbers; however, it is possible to move
to the next variant only if complete permutations for all previous variants are found.
Such a process can

represent the decision tree shown in Fig. 6.1 for n = 3. The circles in the diagram
indicate the numbers chosen at the next step, the arrows indicate the transition to
the choice of the next number.
According to the scheme, the permutation 1–2–3 will be chosen first, then 1–
3–2, 2–1–3 , and so on. When you select the next number, the movement according
to the scheme goes along the down arrow, and if you refuse this choice (to select
another number), the reverse movement (backtracking) occurs. This process can be
easily implemented by a recursive algorithm.
Machine Translated by Google

Backtracking 89

one 2 3

2 3 one 3 one 2

3 2 3 one 2 one

Rice. 6.1

Example 6.1. Recursive procedure for generating permutations. The


permutations are generated in the global array P containing n elements.
The global array R (of n elements) contains signs of numbers being
included in the permutation: if R[i]=1, then the number i is included in the
permutation, and if R[i]=0, then it is not.
The recursive procedure per(k) selects for permutations all the remaining
numbers, starting from the kth position. The word OUTPUT denotes actions in the
algorithm for memorizing or outputting the next generated permutation:

procedure per(k:integer);
var i:integer;
begin
for i:=1 to n do
if R[i]=0 then begin
P[k]:=i; R[i]:=1;
if k=n then OUTPUT
elseper(k+1);
R[i]:=0
end
end;

Before calling the procedure for generating permutations, the array R


must be set to zero, since none of the numbers has yet been included in the
permutation. The per permutation generation procedure is called with an
argument equal to 1:
Machine Translated by Google

90 Lecture 6

for i:=1 to n do R[i]:=0;


per(1);

Loop invariant: The


elements of the array P[1],...,P[k-1] do not change;
P[k] does not match any of the elements of P[1],...,P[k-1];
Those elements of R[i]=1 for which the number i is among the elements of
P[1],...,P[k-1].
The recursive call to per(k+1) keeps the invariant true for k+1.

The recursion depth is n, because for each nested recursive call, the parameter
k is incremented by 1, and the recursion returns when k=n. In this case, the value of
n in practice cannot be large due to the huge number of permutations (for example,
10! = 3628800).
End of example.

Example 6.2. A non-recursive program for generating permutations for


n=3 is obtained from the procedure per by replacing the recursive call with a
direct insertion of the algorithm:
for i:=1 to n do R[i]:=0;
for i1:=1 to 3 do begin
P[1]:=i1; R[i1]:=1;
for i2:=1 to 3 do
if R[i2]=0 then begin
P[2]:=i2; R[i2]:=1;
for i3:=1 to 3 do
if R[i3]=0 then begin
P[3]:=i3; R[i3]:=1;
CONCLUSION;

R[i3]:=0
end;
R[i2]:=0
end;
R[i1]:=0
end;
Machine Translated by Google

Backtracking 91

In this way it is possible to obtain a program for any fixed n, but in this case the flexibility
of the algorithm is lost, and its size also increases.

End of example.
Example 6.3. Recursive procedure for generating arrays of length m
of n numbers with repetitions. As in Example 6.2, the spreads are generated in a global
array P of n elements. There is no need for an R array here, since there is no need to check
which numbers are already in the arrangement:

procedure gen(k:integer);
var i,j:integer;
begin
for i:=1 to n do
beginP[k]:=i; if k=m
then OUTPUT
elsegen(k+1);
end
end;

Calling the gen procedure is similar to calling the generation procedure.


per :
gen(1);

The complexity of the algorithm can be estimated by the total number of loop executions
in all recursive calls:
– at the first call: n times,
2
– with recursion depth 2: n times n, i.e. n once,
3
– with recursion depth 3: n times in n2, i.e. n once,
- etc.
Total (sum of geometric progression):
2 m m
T(n) = n + n + ...+n = (n – 1) n / (n – 1).

End of example.

The complexity of generating permutations. The complexity of the permutation


generation algorithm can be estimated by the total number of loop executions in all recursive
calls to the per procedure. If not in the cycle
Machine Translated by Google

92 Lecture 6

was checked by the if statement, then the complexity would be the same as when
generating constellations using the gen procedure under the condition n = m:
n
T1(n) = (n – 1) n / (n – 1).

On the other hand, if there were no "extra" loop executions in the per procedure, i.e.
the loop would be executed not n times, but only n - k + 1 times, then the total number of
loop executions would be equal to:
nn! ! ÿ one one ÿ
T2nnnn
( ) ( ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ one)
ÿ ÿ ÿ ...
ÿ 1! nn! ! eleven ÿ ... ÿ
ÿ ÿ en !,
2! 2! (n
ÿ ÿ

ÿ
ÿ

one)! ÿ
since the limit of the expression in brackets as n ÿ ÿ is equal to e (the base of natural
logarithms).
The values T1(n) and T2(n) determine the upper and lower limits of the number of
loop executions, respectively. The exact value of this value can be calculated if we take
into account that all loops are executed n times, but the recursive call of them is only n - k
+ 1 times:
n! n!
ÿ 3ÿ( ÿ) (ÿ Tÿ nn
ÿ ÿnn
ÿ nn n one) ... n n
2! one!

ÿ one one one ÿ


ÿ
! 1 ÿ ÿ ÿ nn...ÿ ÿ ÿne !(
ÿ

one) (*)
2! (n n !ÿ
ÿÿ ÿÿ

one)!

Thus, the complexity of generating permutations is of the order


O(nÿn!). The same order of complexity has actions for deriving or storing all generated
permutations.
For example, for n = 3: T3(3) = 3 + 3ÿ3 + 3ÿ3ÿ2 = 30, the estimate of T3(3)
by formula (*) is 3ÿ3!ÿ1.71828 ÿ 30.926. The complexity of the output of the six
generated permutations: 3ÿ3! = 18.
Example 6.4. Generating permutations in a list. Permutations are generated in a
global list of n elements. Pre required
create this list:

new(p); p1:=p;
for i:=2 to n do
begin
new(p2); p1^.p:=p2; p1:=p2end;

p1^.p;=nil;
Machine Translated by Google

Backtracking 93

Just like in the per procedure, the perlist procedure uses the global
array R, which contains the signs that numbers are included in the
permutation:
procedure perlist(p:pel);
var i:integer;
begin
for i:=1 to n do
if R[i]=0 then begin
p^.s:=i; R[i]:=1;
if p^.p=nil then OUTPUT
else perlist(p^.p);
R[i]:=0
end
end;

The perlist generation procedure is called in the same way as the procedure
per:

for i:=1 to n do R[i]:=0;


perlist(1);
End of example.
Example 6.5. Generation of permutations of character strings. Let a
set (array) of character strings be given, i.e. the lines of this set are
renumbered. To generate all possible permutations of lines, it is enough to
generate all permutations of line numbers with the per procedure, which
prints strings instead of printing numbers. For example, if the number of
numbers to be permuted is n=6 , the description
var St:array[1..6]of string[10];

specifies an array of six strings, with each string having a maximum length of 10
characters. If the rows of the array are given specific
values, then in the description of the per procedure, the output of the permutation of strings can be
write like this:

for j:=1 to 6 do
write(St[P[j]],' ');
writeln;
Machine Translated by Google

94 Lecture 6

Each permutation of the six character strings is output in succession to


one output line separated by a space.
End of example.

Example 6.6. Generation of combinations of n numbers by m. From n numbers,


it is necessary to generate all possible combinations (groups) of m numbers each,
and in one group all numbers must be different. If in the procedure
per in the if statement , replace the comparison k=n with the comparison k = m, then
all permutations of such groups will be generated , i.e. accommodation. Number of
placements

m n!
Ann
n
ÿÿ
( one)...( ÿ ÿ ÿ nm one)
.
( nm )!
ÿ

The number of combinations from n to m, equal to the binomial coefficient Cn ,


m m
in m! times less than the number of placements An :

n!
Cnm ÿ

( mm )! !
ÿÿ

In each such group, the order of the numbers is not important, but it is most
convenient to generate numbers in ascending order by placing them in an array C of m
elements. Then the number C1 will be in the first place in the range from 1 to n – m
+ 1, in the second place – the number C2 in the range from C1 + 1 to n – m + 2, …,
in the mth place – the number Cm in range from Cm – 1 + 1 to n.
In this case, there is no need to use the array R, but in order for the procedure
to be performed in the same way both when generating the first number and the rest
of the numbers, the global array C must contain an additional element C[0] with a
preassigned value of 0 to it.
Recursive procedure for generating combinations:
procedure combine(k:integer);
var i:integer;
begin
for i:=C[k-1]+1 to n-m+k do
begin C[k]:=i; if k=m
then OUTPUT
else combine(k+1);
end
end;
Machine Translated by Google

Backtracking 95

Before the call, you must set the zero value for C[0]:

C[0]:=0; combine(1);

In the combine procedure, the recursion depth is m. As regards the


m
complexity, the comb procedure generates Cn combinations, in contrast to the
procedure
per does not do any extra work. End of
example.

6.2. Backtracking with clipping

Many combinatorial problems require the generation of only such volumes.


projects that additionally satisfy certain relations. In this case, you can check these
relationships after the final generation of the object, but then extra work will be done
if the object does not satisfy them. It is much more efficient to determine, even at the
intermediate stages of generation, whether the generated objects will be able to
satisfy these relations in the future.

Such a generation process is called backtracking with pruning, in which a


reverse step is performed along the decision tree as soon as it becomes clear
that there is no prospect of continuing to generate objects.
Example 6.7. Generation of such permutations from n numbers from 1
to n, so that the sum of the first n1 numbers would be equal to the given
value sum. The values n1 and sum must be set as global variables, and the
condition must be satisfied: 1<=n1<n. Generation procedure:
procedure pers(k,s:integer);
var i:integer;
begin
if k=n1 then begin
i:=sum-s;
if (i>=1)and(i<=n)and(R[i]=0) then begin
P[k]:=i; R[i]:=1;
pers(k+1,s+i);
R[i]:=0;
end
end
Machine Translated by Google

96 Lecture 6

else
for i:=1 to n do
if R[i]=0 then begin
P[k]:=i; R[i]:=1;
if k=n then OUTPUT
else pers(k+1,s+i);
R[i]:=0
end
end;

Procedure parameters:
k is the number of the element of the array P, in which the queue will be stored
new generated numbers;
s is the sum of numbers in the previously calculated part of the generated
permutation, s=P[1]+...+P[k-1].
Calling the generation procedure:
for i:=1 to n do R[i]:=0;
pers(1,0);

The pers procedure algorithm is based on the per algorithm. Otley


The other is that in the pers procedure, the check that the generated permutation of
numbers will satisfy the required condition is performed when generating the number
n1 in the permutation. Such a number may not exist, and then the procedure is
exited, i.e. cutting off all continuations of generation options.

End of example.

Example 6.8. Queen problem. Let the cells on the chessboard be numbered
by two numbers - the number of the row and the number of the column. The
figure queen, standing on the cell (i, j), keeps the cells of the chessboard under attack
in row i, column j, as well as cells of two diagonals passing through cell (i, j). We will
consider a "chess" board of size n x n. It is required to place n queens on the board
in such a way that they do not "hit" each other. The arrangement is written as n
numbers, the number i in the jth place determines the queen on the cell (i, j). For
example, for a 4x4 board, the arrangement (2, 4, 1, 3) shown in Fig. 6.1.
Machine Translated by Google

Backtracking 97

F
F
F
F
Rice. 6.1

Thus, the arrangement of queens is such a permutation of n numbers,


which additionally provides the condition that the queens do not hit each
other diagonally. Each cell (i, j) of the board is located on two diagonals,
the right one (its number j – i) and the left one (its number j + i), as shown
in Fig. 6.2.

0123 2345
-1 0 1 2 -2 3456
-1 0 1 4567
-3 -2 -1 0 5678
Rice. 6.2

The recursive procedure Queen calculates the arrangement of queens on the board
n x n:

procedure Queen(j:integer);
var i:integer;
begin
for i:=1 to n do
if (S[i]=0)and(R[ji]=0)and(L[j+i]=0) then begin

S[i]:=1; R[ji]:=1; L[j+i]:=1; Q[j]:=i;


if j=n then OUTPUT
else queen(j+1);
S[i]:=0; R[ji]:=0; L[j+i]:=0;
end;
end;

Unlike the per procedure, in the Queen procedure, when generating the next
number i in the string j , it checks that the cell (i, j) is not on
Machine Translated by Google

98 Lecture 6

on the occupied column (S[i]=0), on the occupied left diagonal (R[ji]=0) and
on the occupied right diagonal (L[j+i]=0).
The main program containing the declarations of global variables enters the
size of the board n (no more than 20), sets the arrays S, R, L to zero, and calls
the Queen procedure:
var S,Q:array[1..20]of integer;
R:array[-19..19]of integer;
L:array[2..40]of integer;
n,i:integer;
begin
readln(n); for
i:=1 to n do S[i]:=0;
for i:=1-n to n-1 do R[i]:=0;
for i:=2 to 2*n do L[i]:=0;
queen(1);
end.
End of example.

Example 6.9. Puzzle "magic number squares". In a quad to


2
An n x n multi- table is required to arrange the numbers 1, 2, ... in n ,
such a way that the sums for all rows, columns and main diagonals were
the same. On fig. 6.3 shows examples of magic squares for n = 3, n = 4 and
n = 5. The problem has a solution only for n ÿ 3.
1 2 13 24 25

9 1 8 16 5 23 22 7 8

276 4 12 5 13 20 19 11 12 3

951 14 6 11 3 21 4 9 16 15

438 7 15 10 2 18 17 10 6 14

Rice. 6.3

We will look for all possible magic squares for a given n. We stretch the square
table into one line with the numbering of its elements from 1 to n
2
. Then the solution will be such a permutation of numbers 1, n 2 , koto
2, ..., paradise satisfies the conditions of the magic square. Therefore, the basis of al-
Machine Translated by Google

Backtracking 99

algorithm, we can take the procedure per, supplementing it with checking the
corresponding conditions.

The value of the sum for checking the conditions will be determined from the following
images. The sum S of all square numbers is:
2 2 2
S = 1 + 2 + +…1)+/ n2. = n (n
This sum is divided into n equal parts (according to the number of rows in the
square). So the required amount is:
2
S0 = nÿ(n + 1)/2.
Unlike permutations in the per procedure, magic squares will be only a small part
of all permutations. So, for n = 3, there are only 8 permutations out of 9! = 362880,
and for n = 4 only 7040 out of 16! = 20922789888000. Let, for example, a computer
be able to generate and check 107 squares in 1 second. Then it will take him more
than 20 days to generate all the magic squares for n = 4!

To reduce unnecessary work, in the procedure sq for generating magic squares,


it is necessary to cut off those variants of the square that are obviously not magic
squares. To do this, the check of the magic square conditions is performed not after
the next permutation is generated, but as soon as possible, when only its initial part is
generated. Pruning is done by immediately returning from the recursive call at the next
step, which avoids generating all permutations that have the same initial part just
received:

procedure sq(k:integer);
var i:integer;
begin
if k mod n=0 then begin {last column}
i:=s0-sumH(k);
if (i>=1)and(i<=n2)and(R[i]=0) then begin
R[i]:=1; T[k]:=i;
if k=n2 then OUTPUT
else sq(k+1);
R[i]:=0
end
end
else if k>n2-n then begin {last line}
i:=s0-sumV(k);
Machine Translated by Google

100 Lecture 6

if (i>=1)and(i<=n2)and(R[i]=0) then begin


R[i]:=1; T[k]:=i;
sq(k+1);
R[i]:=0
end
end
else begin for {other elements}
i:=1 to n2 do
if R[i]=0 then begin
R[i]:=1; T[k]:=i;
sq(k+1);
R[i]:=0
end
end
end;

Clipping is implemented as follows. Let k be the ordinal number of the generated


element in the array T, which is a square stretched in one line. If the number k
corresponds to the last column, then you can immediately determine the number i
that should be in the kth place. The number i is equal to S0 minus the sum of the
previous elements of the square in the current line (in this case, R[i]=0 must be
satisfied). If, in addition, the number k corresponds to the last element of the square,
then you need to additionally check the sum of the last column and the sum of the
two diagonals. If the number k corresponds to the last line, then the next number can
be determined in a similar way.

The variable s0 contains the value of S0 (the sum of one row or column in the
magic square), n2=n*n. The auxiliary function sumH(k) calculates the sum of the
elements in the current row of the square located before the kth element in the array
T , and the function sumV (k) calculates the sum of the elements in the current
column of the square located above the kth element. The OUTPUT function checks
the last column and two diagonals of the square, and if the checks are positive,
outputs the generated magic square.

To speed things up even more, the sq procedure can additionally provide for
clipping at earlier stages of generating the square: when the number k corresponds
to the penultimate column and when the number k corresponds to the penultimate
row of the square. If all these
Machine Translated by Google

Backtracking 101

improvements, it is possible to reduce the running time of the program at n = 4 to


a few seconds on a conventional computer.
End of example.
Now let's take a quick look at the general principles of the backtracking method for
solving problems of a combinatorial nature:
1) you should first define such data structures, using
which you can list all possible options for solving the problem;
2) then it is necessary to write the main recursive procedure with a loop, in
which the options for moving down one step according to the scheme from the
current state are enumerated (using a recursive call), or the issuance of a
completely generated next option;
3) the procedure should provide for checking the correctness of the complete
generated variant;
4) it is necessary to provide a check of the futility of partially generated
variants and their cutting off, for which, in case of a negative check , a recursive
call should not be performed.

Questions and tasks

1. Conduct a complete proof by mathematical induction for the permutation generation


procedure. The induction should be carried out twice: 1) over the induction parameter
n for a fixed k; 2) by the induction parameter k for fixed
number n.

2. Write down a non-recursive algorithm for generating permutations for n = 4. Run it


complete proof by mathematical induction.
3. Carry out a complete proof by the method of mathematical induction for the procedure
for generating constellations with repetitions for a fixed m. The induction should be
carried out twice: 1) with respect to the induction parameter n for a fixed k; 2) by the
induction parameter k for a fixed n.
4. Write down a non-recursive algorithm for generating arrangements with repetitions for
m = 4 and arbitrary n. Carry out its complete proof by the method of mathematical
induction.
5. Write and debug a program that enters the number n and enters n character strings
with the words written in them into the array . After that, the procedure for generating
permutations generates and outputs permutations of these words. Create tests for the
program using the black and white box method and conduct testing.
Machine Translated by Google

102 Lecture 6

6. Write and debug a program that inputs the numbers n and m, then inputs into an array
n character strings with words written in them. After that, the procedure for generating
placements from n to m generates and outputs the placements of these words. Create
tests for the program using the black and white box method and conduct testing.
7. Write and debug a program that inputs the numbers n and m, then inputs into an array
n character strings with words written in them. After that, the procedure for generating
combinations from n to m generates and outputs combinations of these words. Create tests
for the program using the black and white box method and conduct testing.
8. Carry out a complete proof by the method of mathematical induction for the program for
generating combinations from n to m. 9. Write down a non-recursive algorithm for generating
combinations of n by m for m = 3. Carry out its complete proof by mathematical induction.

10. Write an algorithm for generating placements from n to m with the constraint that the sum of
the numbers in the placement must be equal to S. Provide for the return from the recursion
as early as possible. Carry out its full proof by the method of mathematical
induction.
11. Write down an algorithm for generating combinations of n by m with the constraint that the
sum of the numbers in the combination must be equal to S. Provide for a return from the
recursion as early as possible. Carry out its full proof by the method of mathematical induction.
12. Write and debug a program that enters the number n, after which, using the procedure for
generating permutations of queens on an n x n chessboard , it generates and displays all
permutations as an image of a board with queens on it. Test for n from 4 to 8.

13. What algorithm generates all permutations of rooks on a chessboard of size


n x n (rook strikes horizontally and vertically)?
14. Write a program that inputs the square size n and the number of generated squares L. After
that, the program should generate and output the first L magic squares (if for a given n they
turned out to be less than L, then all the generated squares. Test for n from 3 up to 5.
Machine Translated by Google

Lecture 7
Sets

7.1. Specifying a set with a binary array

Let N some objects (a set of objects) be given. If the objects are renumbered by numbers
from 1 to N, then each object will be determined by its number. In this case, different numbers
will be assigned to any two different objects . The whole set of considered objects

is called the original set or universe. Some of the objects of the universe can be selected, then
a subset of selected objects is formed, which is called simply a set. In set theory, both finite
and infinite sets are studied, but any finite algorithm can only deal with finite sets.

properties.

Sets can be defined in several ways. The easiest way is in the form of a binary array, the
size of which is equal to the number
objects in the universe, and each array element can take two values. Let N be the number of
objects in the universe, the array M contains
N elements, which can be specified as integer or byte (then their value is 0 or 1), or boolean
(then their value is false or true). A specific set is defined by a set of values of the elements of
the array M. If M[i]=1 (or true), then the i-th object of the universe is present in the set, and if
M[i]=0 (or false), then it is not. In total , there are 2N such different sets of N zeros and ones,
i.e., 2
N
various
subsets of this universe.

Example 7.1. Performing basic actions with a set given


as an integer array M.

Checking if an object belongs to a set:

if M[i]=1 then {object number i belongs to the set}.

Adding object number i to the set:

M[i]:=1.
Machine Translated by Google

104 Lecture 7

Removing object number i from the set:

M[i]:=0.

Calculation of the power K of the set (the number of elements in it):


K:=0;
for i:=1 to N do
K:=K+M[i];

The loop is executed N times, i.e. the complexity is of the order O(N).
End of example.

Example 7.2. Operations on two sets given by binary arrays A and B. The sets
must be defined on the same universe of N objects, so the size of arrays A and B
must be N. The result is in array C of length N.

Union (A U B) - set C includes those objects that are in set A or in set B or in


both sets at the same time:
for i:=1 to N do
if (A[i]=1)or(B[i]=1) then C[i]:=1
else C[i]:=0;

Intersection (A ÿ B) – the set C includes those objects that


exists in set A and in set B at the same time:
for i:=1 to N do
if (A[i]=1)and(B[i]=1) then C[i]:=1
else C[i]:=0;

Difference (A \ B) - set C includes those objects that are in set A, but are not in
set B:
for i:=1 to N do
if (A[i]=1)and(B[i]=0) then C[i]:=1
else C[i]:=0;

Symmetric difference (A ÿ B) - set C includes those objects that are in set A


or in set B but not in both sets
properties at the same time:
Machine Translated by Google

Sets 105

for i:=1 to N do
if (A[i]=1)xor(B[i]=1) then C[i]:=1 else C[i]:=0;

Complement of the set A (\ A) - the set C includes those objects,


which are not in the set A (but are in the universe):
for i:=1 to N do
C[i]:=1-A[i]; End of
example.

Example 7.3. Operations on sets represented logically


arrays ( boolean type).

Checking that an object belongs to the set M:

if M[i] then {object number i belongs to the set}.

Adding object number i to the set M:

M[i]:= true;

Removal of object number i from the set M:


M[i]:= false;

Calculation of the power K of the set M:


K:=0;
for i:=1 to N do if M[i]
then K:=K+1;

Union (A U B):
for i:=1 to N do C[i]:= A[i] or B[i];

Intersection (A ÿ B) :
for i:=1 to N do C[i]:= A[i] and B[i];

Difference (A\B):
for i:=1 to N do C[i]:= A[i] and not B[i];

Symmetric difference (A ÿ B):

for i:=1 to N do C[i]:= A[i] xor B[i];


Machine Translated by Google

106 Lecture 7

Set Complement A (\A):

for i:=1 to N do C[i]:= not A[i];

End of example.

7.2. Specifying a set with an integer array

If the size of the universe is too large, then it is advisable to specify the sets as an
integer array. Let, for example, the universe be the set of all integers of a certain length
(for example, 32 bits), then its size is 232 = 4294967296. In a computer, for an array of
such a length, there may simply not be enough RAM, and if enough, then the execution
time of operations may be too large.

If the power of the set is always limited by a certain number L, then the size of the
array to represent the set must be equal to L. Then, in addition to the array M of L
elements, a variable K is needed that specifies the current power of the set, and always
K ÿ L.
The values M[1],...,M[K] define the numbers of objects belonging to the set. Note that
an array must not contain elements with the same value! In this case, the numbers of
objects in the array can be located
go both in an arbitrary order and in an orderly manner, for example, according to
ascending.

Example 7.4. Actions with unordered array M, represent


shchim set.

Search for object number i in the set:

j:=1;
while (j<=K)and(M[j]<>i) do j:=j+1;
if j<=K then FOUND else NO;

Adding object number i to the set:

j:=1;
while (j<=K)and(M[j]<>i) do j:=j+1;
if j>K then begin K:=K+1; M[K]:=i end;
Machine Translated by Google

Sets 107

Removing object number i from the set:

j:=1;
while (j<=K)and(M[j]<>i) do j:=j+1;
if j<=K then begin M[j]:=M[K]; K:=K-1 end;

In all three algorithms, a search is first performed in a loop. Loop termination


condition: either j = K+1, then the required object is not in the array, or M[j] = i, then
the object number i is found. In the second algorithm, the object number i is added to
the set only if it was not there, and in the third algorithm, the object number i is
removed from the set only if it was there, and then to the place where it was was, the
object that was in the Kth place is recorded.

All actions with searching, adding, and deleting an object have O(K) complexity.

End of example.
Example 7.5. Operations on two sets given by unordered arrays A and B.
Number of objects in sets K1 and K2
respectively. The result of calculations is written to array C, the number of filled
elements in it is K3, and is calculated in the course of calculations.

Union (A U B):
for i:=1 to K1 do C[i]:=A[i];
K3:=K1;
for i:=1 to K2 do
beginj:=1;
while (j<=K1)and(A[j]<>B[i]) do
j:=j+1; if
j=K1+1 then begin
K3:=K3+1; C[K3]:=B[i]
end
end;

First, all objects from array A are copied to array C. Then , those objects that do
not exist are copied from array B to array C.
Machine Translated by Google

108 Lecture 7

in array A. In the while loop , an element of array B[i] is searched for a match with
any element from array A.
The complexity of the 1st cycle is O(K1), the 2nd (double nested) cycle
O(K1*K2), i.e. total labor input – O(K1*K2).

Intersection (A ÿ B):
K3:=0;
for i:=1 to K2 do
beginj:=1;
while (j<=K1)and(A[j]<>B[i]) do
j:=j+1; if
j<=K1 then begin
K3:=K3+1; C[K3]:=B[i]
end;
end;

Here , only those objects that are in array A are copied from array B to array C.
Labor input is O(K1*K2).

Difference (A\B):
K3:=0;
for i:=1 to K1 do
beginj:=1;
while (j<=K2)and(A[i]<>B[j]) do
j:=j+1; if
j=K2+1 then begin
K3:=K3+1; C[K3]:=A[i]
end;
end;

Here, from array A to array C , those objects that are not in array B are copied.
Labor input is O(K1*K2).

The symmetric difference of sets A and B is equal to (A\B)U(B\A).


To calculate it, you must first write the difference A \ B into the array C ,
and then add the difference B \ A to the array C.
End of example.
Machine Translated by Google

Sets 109

Example 7.6. Actions on an ordered array M representing


lots of.

Search for object number i in the set:


b:=1; e:=K;
while b<e do
begin
c:=(b+e)div 2; if
M[c]<i then b:=c+1 else e:=c

end;
if M[b]=i then FOUND else NO;

It is implemented as a dichotomous search, its complexity is O(log K).


Adding object number i to the set:
b:=1; e:=K;
while b<e do
begin c:=(b+e)div 2; if M[c]<i
then b:=c+1 else e:=c
end;
if M[b]<>i then begin
K:=K+1; j:=K;
if M[b]<i then b:=b+1;
while j>b do begin
M[j]:=M[j-1]; j:=j-1;
end;
M[b]:=i
end;

First, a dichotomous search is performed in the array M for an element with value
i. Adding is done only if there is no such element. When the while loop ends, b=e,
and the new element with value i must be placed at M[b] (or M[b+1] if the maximum
element in the array is less than i). Before placing this element in the array, all
elements located in places from b to K are sequentially shifted to the right by one
position.
Machine Translated by Google

110 Lecture 7

The complexity is O(K) if there was no added object, or O(log K) if this object was already in
the set.

Removing object number i from the set:

b:=1; e:=K;
while b<e do
begin c:=(b+e)div 2; if M[c]<i then
b:=c+1 else e:=c
end;
if M[b]=i then begin while b<K do
begin
M[b]:=M[b+1]; b:=b+1;
end;
K:=K-1;
end;

First, the array M is searched for an element with value i.

Removal is performed only if such an element exists. When the while loop ends, b=e, the element
to be removed is at the location M[b]. To remove this element, all elements located in places from b
+ 1 to K are sequentially shifted to the left by one

position.
The complexity is O(K) if the object to be deleted was, or O(log K) if
this object was not in the set.

End of example.

Example 7.7. Operations on two sets given by ordered arrays A and B with the number of
objects in them K1 and K2 , respectively. The result of the calculations is in array C, the number of
elements in it is K3.

Union (A U B):

i1:=1; i2:=1; K3:=0; while


(i1<=K1)and(i2<=K2) do if A[i1]<B[i2] then begin

K3:=K3+1; C[K3]:=A[i1]; i1:=i1+1 end

else if A[i1]>B[i2] then begin


Machine Translated by Google

Sets 111

K3:=K3+1; C[K3]:=B[i2]; i2:=i2+1 end

else begin
K3:=K3+1; C[K3]:=B[i2];
i2:=i2+1; i1:=i1+1
end;
while i1<=K1 do begin
K3:=K3+1; C[K3]:=A[i1]; i1:=i1+1end; while
i2<=K2 do begin

K3:=K3+1; C[K3]:=B[i2]; i2:=i2+1 end;

The union of sets in the form of ordered arrays is performed according to the
merge principle. However, unlike the merging of ordinary arrays, which may contain
elements with duplicate values, here in the first loop, when the elements in both
arrays have not yet been completely scanned, i.e. the loop continuation condition is
true, there are three options: 1) A[i1]<B[i2], then an element from A is copied into
the array C ;
2) A[i1]>B[i2], then an element from B is copied into array C ;
3) A[i1]=B[i2], then an element from B is copied into array C , but in this
case, both elements are considered scanned: A[i1] and B[i2].
When the first loop ends, either the elements from array A, the elements from
array B, or no elements remain unscanned. Then the remaining elements are added
to the end of the array C.

Labor input O(K1+K2).

Intersection (A ÿ B):
i1:=1; i2:=1; K3:=0; while
(i1<=K1)and(i2<=K2) do
if A[i1]<B[i2] then i1:=i1+1 else if
A[i1]>B[i2] then i2:=i2+1 else begin

K3:=K3+1; C[K3]:=B[i2];
i2:=i2+1; i1:=i1+1
end;
Machine Translated by Google

112 Lecture 7

The intersection of sets in the form of ordered arrays is performed


according to the merge principle. However, unlike the merging of ordinary
arrays, which may contain elements with duplicate values, here, when the
elements in both arrays have not yet been fully scanned, i.e. the loop
continuation condition is true, there are three options: 1) A[i1]<B[i2], then
nothing is copied to array C , it is considered
viewed element A[i1];
2) A[i1]>B[i2], then nothing is copied to array C , it is considered
visited element B[i2];
3) A[i1]=B[i2], then an element from B is copied into the array C , in this case
both the element A[i1] and the element
B[i2].
After the loop ends, the remaining unscanned elements can only be in one of
the arrays; they certainly cannot coincide with elements from another array.

Labor input: O(K1+K2)

Symmetric difference (A ÿ B):


i1:=1; i2:=1; K3:=0; while
(i1<=K1)and(i2<=K2) do if A[i1]<B[i2]
then begin
K3:=K3+1; C[K3]:=A[i1]; i1:=i1+1
end
else if A[i1]>B[i2] then begin
K3:=K3+1; C[K3]:=B[i2]; i2:=i2+1
end
else begin
i2:=i2+1; i1:=i1+1
end;
while i1<=K1 do begin
K3:=K3+1; C[K3]:=A[i1]; i1:=i1+1end; while
i2<=K2 do begin

K3:=K3+1; C[K3]:=B[i2]; i2:=i2+1 end;


Machine Translated by Google

Sets 113

The symmetric difference of sets in the form of ordered arrays is produced


according to the merge principle. In the first loop, when the elements in both arrays
have not yet been fully scanned, i.e. the condition for continuing the loop is true,
there are three options:
1) A[i1]<B[i2], then an element from A is copied into the array C ;
2) A[i1]>B[i2], then an element from B is copied into array C ;
3) A[i1]=B[i2], then nothing is copied to array C , but in this case both element
A[i1] and element
B[i2].
When the first loop ends, either the elements from array A, the elements from
array B, or no elements remain unscanned. Then the remaining elements are
added to the end of the array C.

Labor input O(K1+K2).


End of example.

7.3. Specifying a set by a list of object numbers

From the numbers of the elements of the universe belonging to a particular


set, it is possible to form not only an array, but also a list. For the list elements, we
will use the pel type declarations from Example 5.1. Such a list can be either
ordered or unordered.

Example 7.8. Actions on an unordered list representing a set. The p1 pointer


points to the beginning of the list.

Checking whether the object number i belongs to the set:

P3:=p1;
while (p3<>nil)and(p3^.s<>i) do p3:=p3^.p;
if (p3<>nil)and(p3^.s=i)then FOUND else NO;

Adding object number i to the set. First, it is checked whether the object
number i already belongs to the set. If not, then the object is added to the beginning
of the list.
Machine Translated by Google

114 Lecture 7

P3:=p1;
while (p3<>nil)and(p3^.s<>i) do p3:=p3^.p;
if (p3=nil) then begin
new(p3); p3^.s:=i; p3^.p:=p1; p1:=p3;
end;

Removing object number i from the set: p3:=nil; p4:=p1;

while (p4<>nil)and(p4^.s<>i) do
p3:=p4; p4:=p4^.p end;
if (p4<>nil)and(p4^.s=i) then begin
if p3<>nil then p3^.p:=p4^.p
else p1:=p1^.p;
dispose(p4)
end;

First, it is checked whether the object number i belongs to the set.


Postcondition for loop:
1) (p4=nil)or(p4^.s=i) – object number i does not belong to the set;

2) (p4^.s=i)and(p3<>nil) – object number i is in the gray


din or at the end of the list;
3) (p4^.s=i)and(p3=nil) – object number i is at the beginning
list;

Set cardinality calculation:

K:=0; p3=p1;
while (p3<>nil) do
beginK:=K+1; p3:=p3^.p end;

The complexity of each of the considered actions is O(K), where K is


the number of objects in the set.
End of example.

Example 7.9. Operations on two sets given by unordered lists. The pointer p1 points to the
beginning of the 1st list, the pointer p2 points to the beginning of the 2nd list. As a result, a 3rd list
is created with the pointer p3.
Machine Translated by Google

Sets 115

Union of sets:

new(p3); p4:=p3; p5:=p1;


while p5<>nil do begin new(p6);
p4^.p:=p6; p4:=p6;
p6^.s:=p5^.s; p5:=p5^.p;
end;
p7:=p2;
while p7<>nil do begin p5:=p1;

while(p5<>nil)and(p7^.s<>p5^.s)do p5:=p5^.p;
if p5=nil then begin
new(p6); p4^.p:=p6; p4:=p6; p6^.s:=p7^.s;
end;
p7:=p7^.p;
end;
p4^.p:=nil; p6:=p3; p3:=p3^.p; dispose(p6);

First, a fictitious element of the 3rd list is created (with the pointer p3),
then copies of the elements of the 1st list are attached to it in the 1st cycle.
The pointer p4 points to the last element in the 3rd list.
Then, the loop goes through all the elements of the 2nd list, and for
each of them in the nested loop, a match is checked with any of the
elements of the 1st list. If there was no match, then a copy of the element
from the 1st list is attached to the 3rd list.
Finally, the dummy element at the beginning of the 3rd list is destroyed.
Intersection of many:

new(p3); p4:=p3; p7:=p2;


while p7<>nil do begin p5:=p1;

while(p5<>nil)and(p7^.s<>p5^.s)do p5:=p5^.p;
if p5<>nil then begin
new(p6); p4^.p:=p6; p4:=p6; p6^.s:=p7^.s;
end;
p7:=p7^.p;
end;
p4^.p:=nil; p6:=p3; p3:=p3^.p; dispose(p6);
Machine Translated by Google

116 Lecture 7

Here, too, a dummy element of the 3rd list is created first. Then, in a loop, all the
elements of the 2nd list are viewed, and for each of them in
the nested loop checks for a match with any of the elements of the 1st list. If there
was a match, then a copy of the element from the 1st list is attached to the 3rd list.

Finally, the dummy element at the beginning of the 3rd list is destroyed.
Symmetric set difference:

new(p3); p4:=p3; p7:=p1;


while p7<>nil do begin p5:=p2;

while(p5<>nil)and(p7^.s<>p5^.s)do p5:=p5^.p;
if p5=nil then begin
new(p6); p4^.p:=p6; p4:=p6; p6^.s:=p7^.s;
end;
p7:=p7^.p;
end;
p7:=p2;
while p7<>nil do begin p5:=p1;

while(p5<>nil)and(p7^.s<>p5^.s)do p5:=p5^.p;
if p5=nil then begin
new(p6); p4^.p:=p6; p4:=p6; p6^.s:=p7^.s;
end;
p7:=p7^.p;
end;
p4^.p:=nil; p6:=p3; p3:=p3^.p; dispose(p6);

Similar to the previous operations, a dummy element of the 3rd list is


also created first. Then, the loop goes through all the elements of the 1st
list, and for each of them in the nested loop, a match is checked with any of
the elements of the 2nd list. If there was no match, then a copy of the
element from the 1st list is attached to the 3rd list. As a result, the difference
between the 1st and 2nd sets is formed in the 3rd list. Then, in a similar
way, the difference between the 2nd and 1st sets is added to the 3rd list.
Finally, the dummy element at the beginning of the 3rd list is destroyed.
Machine Translated by Google

Sets 117

The complexity of each of the considered operations is O(K1*K2), where K1


and K2 are the number of objects in the 1st and 2nd sets, respectively.
End of example.

Example 7.10. Actions on an ordered list representing a set. The p1 pointer points to the
beginning of the list.

Checking whether the object number i belongs to the set:

P3:=p1;
while (p3<>nil)and(p3^.s<i) do p3:=p3^.p;
if (p3<>nil)and(p3^.s=i)then FOUND else NO;

Adding object number i to the set:

if p1=nil then begin {create a list}


new(p1); p1^.s:=i; p1^.p:=nil;
end
else begin
p4:=nil; p5:=p1;
while (p5<>nil)and(p5^.s<S0) do
p4:=p5; p5:=p5^.p end;
if (p5=nil)or(p5^.s>S0) then begin
new(p3); p3^.s:=i;
if p4=nil then p1:=p3{insert at the beginning of the list}
else p4^.p:=p3;{insert into list between p4 and p5}
p3^.p:=p5; end;

end;

It is separately checked whether the list is empty, and then it is created from the i element.
Otherwise, the loop checks if the object number i already belongs to the set. If not, the new
element is inserted at the beginning of the list or between p4 and p5.

Removal of object number i from the set is performed by the algorithm


from Example 5-8 (removing an element from an ordered list).

The complexity of each of the considered actions is O(K), where K is


the number of objects in the set.
End of example.
Machine Translated by Google

118 Lecture 7

Example 7.11. Operations on two sets given by ordered lists. The pointer p1
points to the beginning of the 1st list, the pointer p2 points to the beginning of the
2nd list. As a result, a 3rd list with pointer p3 is created.

All operations are implemented by algorithms using the merge method, but,
unlike a normal merge, the created list should not contain elements with the same
value. Similar to operations on unordered lists, first a dummy element of the 3rd list
is created, to which other elements of the list are attached, and at the end this
dummy element is removed.

Union of sets:

new(p3); p4:=p3; p11:=p1; p22:=p2;


{Dummy element created}
while (p11<>nil)and(p22<>nil) do begin
new(p5); p4^.p:=p5; p4:=p5; if
p11^.s<p22^.s then begin
p5^.s:=p11^.s; p11:=p11^.p
end
else if p11^.s>p22^.s then begin
p5^.s:=p22^.s; p22:=p22^.p end
else begin
p5^.s:=p11^.s; p11:=p11^.p; p22:=p22^.p end

end;
while (p11<>nil) do begin
new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p11^.s; p11:=p11^.p end;

while (p22<>nil) do begin


new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p22^.s; p22:=p22^.p end;

p4^.p:=nil; p5:=p3; p3:=p3^.p; dispose(p5);


{dummy element removed}
Machine Translated by Google

Sets 119

The operation differs from the usual merge in that in the first cycle, if two
consecutive elements of the 1st and 2nd list are equal, then only one such element
is copied.

Intersection of many:
new(p3); p4:=p3; p11:=p1; p22:=p2;
{Dummy element created}
while (p11<>nil)and(p22<>nil) do
if p11^.s<p22^.s
then p11:=p11^.p
else if p11^.s>p22^.s then
p22:=p22^.p
else begin
new(p5); p4^.p:=p5; p4:=p5;
p4^.s:=p11^.s;
p11:=p11^.p; p22:=p22^.p end;

p4^.p:=nil; p5:=p3; p3:=p3^.p; dispose(p5);


{dummy element removed}

This operation differs from a normal merge in that if two successive elements
of the 1st and 2nd list are equal, then only one such element is copied, and
unequal elements are not copied.

Symmetric set difference:


new(p3); p4:=p3; p11:=p1; p22:=p2;
{Dummy element created}
while (p11<>nil)and(p22<>nil) do
if p11^.s<p22^.s then begin
new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p11^.s; p11:=p11^.p
end
else if p11^.s>p22^.s then begin
new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p22^.s; p22:=p22^.p end
else begin
p11:=p11^.p; p22:=p22^.p
Machine Translated by Google

120 Lecture 7

end;
while (p11<>nil) do begin
new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p11^.s; p11:=p11^.p end;

while (p22<>nil) do begin


new(p5); p4^.p:=p5; p4:=p5;
p5^.s:=p22^.s; p22:=p22^.p end;

p4^.p:=nil; p5:=p3; p3:=p3^.p; {dummy


dispose(p5); element removed}

The operation differs from a normal merge in that in the first cycle,
only unequal consecutive elements from the 1st and 2nd lists are feasted.
End of example.

Questions and tasks


N
1. Prove that if there are N objects in the universe, then there are 2 in total various
subsets.
2. How are the union, intersection, difference, symmetric difference and complement of sets given by
binary arrays calculated, and with what complexity? 3. Write a program that converts the representation
of a set from a binary
array A of length N elements into an ordered array M of numbers of objects of the set, if it is known
that the cardinality of the set does not exceed L. Calculate the cardinality of the set K.

4. Write a program that converts the representation of a set from an ordered array M of numbers of
objects in the set into a binary array A of length n
elements, if it is known that the cardinality of the set is equal to K. What is its complexity?

5. Write a program that converts the representation of a set from an unordered array M of the numbers
of objects in the set into a binary array A of length n
elements, if it is known that the cardinality of the set is equal to K. What is its complexity?

6. Write a program that converts a set representation from binary


array A with a length of N elements into an ordered list of numbers of objects of the set
Machine Translated by Google

Sets 121

if it is known that the cardinality of the set does not exceed L. Calculate the cardinality
of the set K.
7. Write a program that converts a set representation from an ordered list of numbers of
objects in the set into a binary array A of length n elements, if it is known that the
cardinality of the set is K. What is its complexity?
8. Write a program that converts a set representation from an unordered list of numbers of
objects in the set into a binary array A of length n elements, if the cardinality of the set
is known to be K. What is its complexity?
9. Prove the correctness of a program that adds an object to a set represented by an
integer ordered array. What is its labor intensity?
10. Prove the correctness of a program that removes an object from a set represented by
an integer ordered array. What is its labor intensity?
11. Prove the correctness of the program that calculates the union of the sets given
integer unordered arrays. What is its labor intensity?
12. Prove the correctness of the program that calculates the intersection of the sets given
integer unordered arrays. What is its labor intensity?
13. Write a program for calculating the symmetric difference of sets given
integer unordered arrays. Prove its correctness. What is its labor intensity?

14. Prove the correctness of the program that calculates the union of the sets given
integer ordered arrays. What is its labor intensity?
15. Prove the correctness of the program that calculates the intersection of the sets given
integer ordered arrays. What is its labor intensity?
16. Write a program for calculating the difference of the difference of sets given by ordered
integer arrays. Prove its correctness. What is its labor capacity?

17. To prove the correctness of a program that removes an object from a set is presented
ordered list. What is its labor intensity?
18. Prove the correctness of the program that calculates the union of the sets given
unordered lists. What is its labor intensity?
19. Prove the correctness of the program that calculates the intersection of the sets given
unordered lists. What is its labor intensity?
20. Write a program for calculating the difference of sets given by unordered lists. Prove the
correctness of the program. What is its labor intensity?
21. Prove the correctness of the program that calculates the union of the sets given
ordered lists. What is its labor intensity?
22. Prove the correctness of the program that calculates the intersection of the sets given
ordered lists. What is its labor intensity?
23. Write a program for calculating the difference of sets given by ordered lists. Prove the
correctness of the program. What is its labor intensity?
Machine Translated by Google

Lecture 8
Character strings and tables

8.1. Algorithms for processing character strings

Symbols and their codes. Character variables are of type char.


An example of the description of such
variables: var c1, c2: char;
The following actions are possible with symbolic variables: 1)
assignment, for example: c1:='a'; c2:='+';

2) comparison by the same operations as for numbers, while comparing


character codes are given as integers.
3) the ord function , calculates the character code, its result is an integer.
Character codes are specified by one of several possible encoding tables.
In particular, the standard ASCII table defines character codes,
occupying 1 byte. The code that can be written in 1 byte is a non-negative
number in the range from 0 to 255.
The main part of the ASCII code table contains characters with codes
from 0 to 127, in binary notation from 00000000 to 01111111. Symbols with
codes from 0 to 31 are special control characters (line feed, etc.). This part of
the ASCII table contains the following characters: 1) sign characters, including
the space character;
2) numbers from 0 to 9;
3) capital letters of the Latin alphabet;
4) lowercase letters of the Latin alphabet.
The second part of the ASCII table has several options for different
alphabets and different operating systems. For the Russian alphabet, two options
are used: the CP866 code table (MS DOS) and the CP1251 code table
(Windows). Both versions have uppercase and lowercase letters of the Russian
alphabet (Cyrillic), but the codes of the same letter in these tables are different.

In the table in fig. 8.1 and fig. 8.2 the character code (in hexadecimal) is
calculated as the sum of the row number (from 0 to F) and the number that
marks the column. For example, the character code '<' is:
Machine Translated by Google

Character strings and tables 123

C16 + 3016 = 3C16 = 11210.

The main ASCII table contains characters with codes from 0 to 127, 7
bits are enough to write them. When writing a code to a byte (8 bits), the
most significant bit is set to zero.

00 10 20 30 40 50 60 70
0 0 @P '
p
one ! 1 AQ aq
2 "2BR br
3 # 3 CS cs
four
$ 4 DT dt
5 % 5 EU
6 & 6 FV fv
7 '
7 gw gw
eight
( 8 HX hx
9 ) 9 IY iy
A * :JZ z
j
B + ; K[k{
C , <L\l|
D - = M]m}
E . >N ^ n~
F / ?O _ o

Rice. 8.1

Codes 0 to 31 correspond to special control characters, code


32 has a space character, code 127 has a del (delete) character.
The second part of the ASCII table contains characters with codes from 128 to 255;
when writing the code into a byte (8 bits), the most significant bit is equal to one. In variant CP866
contains Russian letters, pseudographic symbols (which can be used to design
tables), as well as a number of other symbols.
Machine Translated by Google

124 Lecture 8

80 90 A0 B0 C0 D0 E0 F0
0ARaÿÿÿ1BSbÿÿÿsÿ R Yo

2 V T in ÿ ÿ ÿ t ÿ
3GUg ÿÿÿ4DFd y

ÿÿÿfÿ

5EXeÿÿÿxÿ

6 Zh C f ÿ 7 Z W h ÿ ÿ ÿ ÿÿcÿ

hÿ
º
8 I W i ÿ ÿ ÿ w 9 Y SH i ÿ ÿ
ÿ
sch •

AKbkÿÿ ÿÿÿ

BLYlÿÿÿsÿ
C M b m ÿ ÿ ÿ b No.

DNEn ÿ
ÿÿe¤

E O Yu o ÿ ÿ ÿ yu ÿ
ÿÿ I
FPInÿ

Rice. 8.2

Character strings. You can create arrays from characters, just like other
data types. In addition, you can describe character strings as special arrays.
Description example:
var s1, s2: string[20];

Two character strings are described, each string has a memory of 20


characters, which means that a string can contain from 0 to 20 characters. If the
description does not contain a number in square brackets, the default memory
allocation is 255 characters.
Actions with character strings:
- assignment, for example:
s1:=''; {current string length 0}
s1:='abcd'; {current string length 4}
- addition (concatenation, gluing), for example:
Machine Translated by Google

Character strings and tables 125

s2:=s1+'efg'; {current length of string s2 is 7}


- access to a character inside a string as an array element, for example:
ÿ1:=s1[1];
- the length function calculates the actual length of the string;
- string comparison.
The comparison of two character strings is done in lexicographic order:

1) first, the first characters of both strings are compared with each other, then the
second, etc.;
2) the comparison continues until the first mismatch between the two characters
being compared, the larger string is considered to be the one whose code of the next
character is larger;
3) if the first line completely coincides with the beginning of the longer second line,
then the first line is considered to be smaller. Examples of comparing two strings:

'ABC' < 'BBC' (here the first character is 'A'<'B')


'ABC' < 'ABD' (here the 3rd character is 'C'<'D')
'ABC' < 'ABCD'(string 'ABC' is shorter than 'ABCD')
'ABC' < 'abc' (here the first character is 'A'<'a')
'Abc' < 'abc' (here the first character is 'A'<'a')
'HOUSE' < 'SMOKE' (here the 2nd character is 'O'<'S')
'HOUSE' < 'HOUSE' (the string 'HOUSE ' is short for 'HOUSE')
'HOUSE' < 'House' (here the 2nd character is 'O'<'o')

The set of objects is character strings. An array of character strings can be


interpreted as a set if the values assigned to the strings are different. Such a set is
analogous to a set of objects renumbered by integers.

An example of a description of such a set:

varD: array[1..1000]of string[20];

The universe for this example is the set of all possible strings from 0 to 20
characters long. The number of objects in such a universe is usually
much more than what can be renumbered by integers of standard length. So, for this
example:
1+256+2562 +2563 + . . . + 25620 \u003d (25621 - 1) / (256 - 1) ÿ 1.467 ÿ 1048 .
Machine Translated by Google

126 Lecture 8

The algorithms for sets of character strings are identical to the corresponding
algorithms for sets of numbers, since strings can be ordered and compared like
integers. Such a set can be ordered by any sorting algorithm (direct or indirect).

Example 8.1. Insertion sort algorithm for an array of n strings.


Such an algorithm requires an auxiliary string z of the same maximum length as for
the elements of the sorted array D:

varz:string[20];

The form of the algorithm is the same as for sorting an array of numbers:

for i:=1 to n-1 do begin


j:=i; z:=D[j+1];
while(j>0)and(D[j]>z) do begin
D[j+1]:=D[j]; j:=j-1;
end;
D[j+1]:=z;
end;

End of example.

Comparing strings with character encoding. Sometimes, when comparing


strings, some different characters need to be considered equal, such as uppercase
and lowercase letters. For such a comparison, you can use the lookup table, which is
described as a one-dimensional integer array of constants T with numbering from 0
to 255 (for characters occupying one byte). The value of the element T[d] must be
equal to the new code for the character with code d. You can get the numeric code
of a character specified in a variable of type char using the ord function . Then,
instead of directly comparing the two symbolic variables c1 and c2 , it is necessary
to compare T[ord(c1)] and T[ord(c2)].

Example 8.2. Comparison of character strings, identifying uppercase


and lowercase letters. Letters for the Latin and Russian alphabets are
identified. In addition, in the Russian alphabet, the letters 'e' are considered equal
and 'ë'. The conversion table for ASCII code (CP866) can be specified by
defining an array of constants in the program. The type of elements in the table
is described (in order to save memory) as byte:
Machine Translated by Google

Character strings and tables 127

const T:array[0..255]of byte= ( 0, 1, 2, 3, 4,


5, 6, 7, 8, 9,10,11,12,13,14,15, 16,17, 18,19,20,21,22,23,24,25,26,27,28,29,30,31,
32,33,34,35,36,37,38,39,40,41,42, 43.44.45.46.47,
48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63
68,69,70,71,72,73,74,75,76,77,78,79, 80,81,82,83,84,85,86,87,88,89,90,91,92,
93,94,95, 96,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
80,81,82,83,84,85, 86,87,88,89,90,123,124,125,126,
127,128,129,130,131,132,133,134,135,136,137,138,
139,140,141,142,143,144,145,146,147,148,149,150,
151,152,153,154,155,156,157,158,159,128,129,130,
131,132,133,134,135,136,137,138,139,140,141,142,
143,176,177,178,179,180,181,182,183,184,185,186,
187,188,189,190,191,192,193,194,195,196,197,198,
199,200,201,202,203,204,205,206,207,208,209,210,
211,212,213,214,215,216,217,218,219,220,221,222,
223,144,145,146,147,148,149,150,151,152,153,154,
155,156,157,158,159,133,133,242,243,244,245,246,
247,248,249,250,251,252,253,254,255);

In this description, letter codes are shown in italics. The scomp


function implements a comparison of two strings using a lookup table T:

function scomp(var s1,s2:string):integer;


var i,r,n1,n2:integer;
beginn1:=length(s1); n2:=length(s2);
r:=0; i:=1; while
(i<=n1)and(i<=n2)and
(T[ord(s1[i])]=T[ord(s2[i])]) do i:=i+1; if (i<=n1)and(i<=n2)
then begin if T[ord(s1[i])]<T[ord(s2[i])] then r:=-1 else r:=1 end
else if (i<=n1) then r:=1 else if (i<=n2) then r:=-1; scomp:=rend;
Machine Translated by Google

128 Lecture 8

The scomp function returns -1 if the string s1 is less than s2, returns 0,
if the strings are equal, and returns +1 if the string s1 is greater than s2.
It is easy to see that the complexity of the program is linear and is
determined by the number of characters in a shorter string (n1 and n2 are the
string lengths):
O(min(n1, n2)).
End of example.
Example 8.3. Sorting an array of strings, taking into account recoding.
Description of array D and auxiliary string z:
varD: array[1..1000]of string[20];
z:string[20];

Algorithm for sorting by inserts with recoding (n is the number of


used elements of array D):

for i:=1 to n-1 do begin


j:=i; z:=D[j+1];
while (j>0)and(scomp(D[j],z)=1) do begin
D[j+1]:=D[j]; j:=j-1;
end;
D[j+1]:=z;
end;

End of example.

8.2. Context search in character strings

The task of contextual search is that in a string (text)


We want to find a substring that matches the sample string.
Consider a simple contextual search algorithm. Let S be a text of length n
characters, d be a sample string. First, the character of the text S[i] is searched for,
equal to the first character of the pattern d[1]. When matching, the following
characters are compared: S[i+1] and d[2], S[i+2] and d[3] , and so on. to the end of
the sample. If a match is found for all pairs, then the search will succeed, if not, then
i will increase by one, and the comparison will continue. In general, three situations
are possible:
1) there is not a single complete match with the sample;
Machine Translated by Google

Character strings and tables 129

2) there is only one pattern match;


3) there is more than one pattern match, and it is even possible for them to
overlap, as, for example, in the text 'mom' for the pattern
'mother'.

Example 8.4. Contextual search in a character array S of length n by pattern-


string d of length m. Result: p=1, if a match is found, then i is the number of the
character in S[i] that matches the first character of the pattern. If there is no complete
match, then p=0:
m:=length(d); p:=0; i:=1;
while (p=0)and(i<=n-m+1) do
if (d[1]<>S[i]) then i:=i+1
else begin
j:=1;
while(j<m)and(d[j+1]=S[i+j])do j:=j+1;
if j=m then p:=1 else i:=i+1
end;

If the next character in the array S coincided with the first character in the string
d , then in the second (inner) loop the match of the subsequent characters of the
string d with the characters of S is checked . located symbols of the array S: S[i],
S[i+1], .
. ., S[i+m-1].
The program has a worst-case O(nÿm) complexity. Really,
since a mismatch can be detected when comparing the last, m - th character of
the pattern, it takes from 1 to m

character comparisons.
End of example.

Example 8.5. Contextual search in a character array S of length n using a pattern-


string d of length m with recoding and search for all matches. The difference from the
previous algorithm here is that after detecting a complete match between the string d
and consecutive characters of the array S (including recoding), the current value of i
is displayed, and the outer loop continues to run.
Machine Translated by Google

130 Lecture 8

m:=length(d); p:=0; i:=1;


while i<=n-m+1 do begin
if T[ord(d[1])]=T[ord(S[i])] then begin
j:=1;
while (j<m)and
(T[ord(d[j+1])]=T[ord(S[i+j])]do j:=j+1;
if j=m then writeln(i);
end;
i:=i+1
end;

The complexity remains the same: O(m n).


End of example.

If the pattern string to be searched does not contain any characters, but
only some, then the search can be performed more efficiently than the
laborious O(m n) bone. Let, for example, the characters in the sample string
can only be letters. We will consider such a line as a word. Then we can
assume that the entire text in which the search is performed consists of
words separated by other characters (not letters). And then in the search
algorithm for each next character it is necessary to determine whether it
refers to letters or separators. In addition, we will use the variable p in the
algorithm, which is a sign of word recognition:
p=0 if the word hasn't started yet (or there were only delimiters),
p=1 if the word started (there were letters),
p=2 if the word has ended and is recognized.
To check separators, we will use a special Y table (an array of constants):

Y[ord(c)]=1 if character c is a letter,


Y[ord(c)]=0 if character c is not a letter (separator).
Description of the table for checking letters and separators:
const Y:array[0..255]of byte =
(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
Machine Translated by Google

Character strings and tables 131

1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
0,0,0);

Example 8.6. Contextual search for the word d (first match) in sim
free array S of length n , taking into account character recoding:
m:=length(d); p:=0; j:=1;
while (p<2)and(j<=n) do
beginc:=S[j];
if p=0 then start
if Y[ord(c)]=1 then {beginning of word} begin
d1:=c; p:=1; i:=j end
end
else begin
if Y[ord(c)]=1 then d1:=d1+c
{word continues}
else if scomp(d,d1)=0 then p:=2
{word ended and matched the pattern}
else p:=0
{word ended but did not match the pattern}
end;
j:=j+1
end;
if p=0 then i:=0
else if (p=1)and(scomp(d,d1)<>0) then i:=0;

The next word from the array S is accumulated in the variable d1 .


When a non-letter character is encountered at p=1, the next word is
completely formed in d1, and it is compared with the pattern d using the
recollation comparison function scomp. If there is a match, the loop ends.
After the loop terminates, the last selected word in d1 is also compared with
the pattern d.
Machine Translated by Google

132 Lecture 8

The result of the algorithm is the number of character i in the array S,


starting from which a match with pattern d was found. If no match is found,
then i=0.
The complexity in the worst case is O(n), since the loop is executed no
more than n times, and the total length of all words formed in the string d1 is also not
exceeds n.
End of example.

Example 8.7. Recognition of a group of words in a character array S of


length n. Given an array D of k strings of characters, each string contains a
word. It is required to find an occurrence of each word in the text S:
p:=0; j:=0;
while j<=n do begin j:=j+1;

if j<=n then c:=S[j] else c:=' ';


if p=0 then start
if Y[ord(c)]=1 then
begin d1:=c; p:=1; i:=j end
end
else begin
if Y[ord(c)]=1 then d1:=d1+c
else begin {word highlighted, search for a match}
q:=1; p:=0;
while q<=k do {D - array of words of length k}
if scomp(d1,D[q])=0 then begin
writeln(D[q],' ',i); q:=k+1
end
else q:=q+1
end
end
end;

Here the loop runs n+1 times, inside the loop j runs through values from 1
up to n+1, processing the symbol S[j]. When j=n+1 , a space character is
processed, this is necessary so that after the last word in the text there must
be a separator. The next word from the array S is accumulated in the
variable d1 . When a character other than a letter is encountered at p=1, the next
Machine Translated by Google

Character strings and tables 133

the word is fully formed in d1, and it is compared in a loop with all the
samples from the array D using the scomp comparison function with
recollation. When a match is found, the comparison loop is forcibly
interrupted and the word and number i of the beginning of this word in array D are ou
Labor input: O(nÿk), since each selected word from the text can be compared
in the worst case k times, and the total length of all words in
text does not exceed
n. End of example.
Example 8.8. Recognition of a group of words in a character array S of
length n:

p:=0; j:=0;
while j<=n do begin j:=j+1;

if j<=n then c:=S[j] else c:=' ';


if p=0 then begin
if Y[ord(c)]=1 then begin
d1:=c; p:=1; i:=j
end
end
else begin
if Y[ord(c)]=1 then d1:=d1+c
else begin p:=0; {word underlined, match search}
b:=1; e:=k;
while b<=e do begin
{D is an ordered array of words of length k}
q:=(b+e)div 2; r:=scomp(d1,D[q]);
if r<0 then b:=q+1
else if r>0 then e:=q-1
else begin
writeln(D[q],' ',i); b:=e+1
end
end
end
end
end;
Machine Translated by Google

134 Lecture 8

Unlike the algorithm in Example 8-7, this assumes that the array D of k strings
of characters is pre-ordered with respect to recoding. Therefore, the comparison of
the word formed in the string d1 with the words from the array D is performed by a
dichotomous algorithm.
Labor input: O(n log k), since the comparison loop of the generated
words with words from the array D is executed no more than log k times.
End of example.

Example 8.8. Forming a dictionary D from a character array S of length n:

p:=0; j:=0; k:=0;


while j<=n do begin j:=j+1;
if j<=n then c:=S[j] else c:=' ';
if p=0 then start
if Y[ord(c)]=1 then begin
d1:=c; p:=1; i:=j
end
end
else begin
if Y[ord(c)]=1 then d1:=d1+c
else begin {word highlighted, search for a match}
q:=1; p:=0;
while q<=k do if {D - array of words of length k}
scomp(d1,D[q])=0 then q:=k+2
else q:=q+1;
if q=k+1 then begin
{adding another word to array D }
k:=k+1; D[k]:=d1
end
end
end
end;

Here , all words are sequentially selected from the array S and written into the
array of strings D without repetitions. In the variable k is calculated
the number of selected words.
Machine Translated by Google

Character strings and tables 135

Laboriousness: O(nÿk), since each selected word from the text when searching
for repetitions can be compared in the worst case k times, and the total
The total length of all words in the text does not exceed n.
End of example.

8.3. Information tables


An object, as an element of a set, can be specified as a record (structure) of
characteristic values, which can be of various types (numeric, character strings, etc.).
Such characteristics are called fields or keys. The set of objects is given in the form

an information table where each column defines a field and each row is a record. In
the program, the information table can be presented in various ways:

1) as a set of arrays, each field uses a separate


array;
2) in the form of an array or a list of records, for each object - its own
writing;
Let, for example, the table T contains three fields: A (integer type), B
(string type, length up to 20 characters), C (integer type) and it should fit 1000 entries.
Description for the first method:

var A, C: array[1..1000]of integer;


B: array[1..1000]of string[20];

Then the i-th record in the information table is a set of array elements: {A[i], B[i],
C[i]}.
Description for the second method:

var T: array[1..1000]of record


A:integer; B:string[20]; c:integer
end;

In this case, the i-th entry in the table is represented by a set of


lei of the i-th element of the array T: (T[i].A, T[i].B, T[i].C)

Example 8.9. The task of "sports biathlon". Competition results


athletes in two types are summarized in the following table:
Machine Translated by Google

136 Lecture 8

Surname Result for the 1st type Result for the 2nd type

It is required to determine the places occupied by athletes in biathlon, according to


the sum of places in separate types. The winner is determined by the minimum sum
of places.
Let the rules for determining places in biathlon be as follows:
1) if the results in one of the events are different for all athletes and these results
are sorted in ascending order, then the athlete’s place in this event is equal to the
number of this athlete in the ordered list, and if the results of several athletes are the
same, then they should be assigned the same average place in this species;

3) if several athletes have the same amount of places in both events


kova, then they should be assigned the same average place in the biathlon.
Let the input table be given by the following arrays: F (surname, type string), R1
(result for the 1st type, type real), R2 (result for the 2nd type, type real). The number
of records in the table is n.
Let's also define four additional columns in the input table:
Place according Place according Sum of places Common place for
to the 1st type to the 2nd type for two types two kinds

Array M1 – place according to the 1st type, real type; array M2 – place
according to the 2nd type. real type ; MS – the sum of places for two types, real type; M0-
common place for two types, type real. In addition, you will need an index array In
( integer type) to order the columns indirectly .
The program uses the place procedure, which calculates places (in
array M ) by one result (in array R):

procedure place(var R,M:array of real;n:integer);


var i,ib,j:integer;
begin
ssort(R, In, n); {indirect sort}
ib:=1;
for i:=1 to n do
if (i=n)or(R[In[i]]<>R[In[i+1]]) then begin
for j:=ib to i do M[In[j]]:=(ib+i)/2;
ib:=i+1
end
end;
Machine Translated by Google

Character strings and tables 137

The place procedure uses an indirect sort procedure


ssort, which forms the index array In.
In general, the program for calculating places will be as follows:

place(R1,M1,n); {calculation of places M1 by 1st view}


place(R2,M2,n); for {calculation of places M2 by 2nd view}
i:=1 to n do {calculation of the sum of places M0}
MS[i]:=M1[i]+M2[i];
place(MS,M0,n); {calculation of places M0 for both types}

The complexity of the entire program is determined by the complexity of using


sorting algorithm and can be estimated as O(n log n) .
End of example.
Of particular interest is the case when information about the objects of the
universe is scattered over several tables. Such tables can be joined. Joining two
tables is done according to the following rules:
1) each of the input tables defines one or more fields that uniquely define the
objects of the universe; such fields, the same for both tables, are called key;

2) the output table contains fields from both input tables;


3) in the case when some object is present as a record in both of their input
tables, a record is formed for it in the output table containing the values from the
records of both input tables;
4) in the case when some object is present as a record in only one of the input
tables, then the corresponding record is not formed in the output table, i.e. the output
table will be the intersection of feature sets from the input tables.

Example 8.10. Joining individual result tables for two views into a common
table. The results of the athletes' competitions are presented in two tables:

Surname Result for 1st Surname Result on the 2nd


F1 mind R01 F2 mind R02

General table after joining the results:


Machine Translated by Google

138 Lecture 8

Surname F Result for the 1st type R1 Result for the 2nd type R2

The program for calculating the intersection of sets of rows from the 1st and 2nd
tables by last names:

sort2(F1, R01, n1); {ordering 1st input table}


sort2(F2, R02, n2); {ordering 2nd input table}
{Intersection of sets of rows from the 1st and 2nd tables by last name:}
i1:=1; i2:=1; n:=0; while
(i1<=n1)and(i2<=n2) do
if F1[i1]<F2[i2] then i1:=i1+1 else if
F1[i1]>F2[i2] then i2:=i2+1 else begin n:=n+1;

F[n]:=F1[i1];R1[n]:=R01[i1];R2[n]:=R02[i1];
i2:=i2+1; i1:=i1+1
end;

The sort2 procedure used in the program sorts the character strings of the
array given as the first parameter while rearranging the elements of the numeric
array, the second parameter. The third parameter specifies the sizes of the arrays.

The complexity of the entire program is determined by the complexity of using


sorting algorithm and can be estimated as O(n log n) . End of example.

Example 8.11. Calculation of average marks of students according to the table.


Given a table with the grades of students in a certain subject, and each
A student can have several current grades:

Last name F R grade

It is required to calculate the average grade and the number of current grades
for each student:

Last Name FS Average Rating RS Number of Ratings KS

The program for calculating the average marks:


Machine Translated by Google

Character strings and tables 139

sort2(F,R,n);
S:=0; k:=0; m:=0; for i:=1
to n do
beginS:=S+R[i]; k:=k+1;
if (i=n)or(F[i]<>F[i+1]) then begin
m:=m+1;
FS[m]:=F[i]; KS[m]:=k; RS[m]:=S/k
S:=0; k:=0
end
end;

The sort2 procedure sorts the character strings of the array given as the first
parameter while rearranging the elements of the numeric array, the second parameter.
After that, the grades for each student will be placed in a row in the array R.

The loop then calculates the sum of grades and their number for one student. In
the if statement , the condition will be true when the
a group of records with the same last name.
The complexity of the entire program is determined by the complexity of using
sorting algorithm and can be estimated as O(n log n) .
End of example.

Questions and tasks

1. What is the cardinality of the set of all possible strings from 1 to 20 characters long,
containing symbols - only Russian lowercase letters? Is it possible to renumber all
elements of this set as integers (32 bits long) or int64s ( 64 bits long), and why?

2. What does the lexicographic order of character strings mean?


3. Character strings from 1 to 3 characters long contain only the numbers 0 and 1. How
many such strings are possible? Write all these lines in lexicographic order?

4. What is the conversion table used for?


5. Prove the correctness of the function that performs a lexicographic comparison of two
character strings.
Machine Translated by Google

140 Lecture 8

6. Write a procedure for lexicographic ordering of an array of character strings using a lookup
table, based on the merge sort procedure.

7. Write and debug a program that enters two character strings (the first is a long string, the
second is a short one) and performs a contextual search for all matches of the second string
within the first, identifying uppercase and lowercase letters. Create tests for the program
using the black and white box method and conduct testing
nie.

8. Write and debug a program that first enters a character string with a length of up to 20
characters, containing a word from Russian letters. Then the program enters a character-by-
character text of arbitrary length containing words from Russian letters separated by
separators, at the end of the text - the symbol '#', and which, during the input process, looks
for all matches (with identification of uppercase and lowercase letters) of the entered string
with the words in the text. Create tests for the program using the black and white box method
and conduct testing.
9. Write and debug a program that enters character-by-character text of arbitrary length
containing words from Russian letters separated by delimiters, at the end of the text - the '#'
symbol, and which, during the input process, selects all words and forms a dictionary of
these words as an array of strings. In the dictionary, all words must be different (with the
identification of uppercase and lowercase letters). It is assumed that the length of any word
is no more than 20 characters, and the number of different words in the text is no more than
1000. Create tests for the program using the black and white box method and conduct
testing.
10. Write and debug a program that completely solves the problem of "sport triathlon": it enters
three separate tables of results of athletes in separate
types, after which it calculates and displays the places of athletes by the sum of places, I have
providing results for all three types. Each table is entered as follows: 1) number of athletes
participating, 2) athlete's name, space, numerical result, space, etc. It is assumed that the
number of athletes is no more than 100, the length of surnames is no more than 20
characters. Create tests for the program using the black and white box method and conduct
testing.
11. Write and debug a program that calculates the average grades of students in two subjects:
first, it enters the total number of grades n, then n lines, each line contains a last name, a
space, a subject number (1 or 2), a numerical grade. It is assumed that the number of
students is no more than 50, the number of all assessments is no more than 1000. Display
the results for subject 1 first, then for subject 2. Create tests for the program using the black
and white box method and conduct testing.
Machine Translated by Google

Lecture 9
C language. Basic concepts

9.1. Program, data types, operations and statements

C program (or C++). Represents one or more text files with the extension
"c" or "cpp". During translation, each file is first processed by the preprocessor,
which, executing the preprocessor directives, makes some program
transformations, in particular, inserting text from the files specified in the
directives. Then the actual translation into machine instructions is performed,
the result is an object module - a file with the "obj" extension. An object module
is not yet an executable module: as a rule, it contains references to other object
modules, in particular, to standard functions from the program library. At the
final stage, the linker works, which combines all linked object modules into a
single executable module with the "exe" extension. Each text file of the program
may contain the following parts

sti:

- preprocessor directives (insert header files, etc.),


eg: #include <stdio.h>
- descriptions of global data types (typedef... struct ... etc.);
- descriptions of global data (variables, constants);
- descriptions of function headers;
- descriptions of functions.
In this case, one of the files must contain a description of the main function
named main (or _tmain), it is from this function that the execution of the
program's executable module begins.
Descriptions of the data. They can be global or local, in the second case,
data descriptions are located inside the function description and they act only
inside this function. Data can be constants or variables. Constants can be
specified explicitly (for example, as a numeric value) or declared as a name
with the const descriptor . The description specifies the type of variables or
constants. Basic descriptors
Machine Translated by Google

142 Lecture 9

basic types - char (character), int (integer), unsigned (unsigned), float (float), double (double
length float), long (long), short (short) - can be combined. In general, describe

sleigh constants:

const <type descriptors> <name> = <value>;

Description of variables:

<type descriptors> <name-1>, <name-2>, . . .<name-n>;

If the variable is an array, then the number of elements is written after the name in square
brackets. If the variable is a pointer, then an asterisk is written before the name.

For example, description:

const long int L[]={1,2,3,456789};

defines a constant - an array of 4 numbers occupying 8 bytes each with the value specified in
the description. Description:

unsigned short int a, b, c;

defines three integer variables, each occupying 2 bytes in memory, which can have a value in
the range from 0 to 216 - 1. Description:

float A[100], B[10][20];

defines two arrays of floating (real) variables occupying 4 bytes in memory. In array A , the
elements are numbered from 0 to 99, in array B , from 0 to 9 along the first dimension, and from
0 to 19 along the second dimension. The number of elements in arrays is defined in the
description and cannot be changed during program execution. Description:

int *p1, *p2;

sets two pointers (references) to any integer objects of type int.

In some cases, the void specifier is used, which specifies an undefined data type.

Expressions and operations. An expression consists of variables and constants that


have operations applied to them. Each operation has some
Machine Translated by Google

C language. Basic concepts 143

priority that determines the order of execution, from 1st (highest) to 15th
(lower). Operations of the same priority are executed either from left to
right or from right to left, as shown in Table 9.1. Operations are also
subdivided into unary (with one operand), binary (with two) and ternary (with
three operands).
Table 9.1
Priory Order
tet
Operations performance

[] -> . ÿ

12 () ! ~ + - ++ -- & * (<type>) sizeof ÿ


3 */% ÿ

4 +- ÿ

5 << >> ÿ

6 < <= >= > ÿ

7 == != ÿ

8 & ÿ

^
9 ÿ

10 ÿ

11 | && ÿ

12 ÿ

13 || ? : ÿ

14 = *= /= %= += -= &= ^= |= <<= >>= ÿ


15 , ÿ

The parenthesis () operator is used in two versions: unary and binary.


In the unary version, the operand, which is an expression, is written between
brackets, the result of the operation is equal to the value of the operand. In
the binary version (function call), the 1st operand - the name of the function
- is written before the opening bracket, and the 2nd operand - the list of
arguments, which may be missing - between the brackets. Here, the result
of the operation is equal to the value returned by the function, which, in
particular, can be empty (void).
The operation square brackets [] is binary, in it the 1st operand is
array name, 2nd is index expression (integer), result is
array element.

Structure field selection operations - indirect selection -> and direct


choice (dot). Both operations are binary, written as:
Machine Translated by Google

144 Lecture 9

1) <name of pointer to structure> -> <name of structure field>


2) <struct name> . <structure field name>
In both operations, the result is the selected field in the structure.
Unary operations with precedence 2 (! ~ + - & * (<type>)
sizeof) are prefix, i.e. written before the operand.
Operation ! - logical negation. If the operand is 0, i.e. false, the result is 1,
i.e. true. If the operand is not equal to 0, then the result is 0.

Operation ~ - bitwise inversion of binary representation


integer operand, treated as a bit vector.
The operations + and - are unary plus and minus, respectively. The result
is the value of the operand (+) or the value of the operand with the opposite sign
(-).
The & operation is to get the address of the operand, which must be a
variable.
Operation * – access to the object pointed to by the operand
contributor.

A conversion operation to the specified type (<type>). The operand can


be any integer or real type, the result type is determined by the operation. When
performing an operation, there may be a loss of precision (for example, when
converting from a double type to a float type) or a loss of value (for example,
when converting a value of 1000 to a char type, due to lack of bit depth). An
operand of type void can be assigned any type, although no conversion is
performed. If the operation is written in the form (void), then the operand, as it
were, "loses" its type without any conversion.

The sizeof operator calculates the size (in bytes) of the operand. There
are two possible formats for the operation: sizeof <expression> or sizeof
(<type>). The operation does not evaluate the value of the expression, it only determines
lays its type.

Unary operators with precedence 2 (++ --) can be used in prefix or postfix
form. In the first variant, they are written before, and in the second, after the
operand. The operand must be a variable of numeric type. The result of the
operation is the same variable whose value is either increased by 1 (operation +
+) or decreased by
Machine Translated by Google

C language. Basic concepts 145

1 (operation --). In the prefix version, the operand variable is first changed and
then used, in the postfix version, it is first used,
and then changes.

A ternary operation with precedence 13 (? :) is called a conditional


operation. It is written as two separate symbols with three operands as:

<expression-1> ? <expression-2> : <expression-3>


This operation is performed as follows. First, <expression-1> is evaluated.
If its value is not equal to 0, then <expression-2> is evaluated, otherwise
<expression-3> is evaluated. The result of the operation is the value of the 2nd
or 3rd expression.
The remaining operations from Table 9.1 are binary, they are records
between two operands .
Multiplicative operations with priority 3 (* / %). The multiplication (*) and
division (/) operators can have any numeric operands. The type of the result is
determined by the type of the operand with the largest bit width. If at least one
of the operands is real, the result will also be real. If both operands are integer,
then the result will also be integer, and in the division operation, the remainder
of the division is discarded. The modulo operator (%) requires integer operands.
For integer a, b , and b ÿ 0 , the identity

(a/b)*b + (a%b) = a.
Additive operations with precedence 4 - addition (+) and subtraction (-) -
can have any numeric operands and even pointers. The type of the result is
determined by the type of the operand with the largest bit width. If at least one
of the operands is real, the result will also be real. If both operands are integer,
then the result will also be integer. If the first operand is a pointer and the
second operand is an integer, then the result is a pointer.

Priority 5 shift operations (<< >>) require integer operands. The first
operand, treated as a bit vector, is shifted left (<<) or right (>>) by the number
of bits equal to the second operand. When shifting to the left, the vacated
positions are filled with zeros; when shifting to the right, they are also filled with
zeros if the first operand has a type with the descriptor unsigned or the high bit
otherwise.
Machine Translated by Google

146 Lecture 9

Comparison operators with precedence 6 (less than <), (greater than


>), (less than or equal to <=), (greater than or equal to >=) and precedence
7 (equal to ==), (not equal to !=) can have numeric operands , and the last
two operations are also pointer operands. The result of the comparison is an
integer 0 (false) or 1 (true).
Bitwise operations on integer operands (of the same length) treated
as bit vectors - conjunction "AND" (&) with priority 8, exclusive "OR" (^) with
priority 9, disjunction "OR" (|) with priority 10. The result is a bit vector.

Logical operations on integer operands – conjunction “AND” (&&) with


priority 11, disjunction “OR” (||) with priority 12. An operand value equal to
zero is treated as false, and not equal to zero is treated as true. The result is
an integer 0 (false) or 1 (true).
Assignment operators with precedence 14 (= *= /= %= += -= &= ^= |=
<<= >>=) must have a variable as their first operand and a value as their
second operand. When a simple assignment (=) is performed, the value of
the variable (left operand) becomes the value of the right operand. The
types of the operands must match or be convertible from the type of the
second operand to the type of the first. In particular, an integer type can be
converted to a real type, the length of the bit representation of an integer
type can change, etc.

The rest of the assignment operators combine the usual binary operation
(<oper>) with the assignment. The notation a <oper>= b is equivalent to a =
a <oper> b.
The comma (,) operator separates consecutively (left to right) evaluated
expressions. The result of the operation is the value of the last expression
evaluated.
Operators in the C language. An expression ending with a semicolon
is considered an operator. A sequence of statements enclosed in curly
braces is also considered a statement. The if statement can take the full
form:
if (<expression>)<operator-1> else <operator-2>
or shortened form:
if (<expression>)<operator-1>
Machine Translated by Google

C language. Basic concepts 147

First, <expression> is evaluated. If its value is not equal to 0, then


<statement-1> is executed, otherwise, <statement-2> (if any) is executed.

The switch statement implements a branching algorithm:


switch (<expression>)
{ case <constant-1>: <list of statements-1> break;
case <constant-2>: <list of statements-2> break;
...
case <constant-n>: <list of statements-n> break;
default: <list of operators-n+1>

} First, <expression> is evaluated. If its value is the same as <constant-i>,


then <list of statements-i> is executed, followed by
a break statement that terminates the entire switch statement. If the computed
value does not match any of the <const-i>s, then <list of statements-n+1> is
executed.
Several consecutive case labels with different constants are allowed. The
absence of the default label is also allowed , then if there is a mismatch, nothing
is executed at all. In addition, some break statements may be missing, in which
case, after the execution of the previous list of statements, the entire switch
construct will not be completed, but subsequent statements will be executed.

while statement:
while (<expression>)<statement>
implements a cyclic algorithm. When executed, <expression> is evaluated. If it
is equal to 0, then the execution of the loop ends, if not, then the <statement> is
executed, the <expression> is evaluated again, and so on.
for statement:
for(<expression-1>;<expression-2>;<expression-3>)<operator>
also implements a cyclic algorithm. When it is executed, <expression-1> is first
evaluated. Then <expression-2> is evaluated. If it is equal to 0, then the execution
of the loop ends, if not, then the <operator> is executed, after it - <expression-3>,
then <expression-2> is evaluated again, etc.
Machine Translated by Google

148 Lecture 9

do statement:
do {<list of statements>} while (<expression>);
implements a cyclic algorithm with a condition check at the end. When it is
executed, the <list of statements> is executed first, after that the <expression>
is evaluated. If it is equal to 0, then the execution of the loop ends, if not, then
the <list of statements> is executed again, the <expression> is evaluated, and
so on.
return statement:
return <expression>;
is the last executable statement within the function declaration.
After it, the program resumes execution after calling this function. In this
case, the calculated <expression> is the result of the function. The
<expression> may be absent in the statement record if the type of the
value produced by the function is declared as void.
Example 9.1. The program for entering numbers, their ordering and output:
#include ”stdafx.h” /* or <stdio.h> 1*/
void main(void) /*3*/ /*2*/
{int i, n, z, X[1000]; /*four*/
scanf(”%d”,&n);
for(i=0;i<n;i++) scanf(”%d”,&X[i]); /*5*/
i=0; / /*6*/
*7*/
while(i<n-1) /*8*/
if(X[i]<=X[i+1]) i++; else
/*9*/
{z=X[i];X[i]=X[i+1];X[i+1]=z; /*ten*/
if(i>0)i--; } /*eleven*/
/*12*/
for(i=0;i<n;i++) printf("%8d",X[i]); /*13*/
printf("\n"); /*fourteen*/
} /*fifteen*/

Line numbers in the program are written as comments. 1st line -


preprocessor directive that specifies the insertion of text from a file (its name is taken
in double quotes or angle brackets). This file contains descriptions of standard I/O
functions. 2nd line - main title
Machine Translated by Google

C language. Basic concepts 149

programs, followed by descriptions in curly braces, and program statements. The 3rd
line describes the integer variables i,n,z and an array X of 1000 elements.

4th line - call the standard function of data input from the keyboard. The 1st
parameter is a character string that specifies the input format for the 2nd parameter.
Character strings - constants in C are enclosed in double quotes. The % character
means that the character following it specifies a specific format, the d character
indicates an integer format. The 2nd parameter specifies a reference ( by the &
operation) to the variable n, since the result of the input will be the assignment of the
entered value to the variable n .
5th line - loop through the first n elements of array X, at each step
loop - input value for X[i].
In lines from 6 to 12 - the simplest improved sorting algorithm
values for n elements of array X.
13th line - a loop through the first n elements of the X array , at each step of the
loop - the output of the standard function of the value of X[i], the output format is 8
characters for each number, all numbers are displayed in one line in the output
window. 14th line - format output that sets the transition to a new line in the output
window.
End of example.

9.2. Functions

A function description consists of a heading, after which the algorithm is written


- the body of the function - which consists of a sequence of descriptions and operators,
taken in curly brackets:

<type> <name> (<type> <parameter name> . . .)


{<descriptions> <statements> . . .}

The header first indicates the type of the value returned by the function (it can
be void), then the name of the function, then the descriptions of the formal parameters
of the function, taken in parentheses, separated by commas. For example, description:

float sq2(float x, float y)


{float z; z=sqrt(x*x+y*y); return z;}
Machine Translated by Google

150 Lecture 9

defines a function sq2 with two formal parameters of type float, substituted by value,
which evaluates a result of type float. The calculation calls the standard square root
function sqrt. An example of calling this function from another function or main
program:

floatt; t=sq2(4,5.5);

Function header description. It only consists of a title and a semicolon at the


end:

<type> <name> (<type> <parameter name> . . .);

Such a description is necessary in the program text file where the call to
this function is recorded, if the full description of the function is available in
another object file with the obj extension. For example, description:
float sq2(float x, float y);

sets the header of the sq2 function. The title must fully match the full description of
the function.

Example 9.2. A program with a function that exchanges the values of two
variables:
#include ”stdafx.h” void
mov(int *a, int *b)
{int z=*a; *a=*b; *b=z;}
void main(void)
{int x,y;
scanf("%d%d", &x, &y);
mov(&x,&y);
printf("x=%dy=%d\n",x,y);
}

The mov function does not return any value, i.e. used as a procedure. Its two
formal parameters (a and b) are described as references (pointers) to variables of
type int. Inside the function, an auxiliary variable z of type int is declared; its
description is combined with assignment. Since assignments do not refer to references,
but to values to which
Machine Translated by Google

C language. Basic concepts 151

parameters are referenced, they are used in conjunction with the unary
operation *(jump from reference to object).
In the main program, the scanf function enters numerical values for the variables
x and y, after which the mov function is called, in which these variables, as actual
parameters, are written with the unary operation & (transition from an object to a
reference to it).
At the end, the changed values of the variables x and y are displayed. The first
parameter in the printf function (output format string) specifies the following output
order: 1) x= text, 2) x value, 3) two spaces, 4) y= text, 5) y value, 6) change to new
output line.
End of example.

Example 9.3. Program with a function that calculates the minimum value
value in array:

#include ”stdafx.h” void


pmin(int *X, int n, int *r)
{int i; /*i – local variable*/
*r=X[0];
for (i=1; i<n; i++) if (*r>X[i]) *r=X[i];
/* end of procedure description */
} void main()
{int *A,n,i,min;
scanf("%d",&n); A= new int[n];
for (i=0; i<n; i++) scanf("%d",&A[i]);
pmin(A,n,&min); /*call a function as a procedure*/
printf("min=%d\n",min);
delete[]A; }

The pmin function does not return any value, i.e. used as a procedure.
The first formal parameter X is described as a reference to an array of
elements of the int type, the second parameter n must specify the number
of elements in the array X and is declared as a value of the int type, the
third r is a reference to the value of the int type, which is used as a result
of calculations when calling the function .
In the main program, the variable A is declared as a reference to an int value, it
is further used as an array name. After entering the value
Machine Translated by Google

152 Lecture 9

for the variable n , the new operator allocates a memory area for n elements
of type int, and the reference A is assigned the address of the location of this
area. Then the values for the elements of the array A are entered in the loop
and the pmin function is called, the result of its calculations is assigned to the
min variable . At the end, the text min= and the result of the calculations are
displayed, and the memory area for the array A is also freed.
End of example.
Example 9.4. Program with a function that returns the minimum value
value in array:

#include "stdafx.h" int


pmin(int *X, int n)
{int i,r; r=X[0]; /*i,r – local variable*/

for (i=1; i<n; i++) if (r>X[i]) r=X[i];


return r;
/*end of function description*/
} void main()
{int *A,n,i,min;
scanf("%d",&n); A= new int[n];
for (i=0; i<n; i++) scanf("%d",&A[i]);
min=pmin(A,n); /*function call*/
printf("min=%d\n",min);
delete[]A; }

Unlike the program in Example 9-3, here the pmin function returns the result of
the calculation directly, so its call is written as the right side of an assignment
statement.
End of example.
Example 9.5. Function (procedure) of merge sort in an array. The b
and e parameters specify, respectively, the number of the first and the
number of the last element in the array, which will be ordered. Parameters A and B
set references to arrays of the same size. In array A , elements are ordered, array B
is auxiliary. Variables c, i1, i2, j are local, their description is combined with assignment.
Machine Translated by Google

C language. Basic concepts 153

void sort (int b, int e, int *A, int *B)


{if(b<e)
{int c=(b+e)/2;
sort(b,c,A,B); sort(c+1,e,A,B);
int i1=b, i2=c+1, j=b;
while (i1<=c && i2<=e)
if(A[i1]<=A[i2]) {B[j]=A[i1]; i1++; j++;}
else {B[j]=A[i2]; i2++; j++;}
while (i1<=c) {B[j]=A[i1]; i1++; j++;}
while (i2<=e) {B[j]=A[i2]; i2++; j++;}
for (j=b; j<=e; j++) A[j]=B[j];
}
}

The function algorithm here is almost the same as in Example 4.10,


the difference is that here the merging of two parts from array A into array
B is implemented directly, and not through a separate function call.
End of example.
Example 9.6. The program enters numbers and arranges them by calling
merge sort functions:
void main(void)
{ int n, *X, *Y, i;
scanf("%d",&n);
X= new int[n]; Y= new int[n];
for (i=0; i<n; i++) scanf("%d",&X[i]);
sort(0,n-1,X,Y);
for (i=0; i<n; i++) printf("%d ",X[i]);
printf("\n");
delete[]X; delete[]Y;
}

This program must be translated along with the description of the sort
function by adding the #include preprocessor directive . In the program,
the variables X and Y are declared as references to values of type int, they
are used as array names. After entering the value for the variable n , the
new operator allocates two memory areas with n elements of type int for
arrays X and Y. Then, in a loop, values are entered for the elements of array X
Machine Translated by Google

154 Lecture 9

and the sort function is called , after which the values of the elements of
the ordered array X are displayed in a loop, separated by spaces. At the
end, memory areas for arrays are freed.
End of example.

Example 9.7. Function (procedure) for generating permutations of queens:


int n,P[21],H[21],R[41],L[41];/*global declarations */
void queen(int k) {int i,j; /* permutation generation function */
for(i=1;i<=n;i++)

if(H[i]==0 && R[i-k+21]==0 && L[i+k]==0)


{P[k]=i; H[i]=1; R[i-k+21]=1; L[i+k]=1;
if(k==n) /*output generated permutation*/
{for(j=1;j<=n;j++)printf("%2d ",P[j]);
printf("\n");

} else queen(k+1);
H[i]=0; R[i-k+21]=0; L[i+k]=0; }

} The algorithm is completely similar to the procedure from example 6.8, differs
from it only in that the array R, like all other arrays, has element numbering, starting
from zero, so when calculating the index, the number 21 is added. The sizes of the
arrays are set so that the generation is valid up to n equal to 20. End of example.

Example 9.8. The program enters the board size n for the queen problem
and calls the queen function:
void main(void) {int i;

scanf("%d",&n);
for(i=1;i<=n;i++) H[i]=0;
for(i=2;i<=n+n;i++)
{R[i]=0; L[i]=0;}
queen(1); }
Machine Translated by Google

C language. Basic concepts 155

Before calling the queen function , the program resets those elements
in the arrays H, R , and L that are used in the function for a given value.
n.
End of example.
Example 9.8. Calculation of the integral of a continuous function by the
trapezoid method. For an approximate calculation of the integral of the function
f(x) on the interval [a, b], the area under the curve y = f(x) is replaced by the
sum of the areas of the trapezoids obtained by dividing the interval into n equal
parts, as shown in Fig. 9.1.

a b

Rice. 9.1.

Formula for calculating the integral:


b
ÿ one one
ÿ
ÿ ( ) ( ) fx
ÿ
ÿ
dx fafadfad
( ) ÿ ÿ ÿ ÿ ÿÿÿ ( 2 ) ... ( ÿÿ )
fbdfbd () ÿÿ ,
2 2 ÿ
a

where d = (b - a) / n.
The Integr function calculates the integral for the integrand f
on the interval from a to b when splitting the interval into n parts:
float Integr(float(*f)(float),float a,float b,int n)
{int i; float d,s; s=(f(a)+f(b))/
2; d=(ba)/n;
for(i=1;i<n;i++) s=s+f(a+i*d);
return s*d;
}

The f parameter is described as a reference to a function that returns a


value of type float and has one parameter of type float. Examples of
descriptions of integrand functions:
Machine Translated by Google

156 Lecture 9

float f1(float x) {return exp(sqrt(x));}


float f2(float x) {return sqrt(exp(x));}

When translating these functions, a preprocessor directive is needed,


connecting the library of standard mathematical functions:
#include <math.h>

Examples of calls to the Integr function with sub-integral functions f1 and f2:

float y1=Integr(f1,0,1.5,10); float


y2=Integr(f2,-1.2,1.2,30); End of example.

Example 9.9. Using an array of function references using the example of


calculating the integral from Example 9.8:
. . .
void main(void)
{float y, a, b; int i,n;
const float (*F[])(float)={f1,f2};
/* description of the constant array of functions */
scanf("%d%f%f%d",&i,&a,&b,&n); /*put input
data */
y=Integr(F[i-1],a,b,n); /*calculation of the integral*/
printf("%10.5f",y); } /*result output */

The program describes an array of constants F, whose elements are


of the type of references to functions that return a value of the float type
and have one parameter of the float type. This description refers to two
functions named f1 and f2. The first function is called F[0] and the second
function is F[1].
First, the program enters variables that specify the number of the integrand (1 or
2), the beginning and end of the interval, and the number of parts of the interval
partition. After that, the integral calculation function is called and the result is displayed.
End of example.
Machine Translated by Google

C language. Basic concepts 157

Questions and tasks

1. There is a description: int *x,y; What actions are correct and what they mean in operators:
x=y; y=5; *x=10; x=*y; *y=20;
2. In what order will the operations be performed and what do they mean in the operator:
x+=(b*c+d)*2<i&(y=a==b);
3. What is the function header description for without the function algorithm itself?
4. Write and debug a program in C that enters three numbers, checks if it is possible to construct
a triangle with sides of that length, and if so, calculates
its area. Create tests for the program using the black and white box method and
conduct testing.
5. Write in C a description of the function with two parameters: the name of the array is real
of elements and its length, which returns a real value, and which calculates the sum of the
elements of the array. Write an example of calling this function.
6. Write in C a description of a function with three parameters: the name of a real array, its
length, a reference to a real value (the result of calculations), and which calculates the sum
of the array elements. Write an example of calling this function.
7. Write in C and debug a program that contains a description of a function that implements the
sorting of a real array by the insertion method. The program should
enter the size of the array, allocate memory for it, enter the elements of the array, call a
function, print the array after sorting, check that the array has become ordered. Create tests
for the program using the black and white box method and conduct
testing.
8. Write in C a description of the function that generates all combinations of numbers from n to m.
Write an example of calling this function.
9. Write in C a description of the function Sum that returns a real value and has two parameters:
1) a reference to a function f with one integer parameter that returns a real value; 2) the
number of summed elements n. Sum function
should calculate the sum: Sum=f(1)+f(2)+. . .+f(n). Write examples of calling the function
2
Sum for functions f: 1/n, 1/n , n, n 2 .
Machine Translated by Google

Lecture 10
C language. Continuation

10.1. Lists, files, standard functions


Lists. To work with list structures, you first need to declare the data type for the
structure used as an element of the list, for example:

struct el{int s; struct el *p;};

In this description, the field s is the content, the field p is a pointer to the next
element in the list. In the following examples with lists, we will use
This is the type description.

An example of a description of 3 pointer variables:


struct el *p1,*p2,*p3;

Memory allocation for 3 list items:

p1=new struct el;


p2=new struct el;
p3=new struct el;

Chaining these elements into a list:

p1->p=p2; p2->p=p3; p3->p=NULL;

The -> operation performs a transition from a pointer to a structure field (element
list ment) referenced by the pointer.

Example 10.1. Forming, sorting, displaying and deleting a list:


void main(void)
{int i,n,v; struct el *p1, *p2, *p3;
scanf("%d",&n); /*input the number of elements n*/
p1=new struct el; p2=p1;/* 1st list element created*/
for(i=0;i<n;i++) /* in the list formation loop */
{scanf("%d",&v); p2->s=v; /*if not the
if(i<n-1) last element of the list*/
Machine Translated by Google

C language. Continuation 159

{p3=new struct el; p2->p=p3; p2=p3;}


}
p2->p=NULL; /*zero the last link in the list*/
sortlist(&p1,n); /*call the list sort function*/
p2=p1;
while(p2!=NULL) /*output in a loop of list items*/
{printf("%d ",p2->s); p2=p2->p;}
printf("\n");
while(p1!=NULL) /*remove list items*/
{p2=p1; p1=p1->p; deletep2;}
}

Sorting in the program is performed by calling the sortlist function.


End of example.

Example 10.2. Merging ordered lists:

void slist(struct el *p11, struct el *p22,


struct el **p3)
{struct el *p4,*p1,*p2;
p1=p11; p2=p22;
if (p1->s<=p2->s)
{*p3=p1; p4=p1; p1=p1->p;}
else {*p3=p2; p4=p2; p2=p2->p;}
while ((p1!=NULL)&&(p2!=NULL))
if (p1->s<=p2->s) {p4-
>p=p1; p4=p1; p1=p1->p;}
else {p4->p=p2; p4=p2; p2=p2->p;}
if (p1!=NULL) p4->p=p1; else p4-
>p=p2; }

The slist function is similar to the procedure in Example 5.13. Its


laboriousness is O(n).
End of example.

Example 10.3. Ordering a list using the merge method, the slist function is used
in the sortlist function :
Machine Translated by Google

160 Lecture 10

void sortlist(struct el **p, int n)


{struct el *p1, *p2; int k,i;
if (n>1)
{k=n/2; p1=*p;
for (i=1;i<=k-1;i++) p1=p1->p;
p2=p1->p; p1->p=NULL; p1=*p;
sortlist(&p1,k);
sortlist(&p2,nk);
slist(p1,p2,p);
}
}
The sortlist function is similar to the procedure in Example 5.14. Its
complexity is O(n log n).
End of example.
Files. Let's consider working with files using two examples. Example 10-4 uses
text files, in which the data is written as text, while Example 10-5 uses binary files, in
which the data can be in any form and is treated as a sequence of bytes.

Example 10.4. Reading numbers from 1st text file and output to 2nd
text file of the number of numbers and their average value:
void main(void)
{FILE *F1,*F2; intn,k,s;
char *S1="in.txt", *S2="out.txt";
if((F1=fopen(S1,"r"))==NULL||
(F2=fopen(S2,"w"))==NULL) puts("Error!\n"); else

{n=0; s=0;
while(feof(F1)==0)
{fscanf(F1,"%d",&k); s+=k; n++;}
fprintf(F2,"%d %8.3f\n",n,float(s)/n);
fclose(F1); fclose(F2); }

}
Machine Translated by Google

C language. Continuation 161

The FILE descriptor defines two pointers F1 and F2 (file variables). Variables
S1 and S2 are pointers, they are assigned references to character strings, they set
the names of two files.
The 1st call to the fopen function associates the file name S1 (1st parameter)
with the file variable F1, the string "r" (2nd parameter) opens this file for reading as
a text file. The 2nd call to the fopen function associates the file name S2 (1st
parameter) with the file variable F2, the string "w" (2nd parameter) opens this file for
writing as a text file. If at least one of the variables F1 or F2 is set to NULL (which
means that the file could not be opened), then an error message is displayed by
calling the puts function.

If both files can be opened, then in the loop the fscanf function sequentially
reads the integers written in file F1 , calculates their sum and number. The feof
function call checks that the end of the file has not been reached, and then the loop
continues.
After the end of file F1 is detected, the loop ends, and after it the number of
numbers read is output to file F2 , then 3 spaces, and then the average value of the
numbers in real format (8 positions, of which 3 digits after the decimal point). At the
end, both files are closed with the fclose function.

End of example.

Example 10.5. Copying binary data from file to file:

void main(int p,char *S[])


{FILE *F1,*F2; longn,k; charX[4096];
if(p!=3||(F1=fopen(S[1],"rb"))==NULL||
(F2=fopen(S[2],"wb"))==NULL)
puts("Error!\n"); else

{n=0;
while(feof(F1)==0)
{k=fread(X,1,4096,F1);fwrite(X,1,k,F2); n+=k;}
fclose(F1); fclose(F2); printf("File
length=%ld\n",n); }

}
Machine Translated by Google

162 Lecture 10

Here the main program is declared with the parameters p and S, and after
translation it can be called on the command line as a function with two parameters. If
the translation specifies that the compiled program will be given the name fcopy,
then the call from the command line should be in the form:

fcopy file-1 file-2

The p parameter specifies the number of character lines in the program call,
including both the name of the program itself and the names of two files, i.e. in this
example, the correct value for p is 3. The S parameter is of type an array of references
to character strings that are written on the command line, i.e.
S[0] is a link to a line with the program name, S[1] is a link to a line with the name
of the 1st file, S[2] is a link to a line with the name of the 2nd file.
The if statement checks that p is equal to 3, after which the 1st file is opened
with the "rb" option as a binary file for reading, and the 2nd file is opened with the
"wb" option as a binary file for writing. If p is not equal to 3, or at least one of the
variables F1 or F2 is null, then an error message is displayed.

If both files were successfully opened, then in the loop, the fread function
sequentially reads the data written in file F1 into the array X, as in 1 block of 4096
bytes long , and the fread function returns the number of bytes read into the variable
k . The write function writes data from array X to file F2, while the variable n counts
the number of all bytes read. The feof function call checks that the end of the file has
not been reached, and then the loop continues.

After detecting the end of the file F1 , the loop ends, and after it
both files are closed with the fclose function, and the text is displayed on the screen
"File length=" and the value n.
End of example.

Standard features. Header files containing descriptions of standard


function prototypes are included in the program by #include preprocessor
directives. The most important header files:
assert.h - diagnostic tools;
ctype.h - checking characters and converting them;
errno.h - check for program execution errors;
float.h - limit values of real numbers;
Machine Translated by Google

C language. Continuation 163

limits.h - limit values of integer data;


math.h - mathematical functions;
signal.h - means of handling exceptional situations;
stdio.h - input-output means;
stdlib.h - general purpose functions;
string.h – functions for processing character strings;
time.h - functions for determining the date and time.

In particular, the header file stdio.h contains descriptions of the I/O functions.
In the printf and scanf functions, the first parameter is a character string
containing the conversion specifications. Each conversion specification
corresponds to one value in the output list (or one pointer to a variable in the input
list). In general, the specification looks like:

%<flag>< field width>.<precision><modifier><specifier>

The required character here is the percent sign and the specifier letter,
defining the type of input or output data:
d or i – int type, displayed as decimal digits;
u – unsigned type, displayed as decimal digits;
o – type unsigned or int, displayed as an octal digit
mi;
x or X – unsigned or int type, displayed as hexadecimal digits;

f – float or double type, integer and fractional parts are displayed;


e or E - float or double type, mantissa is displayed and while
contributor;

g or G - type float or double, the best pre


setting;
c – type char, displayed as a character;
s – type char *, displayed as a character string.
The modifier consists of the letter l or L. The letter l specifies the data type
long or unsigned long, the letter L is a long double type.
The field width is the number of character positions in the representation of
the data element. Precision is an integer that specifies the number of digits in
the fractional part of a real number. The flag (sign + or -) specifies the mandatory
display of the sign in the number representation.
Machine Translated by Google

164 Lecture 10

The format can contain any other characters besides conversion


specifications, including tabs, newlines, etc. These characters are displayed
on output.

10.2. Character strings

Strings are defined in the program as arrays of char elements or,


equivalently, as pointers to a memory area of char type. The elements in
such an array can have any value except zero, since zero marks the end of
the string's content. When working with strings, you must ensure that you
do not overflow the array and that there is enough space in the array for the
terminating null byte.
The string.h header file describes the prototypes of the character
string processing functions. The most important functions are given in table.
10.1.

Table 10.1
Type of Type of
Function Explanation
arguments result
strcat char *s1, char * String concatenation. After the
char*s2 characters of the string s1 , the
characters from s2 are added. Returns
a pointer to string s1
strchr char*s, char * Searches for the character c in the string s . Returns
char c
a pointer to the found character

strcmp char *s1, int Compares strings s1 and s2 lexicographically.


char*s2 Returns 0 if they are equal, <0 if s1<s2, and >0
if s1>s2

strcpy char *s1, char* Copies string s2 to string s1. Returns a


char*s2
pointer to string s1
strlen char * unsigned Calculates the length of a string

strncat char *s1, char * At most n characters from s2 are added after the
char*s2, characters of the string s1 . Returns a pointer
unsigned n to string s1
Machine Translated by Google

C language. Continuation 165

Table 10.1 (continued)


Type of Type of
Function Explanation
arguments result
strncmp char *s1, int Compares lexicographically at most n characters of
char*s2, strings s1 and s2. Returns 0 if they are equal,
unsigned n <0 if s1<s2, and >0 if s1>s2

strncpy char *s1, char* Copies at most n characters of string s2 to


char*s2, string s1. returns
unsigned n pointer to string s1
strnset char*s, char * Pad at most n characters of string s with character c.
charc, returns
unsigned n pointer to string s
strset char*s, char * Fills the string s with character c. Returns a pointer
char c
to the string s
strstr char *s1, char * In string s1 , searches for string s2. Returns a pointer
char*s2
to the found substring

Example 10.5. Comparing character strings with recoding. In all these


functions, when comparing characters, their codes are compared as integers.
If, during comparison, it is required to identify some characters, for example,
the corresponding uppercase and lowercase letters, then it is necessary to
specify a conversion table - an array of constants T with a size of 256
elements, in which the value T[k] specifies a new code for a character with code k:
const short T[256]=
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
96,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
80,81,82,83,84,85,86,87,88,89,90,123,124,125,126,
127,128,129,130,131,132,133,134,135,136,137,138,
139,140,141,142,143,144,145,146,147,148,149,150,
151,152,153,154,155,156,157,158,159,128,129,130,
Machine Translated by Google

166 Lecture 10

131,132,133,134,135,136,137,138,139,140,141,142,
143,176,177,178,179,180,181,182,183,184,185,186,
187,188,189,190,191,192,193,194,195,196,197,198,
199,200,201,202,203,204,205,206,207,208,209,210,
211,212,213,214,215,216,217,218,219,220,221,222,
223,144,145,146,147,148,149,150,151,152,153,154,
155,156,157,158,159,133,133,242,243,244,245,246,
247,248,249,250,251,252,253,254,255};

The table corresponds to the ASCII encoding, CP866. scomp lek function
sicographic comparison of two strings with recoding:
inline int ord(char c) { return /*type char -> int*/
c<0 ? c+256 : c;}
int scomp(char *s1, char *s2)
{int i=0,r=0;
while(s1[i]!=0 && s2[i]!=0 &&
T[ord(s1[i])]==T[ord(s2[i])]) i++;
if(s1[i]!=0 && s2[i]!=0)
{if(T[ord(s1[i])]<T[ord(s2[i])])r=-1;
else r=1;
}
else if(s1[i]!=0) r=1; else if(s2[i]!
=0) r=-1;
return r;
}

Here, the auxiliary function ord is used, which converts an argument


of type char (as a number of length 1 byte with a sign) to type int from the
range from 0 to 255. The ord function has a descriptor inline, which means
that when the program is translated, the call to such a function is replaced
by a direct text insertion of the function algorithm. This function uses the
ternary operator "conditional expression", denoted by two symbols: a
question mark and a colon. All this is necessary for more efficient translation
of the program into machine instructions.
The scomp function has two parameters, pointers to character strings s1 and
s2. The end of character strings is defined by a null byte. Re-
Machine Translated by Google

C language. Continuation 167

the result of the comparison is of type int: -1 if s1<s2, +1 if s1>s2, and 0 if


s1=s2.
End of example.
Example 10.6. Compiling a dictionary. The problem is that from the
text written in the file, which contains words of letters separated from each
other by other characters (not letters), it is necessary to select all the words
and write them into an array of character strings without repetitions. In this
case, it is necessary to identify uppercase and lowercase letters. Output the
result (sequence of words).
The table for efficient letter recognition is given as an array of constants
(ASCII code, CP866):
const char Y[]=
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

If Y[i]=1, then the character with code i is a letter, and if Y[i]=0, then
the character is not a letter. The dictionary compilation program is described
with the q and S parameters; after translation, it can be called on the
command line as a function with one parameter - a character string
containing the file name. The value of the q parameter must be 2. The if
statement checks that q is equal to 2, after which the file F1 named S[1] is opened.
Machine Translated by Google

168 Lecture 10

#include <string.h> void


main(int q, char *S[])
{FILE *F1;
if (q<2 || (F1=fopen(S[1],"rb"))==NULL)
printf("ERROR\n");
else
{char d1[21],*D[100],c;
int q=1,p=0,n=0,k1,i; while(q)

{if (!feof(F1)) i=fgetc(F1);


else {i=' '; q=0;}
c=i;
if (p==0) {if (Y[i]) {d1[0]=c; k1=1; p=1;}}
else if (Y[i]) {d1[k1]=c; k1++;}
else
{d1[k1]=0; p=0; i=0;
while (i<n && scomp(d1,D[i])!=0) i++;
if (i==n)
{D[n]=new char[k1+1];
strcpy(D[n],d1); n++;
}
}
}
fclose(F1);
for(i=0;i<n;i++) printf(”%s\n”,D[i]);
}
}
The variable d1 is declared as an array of type char with a length of 21
elements, i.e. string d1 can contain up to 20 characters. The variable D is declared
as an array of pointers to the char type with a length of 100 elements.
If the file F1 was successfully opened, then in the while loop , at each step ,
the next character from the file is read into the variable i by the fgetc function ,
if the end of the file is detected by the feof function, then the character code
space is assigned to the variable i .
Further actions are determined by the value of the variable p and the
symbol c. If p=0, then the letter has not yet been found, and if at the same time
Y[i]=1, then the character read is the first letter of the next word, it
Machine Translated by Google

C language. Continuation 169

is stored in line d1. If p=1, and if Y[i]=1, then the read character is added to
the string d1. If p=1 and Y[i]=0, then a null character is added to the string
d1 - a sign of the end of the string, after which the array is searched for a
match of the word in the string d1 with the words previously placed in the
array D. If no match is found, then n is increased by 1, for the nth element of
the array D , memory is allocated, into which d1 is copied.

After the while loop completes, file F1 is closed and n


rows of array D, each line from a new line.
End of example.

10.3. C syntax

A programming language, unlike any natural language,


must be strictly unambiguous, therefore it is given by strict rules.
Syntax - formal rules that must be followed
program in some programming language.
Semantics is the meaning of individual elements of a program written in
syntax rules.
Pragmatics are the rules for converting elements of a program written in
a programming language into program elements in another.
language, such as computer commands.
Syntax is specified in the form of grammar rules, more precisely,
generative grammar. One of the ways to write rules is the extended generating
Backus rules, which are defined as follows:
1) each rule is written from a new line of text;
2) concept - a word taken in angle brackets < and
>; 3) C words - reserved words (for example, int, for , etc.),
used when writing a program;
4) C symbols (for example, +, /, *, =, <= , etc.) are used
when writing a program;
5) the rule begins with the concept being defined, followed by the
symbols ::= (two colons and equal, they should be read “this is”), then comes
the actual definition of the concept;
6) the definition of a concept consists of a sequence of concepts (words,
enclosed in angle brackets < and >), C words, C symbols, | (pain-
Machine Translated by Google

170 Lecture 10

vertical bar, should be read as “or”), characters { } (large curly braces), characters
[ ] (large square brackets);
7) if two parts of the definition are separated by the symbol |, then for a specific
of the new variant of the concept being defined, only one of these parts is used;

8) if a part of the definition is taken in brackets { }, then it is considered


as a whole;

9) if part of the definition is taken in brackets [ ], then in a specific variant


of the defined concept, it may be absent.
The grammar rules of the programming language are applied as follows: one
of the variants is substituted for the concept being defined.
ants of its definition, i.e. a sequence of symbols of the language is obtained, as well
as (possibly) concepts. Instead of each received concept
its definition is substituted, and so on. until there is not a single concept left in the
resulting sequence of characters. In this way
So, each concept generates, after all possible substitutions, some
part of the program in the programming language.
Among all the concepts of the language, the concept <program> is the main
one, any syntactically correct C program is a product of this concept. The following
are some of the grammar rules
C language.

<program> ::= {<type declaration> | <function description> |


<description of objects>} [<program>]
<name> ::= <name> {<letter> | _ | <number>} | {<letter> | _ }
<type description> ::= {typedef <type> <type name> ; |
struct <structure name> {<structure fields>};}
<structure fields> ::= <object description> [<structure fields>]
<function description> ::= <type> <function name>
({<argument list>[,...]| void}) {<block> | ; }
<argument list> ::= [<argument list>,] <type> <object>
<description of objects> ::= <type> <list of objects> ; |
<function pointer>
Machine Translated by Google

C language. Continuation 171

<list of objects> ::= [<list of objects>,] <object> [ = <value>]


<object> ::= { * } <object> { [{<value>|<empty>}] }|
<name> |(<object>)
<function pointer> ::= <type> (* <object>)
({<list of argument types>[,...]| void})[ = <value>];
<list of argument types> ::= [<list of argument types>,] <type>
<block> ::= {<statement list>}
<statement list> ::= <statement> [<statement list>]
<operator> ::= <object description> | <block> | <expression>; |
if (<expression>)<operator> [ else <operator>] |
switch (<expression>)<block> |
while (<expression>)<statement> |
for (<expression>;<expression>;<expression>)<operator> |
do <block> while (<expression>);|
return [<expression>] ;| break;

For example, consider the rule for the concept <name>. One of the variants of
its definition is a letter (capital or lowercase Latin) or underscore. Another definition
is a name followed by a letter or number or underscore. In Russian, the rule for the
<name> concept can be written as follows: a name is a sequence of letters,
numbers, and underscores beginning with a letter or underscore.

The considered grammar rules do not completely specify the C language, since
as not all concepts have their definitions.

Questions and tasks

1. Write and debug a C program that contains a description of a function that implements
merge sort for lists. The program must enter the size of the list, enter values for the
elements of the list and form a list from them, call the sort function, and display the
list after sorting. Create tests for the program using the black and white box method
and conduct testing.
Machine Translated by Google

172 Lecture 10

2. Write and debug a C program that enters the name of an input file, then opens that file for input,
enters n integers from the file , then n numbers, sums them up, and outputs the sum. Create
tests for the program using the black and white box method and conduct testing.

3. Write and debug a program in C that contains a description of the function for comparing two
strings with recoding, enters character by character text of arbitrary length containing words
from Russian letters separated by delimiters, at the end of the text is the '#' symbol, and
which in the process input selects all the words and builds a dictionary of those words as an
array of strings. In the dictionary, all words must be different (with the identification of uppercase
and lowercase letters). It is assumed that the number of different words in the text is not more
than 1000. Create tests for the program using the black and white box method and conduct
testing.
4. What does the C translator check, syntax or semantics? What happens to a C program if there
are no syntax errors in it? Is such a program correct?

5. Which of the names are correct and which are not, and why:
f, 3f, _32, _abc, 3_a, b11110
6. In which case in the if statement before the else should there be a semicolon, and when -
shouldn't, and why?
Machine Translated by Google

Lecture 11
Algorithms of linear algebra. Vectors and matrices

11.1. Addition and multiplication of vectors and matrices

Vectors in programs are represented as one-dimensional, and matrices are


two-dimensional arrays. If the memory for such arrays is allocated
dynamically, i.e. while the program is running, the type of variables for one-
dimensional arrays must be set as references, and for two-dimensional arrays -
like links to links.

Example 11.1. Setting in the program a vector X of n elements, and a


matrix M of n rows and k columns:
void main(void)
{int n, k, i, j;
float *X, **M;
scanf("%d%d",&n,&k);
X = new float[n]; for (i=0;
i<n; i++)
scanf("%f",&X[i]);
M = new *float[n]; for (i=0;
i<n; i++)
M[i] = new float[k];
for (i=0; i<n; i++)
for (j=0; i<k; k++) scanf
("%f",&M[i][j]);
. . .
}

First, the sizes of the arrays n and k are entered, memory is allocated
for an array X of n elements of type float, and n values are entered for all
n elements of the array X in a loop . If increased precision of calculations
with real numbers is required, then the type should be double.
Then memory is allocated for an array M of n elements of float reference type,
and in a loop for each element M[i] memory is allocated for k
Machine Translated by Google

174 Lecture 11

float elements . After that, n*k values are entered in a double loop for all elements of
M[i][j].
At the end of the program, when the arrays are no longer needed, the
allocated memory for them can be freed:
for (i=0; i<n; i++)
delete[] M[i];
delete[]M;
delete[]X;

End of example.
Operations on vectors and matrices. Addition and various types of
multiplication of vectors and matrices are realized directly by the definition of
the corresponding operations.
Example 11.2. Calculation of the sum of vectors (arrays) A and B of
dimension n. The result is a vector (array) C:

for (i=0; i<0=n; i++)


C[i]=A[i]+B[i];

End of example.
Example 11.3. Calculation of the sum of matrices (two-dimensional arrays) A
and B of dimension n×m. The result is a matrix (two-dimensional array) C dimensionally
n × m:

for (i=0; i<n; i++)


for (j=0; j<m; j++)
C[i][j]=A[i][j]+B[i][j];

End of example.
Example 11.4. Calculation of the scalar product of vectors (arrays) A
and B of dimension n. The result is the variable d.
d=0;
for (i=0; i<n; i++)
d+=A[i]*B[i];

End of example.
Machine Translated by Google

Algorithms of linear algebra. Vectors and matrices 175

Example 11.5. Calculation of the product of a row vector (one-dimensional


array) A of dimension n and a matrix (two-dimensional array) B of dimension
n×m. The result is a row vector (one-dimensional array) C of dimension m:

for (j=0; j<m; j++)


{d=0;
for (i=0; i<n; i++)
d+=A[i]*B[i][j];
C[j]=d;
}

In the inner loop, the scalar product C[j] is calculated


row torus A and jth column vector of matrix B.
End of example.
Example 11.6. Calculation of the product of a matrix (two-dimensional
array) A of dimension n×m and a column vector (one-dimensional array) B of
dimension m. The result is a column vector (one-dimensional array) C dimensionally
sti n:

for (i=0; i<n; i++) {d=0;

for (j=0; j<m; i++)


d+=A[i][j]*B[j];
C[i]=d;
}

In the inner loop, the scalar product C[i] of the i-th


row vector of matrix A and column vector B.
End of example.
Example 11.7. Calculating the product of a matrix by a matrix:

for (i=0; i<n; i++)


for (j=0; j<k; j++) {d=0;

for (q=0; q<m; q++)


d+=A[i][q]*B[q][j];
C[i][j]=d;
}
Machine Translated by Google

176 Lecture 11

An n×m matrix A is multiplied by an m×k matrix B. The result is an n × k matrix


C: In the inner loop , C[i][j] is calculated, which is the scalar product of the i-th row
vector of matrix A and the j-th column vector of matrix B.

End of example.

The complexity of the calculations in Examples 11.2–11.7 is determined


by the number of repetitions of the innermost loop. In particular, the complexity
of calculating the product of matrices is of the order O(n k m), and for the square
3
matrices – O(n ).
For square matrices, along with the product operation, the operation of raising a
matrix to a positive integer power is defined. To raise to the pth power, it is enough to
multiply the matrix by itself (p – 1) times. For large p , a more efficient algorithm can
be used, which is suitable for raising any objects to an integer power. It is only required
that the multiplication operation for these objects has the associativity property (the
commutativity property is not required).

Example 11.8. Raising a number (object) x to a positive integer power p. The


result is a number (object) z:

z=1; s=p; y=x;


while (s>0)
{if (s&1) {s--; z*=y;}
s>>=1; y*=y;
}

This program differs from the program in Example 2.10 only in that it is written in
C. The result of the condition in the if statement is 1 (which is true) if s is odd, and 0
if s is even. Shift right by 1 with assignment (>>=) for a non-negative value of s is
equivalent to dividing s by 2.

Loop precondition: z=1, s=p, y=x; postcondition: s=0.


s = x p.
Invariant: z*y
p.
After the cycle ends z = x
The total number of multiplications calculated in Example 2.10 does not exceed
2ÿ(1 + ÿlog2 pÿ), however, in this algorithm, for any p ,
Machine Translated by Google

Algorithms of linear algebra. Vectors and matrices 177

This is two multiplications more than necessary. One extra multiplication occurs when z equal to 1
is multiplied by y. The second extra multiplication is performed when, at the last step of the loop , s
is equal to 1, y is raised to
square, but y is not used further.
If the complexity of multiplication is high, then it is advisable to use an
improved version of the program, the number of multiplications in which
does not exceed 2ÿÿlog2 pÿ:
s=p; y=x;
while (!(s&1)) {s>>=1; y*=y;}
z=y; s--;
while (s>0)
{if (s&1) {s--; z*=y;}
s>>=1; if
(s>0) y*=y;
}

In the first loop, while s is even, s is divisible by 2 and y is quad


rat. In the second loop , y is squared if s is greater than zero. End of example.

Example 11.9. Raising a square matrix X to a positive value


numerical power p, dimension - n, result - matrix Z:

Copymat(X, Y, n); s=p;


while (!(s&1))
{s>>=1; Multimat(Y,Y,C,n); Copymat(C, Y, n);}
Copymat(Y,Z,n); s--;
while (s>0) {if
(s&1) {s--;
Multimat(Z, Y, C, n); copymat(C,Z,n);}
s>>=1;
if (s>0)
{Multmat(Y,Y,C,n); Copymat(C, Y, n);}
}

The difference from the improved version of the program from Example 11.8 is that here the
object to be exponentiated is a square matrix, so
Machine Translated by Google

178 Lecture 11

matrix multiplication is performed by the Multmat function, and copying is


copymat function :

void Copymat(float **A, float **B,int n)


{int i,j;
for(i=0;i<n;i++)
for(j=0;j<n;j++) B[i][j]=A[i][j];
}
void Multmat(float **A, float **B,
float **C,int n)
{int i,j,q; float d;
for (i=0; i<n; i++)
for (j=0; j<n; j++) {d=0;

for (q=0; q<n; q++)


d+=A[i][q]*B[q][j];
C[i][j]=d;
}
}
2
The complexity of one copy of the matrix is O(n ), so the total
the complexity is determined by the number of multiplications and additions in
3
the innermost loop of the Multmat function, equal to, multiplied
n by the number
3
number of matrix multiplications, i.e.: 2ÿÿlog2 pÿÿn .
End of example.

11.2. Solving systems of linear equations

The system of linear equations can be represented in matrix form:


ÿ
ÿ

Axb
ÿÿ
,
ÿ

where A is a square matrix of equation coefficients; x – vector table


ÿ

betz unknown; b is the column vector of the right parts.


The only solution to such a system exists if the matrix A is nondegenerate, i.e. its
determinant is not equal to zero. There are many methods for solving systems of
linear equations. The Gauss method consists in the fact that at first the matrix is
reduced to a triangular one, i.e. one that has
Machine Translated by Google

Algorithms of linear algebra. Vectors and matrices 179

all elements below the main diagonal are zero. This is done by adding one of the
equations to the other, multiplied by a specially selected coefficient, as a result,
the coefficient for one of the variables becomes zero, and this process (forward
move) continues until a triangular matrix is obtained. Further, the elements lying
above the main diagonal are zeroed in the same way (this stage is called the
reverse move). As a result, the matrix becomes diagonal, and the unknowns are
calculated from it by dividing the right side by the corresponding diagonal
coefficient on the left side of the equation. Let us consider the intermediate stage
of the forward move, when it is required to set the coefficients ai + 1,i , ai + 2,i , …,
an,i to zero (in the formula i = 3):

ÿ aaa 11 12 13 ... a ÿ ÿx ÿ ÿb ÿ
one n one one

0 ... a x b
ÿ ÿ ÿ ÿ ÿ ÿ

aa 22 23
ÿ
2n ÿ ÿ
2 ÿ ÿ
2 ÿ

ÿ 00 a 33 ... a ÿ
ÿ
ÿ x ÿ
ÿ
ÿ b ÿ

3n 3 3
ÿ ÿ ÿ ÿ ÿ ÿ

ÿ ÿ ÿ ÿ ÿ ÿ
ÿ ÿ ÿ ÿ ÿ ÿ

ÿ 00 a ... a ÿ ÿx n ÿ ÿb n ÿ
ÿ ÿ ÿ ÿ ÿ ÿ

n3 nn

To do this, from the k-th equation, k = i + 1, …, n, it is necessary to subtract


the i-th equation, multiplied by the coefficient . This
c ÿ / gives
, ,rise to ak i ai i

the following problem: the divisor ai,i may turn out to be equal to zero, and then
the coefficient c cannot be calculated. Moreover, if this coefficient is not equal, but
close to zero, then the result (unknown x1, ..., xn) will be calculated with large
errors. The point is that calculations over real numbers in a computer are performed
approximately, i.e., with rounding errors, and in this case the errors increase many
times over. To solve this problem, in the Gauss method, the leading (having the
maximum absolute value) element is selected among the matrix coefficients that
have not yet been processed. The easiest way is to choose it from ai,i , ai + 1,i ,
…, an,i . So that the solution of the system of equations does not change becauseof
this, it is necessary to interchange the row with the selected element with the i-th
row of the matrix (having also exchanged the corresponding coefficients of the
ÿ

vector b ).
Example 11.10. The program implements the Gaussian method with the
choice of the leading element in the column. Array A of dimension n×(n+1) in the first n
columns contains coefficients for unknowns, and in the last column -
Machine Translated by Google

180 Lecture 11

ce are the coefficients of the right side of the equation. The result is an array X of n
unknown.

Forward move - bringing the matrix to a triangular shape:


for (i=0; i<n-1; i++)
{v=i; /*choose leading element:*/
for (j=i+1; j<n; j++) if (abs(A[j]
[i])>abs(A[v][i])) v=j;
if (v!=i) for /*permutation of the i-th equation with v-m*/
(j=i; j<=n; j++)
{z=A[i][j]; A[i][j]=A[v][j]; A[v][j]=z;}
for (k=i+1; k<n; k++) /*subtraction of equations*/
{c=A[k][i]/A[i][i];
for (j=i; j<=n; j++)
A[k][j]-=c*A[i][j];
}
}

Reverse move - bringing the matrix to a diagonal form:


for (i=n-1; i>=1; i--)
{for (k=0; k<i-1; k++) /*subtraction of equations*/
{c=A[k][i]/A[i][i];
A[k][n]-=c*A[i][n];
A[k][i]=0;
}
}
Calculation of unknowns:

for (i=0; i<n; i++)


X[i]=A[i][n]/A[i][i];

Let's estimate the complexity of the program. At the stage of the forward stroke in the cycle for i
three actions are performed: 1) selection of the leading element, 2) permutation of
equations, 3) subtraction of equations. The number of executions of the innermost
loops in these actions in all loop steps for i , respectively:
2
T1(n) = (n – 1) + … + 1 = nÿ(n – 1)/2 ÿ n /2,
2
T2(n) = n + (n – 1) + … + 1 = nÿ(n + 1)/2 ÿ n /2,
Machine Translated by Google

Algorithms of linear algebra. Vectors and matrices 181

3 2 3
T3(n) = (n – 1)(n + 1) + (n – 2) n + … + 1ÿ3 = (2n +3n – 5n)/6 ÿ n /3.
Number of executions of the inner loop during the reverse stage:
2
T4(n) = n + (n – 1) + … + 1 = nÿ(n + 1)/2 ÿ n /2.
Number of cycle steps at the stage of calculating unknowns:
T5(n) = n.
Thus, the total complexity of the entire algorithm is of the order
3
O(n ), and the main contribution to the complexity is made by the subtraction of the equations
3
in the forward stroke, the number of which is approximately equal to n /3.
End of example.

Example 11.11. The function of solving the system of equations implements the
Gauss method. In the program in Example 11-10, the forward step assumes that
each time A[v,i] is selected as the pivot, a non-zero value is obtained. If this is not
the case, then the subsequent division into it will cause an emergency termination of
the program execution. Moreover, if the leading element is not equal, but close to
zero, then due to the accumulation of rounding errors in calculations over real
numbers
the result obtained (the value of the unknowns) may be far from expected and cannot
be trusted. To detect such a situation, the following check is performed in the system
function:

float eps=0.000001;
int system(int n, float **A, float *X)
{ int i, j, k, v; for (i=0;
i<n-1; i++) { /*select pivot A[v]
[i]*/
if (abs(A[v][i])<eps) return 0;
else
{/*permutation of the i-th equation with v-m*/
/*subtraction of equations*/
}
}
/*reverse*/
/*calculation of unknowns*/
return 1;
}

Formal parameters of the system function:


Machine Translated by Google

182 Lecture 11

n is the number of equations and unknowns;


A is a reference to an array of dimensions n × (n + 1), in the first n columns
it contains the coefficients of the unknowns, and in the last column - the
coefficients of the right side of the equation;
X is the result, a reference to an array of n unknowns. The
eps global variable in the description is set to a small, near-zero value, used for
comparisons with pivots. In the system function , comments indicate actions that are
the same as those in Example 11-10. Unlike the program in Example 11-10, in the
system function, after choosing the leading element, its abso

A hard value with the given eps, and if it is less, then the function exits slowly with a
return value of 0. If the calculation reaches the end, then the function returns the
value 1.
End of example.

Example 11.12. The program enters a matrix, solves a system of equations,


displays the result:
void main(void)
{ int i, j, g, n; float **A, *X;
scanf("%d",&n);
X = new int[n]; A = new *float[n];
for (i=0; i<n; i++)
{A[i]= new float[n+1];
for (j=0; j<=n; j++) scanf("%f",&A[i][j]);
}
g=system(n,A,X); /* Call the solve system function */
i<n; i++) printf("%8.3f",X[i]);
/* There is only one solution */ if (g) { for (i=0;

printf("\n");
}
else printf("ERROR\n");
}

First, the dimension of the system of equations n is introduced, after which


the memory is allocated for the vector of unknowns X and for the array of pointers
to the rows of the matrix A. Then, in the loop, the memory is allocated for the (n +
1)th element in each row of the matrix and the coefficients of the matrix row are entered
Machine Translated by Google

Algorithms of linear algebra. Vectors and matrices 183

(including the vector element of the right side of the system of equations). After
that, the system function is called to solve the system of equations and the
value of g returned by the function is checked . If g is equal to 1, then the values
of the unknowns calculated in the array X are displayed in the loop , otherwise
an error message is displayed.
End of example.

Questions and tasks

1. Write a function for calculating the sum of two vectors, a function for calculating the
scalar product of two vectors, and a function for calculating the product of a vector and
a scalar. Write examples of their calls. What is the complexity of these functions and
why?
2. Write a function for calculating the sum of two matrices. Write an example of calling it.
What is its complexity and why?
3. Write a function for calculating the product of a matrix and a scalar. Write an example
her call. What is its complexity and why?
4. Write a function for calculating the product of a vector and a rectangular matrix. Write an
example of calling it. What is its complexity and why?
5. Write a function for calculating the product of a rectangular matrix by a vector. Write an
example of calling it. What is its complexity and why?
6. Write a function for calculating the product of two rectangular matrices. Napi
give an example of calling it. What is its complexity and why?
7. Write a function that calculates the product of a rectangular matrix and its transposed one.
Write an example of calling it. What is its complexity and why?

8. Write a program that contains a description of the function of raising a square matrix to a
positive integer power, counting the number of matrix multiplications. The program must
input the matrix and degree, call the function, and output the result. Create tests for the
program using the black and white box method and conduct testing. What is its complexity
and why?
9. Write a function for solving a system of linear equations with the following parameters: 1)
the dimension of the system, 2) the square matrix of the coefficients of the equation, 3)
the vector of right-hand sides, 4) the vector of calculated unknowns. Write an example
of calling it. What is its complexity and why?
10. How do the laboriousness of calculating the product of two square matrices compare
and solutions to a system of linear equations of the same dimension, and why?
Machine Translated by Google

Lecture 12
Algorithms of linear algebra. Continuation

12.1. Algorithms with square matrices

Let's consider two most important operations on square matrices -


calculation of the determinant and matrix inversion.
Calculation of the determinant of a square matrix. As is known from
linear algebra, the determinant of a square matrix does not change if
another row multiplied by an arbitrary factor is subtracted from any row. If,
however, the values of two rows of the matrix are exchanged, then the
absolute value of the determinant will not change, and its sign will change
to the opposite. Therefore, using the Gaussian method, one can reduce the
matrix to a triangular one, the determinant of which is calculated as the
product of the diagonal elements. It is also necessary to count the number
of matrix rows permuted when choosing the leading element in order to
correctly take into account the sign of the determinant.
Example 12.1. The program calculates the determinant of a square matrix using
the Gaussian method:

float eps=0.000001;
i=0; r=n; p=1; while
(i<r)
{v=i; /*choose the leading element A[v][i]*/ for (j=i+1;
j<n; j++) if (abs(A[j][i])>abs(A[v][i] )) v=j;

if (abs(A[v,i])<eps) r=i;
else
{if (v!=i) /*reverse lines*/ {p=-p;

for (j=i; j<n; j++)


{z=A[i][j]; A[i][j]=A[v][j];
A[v][j]=z;
}
Machine Translated by Google

Algorithms of linear algebra. Continuation 185

}
/*subtraction of matrix rows*/
for (k=i+1; k<n; k++) {c=A[k][i]/
A[i][i];
for (j=i; j<n; j++)
A[k][j]-=c*A[i][j];
}
i++;
}
}
/*calculation of the determinant*/
if (r<n) X=0;
else
{X=p*A[0,0];
for (i=1; i<n; i++) X*=A[i][i];
}

An n×n array A contains the coefficients of the matrix. Re


the result is calculated in the variable X.
The forward move is implemented when the while loop is executed with the i
parameter changing from 0 to r=n, if the matrix can be converted to a triangular form,
when the leading element is greater (in absolute value) eps at all steps of the loop.
If this is not so, then the cycle terminates prematurely, while r=i<n, which means that
the determinant is equal to zero. The variable p, which specifies the sign of the
determinant, changes from 1 to –1 or vice versa with each permutation of the matrix
rows.
It is easy to see that the total number of executions of the inner loop at the
stage of subtraction of equations (the most time-consuming stage) is approximately /
3
is equal to n 3, as in the program from Example 11.10.
End of example.
Calculation of the inverse matrix. According to the definition, the inverse matrix
must satisfy the equality:
AÿD = I, (*)
where A is the original square matrix; D is the inverse matrix; I is the identity matrix.
This equality can be represented as:
,
Machine Translated by Google

186 Lecture 12

ÿ
ÿ

I j is the j-th column of the identity mat


where DJ is the j-th column of the inverse matrix;
rice.
Thus, to calculate the inverse matrix, it is necessary to solve n systems of
equations with the same square matrix of coefficients, but with different right hand
sides. The complexity of successively solving n systems of equations is O(n
four

). Decide more efficiently


all n systems of equations simultaneously, transforming the matrix A into the identity
matrix, and then the identity matrix on the right side of the equation (*) is transformed
into the inverse:
I ÿD = D.

In this case, one should take into account the case when the determinant of the system is equal to
zero, and then the inverse matrix does not exist.
Example 12.2. The program calculates the inverse matrix D to the original
matrix A by the Gaussian method:

float eps=0.000001;
/*setting the identity matrix D*/
i=0; r=n;
while (i<r)
{/*choose the leading element A[v,i]*/
if (abs(A[v][i])<eps) r=i;
else {/
*permutation of matrix rows*/
/*divide the i-th row by A[i,i]*/
/*subtraction of rows of matrices*/
i++;
}
}

An n×n array A contains the coefficients of the matrix. Comments here indicate
individual actions in the program. Definition of the identity matrix D:

for (i=0; i<n; i++)


for (j=0; j<n; j++)
if (i==j) D[i][j]=1; else D[i][j]=0;
Machine Translated by Google

Algorithms of linear algebra. Continuation 187

Selecting the leading element A[v,i]:


v=i;
for (j=i+1; j<n; j++) if (abs(A[j]
[i])>abs(A[v][i])) v=j;

Permutation of rows of matrices:


if (v!=i)
{for (j=i; j<n; j++)
{z=A[i][j]; A[i][j]=A[v][j]; A[v][j]=z;}
for (j=1; j<n; j++)
{z=D[i][j]; D[i][j]=D[v][j]; D[v][j]=z;}
}

Dividing the i-th row by A[i,i]:

aii=A[i][i];
for (j=i; j<=n; j++) A[i][j]/=aii; for (j=1; j<=n; j++)
D[i][j]/=aii;

Matrix row subtraction:


for (k=0; k<n; k++)
if (k!=i)
{c=A[k,i];
for (j=i; j<n; j++) A[k][j]-=c*A[i][j];
for (j=0; j<n; j++) D[k][j]-=c*D[i][j];
}

If, after the execution of the program, the value of r turned out to be less than
n, then this means that the inverse matrix does not exist.
Due to the fact that, before subtracting the equations, the rows of matrices
A and D are divided by the diagonal element of matrix A, the diagonal
elements of matrix A become unit. In addition, unlike the program from
Example 11.10, here the subtraction of the rows of matrices is implemented in
such a way that the forward and backward steps are simultaneously performed,
in which both matrices A and D participate.
The complexity of the program, as well as the program from example 11.10,
is determined by the number of repetitions in cycles when subtracting equations.
Due to the combination of forward and reverse stroke, this number for matrix A
Machine Translated by Google

188 Lecture 12

3 3 3
/2, and for matrix D – n , those. total - 1.5ÿn
is equal to n . If rea
lize forward and reverse stroke separately, then the labor intensity can be ÿ
3 3 3
reduce to n /3 + n 1.33ÿn .
End of example.

12.2. Other Matrix Algorithms

Matrix rank calculation. The rank of a matrix is the maximum linearly


independent number of rows (columns) of a matrix. If the matrix is non-zero
and it has m rows and n columns, then its rank will be at least
than 1 and at most min(m, n).
Swapping two rows or two columns in a matrix does not change its rank.
The rank also does not change if another row is subtracted from any row,
multiplied by a non-zero coefficient. Using the Gaussian method, you can try
to bring the matrix to a triangular shape. However, the situation may arise that
some diagonal element aii, as well as all elements lying in the same column
below it, turn out to be zero. Then we can continue the transformation of the
matrix by going to the right column and using the element ai,i+1 as the diagonal
element. This situation is shown in a matrix of m rows and n columns, in which
all elements lying in the 3rd column below the element a23 turned out to be
zero, but among the elements of the 4th column a34, . . ., am4 were nonzero:

ÿ aa 11 12 a 13 a 14 a 15 a
ÿ

one n ÿ
ÿ ÿ

0 a 22 a 23 a 24 a 25 ÿ
a
ÿ
2n ÿ

ÿ 0 0 0 a 34 a 35 ÿ
a ÿ

3n
ÿ ÿ

0 0 0 0 a 45 ÿ
a
ÿ
four n ÿ

ÿ ÿ
ÿ ÿ ÿ ÿ ÿ ÿ

ÿ ÿ

0 0 0 0 am ÿ
a
ÿ
ÿ
5 mn ÿ ÿ

In the matrix at this step of calculations, three linearly independent


hanging lines, i.e. her rank will be at least three.
By transforming the matrix in this way, one can keep track of the number
of linearly independent rows.
Machine Translated by Google

Algorithms of linear algebra. Continuation 189

Example 12.3. The program calculates the rank of matrix A using the Gaussian method:

float eps=0.000001;
for (r=0,i=0; (i<n)&&(r<m); i++)
{v=r; /*choose leading element: A[v][i]*/
for (j=r+1; j<m; j++) if (abs(A[j]
[i])>abs(A[v][i])) v=j;
if (abs(A[v][i])>=eps)
{if (v!=r) for /*permutation of the rth row with the vth*/
(j=i; j<n; j++)
{z=A[r][j]; A[r][j]=A[v][j]; A[r][j]=z;}
for (k=r+1; k<m; k++) {c=A[k] /*subtract rows*/
[i]/A[i][i];
for (j=i; j<n; j++) A[k][j]-=c*A[i][j];
}
r++;
}
}

Array A with dimension m×n contains matrix coefficients. The variable r keeps
track of the number of linearly independent rows, at the end of the calculation this will
be the rank of the matrix. When searching for a pivot element in the i-th column of the
matrix, it is checked that the pivot element is greater than a given small value eps,
otherwise it is considered zero. r=n. Choice of leading element
The complexity of the program for the case m ÿ n ,
menta:

T1(n,m) = (m – 1) + (m – 2) + … + (m – n + 1) =
2
= (2m – n) (n – 1)/2 ÿ mÿn – n /2.

String permutation:
2
T2(n) = n + … + 1 = (n + 1)ÿn / 2 ÿ n /2.

Row subtraction:
2 3
T3(n,m) = m n + (m – 1) (n – 1)… + (m – n + 1) 1 ÿ m n /2 – n /6.

General workload:
2 3
T(n,m) ÿ m n /2 – n /6.
Machine Translated by Google

190 Lecture 12

Total labor input for m = n:

T(n) ÿ n3 /3.
End of example.

General solution of a system of m equations and n unknowns. In this case,


the coefficient matrix A is rectangular, consists of m rows and n
columns. There are three possible solutions for such a system:
1) the system has a unique solution if and only if the rank r of the matrix A is
equal to n and the “extra” m – r equations (if any) are linear combinations of the
remaining r equations;
2) the system is inconsistent and has no solution if the “extra” m – r equations
are not linear combinations of the remaining r equations;

3) the system has infinitely many solutions if the rank r of the matrix A is less than
n, and the “extra” m – r equations are linear combinations of the remaining r equations.
In the latter case, the “extra” n – r unknowns can be considered independent and an
arbitrary set of values can be set for them, each time obtaining a new solution for
r “basic” unknowns.

The algorithm for obtaining a general solution must bring the matrix A to such a
form that the system of equations is as follows:

ÿ ten 0 a a ÿ ÿ b ÿ
1,r ÿ1
ÿ ÿ

one,
n one

ÿ ÿ ÿ ÿ

01 ÿ 0 a ÿ a b
2, r1ÿ 2, n 2
ÿx
ÿ ÿ ÿ ÿ

ÿ
ÿ ÿ ÿÿÿ ÿ ÿ ÿ
one

ÿ ÿ ÿ

ÿ ÿ

ÿ ÿ
x ÿ ÿ

00 a a 2
ÿ one ÿ
ÿ ÿ ÿ
ÿ
b
1rr, ÿ _ 2, n ÿ

ÿ ÿ ÿ
ÿ
r ÿ

ÿ
00 ÿ 00 ÿ 0 ÿ

ÿ ÿ
ÿ
b ÿ

rÿ
ÿ ÿ x one

ÿ nÿ
ÿ ÿ

ÿ ÿ ÿ ÿ ÿ ÿ
ÿ ÿ ÿ ÿ

00 00 0
ÿ

b
ÿ ÿ

ÿ ÿ

ÿ ÿ m ÿ

To do this, when choosing the leading element ai,i, it is necessary to look through
all the elements of the matrix A that lie in rows from the ith to the nth and in the
columns from the ith to the nth, performing the subsequent permutation of both two
rows and two columns. Then the solution variant of the system is determined by the
following checks:
Machine Translated by Google

Algorithms of linear algebra. Continuation 191

1) the system has a unique solution if the rank r = n and the coefficients of the
right side br + 1, …, bm (if they exist) are errors);
rounding all equal to zero (or close to zero due to

2) the system has no solution if at least one of the coefficients of the right side
br + 1, …, bm is not equal to zero (or is not close to zero);
3) the system has infinitely many solutions if the rank r < n and the coefficients
of the right side br + 1, …, bm (if they exist) are all equal to zero
(or close to zero due to rounding errors).
In the first case, the values of the unknowns are equal to the first n coefficients
of the right side:
x1 = b1, …, xn = bn .

In the third case, the unknowns xr + 1 , …, xn are considered independent, and the unknowns
x1 , …, xr are calculated through them according to the formulas:

x1 = b1 – a1,r + 1ÿxr + 1 – … – a1,nÿxn ,



xr = br – ar,r + 1ÿxr + 1 – … – ar,nÿxn .

Example 12.4. The program for calculating the general solution of the system of
equations:

/*set index array L*/


i=0;
if (n<m) r=n; else r=m;
while (i<r) {/
*choose the leading element A[v][u]*/
if (abs(A[v][u])<eps) r=i;
else {/
*permutation of equations*/
/*column swapping*/
/*divide the i-th row by A[i][i]*/
/*subtraction of equations*/
i++;
}
}
/*checking the solution of the system of equations*/
Machine Translated by Google

192 Lecture 12

The array A with dimensions m×(n+1) contains the coefficients of the


unknowns and the vector of the right parts of the equations in the last
column. Array L is an index array for enumeration of unknowns. Array X is
an array of computed values of unknowns. Comments here denote individual
actions in the program.
Specifying an index array L:

for (i=0; i<n; i++) L[i]=i;

Selecting the leading element A[v][u]:


v=i; u=i; for
(j=i; j<m; j++)
for (k=i; k<n; k++)
if (abs(A[j][k])>abs(A[v][u])) {v=j; u=k;}

Rearrangement of equations:
if (v!=i) for
(j=i; j<=n; j++)
{z=A[i][j]; A[i][j]=A[v][j]; A[v][j]=z;}

Column permutation:
if (u!=i)
{ for (k=0; k<m; k++)
{z=A[k][i]; A[k][i]=A[k][u]; A[k][u]=z;}
p=L[i]; L[i]=L[u]; L[u]=p;
}

Dividing the i-th row by A[i][i]:

c=A[i][i]; for
(j=i; j<=n+1; j++) A[i][j]/=c;

Subtraction of equations:
for (k=0; k<m; k++)
if (k!=i)
{c=A[k][i];
for (j=i; j<=n; j++) A[k][j]-=c*A[i][j];
}
Machine Translated by Google

Algorithms of linear algebra. Continuation 193

Checking the solution of the system of equations:

i=r;
while (i<m && abs(A[i][n])<eps) i++;
if (i<m) {/*system solution does not exist*/} else if (r==n) /
*system solution is unique*/
{for (j=0; j<n; j++) X[L[j]]=A[j][n];}
else
{/* setting values for independent variables:
X[L[r]], ..., / X[L[n-1]]*/
*calculation of dependent variables:*/
for (j=0;j<r;j++)
{X[L[j]]=A[j][n];
for (k=r;k<n;k++)
X[L[j]]-= A[j][k]*X[L[k]];
}
}

Here the leading element is searched not in one i-th column of the matrix
A, but in the rectangular part of the matrix, from the i-th to the m-th row, and
from the i-th to the n-th column inclusive. And if it turns out that the leading
element is close to zero (thus, the first r rows and r columns of the matrix A
contain the identity submatrix), then the while loop terminates ahead of
schedule. When the columns are rearranged, the unknowns are renumbered
by exchanging the values in the index array L. The equations are subtracted in
such a way that the forward and reverse moves are combined.
After the while loop completes, it checks that the elements A[m][n] are close to
on the right hand side: A[r][n], ..., If it turns out that at least
zero.
one
elements
of them of
is the
greater
vector
(in
absolute value) eps, then the system of equations is inconsistent and has no solution.
If the check was successful, then two options are possible:

1) r=n, then the system has a unique solution, the values are unknown
known are calculated in the vector of the right side of the equations;
2) r<n, then the system has an infinite number of solutions, the unknowns
are divided into two groups: a) independent, they can be assigned any values,
their numbers are from r to n-1; b) dependent, are calculated through the
values in the vector of the right side of the equations with numbers from 0 to r-1.
Machine Translated by Google

194 Lecture 12

If all independent unknowns are set to zero, then


the dependent unknowns will be equal to the elements of the vector of the right parts.
The complexity of the program is determined by two actions: the choice of the
leading element and the subtraction of equations at all steps of the while loop. Other
actions have a much lower order of complexity: O(n
2
) or O(mÿn).
Leading element selection:

T1(n,m) = m n + (m – 1) (n – 1) + + … + (m – n + 1) 1 =
2 2
…/3+1
= n (n – 1)2 + ÿ n3 + (m+ –(mn)–nn) n + / … + (m – n) 1 ÿ
2 2 3
/2 = m n 2 – n /6.

Subtraction of equations:
2
T2(n,m) = m (n + 1) + m n + … +m2ÿmn /2.
2 3
Total labor input: T(n,m) ÿ m n – n /6.
3
Total labor intensity at m = n: T(n) ÿ 5/6 n .

End of example.

Questions and tasks

1. Write a function for calculating the determinant of a square matrix. Write at


measures to call it. What is its complexity and why?
2. Write a function for calculating the inverse matrix to a given square matrix. Write an example
of calling it. What is its complexity and why?
3. Write a program that enters a square matrix, calculates the inverse matrix, and calculates the
product of the original matrix by the inverse. Create tests for the program using the black
and white box method and conduct testing. What is its complexity and why?

4. Write a program that enters a rectangular matrix and calculates its rank. Create tests for the
program using the black and white box method and conduct testing. What is its complexity
and why?
5. Write a program that calculates the general solution of a system of n linear equations with m
unknowns using the Gaussian method. What is its complexity and why?
6. How do the complexity of calculating the determinant of a square matrix,
rank of the same matrix and calculating the inverse of a matrix of the same dimension, and
why?
Machine Translated by Google

Lecture 13
Counts. Start

13.1. Representation of graphs in the program

A graph is defined by two sets: a set of vertices and a set of edges (arcs).
An edge (arc) is defined by a pair of vertices (which it connects). There are
different types of graphs:
1) undirected graphs, in such graphs, for edges, the order of specifying
vertices in a pair is not important, if vertex i is connected by an edge to vertex
j, then this is the same as vertex j is connected by an edge to vertex i;
2) directed graphs, or digraphs, in such graphs, the arc determines the
order in which the vertices in a pair are specified; if there is an arc from vertex
i to vertex j, then the arc from vertex j to vertex i may or may not exist; loops
can also exist in such a graph, i.e. arcs from a vertex to itself;

3) weighted undirected graphs, in which each edge is assigned a weight;

4) weighted directed graphs, in which each arc is given a weight;

In addition, there are also multigraphs in which the same


ru vertices can be connected by several different edges (arcs).
A relation on a finite set can be represented as a directed graph, so
problems for relations can be reformulated as problems for digraphs.

Representation of a graph by a list of edges (arcs). To specify the set


of graph vertices, it suffices to indicate their number, assuming that the
vertices are renumbered by natural numbers 1, 2, …, n. In arcs
a directed
or in angraph,
undirected graph, edges can be specified by a list (array) of pairs of vertices,
and for a directed graph, the order in which the vertices in each of the pairs
are specified is important. If the graph is weighted, then additionally, with each
pair of vertices, the weight of an edge (arc) is specified. This method is usually
used to store information about a graph, but at the same time it does not
Machine Translated by Google

196 Lecture 13

too efficient for use in algorithms that have to look at vertices and edges multiple
times.
Representation of a graph in graphical form. For clarity, the graph can be
depicted on a sheet of paper: graph vertices as dots or circles, edges in an
undirected graph as lines connecting pairs of vertices, arcs in a directed graph as
lines with arrows showing the order in which the vertices are connected. In this
case, the coordinates of the vertices do not matter, the vertices can be depicted
in any place on a sheet of paper. Lines can be straight or curved segments; line
intersections are not taken into account. A graph that can be drawn so that lines
(edges or arcs) do not intersect is called planar.

Representation of a graph in the form of an adjacency matrix (incidence


matrix). The edges (arcs) of a graph can be specified as a square adjacency
matrix whose element Mij = 1 if there is an edge connecting vertices i and j (or an
arc going from vertex i to j), and equal to zero otherwise. For an undirected graph,
the adjacency matrix is symmetric (Mij = Mji), since each edge is represented by
two elements of the matrix. In a weighted graph, the matrix element is equal to
the weight of an edge (arc) or a special value (infinity) if there is no edge (arc)
between the corresponding vertices.

To view all vertices that are adjacent along edges to vertex i (or vertices to
which arcs from i go), it is necessary to iterate over the elements of the i-th row of
the matrix. To view the vertices from which arcs go to vertex i, it is necessary to
enumerate the elements of the i-th column of the matrix.
Representation of a graph by lists of adjacent vertices. If the number of edges
2
graph with a large number of vertices n is significantly less than n , then in
adjacency matrix, most of the elements will be zero, and the complexity of the
algorithms that look through all the edges will be much more than the minimum
possible. So, for example, according to the Euler theorem, in a planar graph, the
number of edges is less than 3ÿn, but the complexity of viewing all the edges by
2
the adjacency matrix will be of the order of O(n ).
In this case, it is more efficient to represent the graph as an array
of n pointers and n lists of vertices such that the i-th pointer refers to the list
containing the numbers of vertices adjacent to the i-th vertex. Those. each of the
lists is a set of adjacent vertices.
Machine Translated by Google

Counts. Start 197

the representation is suitable for both directed and undirected graphs. If


the graph is weighted, then in each of the list elements it is necessary to
store the weight of the corresponding edge (arc) in a separate field.
To view all vertices that are adjacent along edges to vertex i (or vertices to which
arcs from vertex i go), it is necessary to enumerate all elements of the i-th list.
However, to view the vertices from which arcs go to vertex i, it is necessary to
enumerate all elements in all
lists.

On fig. Figure 13.1 shows an example of an undirected graph and its


graphical representation, a list of edges, an adjacency matrix, and lists of
adjacent vertices.

1234567111
(1,2)
one

one 2 3
3 (3.1)
2 2 one 3
(2.3)
3 111 one one 2 four
four
(4.3)
four one 3
5 6 (5.6) 6
5 one

6 one 5
7 7

Rice. 13.1

Example 13.1. Defining a graph as an adjacency matrix:


int **M, i, j, n, m, k;
scanf("%d",&n);
M=new int*[n];
for (i=0;i<n;i++)
{M[i]=new int[n];
for (j=0;j<n;j++) M[i][j]=0;
}
scanf("%d",&m);
for (k=0;k<m;k++)
{scanf("%d%d",&i,&j);
M[i][j]=1; M[j][i]=1; }
Machine Translated by Google

198 Lecture 13

First, the number of graph vertices n is entered, memory is allocated for an array
of n pointers to rows of the adjacency matrix. After that, memory is allocated in the
loop for n rows of the matrix with zeroing in the rows of matrix elements. Since in C
the numbering of array elements always starts from zero, the numbering of graph
vertices will also be from zero to n-1.
Next, the number of edges of the graph m is entered , after which m pairs of numbers i
and j of the vertices of the graph are entered in the cycle , and into the matrix M of the undirected
two units are entered in the graph: in the i-th row, j-th column, and also in the j-th
row, i-th column.
If a directed graph is given, then the unit for the edge is entered into the matrix
only once, in the i-th row, j-th column.
2
The complexity is of the order O(n ), since m ÿ n.
End of example.

Example 13.2. Defining a graph as an array of lists:

struct el {int s; struct el *p;};


struct el *S[], *ps; int i, j, n, m, k;
scanf("%d",&n);
S=new *struct el[n];
for (k=0;k<n;k++) S[k]=NULL; /*zero pointers*/
for (k=0;k<m;k++) /*formation of lists*/
{ps=new *struct el;
scanf("%d%d",&i,&j); /* kth edge - (i,j)*/ ps->s=j; ps-
>p=S[i]; S[i]=ps;
/*formation of the 2nd element for an undirected graph:*/
ps=new *struct el;
ps->s=i; ps->p=S[j]; S[j]=ps;
}

First, the number of graph vertices n is entered, memory is allocated for the array
S of n pointers to lists. After that, pointers in the array S are set to zero in the loop.
Then lists of numbers of adjacent vertices are formed. For an undirected graph, two
list elements are formed for each edge, and for a directed graph, one element for each
arc. The complexity of both versions of the program is of the order O(n + m).

If the lists need to be ordered, then when filling the lists, it is necessary to count
their lengths in an integer array L , and then
Machine Translated by Google

Counts. Start 199

An additional step is to sort each of the lists separately.


If the graph is weighted, then one more field must be provided in the el structure -
the weight of the edge.
End of example.

Representation of a graph by an array of numbers of adjacent vertices. If


the lists of numbers of adjacent vertices are placed tightly in a row, in an array
common to all lists, we get the structure shown in Fig. 13.2.

1 2 3 4 5 6 7 8 9 10 2 3 1 3
LS D 124365
2 one

2 3
3 5
one eight

one 9
one ten
0 eleven

Rice. 13.2

Here, all lists are placed in an integer array D, the size of which for a directed
graph is equal to the number of edges, and for an undirected one, twice the number
of edges. These lists can be ordered to speed up searching. The array S contains
the indexes of the beginning for each of the lists, and the array L contains the lengths
of the lists. The length of arrays S
and L is equal to the number of graph vertices. If the graph is weighted, then another
array is needed (of the same length as array D) with edge weights.
To determine whether there is an edge (arc) connecting vertices i and j, it is
necessary among the elements of the i-th list:

D[S[i]], ..., D[S[i]+L[i]-1]

find the element equal to j. When ordering lists, you can use
to develop a dichotomous search algorithm.
To view all vertices that are edge-adjacent to vertex i
(or vertices to which arcs from i go), it is necessary to enumerate all elements of
the i-th list. And to view the vertices from which arcs go to vertex i, it is necessary
to enumerate all lists, i.e. all elements of array D.
Machine Translated by Google

200 Lecture 13

Example 13.3. Defining a graph by an array of numbers of adjacent vertices:

int *v1, *v2, *D, *S, *L, *U;


int i, n, m;
scanf("%d%d",&n,&m);
v1=new int[m]; v2=new int[m]; for (i=0;i<m;i+
+)
scanf("%d%d",&v1[i],&v2[i]);
D=new int[m+m]; S=new int[n];
L=new int[n]; U=new int[n];
for (j=0;j<n;j++) L[j]=0;//set list lengths to zero
for (i=0;i<m;i++) //calculate list lengths
{L[v1[i]]++; L[v2[i]]++;}
S[0]=0; //calculation of initial indexes for lists in array D
for (j=1;j<n;j++) S[j]=S[j-1]+L[j-1];
for (j=0;j<n;j++) U[j]=S[j];
//duplication of initial indices for (i=0;i<m;i+
+) //distribution of adjacent vertices
//by lists of array D
{k=v1[i]; D[U[k]]=v2[i]; U[k]++;
k=v2[i]; D[U[k]]=v1[i]; U[k]++;
}

First, the number of graph vertices n and the number of edges m are entered,
and memory is allocated to auxiliary arrays v1 and v2 of length m for entering edges.
Then, in a loop, pairs of numbers are entered into the arrays v1 and v2 , specifying
the numbers of the edge vertices. Next, memory is allocated to the arrays D, S , and
L of the graph structure, as well as to the auxiliary array U.
After that, the array L is reset to zero, then in this array, by looking at
the arrays v1 and v2 , the lengths of the lists are calculated, which will later
be placed in the array D. After that, the initial indexes of the placement of
these lists are calculated in the array S. At the final stage, the array D is
filled, while the elements are distributed among the lists in one scan due to
the duplication of the initial indices in the array U, which keeps track of free
places in the lists. It is easy to see that the complexity of all stages is of the
order O(n + m).
Lists in array D can be ordered, then additional
telny stage at which each of the lists is sorted separately.
Machine Translated by Google

Counts. Start 201

The program in the example defines an undirected graph when each edge in the
array D is represented in two lists. To specify a directed graph in the program, a
number of changes should be made:
1) allocate memory to the array D in the amount of m elements;
2) when calculating the lengths of lists in the array L , use only the array v1;

3) when distributing adjacent vertices over lists of the array D , place each edge
in only one list.
End of example.

13.2. Viewing an undirected graph

The task of viewing all the vertices of an undirected graph, taking into account
the edges connecting them with neighboring vertices, is an important step in solving
many more complex problems. There are two main methods: depth-first lookup and
breadth-first lookup. The scan can start from any starting vertex. In this case, the
vertices can receive new but
measure in viewing order.
When browsing in depth, the loop looks for an unscanned vertex adjacent to the
current one. Once such a vertex is found, the algorithm starts recursively from this
new starting vertex.

Example 13.4. Recursive lookup function deep into the graph, given
adjacency matrix:

void deep(int k)
{int i;
for (i=0;i<n;i++)
if ((M[k][i]==1)&&(R[i]==0))
{nom++; R[i]=nom;
deep(i);
}
}

A graph of n vertices is defined by an adjacency matrix M. The array R contains


the numbers of vertices in the viewing order, starting from one, with R[i]=0 if vertex i
has not yet been viewed.
Machine Translated by Google

202 Lecture 13

Before calling the procedure, all n elements of the global array R


it is necessary to assign zeros, and set the initial value of the global variable nom
equal to the number for the vertex from which the scan starts. If the view starts from
vertex number a, then the deep function call:

for (i=0;i<n;i++) R[i]=0;


R[a]=1; nom=1;
deep(a);

The termination of the function execution follows from the fact that before the
recursive call deep(i) it is checked that R[i]==0 and R[i]=nom is assigned . Therefore,
the total number of calls cannot exceed n. The recursion depth is limited to the same
value n.
If the graph is connected, then by mathematical induction it can be proved that
all its vertices will be scanned. If the graph is disconnected, then all those vertices
that can be reached along the edges from the initial vertex a will be searched, i.e., to
all vertices of the connected component, where
vertex a enters.
The complexity of the program is determined by the fact that each time the
function is called, the loop is executed n times, looking through all the elements of
2
the row of the adjacency matrix, i.e. the total complexity is of the order O(n
). Depth
recursion does not exceed n.
End of example.

Example 13.5. Recursive lookup function deep into the graph, given
th array of lists:

void deeps(int k)
{el *p1;
for (p1=S[k];p1!=NULL;p1=p1->p;)
if (R[p1->s]==0)
{nom++; R[p1->s]=nom;
deep(p1->s);
}
}

The deeps function is similar to the function in Example 13-4, the calls to the two
functions are identical. However, in contrast to the deeps function, here the graph from
Machine Translated by Google

Counts. Start 203

n vertices are given by an array of lists S, so each time the function is called
the loop is executed exactly as many times as there are adjacent vertices with vertex
k. In this case, the total execution of all cycles in all recursive calls does not exceed
twice the number of edges, i.e., the total labor capacity is of the order O(n + m) .

End of example.

Using the algorithm of viewing the graph in depth, it is possible to solve the problem of
extracting the connected components of the graph.

Example 13.6. The program for extracting the connected components of a graph from
n vertices:

for (i=0;i<n;i++) C[i]=0;


q=0;
for (i=0;i<n;i++)
if (C[i]==0)
{q++; C[i]=q;
cdeep(i);
}

The program generates the elements of array C, which determine the connectivity
components for graph vertices. The global variable q is the counter of connected
components. C[i]=q if the ith vertex belongs to the qth connected component. Initially,
all elements of the array C are assigned zeros, which means that none of the vertices
of the graph is assigned to any of the components. The outer loop in the program
finds the next vertex for which C[i]=0, assigns the number of a new component to this
vertex, and then scans the graph, starting from this vertex, assigning to all vertices of
the component (in array C) the same room. For the example of the graph shown in
Fig. 13.2, the result of calculations in array C will be as follows:

1, 1, 1, 1, 2, 2, 3.

In this program, the depth-first lookup function must be such that it has the
assignment C[i]=q before the recursive call. The modified cdeep function is shown
below. In addition, it uses another representation of the graph, in the form of an array
of numbers of adjacent vertices D:
Machine Translated by Google

204 Lecture 13

void cdeep(int k)
{int i, j;
for (i=S[k];i<S[k]+L[k];i++)
{j=D[i];
if (C[j]==0) {C[j]=q; cdeep(j);}
}
}

It is easy to see that the total complexity of the program, including time
execution of all cdeep function calls has the order O(n + m).
End of example.
In breadth-scanning, the given initial vertex is assigned a level of 1. Then, the
loop searches for all unscanned vertices adjacent to
with the current one, and they are assigned a level that is 1 greater than
the level of the current vertex. Then the next vertex among the sequence
of scanned vertices becomes current, and so on. As a result, all vertices
reachable from the initial vertex are assigned a level that is 1 greater than
the shortest distance from the initial vertex, measured by the number of
edges traversed.
Example 13.7. Breadth Viewer for a Graph of n Vertices, Given
in the form of an array of numbers of adjacent vertices:
P[0]=a; r=0; t=0; //queue of one vertex a
for (i=0;i<n;i++) V[i]=0;
V[a]=1; //level for vertex a
while (t<=r)
{k=P[t]; q=V[k]+1;
for (i=S[k];i<=S[k]+L[k]-1;i++)
{j=D[i];
if (V[j]==0) {V[j]=q; r++; P[r]=j;}
}
t++;
}

The V array contains the levels for the viewed vertices, and the P array
contains the queue of numbers of the viewed vertices. The size of these
arrays is n. Initially, the element P[0] is assigned the number of the initial
vertex a, the element V[a] is assigned one, and all other elements of the mass
Machine Translated by Google

Counts. Start 205

siva V are zeros. The variable r denotes the number of scanned vertices placed in
the queue P, and the variable t is an element in the array P containing the number
of the current processed vertex.
The processing of the current vertex P[t] consists in the fact that all unseen
vertices adjacent to it are placed in the queue P, and they are assigned a level that
is 1 higher than the level of the vertex P[t].
For a connected graph, all n vertices will end up in queue P, and for each of
them, the for loop will be executed as many times as it has adjacent vertices, so the
total complexity is of the order of O(n + m).
End of example.

Example 13.8. Breadth Viewer for a Graph of n Vertices, Given


in the form of an adjacency matrix M:

P[0]=a; r=0; t=0; //queue of one vertex a


for (i=0;i<n;i++) V[i]=0;
V[a]=1; //level for vertex a
while (t<=r)
{k=P[t]; q=V[k]+1;
for (i=0;i<n;i++)
if ((V[i]==0)&&(M[k][i]==1))
{V[j]=q; r++; P[r]=j;}
t++;
}

Unlike the program in Example 13-7, here the for loop is executed n times when
processing the current vertex , so the total time is of the order of O(n
2
).
End of example.

The breadth-scanning algorithm can be used to solve the problem of identifying


the connected components of a graph with the same complexity as when using the
depth-scanning algorithm. In addition, the breadth-first lookup algorithm calculates
along the way the shortest distances, measured by the number of edges traversed,
from a given initial graph vertex to all reachable
peaks.
Machine Translated by Google

206 Lecture 13

Questions and tasks

1. Write a program that enters: 1) the number of graph vertices, 2) the number of directed
arcs, 3) arcs are pairs of vertices. The program must generate an adjacency matrix of a
directed graph. What is its complexity and why?
2. Write a program that enters: 1) the number of graph vertices, 2) the number of undirected
weighted edges, 3) edges - pairs of vertices and weight. The program must form the
adjacency matrix of an undirected weighted graph.
What is its complexity and why?
3. Write a program that enters: 1) the number of graph vertices, 2) the number of undirected
weighted edges, 3) edges - pairs of vertices and weight. The program must generate lists
of numbers of adjacent vertices with weights of an undirected weighted graph. What is its
complexity and why?
4. Write a program that enters: 1) the number of graph vertices, 2) the number of oriented
weighted edges, 3) edges - pairs of vertices and weight. The program must form an array
of numbers of adjacent vertices with weights of an undirected weighted graph. What is its
complexity and why?
5. Write a program that converts the representation of an undirected graph from an adjacency
matrix into an array of numbers of adjacent vertices. What is its laborious bone and why?
How should the program be modified if the graph is directed?
6. Write a program that converts the representation of an undirected graph from an array of
numbers of adjacent vertices into an adjacency matrix. What is its laborious bone and
why? How should the program be modified if the graph is directed?
7. An undirected graph is defined by an adjacency matrix. Write a program that singles out the
connected components of a graph by depth-first scanning and displays the following data
for each component: 1) the number of the component; 2) the numbers of the vertices
included in it. What is its complexity and why?
8. An undirected graph is given by an array of numbers of adjacent vertices. Write a program
that selects the connected components of a graph by depth-first scanning and displays the
following data for each component: 1) the number of the component; 2) but the measure
of the vertices included in it. What is its complexity and why?
9. An undirected graph is defined by an adjacency matrix. Write a program that singles out the
connected components of a graph by a breadth scan and displays the following data for
each component: 1) the number of the component; 2) the numbers of the vertices included
in it. What is its complexity and why?
10. An undirected graph is given by an array of numbers of adjacent vertices. Write a program
that selects the connected components of a graph by scanning in breadth and displays
the following data for each component: 1) the number of the component; 2) but the
measure of the vertices included in it. What is its complexity and why?
Machine Translated by Google

Counts. Start 207

11. An undirected graph is defined by lists of numbers of adjacent vertices. Write a program
that selects the connected components of a graph by depth-first scanning and displays
the following data for each component: 1) the number of the component; 2) but the
measure of the vertices included in it. What is its complexity and why?
12. An undirected graph is given by lists of numbers of adjacent vertices. Write a program
that selects the connected components of a graph by scanning in breadth and displays
the following data for each component: 1) the number of the component; 2) but the
measure of the vertices included in it. What is its complexity and why?
13. An undirected graph is given by an array of numbers of adjacent vertices. The number
u of one of the vertices is also given. Write a program that, by breadth-first scanning for
all vertices, calculates the distance to them from the vertex u in the number of arcs
traversed. What is its complexity and why? How should the program be modified if the
graph is directed?
Machine Translated by Google

Lecture 14
Counts. Simple Algorithms

14.1. Finding the shortest path in the maze

The problem of finding the shortest path in a maze can be solved by the
breadth-wise graph scanning algorithm. For this task, there is no need to use any
special structure for the graph: the representation of the maze as a two-
dimensional array is itself a graph.
Let the labyrinth be given by a numerical two dimensional array L, which
corresponds to an n × n square matrix . The zero element of the array
corresponds to the passage in the maze, and the element with a large value (for
example, 1000) corresponds to the wall in the maze. From an arbitrary cell of the
labyrinth, you can move (if there is no wall) in four directions: right, left, up or
down. Let also the initial cell L[i0,j0] and the final cell L[ik,jk] be given. On fig.
Figure 14.1 shows an example of a labyrinth, in which cells with a wall are marked
with a '#' , the upper left cell is the beginning of the path, and the lower right cell
is the end of the path.

########

H# # # one # # #

## # # 2 ## # eleven #

# # 3 four 5 # 9 10 #

# four 5 678 9 #

## # # 5 ## eight # ten #

# To # 6 # 10 9 10 11 #

########

Rice. 14.1

In this problem, the matrix L defines the graph itself. Cells with a value of 0
(cells with spaces in Figure 14.1) define the vertices of the graph. Each of the peaks
Machine Translated by Google

Counts. Simple Algorithms 209

the graph has edges with those of the four adjacent vertices that also
contain 0. To simplify the checks that moving through the maze does not
go beyond its limits, the maze can be surrounded by walls on all sides, as
shown on the right in Fig. 14.1. The solution of the whole problem can be
performed in 4 stages: 1) data entry and formation of the maze structure;
2) marking the labyrinth; 3) tracking the shortest path along the markup; 4)
output of the result.
Example 14.1. Data entry and formation of the maze structure:
int L[22][22],i,j,n,i0,j0,ik,jk; charS[21]; scanf("%d\n",&n);

for (i=1;i<=n;i++)
{S=gets();
for (j=1;j<=n;j++) if
(S[j-1]=='-') L[i][j]=0;
else L[i][j]=1000;

} for (j=0;j<=n+1;j++)
{L[0][j]=1000; L[n+1][j]=1000;}
for (i=1;i<=n;i++)
{L[i][0]=1000; L[i][n+1]=1000;}
scanf("%d%d\n",&i0,&j0); scanf("%d%d\n",&ik,&jk);

The maximum dimensions of the labyrinth are 20×20. First, the size of the maze
n is entered, then line by line (in the character string S) the maze itself is entered,
after that the line i0 and column j0 of the beginning of the path, as well as the line ik
and column jk of the end of the path in the maze. Input data for the maze in fig. 14.1:
6
-#---#
-##-#-
---#--
------
-##-#-
-#----
eleven

66
Machine Translated by Google

210 Lecture 14

Here the symbol "-" corresponds to the zero cell, and the symbol "#" corresponds to the
cell with the wall in the labyrinth.
End of example.
Example 14.2. Labyrinth layout:

int Pi[401], Pj[401], r, t, i, j;


Pi[1]=i0; Pj[1]=j0; r=1; t=1;
L[i0][j0]=1;
while (t<=r)
{i=Pi[t]; j=Pj[t]; q=L[i][j]+1;
if (L[i-1][j]==0)
{L[i-1][j]=q; r++; Pi[r]=i-1; Pj[r]=j;}
if (L[i][j-1]==0)
{L[i][j-1]=q; r++; Pi[r]=i; Pj[r]=j-1;}
if (L[i+1][j]==0)
{L[i+1][j]=q; r++; Pi[r]=i+1; Pj[r]=j;}
if (L[i][j+1]==0)
{L[i][j+1]=q; r++; Pi[r]=i; Pj[r]=j+1;}
t++;
}

The program implements a view of the graph in breadth. Here, instead of


a single array P , two arrays are used: Pi and Pj, which determine the row
number and column number of the cell. The value of the vertex level when the
program is running is written directly to the array L, in a cell with zero. On fig.
14.1, on the right, shows the array L after the markup has been performed.
2
Note that the complexity of the program is of the ), since
orderthe
O(nloop is executed more
more than n2 than once (cannot exceed the number of cells in the maze), and
each time the loop is executed, 4 checks are made in the if statements.
End of example.
Example 14.3. Tracking the shortest path on the markup. The program
forms a path in arrays Mi and Mj from the end to the beginning. The Mi[k]
element stores the cell row number, and the Mj[k] element stores the cell
column number at the k-th step of the route. The shortest path may not be
the only one, but in any case, one of the options for the shortest path is
calculated.
Machine Translated by Google

Counts. Simple Algorithms 211

int Mi[401], Mj[401], k, i, j;


k=L[ik][jk]; i=ik; j=jk;
while (k>0) do
{Mi[k]=i; Mj[k]=j;
if (L[i-1][j]<L[i][j]) i--;
else if (L[i][j-1]<L[i][j] j--;
else if L[i+1][j]<L[i][j] i++;
elsej++;
k--;
}

It is easy to see that the number of loop executions in the program is


equal to the length of the shortest path, which in any case cannot exceed
the number of cells in the maze.
End of example.
For comparison, consider another way to find the shortest path in a
maze instead of the considered steps of marking the maze and tracking
the shortest path along the markup.
Example 14.3. Finding the shortest path in a maze using the backtracking
algorithm:
int Mi[401], Mj[401], kmin=1000;
void Lab(int i,int j, int k)
{if (i==ik && j==jk)
{kmin=k; REMEMBER PATH} else
if (k<kmin)
{if (L[i-1][j]==0)
{L[i-1][j]=1; Mi[k]=i-1; Mj[k]=j;
Lab(i-1,j,k+1);
L[i-1][j]=0;
}
if (L[i][j-1]==0)
{L[i][j-1]=1; Mi[k]=i; Mj[k]=j-1;
Lab(i,j-1,k+1);
L[i][j-1]=0;
}
if (L[i+1][j]==0)
Machine Translated by Google

212 Lecture 14

{L[i+1][j]=1; Mi[k]=i+1; Mj[k]=j;


Lab(i+1,j,k+1);
L[i+1][j]=0;
}
if (L[i][j+1]==0)
{L[i][j+1]=1; Mi[k]=i+1; Mj[k]=j;
Lab(i,j+1,k+1);
L[i][j+1]=0;
}
}
}

The current tracked path is stored in arrays Mi and Mj. In addition, two
more arrays must be described to store the best path among the previously
tracked paths. The variable kmin stores the length of the best path. The i
and j parameters of the Lab function set the row number and column
number of the current cell, and the k parameter specifies the length from
the beginning of the tracked path to the current cell.
The exit from the recursion in occurs in two cases:
1) the current cell turned out to be the final cell of the path, and then the
new path is stored; 2) the current length of the route k turned out to be such
that when the path continues to some neighboring cell, its length will be no
shorter than the length of the previously memorized best path.

If there is no way out of the recursion, then 4 neighboring cells are


sequentially checked for a possible continuation of the path. In this case,
the cells that the current tracked path has passed through are marked with
one, the Lab function is called recursively , and when the function returns,
the checked maze cell is reset to zero.
It is assumed that the data entry and the formation of the maze structure should
be the same as in example 14.1. Calling the Lab function to track the path, starting
from cell i0, j0:

L[i0][j0]=1; Mi[1]=i0; Mj[1]=j0;


Lab(i0,j0,1);

The complexity is determined by the fact that from the second, etc. of the current
cell, no more than three possible movements along the path are possible (since at least
Machine Translated by Google

Counts. Simple Algorithms 213

one neighboring cell is marked with one), and then the total number of checked
cells will be no more than:

T (k) = 4 + 32 + 33 . . . + 3k = 1 + 3(3k – 1)/2,

+ where k is the maximum path length, k ÿ n2.


Thus, in the vast majority of cases, the solution of this problem by the
backtracking algorithm is extremely inefficient.
End of example.

The output of the result for both variants of finding the shortest path before
is supposed to be implemented independently.

14.2. Topological sort calculation

A cycle in a graph is a closed path starting at some vertex and passing


through a series of edges (arcs) of the graph, and no edge (arc) can enter the
cycle more than once. If the graph is undirected, then the cycle can pass along the
edges in any direction, and if the graph is directed, then along each of the arcs
included in it, the direction of passage must coincide with the orientation of the
arc. A graph in which there is no cycle is called acyclic. In a directed acyclic graph
with n vertices, one can label the vertices with numbers 1, …, n in such a way that
if from vertex i

there is an arc to vertex j, then the label of vertex i must be less than the label of
vertex j. This markup is called topological sorting.
As is proved in graph theory, in a directed acyclic graph there must be at least
one vertex that does not contain any arcs. Then this vertex can be assigned the
label number 1. After deleting this vertex and the edges leaving it from the graph,
at least one more vertex will appear in the graph (if it did not exist before), which
does not include any arc, the next label is assigned to it, and t .d., while there are
still unlabeled vertices in the graph. This algorithm calculates

one of the possible topological sorts, of which there may be several for the same
graph. An example of a problem that reduces to computing a topological sort

alignment, is the task of building a sequence of interdependent


Machine Translated by Google

214 Lecture 14

my works. In order to cook fried potatoes, you need to do the following work:

a) peel potatoes
b) wash the potatoes;
c) put the potatoes in the pan;
d) put oil in a frying pan;
e) turn on the stove and put the pan;
e) cut potatoes;
g) fry, turning, potatoes;
h) salt;
i) Remove the pan from the stove.
The sequence of work, as one of the solutions
task:

bÿaÿfÿeÿdÿcÿhÿgÿi

To reduce this problem to a problem on graphs, we associate each


job with a vertex of the graph. If for some pair of jobs the execution
sequence is strictly specified, then we define an oriented arc between the
corresponding vertices.
Example 14.4. Computing a topological sort on a directed acyclic
graph with n vertices. The graph is given as an array D of numbers of
adjacent vertices. Counting in the array R the number of edges included in
all vertices:
for (i=0;i<n;i++) R[i]=0;
for (i=0;i<m;i++) {j=D[k]; R[j]++;}

Writing to the array P (queue) the numbers of vertices that do not include any
one of the arcs:

k=-1; t=0;
for (i=0;i<n;i++)
if (R[i]==0) {k++; P[k]=i;}

When viewing the graph in width for each written in the array P
vertex i , as it were, the edges emerging from it are removed (in fact, 1 is subtracted from
each element q of the array R if there is an arc from vertex i to vertex q). In this case, as
soon as a new vertex with zero
Machine Translated by Google

Counts. Simple Algorithms 215

the number of edges included in it, it is also written to the array P. The
program terminates its work when there are no vertices left in the graph
that can still be written to the array P. Scanning the graph in width according
to the queue in the array P:
while (t<k)
{i=P[t];
for (j=S[i];j<=S[i]+L[i]-1;j++)
{q=D[j]; R[q]--;
if (R[q]==0) {k++; P[k]=q;}
}
t++;
}
The complexity of the program is O(n + m), since each vertex is scanned once,
and all the arcs emerging from it are scanned.
gi.

Note also that if the graph is not acyclic, then there will definitely come
a moment when the while loop is executed when there is not a single
vertex with a zero number of edges included in it, although not all vertices
have yet been written to the array P. In this case, the while loop will stop
execution ahead of schedule, while k<n-1.
End of example.

Example 14.5. Computing a topological sort for a graph given an adjacency


matrix. Counting in the array R the number of arcs included in all vertices:

for (i=0;i<n;i++) R[i]=0;


for (i=0;i<n;i++)
for (j=0;j<n;j++) if (M[j]
[i]==1) R[i]++;}

Writing to the array P (queue) the numbers of vertices that do not include any
one of the arcs:

k=-1; t=0;
for (i=0;i<n;i++)
if (R[i]==0) {k++; P[k]=i;}

Viewing the graph in width according to the queue in array P:


Machine Translated by Google

216 Lecture 14

while (t<k)
{i=P[t];
for (j=0;j<n;j++)
{if (M[i][j]==1)
{R[j]--;
if (R[j]==0) {k++; P[k]=j;}
}
}
t++;
}
2
The complexity of the program is O(n ), since each vertex is scanned once, and all
arcs outgoing from it are scanned.
End of example.
The result of topological sorting is a sequence of vertex numbers in the array P.
If for a graph vertex it is necessary to find its ordinal number in the array P, then the
permutation inverse to P should be calculated . The reverse permutation is defined as
follows. Let's fill the elements in the array Q with numbers 0, ..., n-1 so that Q[i]=i.
Then the i-th place in the topological sorting array P is the vertex
If weP[Q[i]]
sort theofarray
the graph.
P
while simultaneously permuting the elements of the array Q, then Q will be the reverse
permutation array. In fact, you don't need to sort anything: there is a much simpler
algorithm.

Example 14.6. Calculation of the reverse permutation. An array P is given,


n elements of which form some permutation of the numbers 0, ..., n-1.
Calculation in the array Q of the reverse permutation of numbers:
for (i=0;i<n;i++) Q[P[i]]=i;

An example of permuting numbers from 0 to 5 (array P):

2, 3, 0, 5, 1, 4
Reverse permutation (array Q):

2, 4, 0, 1, 5, 3

End of example.
Machine Translated by Google

Counts. Simple Algorithms 217

14.3. Calculation of the shortest distances in weighted


columns

The adjacency matrix of a weighted graph contains the weights (lengths)


of the edges, which is why it is called the distance matrix. This matrix is
generally asymmetric. We will assume that the edge weights are
nonnegative, and if there is no edge, a very large value (infinity) is written
instead. The distance from one vertex to another is the sum of the lengths
of edges that must be successively passed through intermediate vertices
along a given path. Among all possible paths, the path with the shortest
distance is the most important. E. Dijkstra's algorithm calculates all the
shortest distances from a given vertex to
all other peaks.
Consider the calculation of the shortest distances by E. Dijkstra's
algorithm for the vertex a=0 using the distance matrix of a graph of 5
vertices shown in Fig. 1 as an example. 14.1. A dash in the diagonal
elements of a matrix denotes infinity.
01 2 34

0 – 10 5 4 15
18–28 one

27 _ fifteen 3
3 12 3 13 – 7
4 20 11 9 10 –

Rice. 14.1

The zero row of the matrix is the length of the edges between vertex 0
and all the others. Among them, the minimum length to the 3rd vertex, this
will be the minimum distance, since the path through any other vertex to the
3rd will be longer. We recalculate the lengths of paths from vertex 0 through
vertex 3 to other vertices if they have become smaller. As a result, we
obtain the path lengths in Fig. 14.2.
Machine Translated by Google

218 Lecture 14

01234

– 7 5 4 11

Rice. 14.2

Among the calculated lengths of paths, the minimum length (with ignoring
vertex 3) is at vertex 2. Recalculating the lengths of paths through vertex 2 to
other vertices, we obtain the lengths of paths in Fig. 3a. 14.3.

01234

–6548

Rice. 14.3

Among the computed path lengths, the minimum length (ignoring


vertices 3 and 2) is at vertex 1. Recalculating the length of the path through
vertex 1 to vertex 4, we obtain the shortest path lengths in Fig. 3a. 14.4.

01234

–6547

Rice. 14.4

If, in parallel with the recalculation of the lengths of the paths at each step, in
a separate array, for each vertex, we mark from which other vertex there was a
transition to it, then we obtain an array of references in Fig. 14.5.

01234

–2001

Rice. 14.5
Using such an array, you can restore (in reverse order) each shortest
path from vertex 0. For example, the shortest path from vertex 0 to vertex 4:

0ÿ2ÿ1ÿ4

Example 14.7. Calculation of the shortest distances. The program uses


arrays R, P, Q of length n. The R[i] element contains the computed
Machine Translated by Google

Counts. Simple Algorithms 219

current minimum distance from the given vertex a to vertex i. The element P[i]
contains the number of the vertex from which the last edge of the current shortest path
goes to vertex i. The array Q contains at signs that the vertices belong to two sets:

1) the set of vertices up to which the shortest path has already been calculated
distance. If vertex i belongs to this set, then Q[i]=0;
2) the set of other vertices, the shortest distance to them is calculated among
the paths passing only through the vertices of the 1st set. If vertex i belongs to this
set, then Q[i]=1.

for (i=0;i<n;i++) //initialization of arrays Q,R,P


{Q[i]=1; R[i]=M[a][i]; P[i]=a;}
Q[a]=0; P[a]=0;
for (j=1;j<n;j++) //loop n-1 times
{rmin=1000000;
for (i=0;i<n;i++)
if (Q[i] && (R[i]<rmin)) {rmin=R[i]; k=i;}
Q[k]=0; //the shortest distance to vertex k is calculated
for (i=0;i<n;i++) //correction of arrays R and P
if (Q[i] && (z=M[k][i]+R[k])<R[i])
{R[i]=z; P[i]=k;}
}

When arrays are initialized, vertex a is attached to the 1st set, and all other
vertices, to the 2nd set. The second for loop is executed n-1 times, at each step of
the loop, one vertex from the 2nd set is added to the 1st set, to which there is the
shortest path from the vertex a, after which the arrays R and P are corrected. execution
of the loop keeps the assertions about the 1st and 2nd sets true, which proves the
correctness of the program.

After the completion of the second for loop, the array R will contain the shortest
distances from vertex a to all other vertices, array P -
references to the tops, the last ones in the shortest
2
The complexity of the program is O(n paths. ), since the second for loop is
executed n-1 times, and inside it there are sequentially two loops of n steps each.
End of example.
Machine Translated by Google

220 Lecture 14

Example 14.8. Calculation of the shortest distance and path between two
vertices a and b. In order not to perform unnecessary actions in the program from
Example 14-7, at each step of the second for loop , it is necessary to additionally
check that the vertex b is still in the 2nd set. To do this, the heading of the second for
loop must be replaced with the following:

for (j=1;(j<n) && Q[b];j++) //loop no more than n-1 times

Then this cycle can end ahead of schedule, as soon as the shortest distance to
the vertex b is calculated, but in the worst case, the complexity will still be O(n
2
).
Recovering the shortest path between vertices a and b (in reverse order):

i=P[b]; k=0; S[0]=b;


while (i!=P[a])
{i=P[i]; k++; S[k]=i;}

The shortest path will pass through the following k vertices:

S[k-1], S[k-2], . End of . . S[0].


example.

Questions and tasks

1. Write a program that enters the maze, the beginning and end of the path in the maze,
calculates by viewing in breadth and outputs the shortest route in the form of a maze with
labeled cells along which the path passes. Create tests for the program using the black
and white box method and conduct testing. What is its complexity and why?

2. Write a program that enters the maze, the beginning and end of the path in the maze,
calculates it using the backtracking algorithm, and displays the shortest route in the form
of a maze with labeled cells along which the path passes. Create tests for the program
using the black and white box method and conduct testing. What is its labor capacity and
why?
3. Write a program that inputs directed graph data and represents
Returns it as an array of adjacent vertex numbers, then computes a topological sort and
determines whether the graph is acyclic. If the graph is acyclic, then it also computes the
inverse permutation of vertex numbers. Create those
Machine Translated by Google

Counts. Simple Algorithms 221

sty for the program using the black and white box method and conduct testing. What is
its complexity and why?
4. Write a program that takes the data of a directed graph and represents it as an adjacency
matrix, then computes a topological sort and determines whether the graph is acyclic. If
the graph is acyclic, then it also calculates the reverse permutation of the vertex numbers.
Create tests for the program using the black and white box method and conduct testing.
What is its labor capacity and why?

5. Write a program that, for a directed graph represented as an array of lists of vertex
numbers, calculates the topological sort and determines whether the graph is acyclic.
What is its complexity and why?
6. Prove the correctness of the program for calculating the inverse permutation.
7. Write a program that enters the matrix of distances between the vertices of the graph and
the number of the initial vertex, after which it calculates and displays all the shortest
distances and all paths from the initial vertex to all the others. Create tests for the
program using the black and white box method and conduct testing. What is its complexity
and why?
8. Write a program that enters a matrix of distances between the graph vertices and the
numbers of the start and end vertices, after which it calculates and displays the shortest
distance and the path from the start vertex to the end vertex. Create tests for the program
using the black and white box method and conduct testing. What is its complexity and
why?
Machine Translated by Google

Lecture 15
Cycles and paths in graphs

15.1. Euler cycles and paths

An Euler cycle is such a closed path that starts at some vertex and passes
through all edges (arcs) of the graph
exactly one time. In this case, the cycle can go through some euler vertices
several times. If the graph is undirected, then the cycle can pass along the
edges in any direction, and if the graph is directed, then along each of the
arcs included in it, the direction of passage must coincide with the
orientation of the arc.
For an Euler cycle to exist, the graph must be connected, but this is not enough.
As is known from graph theory, in an undirected Euler graph, a cycle exists if and only
if all graph vertices are incident to an even number of edges. For a directed Euler
graph, a cycle exists if and only if exactly as many arcs enter each vertex as there are
exits from it. Therefore, before constructing an Euler cycle, it is necessary to check
the condition for its existence.

Example 15.1. Checking the existence of an Euler cycle. Graph set


an array D of numbers of adjacent vertices. If the graph is undirected:
i=0;
while ((i<n) && ((L[i]&1)==0)) i++;
if (i==n) {exists} else {does not exist}

If the graph is directed, then first the number of arcs entering all
vertices is calculated in the array R , then it is compared with the number
of outgoing ones:
for (i=0;i<n;i++) R[i]=0;
for (j=0;j<m;j++) R[D[j]]++;
i=0;
while ((i<n) && (L[i]==R[i])) i++;
if (i==n) {exists} else {does not exist}
Machine Translated by Google

Cycles and paths in graphs 223

The complexity of the first check is O(n), the second check is O(n + m).
End of example.
If an Euler cycle exists, then, as a rule, it is not unique. Euler's algorithm
calculates any of the possible cycles. at first
a cycle is built, starting from an arbitrary, for example, the first, vertex. Then all
the vertices on the constructed cycle are looked through, and as soon as the
vertex i is found , from which the vertex i, which is not yet included in the cycle, leaves
arc, a side cycle is traced, which is then inserted after vertex i.

Since new fragments are repeatedly inserted into the loop being formed, it
is most convenient to use list structures for this. On fig. 15.1 shows how the
formed side cycle is inserted into the initial loop written in the linear list.

one i k one

pb p

i1 i

one i k one

pb p

Rice. 15.1

Example 15.2. Computing the Euler cycle in a directed graph from


n vertices. The graph is given by an array D of numbers of adjacent vertices.
First, a one-element list is created with node 0 written to it, representing an
empty initial cycle of one node. The pb pointer points to the beginning of the list.
The p pointer points to the currently viewed element of the list and moves to the
next element of the list only when there are no unscanned side arcs for the
current vertex in the loop. When tracking a side cycle like

as soon as a new list element is formed, the arc included in the cycle is removed
from the graph representation (for this, the element L[i] is reduced by 1, and
Machine Translated by Google

224 Lecture 15

S[i] is increased by 1). At the end of the formation of the side cycle, it is inserted into
the main list as a separate list.

struct el *pb, *p *p0, *p1, *p2; int i, i0; p=new


struct el; pb=p; p->s=0; p->p=NULL; while (p!
=NULL) {if (L[p->s]>0)

{p1=p->p; i0=p->s; i=-1; p0=p; while (i!=i0)


{p2=new struct el; p->p=p2; i=p->s;

i1=D[S[i]]; S[i]++; L[i]--; i=i1; p2->s=i;


p=p2; }p->p=p1; p=p0;

} else p=p->p
}
The complexity of the program is O(n + m), since all arcs of the graph are viewed
rush one time. End of
example.

Example 15.3. Checking the existence of an Euler cycle. Graph set


adjacency matrix M. If the graph is undirected:

q=1;
for (i=0;(i<n) && q;i++) {s=0; for
(j=0;j<n;j++) if (M[i][j]) s++; if (!
(s&1)) q=0; //if not an even number of edges }

if (q) {exists} else {does not exist}

If the graph is directed:

q=1;
for (i=0;(i<n) && q;i++) {s1=0; for
(j=0;j<n;j++) if (M[i][j]) s1++;
Machine Translated by Google

Cycles and paths in graphs 225

s2=0;
for (j=0;j<n;j++) if (M[j][i]) s2++;
if (s1!=s2) q=0; //if quantity doesn't match }
//incoming and outgoing arcs
if (q) {exists} else {does not exist}
2 2
The complexity of both checks is O(n ), since all n
elements of the adjacency matrix M.
End of the example.

Example 15.4. Computing an Euler cycle in an undirected graph


out of n vertices. The graph is given by the adjacency matrix M:

p=new struct el; pb=p;


p->s=0; p->p=NULL;
for (i=0;i<n) U[i]=0;
while (p!=NULL)
{j=p->s; k=U[j];
while ((k<n) && (M[j][k]==0)) k++;
U[j]=k;
if (k<n)
{p1=p->p; i0=p->s; i=-1; p0=p;
while (i!=i0)
{p2=new struct el;
p->p=p2; i=p->s; k=U[i]; while (M[i]
[k]==0) k++;
U[i]=k; M[i][k]=0; M[k]
[i]=0; //for digraph remove this assignment
i=k; p2->s=k; p=p2;
}
p->p=p1; p=p0;
}
else p=p->p;
}

In contrast to the program from Example 15.1, an auxiliary array U is used here,
the element U[i] points to the element of the matrix M[i][k], from which one should
continue scanning the i-th row of the matrix to find the next vertex adjacent to i- and
top. When about-
Machine Translated by Google

226 Lecture 15

When an edge (i,k) is found, the matrix element M[i][k], as well as M[k][i], is
set to zero, since in an undirected graph each edge is represented by two matrix
elements. Otherwise, the program is equivalent to the program in Example 15.1.

If the graph is directed, then the assignment M[k][i]=0 should be removed.

2 2
The complexity is of the order O(n ), since all n
elements of the adjacency matrix M, and only once due to the use of the array
U.
End of example.
If the conditions for the existence of an Euler cycle are not met, then there
can be a non-closed Euler path in the graph that starts at one vertex and ends
at another. The condition for the existence of an Euler path in an undirected
graph is as follows: the valencies of two vertices are odd, and all other vertices
are even. The path starts at one of the odd-valency vertices and ends at another,
so the program tracks the initial path between these vertices first. The rest of
the path-building program remains the same.

program for constructing the Euler cycle.


The condition for the existence of an Euler path in a directed graph is as
follows: at one of the vertices i , the number of outgoing arcs is 1 greater than the
number of incoming ones, while at the other j, on the contrary, the number of
incoming arcs is 1 more than the number of outgoing ones. Moreover, for each of
the other vertices, the number of incoming arcs must be equal to the number of
outgoing arcs. Under this Euler condition, the path starts at vertex i and ends at vertex j.

Example 15.5. Checking for the existence of an Euler path or cycle. Graph
is given by an array D of numbers of adjacent vertices. If the graph is
undirected:

q=0;
for (i=0;(i<n)&&(q<3);i++)
if (L[i]&1) {q++; ib=ik; ik=i;}
if (q==0) {there is a loop}
else if (q==2) {path exists}
else {doesn't exist}
Machine Translated by Google

Cycles and paths in graphs 227

If the graph is directed, then first the number of arcs entering all
vertices is calculated in the array R , then it is compared with the number
of outgoing ones:
for (i=0;i<n;i++) R[i]=0;
for (j=0;j<m;j++) R[D[j]]++;
q=0;
for (i=0;(i<n)&&(q<3);i++)
if (L[i]!=R[i])
{q++;
if(R[i]==L[i]+1) ib=i;
else if(R[i]==L[i]-1) ik=i;
else q=3;
if (q==0) {there is a loop}
else if (q==2) {path exists}
else {doesn't exist}

The q variable counts how many vertices have an edge imbalance. In the ib
variable , the beginning is calculated, and in the ik variable , the end of the path
is calculated, while for an undirected graph, the beginning and end can be
exchanged with each other. The complexity of the first check is O(n), the second
check is O(n + m).
End of example.
Example 15.6. Calculation of the initial Euler path in a directed graph
with n vertices. The graph is given by an array D of numbers of adjacent vertices:
p=new struct el; pb=p; p->s=ib; i=ib; while (L[i]>0)

{p1=new struct el; p->p=p1;


i1=D[S[i]]; S[i]++; L[i]--;
p1->s=i1; p=p1; i=i1;
}
p->p=NULL;

If the path exists, then a linear list will be built, in the first element of which the
vertex ib, and in the last element - ik. To build the entire Euler path after this program,
you need to execute the program from Example 15.2, without the initial assignments
before the main loop.
Machine Translated by Google

228 Lecture 15

The complexity of calculating the entire path is O(n + m), the same as for
the structure of the Euler
cycle. End of example.

Example 15.7. Checking for the existence of an Euler path or cycle. Graph
is given by the adjacency matrix M. If the graph is undirected:
q=0;
for (i=0;(i<n)&&(q<3);i++) {s=0; for
(j=0;j<n;j++) if (M[i][j]) s++; if (s&1)
{q++; ib=ik; ik=i;} }

if (q==0) {cycle exists} else if (q==2)


{path exists} else {does not exist}

If the graph is directed:


q=0;
for (i=0;(i<n)&&(q<3);i++) {s1=0; for
(j=0;j<n;j++) if (M[i][j]) s1++; s2=0;
for (j=0;j<n;j++) if (M[j][i]) s2++; if (s1!=s2) {q++;
if(s1==s2-1) ib=i; else if(s1==s2+1) ik=i; else q=3; }

}
if (q==0) {cycle exists} else if (q==2)
{path exists} else {does not exist}

Similar to the program in Example 15-5, the variable q counts how many
vertices have edge imbalances. In the ib variable , the beginning is calculated, and
in the ik variable , the end of the path is calculated, while for an undirected graph,
the beginning and end can be exchanged with each other.
Machine Translated by Google

Cycles and paths in graphs 229

2
The complexity of each verification option is O(n ).
End of example.

Example 15.8. Calculation of initial Euler path in nonorientable


a graph of n vertices. The graph is given by the adjacency matrix M:
for (i=0;i<n) U[i]=0; p=new
struct el; pb=p; p->s=ib; i=ib;
k=0;
while (M[i][k]==0) k++;
while (k<n)
{p1=new struct el; p->p=p1;
M[i][k]=0;
M[k][i]=0; //for digraph - delete
k++;
while (k<n && M[i][k]==0) k++;
U[i]=k;
p1->s=k; p=p1; i=k;
}
p->p=NULL;

Similar to the program from Example 15.6, a linear list will be built, in the first
element of which the vertex ib, and the last element ik. To build the entire Euler path
after this program, you need to run the program from Example 15.4, removing the
initial assignments in it before the main loop.

If the graph is directed, then the assignment M[k][i]=0 should be removed.

2
The complexity of calculating the entire path is O(n ), the same as for
the structure of the Euler cycle.
End of example.

15.2. Hamiltonian cycles and paths

A Hamiltonian cycle must traverse all vertices exactly once, and along edges
(arcs) no more than once. In this case, some edges (arcs) may not be included in the
cycle at all. The Hamilton cycle also does not exist for every graph.
Machine Translated by Google

230 Lecture 15

Despite some similarities, the problems of constructing an Euler cycle and a


Hamiltonian cycle are fundamentally different. The Euler cycle (if it exists) can be
constructed quite efficiently (complexity is proportional to the number of graph edges).
However, the Hamiltonian cycle can be constructed in the general case only in
exponential time.
A Hamiltonian cycle can be viewed as a cyclic permutation of graph
vertices, which can start from any vertex, for example, the first one.
Therefore, we can take the program for generating permutations of numbers
as a basis, making the following changes to it:
1) in the first position of the generated permutations, 1 should be written if
the vertices are numbered starting from 1, or 0 should be written if the vertices
are numbered starting from 0;
2) the next vertex should be included in the permutation only if
if there is an edge (arc) from the previous vertex to it;
3) before including the last vertex, the presence of
edges (arcs) between the last and first vertices in the permutation.
Example 15.9. Calculation of all Hamiltonian cycles in a graph of n
peaks. The graph is defined by an adjacency matrix (global array) M:
void hamilton(int k)
{int i,j;
i=P[k-1];
for (j=0;j<n;j++)
if ((R[j]==0)&&(M[i][j]))
{P[k]=j; R[j]=1;
if (k==n-1) {if (M[j][0]) LOOP OUTPUT}
else hamilton(k+1);
R[j]=0;
}
}

Similarly to the program for generating permutations of numbers, the global


array P contains the sequence of numbers of vertices in the cycle, and the global
array R contains signs of the occurrence of vertices in the cycle. Function call:
for (i=0;i<n;i++) R[i]=0;
P[0]=0; R[0]=1;
hamilton(1);
Machine Translated by Google

Cycles and paths in graphs 231

The complexity of the program in the worst case is the same as that of the program
generating permutations for n - 1, i.e. O(nÿ(n – 1)!) = O(n!). If it turns out that there
are no Hamiltonian cycles for the given graph, then the program will not output
anything.
Note also that it is completely unimportant for the program whether the graph is
directed or not, since the program checks only edges or arcs outgoing from vertices.

End of example.

Example 15.10. Calculation of one Hamiltonian cycle in a graph of n


peaks. The graph is given by the adjacency matrix M:

void hamilton1(int k)
{int i,j;
i=P[k-1];
for (j=0;(j<n)&&(z==0);j++)
if (R[j]==0)&&(M[i][j]))
{P[k]=j; R[j]=1;
if (k==n-1)
{if (M[j][0])
{OUTPUT LOOP; z=1;}
}
else hamilton1(k+1);
R[j]=0;
}
}

The global variable z=0 while no cycle has yet been evaluated. As soon as the
first loop is built, z=1, after which all recursive calls exit. Calling the hamilton1
function:

for (i=0;i<n;i++) R[i]=0;


P[0]=0; R[0]=1; z=0;
hamilton1(1);

The complexity of the program in the worst case is the same as that of the program
calculation of all Hamiltonian cycles, i.e. O(nÿ(n – 1)!) = O(n!).
End of example.
Machine Translated by Google

232 Lecture 15

If a graph does not contain a Hamiltonian cycle, then a nonclosed Hamiltonian


path can exist in it. To find it, it is necessary to try to build a path starting, in turn, from
each of the vertices of the graph.

Example 15.11. Computing all Hamiltonian paths in a graph of n


peaks. The graph is given by the adjacency matrix M:

void hamiltonp(int k)
{int i,j;
i=P[k-1];
for (j=0;j<n;j++)
if ((R[j]==0)&&(M[i][j]))
{P[k]=j; R[j]=1;
if (k==n-1) {OUTPUT PATH}
else hamilton(k+1);
R[j]=0;
}
}

Calling the hamiltonp function:


for (i=0;i<n;i++) R[i]=0;
for (i=0;i<n;i++)
{P[0]=i; R[i]=1; hamiltonp(1); R[i]=0;}

The complexity of the program in the worst case is similar to the


complexity of the program for generating permutations for n - 1, i.e. O(n!). If
it turns out that there are no Hamiltonian cycles for the given graph, then
the program will not output anything.
End of example.
Example 15.12. Computing one Hamiltonian path in a graph of n
peaks. The graph is given by the adjacency matrix M:

void hamiltonp1(int k)
{int i,j;
i=P[k-1];
for (j=0;(j<n)&&(z==0);j++)
if ((R[j]==0)&&(M[i][j]))
Machine Translated by Google

Cycles and paths in graphs 233

{P[k]=j; R[j]=1; if
(k==n-1) {OUTPUT PATH; z=1;} else
hamilton1(k+1);
R[j]=0; }

Calling the hamiltonp1 function:


z=0;
for (i=0;(i<n)&&(z==0); i++)
{P[0]=i; R[i]=1; hamiltonp1(1); R[i]=0;}

The complexity of the program in the worst case is the same as that of the program
calculation of all Hamiltonian paths, i.e. O(nÿ(n – 1)!) = O(n!). End of
example.

Example 15.13. Calculation of all Hamiltonian cycles in a graph of n


peaks. The graph is given by an array D of numbers of adjacent vertices:

void hamiltons(int k) {int i,j,q,r;


i=P[k-1]; for (j=S[i]; j<S[i]+L[i];
j++) {q=D[j]; if (R[q]==0)

{P[k]=q; R[q]=1; if
(k==n-1) {r=S[q];
while (r<S[q]+L[q])
if (D[r]==0) {OUTPUT LOOP;
r=S[q]+L[q];} else r++;

}
else hamiltons(k+1);
R[q]=0; }

}
}
Machine Translated by Google

234 Lecture 15

Unlike the program in Example 15-9, here, to check for loop closure, we have to
iterate over the numbers of vertices adjacent to the last vertex to find vertex 0, where
the loop begins.
The complexity in the worst case can be estimated as O(m1ÿm2ÿ . . .ÿmn),
where mi is the number of vertices adjacent to the i-th vertex, since at the next
step of the recursion the loop is executed mi times. Calling the hamiltons
function is similar to calling the hamilton function.
End of example.

Example 15.14. Computing all Hamiltonian paths in a graph of n


peaks. The graph is given by an array D of numbers of adjacent vertices:

void hamiltonsp(int k)
{int i,j,q,r;
i=P[k-1];
for (j=S[i]; j<=S[i]+L[i]; j++)
{q=D[j];
if (R[q]==0)
{P[k]=q; R[q]=1;
if (k==n-1) {OUTPUT PATH}
else hamilton(k+1);
R[q]=0;
}
}
}
Calling the hamiltonsp function is similar to calling hamiltonp. Trudeau
capacity in the worst O(m1ÿm2ÿ . . .ÿmn), the same as in Example 15.13.
End of example.

Example 15.15. Computing one Hamiltonian path in a graph of n


peaks. The graph is given by an array D of numbers of adjacent vertices:

void hamiltonsp1(int k)
{int i,j,q,r;
i=P[k-1];
for (j=S[i];(j<S[i]+L[i])&&(z==0); j++)
{q=D[j];
if (R[q]==0)
{P[k]=q; R[q]=1;
Machine Translated by Google

Cycles and paths in graphs 235

if (k==n-1) {OUTPUT PATH; z=1;}


else hamilton(k+1);
R[q]=0;
}
}
}
Calling the hamiltonsp1 function is similar to calling hamiltonp1. Worst-case
cost O(m1ÿm2ÿ . . .ÿmn) is the same as in Example 15.13.
End of example.

15.3. The traveling salesman problem

The famous traveling salesman problem is as follows. A traveling merchant


(traveling salesman) needs to travel around n cities, starting from some city, visit each
city once, and return to the starting city. In this case, it is required that the sum of all
distances covered by it be minimal. In English, this task is called Traveling.

Salesman Problem (TSP).


This problem is closely related to the problem of constructing a Hamiltonian cycle
on a graph, but, unlike the latter, here the graph is weighted, and its incidence matrix
contains the lengths of edges between pairs of vertices. The absence of an edge
between a pair of vertices is given by infinity (or a very large number). Diagonal
elements are also expediently set to infinity. In the general case, the matrix may be
non-symmetric, i.e. directed graph. The solution of the problem is a Hamiltonian cycle
with the minimum sum of the weights of the edges included in it.

Let the following matrix of distances between five cities be given:


12345

1 – 8 12 3 15

26 _ - 9 7 11

3 10 4 – 16 8

4 3 12 15 – 7

5 13 9 5 6 –

Rice. 15.2
Machine Translated by Google

236 Lecture 15

There are (n – 1) in total! variants of Hamiltonian cycles, i.e. 24. The solution
(traveling salesman route) is the cycle: 1 – 4 – 5 – 3 – 2 – 1. Its length is: 3+7+5+4+6=25.

Example 15.16. Calculation of a Hamiltonian cycle of minimum length in a


graph of n vertices. The graph is given by a matrix of distances M between vertices:
void TSP(int k,float S)
{int i,j,q; float mij;
i=P[k-1];
for (j=0;j<n;j++)
if ((R[j]==0)&&((mij=M[i][j]+S)<Smin))
{P[k]=j; R[j]=1;
if (k==n-1)
{if (mij+M[j][0]<Smin)
{Smin=mij+M[j][0];
for (q=0;q<n;q++) P0[q]=P[q];
}
}
else TSP(k+1,mij);
R[j]=0;
}
}
The program should describe the following global changes
and arrays:

n is the number of vertices in the graph,


Smin is the length of the minimum route (the initial value is infinity),

P0 is an array (of n elements) with numbers of vertices of the minimum


route,
P is an array (of n elements) with numbers of vertices of the current route,
R is an array (of n elements) with signs of inclusion of vertices in the current route.

The k parameter in the TSP function specifies the number in the P array ,
which contains the number of the current route vertex, and the S parameter
specifies the length of the partially constructed route from vertex 0 to vertex P[k-1].
Calling the TSP function and outputting the result:
Machine Translated by Google

Cycles and paths in graphs 237

for (i=1;i<n;i++) R[i]=0;


R[0]=1; P[0]=0; Smin=999999;
TSP(1.0);
printf("Smin=%8.3f\n", Smin);
for (i=0;i<n;i++)
printf("%d ",P0[i]);
printf("\n");

The complexity of the program in the worst case is the same as that of the
program for computing all Hamiltonian cycles of the graph given by the adjacency
matrix, i.e., O(nÿ(n – 1)!) = O(n!).
End of example.

Questions and tasks

1. A directed graph is given by an array of numbers of adjacent vertices. Write a program


that checks for the existence of an Euler cycle or path for this graph. If the loop
exists, then the loop is built; if the path exists, then the loop is built.
What is its complexity and why?
2. An undirected graph is given by an array of numbers of adjacent vertices. Write a
program that checks for the existence of an Euler cycle or path for this graph. What
is its complexity and why?
3. An undirected graph is defined by an adjacency matrix. Write a program that checks
for the existence of an Euler cycle or path for this graph. If the loop exists, then the
loop is built; if the path exists, then the loop is built. What is its labor capacity and
why?
4. A directed graph is given by an array of lists of numbers of adjacent vertices. Write a
program that checks for the existence of an Euler cycle or path for this graph. If the
loop exists, then the loop is built; if the path exists, then the loop is built. What is its
complexity and why?
5. The graph is given by lists of numbers of adjacent vertices. Write a program that builds
and prints all existing Hamiltonian cycles in a graph. What is its labor capacity and
why?
6. Write a program that inputs directed graph data and represents
Expresses it in the form of an adjacency matrix, after which it calculates all existing
Hamiltonian cycles in the graph. What is its complexity and why? Create tests for the
program using the black and white box method and conduct testing.
Machine Translated by Google

238 Lecture 15

7. Write a program that inputs the data of a directed graph and represents it as an adjacency
matrix, after which it calculates one (any) Hamiltonian cycle in the graph. What is its
complexity and why? Create tests for the program using the black and white box method
and conduct testing.
8. Write a program that takes the data of a directed graph and presents it as an array of
numbers of adjacent vertices, after which it calculates one (any) Hamiltonian path in the
graph. What is its complexity and why? Create tests for the program using the black and
white box method and conduct testing.
9. Write a program that enters a matrix of distances between the vertices of a graph, then
calculates and outputs the optimal traveling salesman route. What is its labor capacity and
why? Create tests for the program using the black and white box method
and do testing.
Machine Translated by Google

Lecture 16
Programming technology

16.1. Programs, software packages and software


products

The scientific discipline of programming technology began to be created in the 60s, when
large programs began to be developed by teams of programmers, and when the requirements for
program reliability increased. Subsequently, the recommendations of this scientific discipline were
included in various industrial and state standards. Recently, programming technology has become
known as software engineering.

Consider the basic terms of this scientific discipline:


1) the program has an input and output, may consist of one or not
how many software modules;
2) software (executable) module - a program that is stored
as a file and, when executed, is pre-recorded (loaded) into
working memory;
3) program text - can exist in different forms (in the source programming language, in
assembly language, in machine language);
4) a software system, or simply a system - a program or a set of programs that interact with
people, technical devices and
etc., has many inputs and outputs, but is a single entity;
5) software package - a set of programs designed to solve a certain set of tasks; not all
programs of the complex are usually required to solve a particular problem;

6) instrumental software complex - a set of software tools designed to develop programs for
solving a certain set of tasks, can exist in the form of libraries of procedures,

classes, etc.;
7) software product - a program (system, complex) + documentation required for its use;
Machine Translated by Google

240 Lecture 16

8) the life cycle of a software product - the time from its beginning to
buildings until the last use.
Further, for brevity, a software product or a complex software product will also be
called a system. When planning the timing of system development, the labor costs
required for this should be taken into account. Creation of a software product, i.e.
program and documentation, requires about 3 times more work than writing a simple
program of the same size. Creating a software system or complex requires 3–4 times
more labor than writing a simple program of the same size. If a complex software product
is created, then in general, labor costs increase by approximately 10 times, see Fig. 16.1.

complex
complex program
3 product

program
one

one
2 3 program
program product
Fig.16.1

The system life cycle can be divided into the following phases:
1) analysis of requirements for the system - development of terms of reference;
2) design - development of a model for the functioning of the system,
its general structure, basic data structures;
3) coding - writing system components in a programming language
nie, their compilation and debugging;
4) testing of system components and the system as a whole;
5) documentation - creation of documentation for the system;
6) maintenance - control of the use of the system and, if necessary, refinement of
the system, release of new versions.
These actions may not be performed strictly sequentially; after any phase, a return
to the first phase can, if necessary, and the subsequent execution of subsequent phases
be repeated based on the corrected
Machine Translated by Google

Programming technology 241

technical task. The system lifecycle continues as long as the system is maintained.

16.2. Software Documentation


Program documentation begins to be created from the very beginning of system
development.

Operational documentation includes documents required


necessary for the use of the system, such as the installation manual for the system, the
description of the application, and, if necessary, other documents. The number of such
documents, their specific content is determined by the complexity of the system and the level
of training of users, which
which the system is calculated.
The system installation manual (or the appropriate section of the application description)
should describe the technical specifications of the hardware, the operating system on which
the system can operate. User actions for deploying the system on a computer should be
described. As a rule, files with programs are delivered to users in a finished (compiled) form.
The description of the application should indicate the technical characteristics of the computer
and the operating system, as well as the technical characteristics of the system itself (its
performance, etc.). Information should be provided on the direct use of the system (how
to specify input data, how to communicate with the system in a dialogue, in what form the
system produces results, etc.). In addition, examples of system implementation (test cases)
with detailed explanations should be given. Since the description of the application is not
designed for a programmer, it should not be used without special need for special

computer terms.
A good system should also contain software tools that help the user quickly get used to
the system, help him if the user has forgotten something in the description of the application.
Usually these tools are implemented in the form of help texts called at any moment of the
dialogue with the system.
Machine Translated by Google

242 Lecture 16

The maintenance documentation should contain a complete description


of the design of the system and should be sufficient to enable errors to be
corrected and changes to the system to be made when necessary. It is
necessary even in cases where the author is engaged in maintenance
systems.

The text of the program (system) is presented in the source programming


language in the form of files so that it would be possible to repeat the
compilation and assembly of the system from components. The text should
be well structured and contain comments to make it easier to understand.
nie.

The description of the program (system) supplements the source text


and comments in it. The description of the program should contain a complete
design of the system as a whole and all components of the system, describe
the structures of input, output and internal data for the entire system, as well
as for all its components. All designations in the description must strictly
correspond to the designations in the source code of the program. The
compilation and assembly of the system from components should also be
described. Tests submitted as files should be described so that tests can be repeated.
ing.

16.3. Analysis, design and coding

The implementation of the individual phases of the system life cycle


depends on which programming technology is used. Consider briefly a
number of the most well-known technologies.

Modular programming. There is no other way to write a large program


than to break it into parts, i.e. modules does not exist. A program divided into
parts can be considered modular if:
1) the size of each module is such that the average maintainer can
understand it in its entirety, hence the limit of 50–100 lines of text;

2) each module should be as independent as possible from other


modules, possible changes in one module (with unchanged inputs and
outputs) should not lead to the need to make changes in other modules.
Machine Translated by Google

Programming technology 243

Modular programming arose around the turn of the 1960s, when


programming languages made it possible to implement procedures and
functions.
Structural programming. Structural programming can be considered a
further development of modular programming, when the internal structure of
the module consists only of such structural units, each of which has only one
input and one output. To write such structured modules, it is sufficient to use
sequential, branching, and cyclic statement structures or their equivalent.

ribbons.

This is now the accepted method of writing programs, although at the


time of its birth, in the late 1970s, its use was the exception rather than the
rule. At that time, the “antistructural” goto operator was widely used in
practical programming, because of which, in particular, it was impossible
to carry out analytical verification of programs, which significantly increases
their reliability.
Error-proof programming. To detect possible errors, additional
statements are inserted into the program that check the current values of
variables or the logical relationships between them, and issue diagnostic
messages if these values have become invalid. This greatly simplifies and
speeds up debugging, i.e. search for errors when testing the program. To
prevent such checks from slowing down the execution of the entire program
during its operation, checks can be made disabled by setting a special
parameter. At the same time, if a situation is detected in which the program
produced an incorrect result, one should only repeat this version of the
calculation with checks enabled, and the place of the error will be localized.
Thus, the method of programming with error protection facilitates and speeds
up not only testing and debugging during the development of the program,
but also its subsequent maintenance.

Object-oriented programming (OOP). It emerged at the turn of the


1990s as a development of modular programming, when the first object-
oriented programming languages were created. The program describes
classes of objects, each class is a data structure that describes data types
and access modes for
Machine Translated by Google

244 Lecture 16

them, and also describes the functions associated with this data, called
methods. Objects are created, used, and destroyed during program
execution. All this facilitates the creation of large programs.

CASE (computer-aided software engineering) technologies.


Appeared at the turn of the 2000s in the form of software products - technological
systems focused on creating complex systems and supporting their full life
cycle or its main phases. They include tools for analyzing requirements,
designing specifications and system structure, editing interfaces, automatic
generation of source texts in a programming language, tests, documentation,
and maintenance of specifications.

UML (Unified Modeling Language) is a graphical description language


for object modeling. It is used in CASE tools that allow you to interactively
create a formalized description of the system, its components, data
structures using graphic diagrams, diagrams and formal languages. CASE-
tools most fully facilitate the work of programmers when creating information
systems with complex data structures, when it is the data that determines
the algorithms for their processing.

Requirements analysis. When performing this phase of the life cycle,


the requirements of the technical specifications are specified and formalized,
the input and output data, their structure, and the order of interaction
between the system and the user are determined. For this, various schemes
and diagrams are built and analyzed, in particular, a diagram of options for using
vanity.

System design. When designing, the main internal data sets and their
structure are determined, a general enlarged structure of the system is
created, and it is divided into main components. The main algorithms for
implementation in the system are selected. The designed structure is
carefully analyzed to what extent it will satisfy the requirements of the
technical assignment. Often, for this purpose, a simplified operating layout
of the system (prototype) is created in order to analyze its compliance with
the terms of reference and clarify the requirements. When creating complex
systems, this process unwinds like a spiral,
Machine Translated by Google

Programming technology 245

in order to get a detailed design as a result, in which an algorithm and data structures for it
were defined for each component.

Coding. If the component algorithms are standard, then they can be automatically
coded using CASE tools. In other cases, you must create component programs manually.
You can use the drill down method, also known as the top-down method, to do this. The
essence of the method is that, first, the algorithm of the program as a whole (the first level
module) is developed, in which the second level modules are used, which have not yet been
described, but the input and output data are defined for them. Then, algorithms are
developed separately for each of the modules of the second level, within which, in turn,
modules (of the third level) are also allocated, etc., until it is possible to write all the modules
directly in the programming language. For complex programs, it is useful to represent their
structure in graphical form, as

module hierarchy.

Example 16.1. A program that calculates the determinant of a square matrix. The
program must take input, calculate the determinant, and output its value. Input order:

1) a value that determines the accuracy of calculations;


2) matrix size n;
2
3) matrix elements separated by a space (their number is n ).
Order of output data output: define a real value
body.

The program as a whole has the following structure: - the


"Descriptions" module contains descriptions of all input, output and
internal variables;
– module "Input data input" contains input operators
data in the above order and memory allocation for the matrix;
– module "Calculation of the determinant" contains the implementation of the method
Gauss to calculate the determinant;
– module “Output data output” contains the output statement you
the numerical value of the determinant.
Once defined in this way, the second-level modules "Descriptions", "Input data input"
and "Output data output" are easy to program.
Machine Translated by Google

246 Lecture 16

The module of the second level "Calculation of the determinant" contains in its
queue, modules of the third level - algorithms from example 12.1.
On fig. 16.2 shows the hierarchy of program modules.

Program

Input data entry calculation Output output


Descriptions
determinant data

Subtraction calculation
Selecting
a Leading Element Rearranging lines lines determinant

Rice. 16.2
End of example.
As attractive as top-down design is, sometimes you have to consciously
deviate from it. An example is the development of a system in which, at lower
levels, many of the modules repeat identical actions, perhaps with slight variations.
In this case, it is desirable to design such identical modules once and, having
implemented them in the form of procedures or functions, simply call them in the
right place. In essence, designing a set of such universal (for a given system)
modules does not differ from designing a complex

programs.

16.4. Testing and Debugging

As is known, testing of small programs can be performed either by the black


box method, when tests are compiled based on the description of input and output
data, ignoring the structure of the program, or by the white box method, when tests
are compiled based on the internal structure of the program. After an incorrect test
execution, it is necessary to localize and fix the error, for which it is useful to use
step-by-step testing when performing a test of a small dimension.
Machine Translated by Google

Programming technology 247

A complex program consisting of many modules to test


as a whole is almost impossible: most likely, at the very beginning
testing, there will be so many errors in it that all tests will be executed incorrectly, and localization of
errors will be difficult.
There are two technologies for testing complex programs:
top-down (descending) and bottom-up (ascending).

Bottom-up testing technology. Bottom-up testing begins when the design and programming
of all modules of the system is completed and inputs and outputs are defined for each module. First,
the modules of the lowest level are tested separately, which are called in the system from modules
of higher levels. To run a module under test for execution, it is necessary to write a test program for
it, which first enters the input data, then calls the module, and finally displays the output data.
In this case, black and white box methods are used. Then the higher-level modules are tested by
assembling them from lower-level modules and also creating test programs for them. And only at
the very last stage, the module of the highest level is tested, i.e. the whole program

face.

If testing really begins after the design of the entire system, then the bottom-up method provides
a fairly good quality. However, in practice, due to lack of time, testing often begins before the end of
the full design, and the system is also designed using the bottom-up method. As a result, when
testing high level modules, an error in matching the inputs and outputs of the called modules can be
detected, which will necessitate their modification. The higher the level of the unit under test, the
higher the probability of such an error and the more work is required to correct it.

Top-down testing technology. Top-down testing begins when the highest level module is
designed and programmed, and the modules it calls and their inputs and outputs are defined. Since
these modules do not yet exist, they are replaced with stubs for the duration of testing, i.e. modules
that, for a pre-prepared set of input data (test), produce a pre-prepared set of output data. Sometimes
a stub is implemented in the form of an extremely simplified
Machine Translated by Google

248 Lecture 16

a variant of the algorithm that solves the same problem that the module should
solve, but, perhaps, not in full.
At the subsequent stages of testing, each of the stubs is replaced in turn
with a real module, but with its own stubs. Thus, the system becomes more
and more complete during testing, and the whole process ends when the stubs
at all levels are replaced by real modules.

Top-down testing has the following advantages:


properties:

1) testing can begin almost immediately after design has begun;

2) when designing and testing the next module, the probability of an error
in matching the inputs and outputs of the modules it calls decreases;

3) from the very beginning of testing, the system looks like a single whole,
which gives the programmer confidence in the successful completion of
development.
However, this testing method is not without some drawbacks, the main of
which is that it is difficult to carry out sufficiently complete testing for low-level
modules. Therefore, for full-fledged testing, it is desirable to use both methods
with the primacy of the top-down testing technology. After testing is complete,
all used stubs, testers, and test datasets are saved and their descriptions
included in the maintenance documentation. This is necessary so that, if
necessary, you can always repeat testing of any module in the system and the
system as a whole. Testing system parameters. As a rule, the terms of
reference provide the main characteristics that the system must satisfy. These
include, in particular, the maximum amount of input data, the maximum
requirements for RAM and disk memory to accommodate the system itself
and the data being processed, the processing time for large amounts of data,
etc. Therefore, to determine the real characteristics, it is required to test on
special test data sets. When executing a system with such tests, it is also
necessary to include an operator in the system.
Machine Translated by Google

Programming technology 249

ry measuring characteristics (the amount of occupied memory, time measurements before and after
processing, etc.). At the same time, it should be taken into account that the measured time is a
random variable that , with the same amount of processed data, depends on the specific values of
these data. Therefore, as characteristics of the system, it is necessary to use not the measured
values themselves, but their averaged values. In the simplest case, it is possible to average the
processing time intervals for a sufficiently large number of input data sets of the same size but
different content .

go.

Documentation testing. Operational documentation is written for the user, and it should
make it easier for him to work with the system. It has the following requirements:

1) the documentation must be understandable to the user, who,


usually not a programmer;
2) the documentation must be complete, i.e. it should describe all the functions of the system,
all options for setting and presenting output data;

3) documentation must be accurate, i.e. it should not contain any deviations from the truth in
the description of the functioning of the system and in the presentation of data.

Accordingly, three types of document testing can be distinguished


tions: for clarity, for completeness and for accuracy.

Analytical testing (proof) in designing and writing programs. The technology of top-down
design allows the proof to be carried out during the design process. When designing the main
module of the system, an algorithm is created for which inputs and outputs are specified. If they are
written in the form of logical conditions, then this will be a precondition and a postcondition. Analysis
of the structure of the module algorithm allows us to formulate the input and output (i.e. precondition
and postcondition) for each lower level module called in it. Assuming the correctness of all called
modules (using the abstraction method), it is possible to prove the correctness of the main module.

Further, this process is repeated for all called modules of the second, then the third level, and
so on. In this way one can prove the correctness of all modules, even of a very large system. In
spite of extra efforts, carrying out the proof will eventually justify itself.
Machine Translated by Google

250 Lecture 16

It works because it allows you to design the system with fewer errors and makes it
much easier to test and debug the entire system due to potentially fewer remaining
errors.
In an analytical study, the complexity is most often derived for the worst case;
this allows us to assess in advance, even before testing the performance, whether
the algorithms used in the system can, in principle, satisfy users.

Independent testing. As you know, the purpose of testing is to find possible


errors in the system. However, during the acceptance of the system by the
customer, its public testing just serves to demonstrate the operability of the
system. As a result, there is always a temptation for the developer to significantly
cut down on testing work. That is why, in the development team, the final testing
should be carried out not by the one who wrote the unit under test, but by another
programmer. But even these measures are not enough to be sure of the reliability
of the system. Therefore, it is not uncommon for an independent organization to
test the system. Such testing can be implemented for the whole system using
the black box method. At the same time, not only the system is tested, but also
the documentation for it.

16.5. Organizational problems of programming


technology

Back in the 1960s, project managers discovered that applying


traditional methods of management, which have been successfully used, for
example, in the design of buildings, often do not lead to success in
building large systems.
First, very often programmers underestimate the duration of a project. The
fact is that before development begins, it is impossible to foresee all the
complexities that will be discovered during direct development. In addition, the
actual productivity of programmers, even those with the same qualifications, varies
greatly, sometimes by 5-10 times! Therefore, if we assume that during the
implementation of the project all
Machine Translated by Google

Programming technology 251

will work equally effectively, it is easy to make a mistake with planning the timing of
the completion of the project.
Secondly, if you add people to the development team while delaying the project,
then, as a rule, the deadline for completion of the work will be pushed back even
more: the new programmer needs time to understand the task and study the created
part of the project. At the same time, new performers will distract from the work of
those who have been involved in the work from the very beginning.

All this allowed F. Brooks to formulate a law that bears his name: “If you add
performers during the project implementation, then the final deadline for completing
the project can only increase.”
In order to reduce the negative effect of this law, it is necessary to immediately
plan longer periods for the implementation of the project than the performers
themselves estimate, and monitor the progress of the project in the process of work.
In addition, it is necessary to distribute the work among the performers in such a way
that in the event of the retirement of any of them there would be no significant
slowdown in the performance of work. When developing large projects that involve
large teams of people, a significant part of the effort is spent not directly on
programming, but on the interaction between project participants. Special studies
show that: “When the complexity of the system increases by n times, to create it in
the same time frame, it usually takes

2
n times more people.
So that each programmer could apply his abilities in a team with maximum
efficiency, F. Brooks, back in the late 1960s, suggested organizing teams of
programmers in the form of teams, in which the individual roles of each are described.
The team is formed around the chief programmer, who has the highest qualifications.
The basic principles of the organization of the brigade:

1) each member of the brigade has his own specialization and basic duties
tasks along with the current tasks received from the head;
2) any member of the team works in direct contact with one or two colleagues so
that even in the event of his departure, work on the project is not suspended.

The chief programmer makes major decisions when designing the system
architecture, creates the most critical modules in the system, and writes or supervises
the writing of documentation. In the brigade
Machine Translated by Google

252 Lecture 16

The wife will be his deputy, who, like the chief programmer, owns all the most essential information
on the project, and, if necessary, can replace him. In addition, some responsibilities are shared
among individual programmers in the team. The archivist is responsible for maintaining all copies of
written system components and documentation, and all copies must be updated regularly. The tool
maker is responsible for preparing and mastering the auxiliary software tools required by all other
members of the team. The tester is responsible for testing the system and its modules. The Literary
Editor is responsible for preparing the documentation.

When using top-down design technology, it is possible already at the first stages of development
to more or less evenly load the work of all members of the team in order to complete the project on
time. In general, the team multiplies the abilities of the main programmer, on whom the success of
the work depends to the greatest extent.

When planning the development of the system, one should


take into account other laws of programming technology.
"The first system (its 1st variant) is always created to be discarded." The fact is that the
customer is fully aware of what he needs in the system, as a rule, only after he sees it in action.
Therefore, no matter how carefully and carefully the developer tries to implement the system, it

still have to redo, perhaps from the very beginning. Hence the conclusion: the first system should be
created in such a way that it would not be a pity to throw it away. Those. first it is advisable to create
a valid layout
a system in which the main functions are implemented, but in a simplified form with the simplest
variants of algorithms, etc. After the customer or user has studied the developed system, determines
what does not suit him in it, it is possible to develop a draft of the second version of the system.

stems.

"The second system (its 2nd version) has the effect of the second system." When creating the
second version of the system, it is easy to go to extremes and implement unnecessary functions in
the system that the user does not really need, make the system architecture excessively cumbersome
and clumsy, because of which it will again not suit the customer. Hence the conclusion: it makes no
sense to “bring to a shine” the second system, after its testing and testing, it is necessary to quickly
move on to creating 3-

th version of the system.


Machine Translated by Google

Programming technology 253

Questions and tasks

1. Why is the labor costs of creating a complex software product an order of magnitude greater
than the labor costs of developing a simple program of the same size?

2. What are the goals and objectives of the main stages of system development?
3. What is the maintenance of the software product? What is vital
system cycle?
4. Which documents relate to operational documentation, and what information
should be in these documents?
5. Which documents are related to maintenance documentation, and what information
should be in these documents?
6. Implement the project of the program for calculating the determinant of a square matrix, described
in example 16.1. Perform downward testing of the program. Write operational and maintenance
documentation.
7. In which case is it advisable to combine the top-down design method with bottom-up design?

8. What qualities should a program have to be considered


modular?
9. What is structured programming?
10. What is error-proof programming?
11. When is it advisable to use object-oriented programming?
vanity?

12. What are CASE technologies for?


13. How to carry out the proof of correctness and the derivation of the complexity of a large pro
grams by top-down method?
14. What are the advantages of top-down testing technology compared to bottom-up? In what
cases, however, it is necessary to apply both technologies?
15. How to test the complexity of the program?
16. What are the requirements for operational documentation, and what is its testing?

17. How to minimize the negative effect of Brooks' laws?


18. What are the basic principles of organizing a team of programmers?
19. Why is independent testing necessary and how to organize it?
Machine Translated by Google

Literature
There are a vast number of books and textbooks on Pascal and C. The books of
the author of the language Pascal N. Wirth [6, 13] and the author of the C language
D. Ritchie [15] are of interest. The book [22] is devoted to the C++ language, which is
an object-oriented development of C. For practical work on a computer, books or
manuals for a specific translator, as well as appropriate software, are required. For
example, to work with the Free Pascal and Lazarus translators, you can use the
textbook [2], and to work with the C translator, you can use the textbook [20]. The site
[23] hosts the Lazarus translator, and the site [24] hosts the Dev C++ translator.

See the books [1, 6, 9, 10, 11] for the main ideas of proving algorithms. The
problems of programming technology are discussed in books [3, 4, 8, 14, 18], the site
[25] hosts the StarUML system for creating program projects in the UML language.
See [1, 6, 7] for recurrent sequences and algorithms. See [7, 17] for sorting and
searching algorithms. For recursive algorithms and backtracking, see [1, 21]. Context
search algorithms are available in [7, 17], and Graph Theory and Algorithms with
Graphs are in [12, 19, 21]. Computational algorithms of linear algebra can be found
in [5].
This book has been substantially revised and supplemented
textbook [16]. The book comes with a set of 16 presentations.

1. Abramov S. A. Mathematical constructions and programming. – M.:


Nauka, 1978. 192 p.
2. Alekseev E. R., Chesnokova O. V., Kucher T. V. Free Pascal and Lazarus:
Programming Textbook. Moscow: Alt Linux, DMK Press, 2010. 440 p.
3. Brooks F. P. (Jr.) How software systems are designed and created. Mythical man-
month / Per. from English. – M.: Mir, 1979. 159 p.
4. Butch G., Jacobson A., Rambo J. UML. CS classic. 2nd ed. / Per. from English. -
SPb.: Peter. 2006. 736 p.
5. Verzhbitsky V.M. Fundamentals of numerical methods. - M .: Higher school, 2002.
840 p.
6. Wirth N. Systematic programming. Introduction / Per. from English. – M.:
Mir, 1977. - 184 p.
Machine Translated by Google

Literature 255

7. Wirth N. Algorithms + data structures = programs Per. from English. / Per. With
English – M.: Mir, 1985. 406 p.
8. Glass R. A Guide to Reliable Programming / Per. from English. – M.: Mir, 1982. 280 p.

9. Gris D. Science of programming / Per. from English. – M.: Mir, 1984. 416 p.
10.Dal W., Dijkstra E., Hoor K. Structural programming / Per. With
English – M.: Mir, 1975. 245 p.
11. Dijkstra E. Programming discipline / Per. from English. – M.: Mir,
1978. 275 p.
12. Zykov A.A. Fundamentals of graph theory. – M.: High school book. 2004. 664 p.
13. Jensen K., Wirth N. Pascal: User manual and description
language / Per. from English. - M.: Finance and statistics, 1982. 151 p.
14. Yodan E. Structural design and program design / Per. from English. – M.: Nauka, 1979.
410 p.
15. Kernigan B., Ritchie D. Programming language C / Per. from English. – M.: Finansy i
statistika, 1992. 272 p.
16. Kostyuk Yu.L. Basics of programming. Development and analysis of algorithms: a
tutorial. - Tomsk: Publishing House Vol. university 2006. 244 p.
17. Knut D. The Art of Computer Programming: In 3 vols. Vol. 3: Sorting and
search / Per. from English. – M.: Mir. 1978. 846 p.
18. Myers G. The art of software testing / Per. from English. - M.: Finance and statistics,
1982. 175 p.
19.Ore O. Graph Theory. 2nd ed. / Per. from English. – M.: Nauka, 1980. 336 p.
20. Podbelsky V.V., Fomin S.S. C Programming: Proc. allowance. - 2nd ed., add. – M.:
Finance and statistics, 1999. 600 p.
21. Reingold E., Nivergeld Yu., Deo N. Combinatorial algorithms. Theory and
practice / Per. from English. – M.: Mir, 1980. 476 p.
22. Stroustrup B. Programming language C++ / Per. from English. - M .: Radio and
connection, 1991. 352 p.
23.https://lazarus-rus.ru
24.https://soft.mydiv.net/win/download-DEV-C.html
25.https://freeanalog.ru/StarUML
Machine Translated by Google

Table of contents

Foreword ................................................................ ................................................. ........ 3

Lecture 1. Algorithms and programs. Testing. Analytical


verification ................................................. ........................................... 5
1.1. Algorithms and programs ............................................................... ......................... 5
1.2. Testing................................................. .......................................... eight
1.3. Analytical Verification .................................................................. ................ 12
1.4. Labor intensity analysis .............................................................. ...................... 28
Questions and tasks ............................................... ......................................... 31

Lecture 2. Recurrent algorithms .............................................. ................... 33


2.1. Recurrent sequences and limits............................................... 33
2.2. Other Recursive Algorithms............................................................... ......... 38
Questions and tasks ............................................... ......................................... 43

Lecture 3. Search and sorting .............................................. ................................. 45


3.1. Search in array 45 ................................................. ...................................
3.2. Simple sorting algorithms............................................................... ......... 48
3.3. Dynamic Arrays and Random Numbers.................................................... 54
Questions and tasks ............................................... ......................................... 57

Lecture 4. Recursion ............................................... ................................................. 60


4.1. Procedures and Functions............................................................... .............................. 60
4.2. Recursive Algorithms................................................... ....................... 63
4.3. Merge sort algorithm ............................................................... ........... 69
Questions and tasks ............................................... ......................................... 74

Lecture 5 ........................... 76
5.1. Algorithms with lists ............................................................... ......................... 76
5.2. Search in lists............................................................... ............................................... 80
5.3. Sorting lists .................................................................. .............................. 83
Questions and tasks ............................................... ......................................... 86

Lecture 6. Backtracking ............................................... ............................................... 88


6.1. Combinatorial algorithms .................................................................. ................ 88
6.2. Backtracking with clipping .......................................................... ...................... 95
Questions and tasks ............................................... ............................................... 101

Lecture 7. Sets ............................................... ......................................... 103


7.1. Specifying a Set with a Binary Array.............................................. 103
7.2. Specifying a Set with an Integer Array............................................... 106
7.3. Specifying a set by a list of object numbers 113 ...............................
Machine Translated by Google

Literature 257

Questions and tasks ............................................... ............................................... 120

Lecture 8 ............ 122


8.1. Algorithms for processing character strings .............................................. 8.2. 122
Contextual search in character strings .............................................. 128
8.3. Information tables ................................................................ ................ 135
Questions and tasks ............................................... ............................................... 139

Lecture 9. C language. Basic concepts .................................................. .............. 141


9.1. Program, Data Types, Operations, and Operators....................................... 141
9.2. Functions ................................................. ............................................... 149
Questions and tasks ............................................... ............................................... 157

Lecture 10. C language. Continuation................................................. ................. 158


10.1. Lists, files, standard functions .................................................. 158
10.2. Character strings ................................................................ ........................... 164
10.3. C syntax .................................................. .................................... 169
Questions and tasks ............................................... ............................................... 171

Lecture 11. Algorithms of linear algebra. Vectors and Matrices ................. 173
11.1. Addition and multiplication of vectors and matrices.................................... 173
11.2. Solving systems of linear equations .............................................................. 178
Questions and tasks ............................................... ............................................... 183

Lecture 12. Algorithms of linear algebra. Continued.............................. 184


12.1. Algorithms with square matrices .......................................... 184
12.2. Other Algorithms with Matrices ............................................................... ...... 188
Questions and tasks ............................................... ............................................... 194

Lecture 13. Graphs. Start ................................................. ............................... 195


13.1. Representation of graphs in the program .............................................. .. 195
13.2. Viewing an Undirected Graph .............................................. 201
Questions and tasks ............................................... ............................................... 206

Lecture 14. Graphs. Simple Algorithms ................................................... .......... 208


14.1. Finding the Shortest Path in a Maze .................................................. 14.2. 208
Computing a Topological Sort............................................... 213
14.3. Computing Shortest Distances in Weighted Graphs....... 217
Questions and tasks ............................................... ............................................... 220

Lecture 15 ......................... 222


15.1. Euler cycles and paths.................................................... ....................... 222
15.2. Hamiltonian cycles and paths.................................................... .............. 229
15.3. The traveling salesman problem .................................................. ...................... 235
Questions and tasks ............................................... ............................................... 237
Machine Translated by Google

258 Literature

Lecture 16 ........ 239


16.1. Programs, software packages and software products ...... 239
16.2. Software Documentation ................................................................ ............ 241
16.3. Analysis, design and coding .................................................. 242
16.4. Testing and Debugging .................................................................. .................... 246
16.5. Organizational problems of programming technology ....... 250
Questions and tasks ............................................... ............................................... 253

Literature ................................................. ................................................. ...... 254

You might also like