Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_csharp_2

.pdf
Скачиваний:
69
Добавлен:
10.02.2015
Размер:
1.83 Mб
Скачать

Рис.1.1. Демонстрация операций с классом Fraction.

1.4. Быстрая сортировка массива дробей

Тип Fraction может использоваться в качестве подставляемого вместо обобщенного типа данных при генерации функций и классов на основе обобщений. Приведем пример использования обобщения функции быстрой сортировки для массива дробей. В сгенерированном методе с типом Fraction требуется организовать сравнение двух дробей. Для этого класс Fraction должен раскрывать интерфейс IComparable и иметь метод сравнения дробей CompareTo().

class Fraction : IComparable

{

 

 

int sign;

// знак дроби (+ или -)

int intPart;

// целая часть

дроби

int numerator;

// числитель дроби

int denominator;

// знаменатель

дроби

// метод сравнения двух дробей public int CompareTo(object ob)

{

if (this < (ob as Fraction)) return -1; if (this > (ob as Fraction)) return 1; return 0;

}

. . .

}

20

Определим обобщенный метод быстрой сортировки, указав ограничение на использование в качестве обобщенных типов только тех, которые раскрывают интерфейс IComparable. Пусть этот метод является статическим для главного класса приложения Program.

class Program

{

static void QuickSort<T>(T [] m, int l, int r) where T: IComparable

{

if(l == r) return;

int i = l, j = r;

//выбирается элемент, делящий массив на две части

T selected = m[l];

//осуществляется поиск и перестановка элементов, меньших

//выбранного, с конца массива и больших выбранного

//с начала массива.

while(i != j)

{

while(m[j].CompareTo(selected) >= 0 && j > i) j--;

if(j > i)

{

m[i] = m[j];

while(m[i].CompareTo(selected) <= 0 && i < j) i++;

m[j] = m[i];

}

}

//выбранный элемент устанавливается на надлежащее место m[i] = selected;

//если существуют элементы слева от выбранного,

//сортируем эту часть массива

if(l <= i-1) QuickSort(m, l, i-1);

//то же проводится с правой частью массива,

//если она существует

if( i+1 < r)

QuickSort(m , i+1, r);

}

static void Main(string[] args)

{

Fraction[] a = { new Fraction(4,2,3,1), new Fraction(1,6,5,-1), new Fraction(0,2,7,1), new Fraction(10,2,3,-1), new Fraction(2,13,20,1),

21

new Fraction(0,1),

new Fraction(1,1,3,1), new Fraction(2,5,7,-1), new Fraction(4,8,2,-1), new Fraction(4,1,3,-1)};

for (int i = 0; i < a.Length; i++) Console.Write(a[i]+" ");

Console.WriteLine();

//генерация и вызов функции быстрой сортировки

//для массива дробей

QuickSort(a, 0, 9);

for (int i = 0; i < a.Length; i++) Console.Write(""+a[i]+" ");

Console.WriteLine();

. . .

}

Это же обобщение можно применять и для массива чисел типов int, double, а также для массивов, элементами которых являются объекты классов (пользовательских или библиотечных), которые раскрывают интерфейс IComparable.

Задания для самостоятельной работы

1.Дополнить класс Fraction перегруженными арифметическими операциями, в которых один из операндов является вещественным числом.

2.Дополнить класс Fraction перегруженными операциями сравнения для дробей и вещественных чисел.

3.Дополнить класс Fraction перегруженным конструктором, осуществляющим преобразование вещественного числа к типу Fraction. Предполагается, что дробная часть вещественного числа содержит до 10 знаков после запятой.

4.Разработать класс «Комплексное число». Определить в нем конструктор, перегрузить арифметические операции, операции сравнения, операцию преобразования в строку и статический метод получения комплексного числа из строки.

5.Разработать класс «Комплексное число в тригонометрической форме». Определить в нем конструктор, перегрузить арифметические операции,

22

операции сравнения, операцию преобразования в строку и статический метод получения комплексного числа из строки.

6.Разработать класс «Комплексное число», в котором данные хранятся в двух видах: алгебраической и тригонометрической формах. Определить в нем конструкторы и деструктор, перегрузить арифметические операции, операции сравнения, операцию преобразования в строку и статический метод получения комплексного числа из строки, написать методы преобразования числа из одной формы в другую. Протестировать все возможности класса.

7.Разработать класс «Дата». Определить в нем конструкторы и деструктор, перегрузить операцию добавления к дате заданного количества дней, операцию вычитания двух дат, операции сравнения и операцию преобразования в символьную строку, а также статический метод получения даты из строки.

8.Разработать класс «Время». Определить в нем конструкторы и деструктор, перегрузить операцию добавления к времени заданного количества минут, операцию вычитания двух моментов времени, операцию преобразования в символьную строку и метод получения момента времени из строки.

9.Разработать класс «Прямоугольник». Определить в нем конструкторы и деструктор, перегрузить операцию пересечения прямоугольников

(операция “*”), операцию вычисления площади прямоугольника операции сравнения (по площади), операцию преобразования в символьную строку и метод получения объекта-прямоугольника из строки.

10.Разработать класс «Треугольник». Определить в нем конструкторы и деструктор, перегрузить операцию преобразования в вещественное число (площадь треугольника), операцию проверки включения точки в треугольник, операции сравнения треугольников (по площади), операцию преобразования в символьную строку и метод получения объекта-треугольника из строки.

23

2. Методы решения уравнений

Решим следующую математическую задачу. Требуется найти корень уравнения вида f(x) = 0 на отрезке [a, b] с точностью (точность по функционалу), т.е. нужно найти точку x [a, b], для которой выполняется неравенство | f (x)| ≤ .

Для решения этой задачи существует целый ряд различных методов, самыми известными из которых являются метод деления отрезка пополам, метод хорд и метод касательных.

Для применения этих методов необходимо, чтобы:

1)функция f (x) являлась непрерывной,

2)значения функции f(x) на концах отрезка имели противоположные знаки (тогда гарантируется, что корень на отрезке существует);

3)для метода касательных обязательно требуется, чтобы функция f(x) была выпуклой или вогнутой на отрезке.

Все три метода нахождения корня основываются на единой процедуре.

1.Некоторым образом выбирается точка с [a, b].

2.Выбирается один из отрезков [a, с] или [с, b], на котором далее будет производиться поиск корня. Должен быть выбран тот из отрезков, на котором в точках-концах функция принимает значения различных знаков:

если f (a)* f (c) < 0, то b = с, иначе a = с .

3. Если на новом отрезке | f (a)| ≤ , то a – приближенное значение корня. Если же | f (b)| ≤ , то b – приближенное значение корня.

Отличия метода деления отрезка пополам, метода хорд и метода касательных заключаются в реализации пункта 1 указанной процедуры. Так, метод деления отрезка пополам выбирает в качестве точки c середину отрезка [a, b], метод хорд – точку пересечения отрезка, соединяющего точки (a, f(a)) и (b, f(b)), с осью абсцисс, метод касательных – точку пересечения одной из касательных к графику функции f(x), построенных в точках a и b, с осью абсцисс (выбирается та из точек пересечения, которая принадлежит отрезку [a, b]).

На рис. 2.1 изображены варианты выбора следующего отрезка, на котором будет осуществлен поиск корня, когда точка c определена методом хорд. На рис. 2.1 а приведено изменение левого конца отрезка (на следующей итерации точку a перенесем в точку x), на рис. 2.1 б – правого (точку b перенесем в точку x).

24

Рис. 2.1. Демонстрация одной итерации метода хорд.

При решении этой задачи будем использовать делегаты. Напомним, что делегаты – это классы, которые предназначены для хранения информации о каких-либо методах с одинаковым прототипом. Объект делегата становится псевдонимом хранимого метода (ссылкой на него). Как и любой объект, делегат можно передавать в функцию как параметр, он может являться полем класса или возвращаемым значением функции. Делегат также можно использовать для вызова хранимого метода в тот момент, когда это станет необходимо.

Создадим класс Solver, который будет решать уравнение. Пусть этот класс будет содержать только один открытый статический метод RootEquation() для решения уравнения. Остальные методы – методы определения точки-середины отрезка (GetX1()), точки пересечения хорды (GetX2()), и точки пересечения касательной с осью абсцисс (GetX3()), будут закрытыми, т.е. будут играть служебную роль. Выбор метода решения будет осуществляться случайным образом в статическом конструкторе.

Такая структура класса предусматривает использование делегатов в двух ситуациях:

1) Делегаты часто используются в качестве параметров других методов. В нашем случае уравнение будет задано посредством определения функции f(x). Для хранения информации о том, как ее вычислять, создадим делегат:

25

// делегат для представления математической функции delegate double Function(double x);

Экземпляр данного делегата может хранить информацию о любом методе, который получает в качестве параметра и возвращает вещественное число, например, таким методом может быть статический метод Sin() класса Math.

Таким образом, метод RootEquation() может получить указание как вычислять функцию f(x) с помощью параметра типа указанного делегата.

2) Второй случай использования делегатов – настройка алгоритма решения задачи. Как уже было сказано, все три метода поиска корня основываются на единой процедуре, которая отличается только реализацией одного шага – выбора точки, разбивающей отрезок на два. Для реализации этого шага создадим в классе Solver три различных метода, информацию о которых будет хранить экземпляр следующего делегата:

// делегат для определения точки деления исходного отрезка delegate double PointIn (Function f, double a, double b);

В классе Solver данный экземпляр будем хранить в виде статического поля method и инициализируем его в статическом конструкторе:

class Solver

{

// делегат для определения точки деления отрезка static PointIn method;

// статический конструктор static Solver()

{

// инициализация генератора случайных чисел

Random r = new Random(); int q = r.Next(300)%3; switch (q)

{

case 0:

// инициализация делегата функцией GetX1 method = GetX1; Console.WriteLine("Выбран метод деления

отрезка пополам");

break;

26

case 1:

// инициализация делегата функцией GetX2 method = GetX2; Console.WriteLine("Выбран метод хорд"); break;

case 2:

// инициализация делегата функцией GetX3 method = GetX3;

Console.WriteLine("Выбран метод касательных"); break;

}

}

// получение середины отрезка

static double GetX1(Function f, double a, double b)

{

return (a + b) / 2;

}

//получение точки пересечения хорды с осью OX static double GetX2(Function f, double a, double b)

{

return a - f(a) * (b - a) / (f(b) - f(a));

}

//получение точки пересечения касательной с осью OX static double GetX3(Function f, double a, double b)

{

//приближенное вычисление производной в точке a double fa = (f(a + 0.001) - f(a)) / 0.001;

if (fa != 0)

{

//получение точки пересечения касательной

//с осью абсцисс

double c = a - f(a) / fa;

//если точка c принадлежит отрезку,

//выбираем ее

if (a <= c && c <= b) return c;

}

// приближенное вычисление производной в точке b double fb = (f(b + 0.001) - f(b)) / 0.001;

if (fb != 0)

{

//получение точки пересечения касательной

//с осью абсцисс

double c = b - f(b) / fb; if (a <= c && c <= b)

return c;

}

27

//функция не удовлетворяет тем свойствам,

//которые гарантируют существование корня throw new Exception("Возможно, на этом

отрезке корней нет");

}

. . .

}

Для решения уравнения достаточно иметь только одну функцию, которая реализует общую процедуру всех методов решения уравнений. Реализация пункта 1 процедуры в этой функции осуществляется посредством вызова через делегат method именно того метода деления отрезка, который был выбран при инициализации класса Solver. В указанном далее коде использование делегатов выделено.

//метод решения уравнения

//1 параметр – делегат функции, которая определяет уравнение,

//2, 3 параметры - концы отрезка,

//4 параметр - точность решения

static public double RootEquation(Function f, double a, double b, double eps)

{

//корень гарантировано существует,

//если на концах отрезка

//функция принимает значения различных знаков. if(f(a) * f(b) > 0)

throw new Exception("Возможно, на этом отрезке корней нет");

//цикл метода - вычисления продолжается до тех пор,

//пока на одном из концов отрезка не будет получено

//значение функции с заданной точностью

while(Math.Abs(f(a))>eps && Math.Abs(f(b))>eps)

{

//поиск точки деления отрезка - вызов делегата double c = method(f,a,b);

//если найденная точка - корень, это решение if(f(c) == 0)

return c;

//выбор следующего отрезка

if(f(a)*f(c) < 0) b = c;

else

a = c;

}

// выбор приближенного значения корня if(Math.Abs(f(a))<=eps )

return a; else

return b;

}

28

Использование разработанного класса для приближенного решения квадратного уравнения x2 – 2x – 5 = 0 может быть таким:

class Program

{

// функция определения уравнения static double func(double x)

{

return x * x - 2 * x - 5;

}

static void Main(string[] args)

{

Console.WriteLine("Введите отрезок"); Console.WriteLine("Введите a");

double a = double.Parse(Console.ReadLine()); Console.WriteLine("Введите b");

double b = double.Parse(Console.ReadLine()); Console.WriteLine("Введите точность решения"); double eps = double.Parse(Console.ReadLine()); try

{

// вызов метода решения уравнения

Console.WriteLine("Корень = {0}",

Solver.RootEquation(func, a, b, eps));

}

catch (Exception e)

{

Console.WriteLine(e.Message);

}

}

}

Рис 2.2. Демонстрация работы программы при выборе метода деления отрезка пополам

29

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]