Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


Рекурсивный алгоритм решения задачи Ханойская башня




#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

 

void move(int i, int j, int d)

 

{

int m;

for(m = 0; m < d; m++) printf(" ");

printf("перемещение(%d,%d)\n", i, j);

}

 

void hanoy(int i, int j, int k, int d)

 

{

// для отладки

//int m;

//for (m = 0; m < d; m++) printf(" ");

//printf("hanoy(%d,%d,%d)\n", i, j, k);

 

if (k == 1)

move(i, j, d);

else

 

{

hanoy(i, 6-i-j, k-1, d+1);

hanoy(i, j, 1, d+1);

hanoy(6-i-j, j, k-1, d+1);

}

}

 

int main()

{

int n = 4;

printf("Введите количество дисков: ");

scanf("%d",&n);

printf("\nХаной %d дисками:\n", n);

hanoy(1, 3, n, 0);

getch();

// system ("pause");

}

 

Результат выполнения программы

Введите количество дисков: 4

 

Ханой с 4 дисками:

перемещение(1,2)

перемещение(1,3)

перемещение(2,3)

перемещение(1,2)

перемещение(3,1)

перемещение(3,2)

перемещение(1,2)

перемещение(1,3)

перемещение(2,3)

перемещение(2,1)

перемещение(3,1)

перемещение(2,3)

перемещение(1,2)

перемещение(1,3)

перемещение(2,3)

 

Пример 13. Функция решение кубического уравнения с действительными коэффициентами методом Виета-Кардано. Корни могут быть комплексными.

Кубическое уравнение записывается в виде:

x3+a*x2+b*x+c=0.

 

Для нахождения его корней, в случае действительных коэффициентов, вначале вычисляются:

Q = (a2-3b)/9

R = (2a3-9ab+27c)/54.

 

Далее, если R2 < Q3, то уравнение имеет три действительных корня, вычисляющихся по формулам (Виета):

е = acos(R/sqrt(Q3))/3,

x1 = -2*sqrt(Q)cos(t)-a/3,

x2 = -2*sqrt(Q)cos(t+(2*pi/3))-a/3,

x3 = -2*sqrt(Q)cos(t-(2*pi/3))-a/3.

 

В том случае, когда R2 >= Q3, то действительных корней один (общий случай) или два (вырожденные случаи). Кроме действительного корня, имеется два комплексно-сопряженных. Для их нахождения вычисляются (формула Кардано):

A = -sign(R)[|R|+sqrt(R2-Q3)]1/3,

B = Q/A при A!= 0 или B = 0 при A = 0.

 

Действительный корень будет:

x1 = (A+B)-a/3.

 

Комплексно-сопряженные корни:

x2,3 = -(A+B)/2-a/3 + i*sqrt(3)*(A-B)/2

В том случае, когда A = B, то комплексно-сопряженные корни вырождаются в действительный:

x2=-A-a/3.

 

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

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

#define M_PI (3.141592653589793)

#define M_2PI (2.*M_PI)

 

/*

int Cubic(double *x,double a,double b,double c);

Параметры:

a, b, c - коэффициенты

x - массив решения (size 3).

На выходе:

3 действительных корня -> затем x ими заполняется;

1 действительный + 2 комплексных -> x[0] - действительный, x[1] действительная часть комплексных корней, x[2] - неотрицательная мнимая часть.

Результаты:

1 - 1 действительный + 2 комплексных;

2 - 1 действительный корень + мнимая часть комплексных корней, если 0 (т.е. 2 действительных корня).

3 - 3 действительных корня;

*/

 

int Cubic (double *x, double a, double b, double c)

{

double q, r, r2, q3;

q = (a * a - 3. * b) / 9.;

r = (a * (2. * a * a - 9. * b) + 27.*c) / 54.;

r2 = r * r;

q3 = q * q * q;

if (r2 < q3)

{

double t = acos(r / sqrt (q3));

a /= 3.;

q = -2. * sqrt (q);

x[0] = q * cos (t / 3.) - a;

x[1] = q * cos ((t + M_2PI) / 3.) - a;

x[2] = q * cos ((t - M_2PI) / 3.) - a;

return (3);

}

else

{

double aa, bb;

if (r <= 0.) r =- r;

aa = -pow (r + sqrt (r2 - q3), 1. / 3.);

if(aa!= 0.) bb = q / aa;

else bb = 0.;

a /= 3.;

q = aa + bb;

r = aa - bb;

x[0] = q - a;

x[1] = (-0.5) * q - a;

x[2] = (sqrt (3.) * 0.5) * fabs (r);

if (x[2] == 0.) return (2);

return (1);

}

}

 

Пример 14. Алгоритм Краута (нижне-верхняя (LU) декомпозиция матрицы).

Алгоритм Краута - это фактически метод решения систем общего вида, конкурирующий по быстродействию с общеизвестным методом Гаусса-Жордана, но позволяющий более эффективно использовать решение.

Если мы можем разложить матрицу линейной системы A в произведение A=L*U, где L - нижняя, а U - верхняя треугольные матрицы, то решение системы уравнений с произвольной правой частью производится весьма просто, применением двух обратных подстановок. Более того, в отличие от известного метода Гаусса-Жордана, разложенная матрица позволяет быстро решать серии линейных уравнений с различными правыми частями при одной и той же матрице.

Метод Краута позволяет провести LU-декомпозицию матрицы примерно за то же число операций, что и "прямая" часть метода Гаусса-Жордана. Итоговые коэффициенты двух треугольных матриц упаковываются в матрицу того же размера, что и A, и на том же месте в памяти; при этом верхняя матрица U размещается в наддиагональной части и на диагонали; нижняя L в поддиагональной части, а диагональные элементы L считаются все равными 1 (без ограничения общности) и не выводятся.

Алгоритм Краута представляет из себя следующее:

1. Положить Lii=1 i=0,...,N-1 (здесь ничего не производится, а только имеется в виду для дальнейших шагов;

2. Для каждого j=0,1,...,N-1 проделать:

1. Для всех i=0,...,j вычислить Uij:

Uij=Aij - SUM(0<=k<i)(Lik*Uik) (при i=0 сумму полагать нулем);

 

2. Для всех i=j+1,...,N-1 вычислить:

Lij = Aij - SUM(0<=k<j)(Lik*Uik))/Uii

 

Обе процедуры выполняются до перехода к новому j.

Устойчивая работа алгоритма Краута возможно только при условии выбора ведущего элемента для каждого обращения к j из п.2 алгоритма. Выбор производится перед выполнением п.2 для очередного j перестановки необработанных строк матрицы так, чтобы строка, подставляемая на место j, содержала наибольший по модулю элемент в колонке j. Порядок перестановки необходимо запомнить, для чего достаточно использовать дополнительный вектор целых величин. Еще один вектор используется внутри алгоритма для масштабирования.

Алгоритм Краута имеет несколько приложений, одно из которых - решение системы линейных уравнений (обратная подстановка с учетом порядка перестановки строк), другие - вычисление обратной матрицы и вычисление детерминанта. Подробнее вызовы функций, связанных с алгоритмом Краута и реализация деталей алгоритма находятся в комментариях к программе и в ней самой.

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

#define TINY 1.e-30

 

/*

LU-декомпозиция в соответствии с алгоритмом Краута.

Описание:

 

int LU_decompos(double **a,int n,int *indx,int *d,double *vv);

 

Параметры:

a - исходная матрица (n x n) на входе;

n - размер матрицы;

indx - массив целых чисел (размера n) для запоминания перемещений;

d - на выходе, содержит +1 или -1 для четного или нечетного числа перемещений.

vv - временный массив (size n).

Результат:

0 - некорректная входная матрица (непригодна для декомпозиции),

1 - положительный результат.

 

Обратная замена, использующая LU-декомпозицию матрицы.

Описание:

 

void LU_backsub(double **a,int n,int *indx,double *b);

 

Параметры:

a - матрица, разложенная по Крауту;

n - размер матрицы;

indx - порядок перемещения, полученный алгоритмом декомпозиции;

b - вектор (размера n).

 

Примечание: a и indx не изменяются в этой функции и могут быть использованы повторно.

 

Инвертирование матрицы с использованием LU-разложенной матрицы.

Описание:

 

void LU_invert(double **a,int n,int *indx,double **inv,double *col);

 

Параметры:

a - матрица, разложенная по Крауту;

n - размер матрицы;

indx - порядок перестановки, полученный алгоритмом декомпозиции;

inv - матрица назначения;

col - временный массив (размера n).

 

Получение матрицы, полученной с использованием LU-разложенной матрицы

Описание:

 

double LU_determ(double **a,int n,int *indx,int *d);

 

Параметры:

a - матрица, разложенная по Крауту;

n - размер матрицы;

indx - порядок перемещения, полученный алгоритмом декомпозиции;

d - знак равенства (+1 или -1) полученный при декомпозиции.

 

Результат: определяющее значение.

*/

 

 

/* декомпозиция */

int LU_decompos (double **a, int n, int *indx, int *d, double *vv)

{

register int i, imax, j, k;

double big, sum, temp;

for (i = 0; i < n; i++)

{

big = 0.;

for (j = 0; j < n; j++)

if ((temp = fabs (a[i][j])) > big) big = temp;

if (big == 0.) return (0);

vv[i] = big;

}

 

/* главный цикл алгоритма Краута */

for (j = 0; j < n; j++)

{ /* это часть a) алгоритма, исключая i==j */

for (i = 0; i < j; i++)

{

sum = a[i][j];

for (k = 0; k < i; k++) sum -= a[i][k] * a[k][j];

a[i][j] = sum;

}

/* инициализация для поиска наибольшего элемента */

big = 0.;

imax = j;

for (i = j; i < n; i++)

{

sum = a[i][j];

for (k = 0; k < j; k++) sum -= a[i][k] * a[k][j];

a[i][j] = sum;

if ((temp = vv[i] * fabs (sum)) >= big)

{

big = temp;

imax = i;

}

}

/* обмен строк, если нужен, смена равенства и размера множителя */

if (imax!= j)

{

for (k = 0; k < n; k++)

{

temp = a[imax][k];

a[imax][k] = a[j][k];

a[j][k] = temp;

}

*d =- (*d);

vv[imax] = vv[j];

}

/* сохраняем индекс */

indx[j] = imax;

if (a[j][j] == 0.) a[j][j] = TINY;

if (j < n - 1)

{

temp = 1. / a[j][j];

for (i = j+1; i < n; i++) a[i][j] *= temp;

}

}

return (1);

}

 

/* обратная замена */

void LU_backsub (double **a, int n, int *indx, double *b)

{

register int i, j, ip, ii = -1;

double sum;

/* первый шаг обратной замены */

for (i = 0; i < n; i++)

{

ip = indx[i];

sum = b[ip];

b[ip] = b[i];

if (ii >= 0)

for (j = ii; j < i; j++) sum -= a[i][j] * b[j];

else if (sum) ii = i; /* получен ненулевой элемент */

b[i] = sum;

}

/* второй шаг */

for (i = n - 1; i >= 0; i--)

{

sum = b[i];

for (j = i + 1; j < n; j++) sum -= a[i][j] * b[j];

b[i] = sum / a[i][i];

}

}

 

/* инвертирование матрицы */

void LU_invert (double **a, int n, int *indx, double **inv, double *col)

{

register int i, j;

for (j = 0; j < n; j++)

{

for (i = 0; i < n; i++) col[i] = 0.;

col[j] = 1.;

LU_backsub (a, n, indx, col);

for (i = 0; i < n; i++) inv[i][j] = col[i];

}

}

 

/* определяющее вычисление*/

double LU_determ (double **a, int n, int *indx, int *d)

{

register int j;

double res = (double)(*d);

for (j = 0; j < n; j++) res *= a[j][j];

return (res);

}

 

Пример 15. Алгоритм поиска интервала нахождения корня нелинейной функции.

Выяснение интервала, на котором корень содержится является важной проблемой поиска корня нелинейной функции действительной переменной. Здесь приведен алгоритм поиска такого интервала и ограничения на его применение. Примем, что корень функции f(x) окружен на интервале [a,b], если f(a) и f(b) имеют противоположные знаки. Чтобы окруженный согласно этому определению корень действительно существовал на этом интервале, достаточно непрерывности f(x), а для его единственности - еще и монотонности. При невыполнении этих свойств возможно отсутствие корня на [a,b] или неопределенность его позиции.

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

 

Окружение корня функции при гарантии ее определения на неограниченном интервале, производится по следующему итерационному алгоритму.

1. Для начального приближения x0, найти f0=f(x0), задать начальный интервал поиска D и его инкремент d>1.

2. Вычислить a=x0-D, b=x0+D; fa=f(a), fb=f(b).

3. Увеличить интервал поиска: D=D*d. Если интервал превысил некоторый заданный предел, то выйти с индикацией ошибки.

3.a. Если знаки fa и f0 отличаются, то считать корень окруженным на [a,x0] -> выход.

3.b. Если знаки fb и f0 отличаются, то считать корень окруженным на [x0,b] -> выход.

3.c. Если f0 > 0 (случай меньше нуля делается аналогично) алгоритм продолжается:

4. Проверяется, какое из fa или fb наименьшее. Если оба одинаковы, то переходим к 4a (двусторонний поиск), если fb - производим поиск вправо 4b, иначе - поиск влево 4c.

4.a. Находим a=a-D, b=b+D, fa=f(a), fb=f(b), идем к пункту 3.

4.b. Присваиваем последовательно a=x0, x0=b, fa=f0, f0=fb; находим b=b+D, fb=f(b), идем к пункту 3.

4.c. Аналогично 4b, только направление поиска - влево.

Так как интервал поиска постоянно расширяется, то в конце концов используя указанный алгоритм корень будет окружен. Возможны модификации алгоритма в двух направлениях:

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

2. Если область определения функции заведомо ограничена, то расширение интервала поиска также следует ограничивать имеющимися пределами, либо доопределять функцию там, где ее оригинал не определен.

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

 

/*

Предполагается, что функция существует на бесконечном и непрерывном промежутке.

 

int BracketRoot (double x0, double *a, double *b, double d0, double di, double dmax, double (*fun)(double));

 

Параметры:

x0 - начальное приближение;

a - левая граница;

b - правая граница;

d0 - начальный интервал поиска;

di - интервал расширения (в геометрической прогрессии);

dmax - максимальный интервал;

fun - указатель на функцию.

Возвращает:

1 - если корень окружен;

0 - сбой

*/

 

int BracketRoot (double x0, double *a, double *b, double d0,

double di, double dmax, double (*fun)(double))

{

double fa, fb, f0;

f0 = (*fun)(x0);

*a = x0 - d0;

*b = x0 + d0;

fa = (*fun)(*a);

fb = (*fun)(*b);

while ((d0 *= di) < dmax)

{

if (f0 >= 0.)

{

if (fa < 0.)

{

*b = x0;

return (1);

}

if (fb < 0.)

{

*a = x0;

return (1);

}

if (fa > fb)

{

*a = x0;

x0 = (*b);

*b += d0;

fa = f0;

f0 = fb;

fb = (*fun)(*b);

}

else

if (fa < fb)

{

*b = x0;

x0 = (*a);

*a -= d0;

fb = f0;

f0 = fa;

fa = (*fun)(*a);

}

else

{

*a -= d0;

*b += d0;

fa = (*fun)(*a);

fb = (*fun)(*b);

}

}

else

if (f0 < 0.)

{

if (fa >= 0.)

{

*b = x0;

return (1);

}

else

if (fb >= 0.)

{

*a = x0;

return (1);

}

if (fa < fb)

{

*a = x0;

x0 = (*b);

*b += d0;

fa = f0;

f0 = fb;

fb = (*fun)(*b);

}

else

if (fa > fb)

{

*b = x0;

x0 = (*a);

*a -= d0;

fb = f0;

f0 = fa;

fa = (*fun)(*a);

}

else

{

*a -= d0;

*b += d0;

fa = (*fun)(*a);

fb = (*fun)(*b);

}

}

}

return (0);

}

 

Пример 16. Алгоритм нахождения Полинома Лагранжа (интерполяция Лагранжа).

Интерполяционный многочлеен Лагранжа - многочлен минимальной степени, принимающий данные значения в данном наборе точек. Для n + 1 пар чисел (x_0, y_0), (x_1, y_1) \ dots (x_n, y_n), где все xi различны, существует единственный многочлен L(x) степени не более n, для которого L(xi) = yi.

В простейшем случае (n = 1) - это линейный многочлен, график которого - прямая, проходящая через две заданные точки.

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

 

 

// таблица из учебника по вычислительной математике

float x[6] = {1.5, 1.54, 1.56, 1.60, 1.63, 1.70};

float y[6] = {3.873, 3.924, 3.950, 4.00, 4.037, 4.123};

 

/* Функция, вычисляющая коэффициенты Лагранжа

x - аргумент функции

n - степень многочлена (или число x-ов)

i - номер узла

*/

float L(float xp, int n, int i)

{

// числитель и знаменатель

float Chesl;

float Znam;

 

Chesl = 1;

Znam = 1;

 

int k;

// вычисление числителя

for (k = 0; k!= n; k++)

{

if (k == i) continue;

// убираем множитель x - x(i)

Chesl *= xp - x[k];

}

// вычисление знаменателя

for (k= 0; k!= n; k++)

{

if (x[i] == x[k]) continue;

// убираем, а то ноль в знаменателе

Znam *= x[i] - x[k];

}

return Chesl / Znam;

}

 

int main()

{

// вычисляем степень полинома

int n = sizeof(y) / sizeof(float);

// начальное значение

float R = 0;

// произвольная точка для проверки

float px = 1.55;

// вычисляем значение интерполяционного многочлена в точке должно получиться 3. 937075

for (int i = 0; i!= n; i++)

{

R += y[i] * L(px,n,i);

}

printf("Результат: %f \n",R);

 

system ("pause");

}

 

Результат выполнения программы

Результат: 3.937075

 

Пример 17. Алгоритм вычисление квадратного корня по алгоритму Ньютона.

Для вычисления квадратного корня в этом примере использован метод Ньютона. Рассмотрены машинно-зависимый и машинно-независимый варианты. Перед применением алгоритма Ньютона область определения исходного числа сужается до [0.5,2] (во втором варианте до [1,16]). Второй вариант машинно-независим, но работает дольше.

Вообще, это не самый быстрый вариант, но один из самых быстрых. Основная проблема заключается в том, что нет универсального машинного представления чисел с плавающей точкой, поэтому разделение числа на мантиссу и двоичную экспоненту как составляющих его компьютерного представления нельзя записать единым образом. Имено поэтому подключено описание математической библиотеки, из которой, впрочем, используются только frexp() и ldexp(). Конкретная реализация этих функций очень проста, но машинно-зависима. При разделении числа на мантиссу и экспоненту, мантисса оказывается в пределах [0.5,1). Если при этом экспонента нечетная, мантисса умножается на 2, тем самым область определения становится [0.5,2). К мантиссе применяется алгоритм Ньютона с начальным приближением 1. Окончательный результат получается с помощью применения ldexp() с половинным значением экспоненты.

Для машинно-независимого варианта выделение экспоненты заменяется серией последовательных делений исходного значения на 16, пока аргумент не станет определяться на интервале [1,16]. Если исходный аргумент был меньше 1, то перед серией делений обращаем его. Алгоритм Ньютона применяется с начальным приближением 2. Окончательный результат получается серией последовательных умножений на 4 и дальнейшим обращением, если аргумент обращался.

Сам алгоритм Ньютона для вычисления a = Sqroot(x) представляет быстро сходящуюся (при хорошем начальном приближении) серию итераций: ai+1=0.5*(ai+x/ai), где i - номер итерации.

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <conio.h>

#include <string.h>

/* для одинарной точности нужны 4 итерации */

#define ITNUM 4

 

 

/*

Разложение квадратного корня аргумента в мантиссу и экспонент (быстрый алгоритм):

float Sqroot (float x);

 

Вычисление квадратного корня без использования внешних фукнций (медленнее, но машинно-независим):

float Sqroot1 (float x);

 

В случае некорректного промежутка (x<0.) фукнции возвращают 0 и не генерят ошибку.

*/

 

/* вариант с использованием внешних фукнций разложения/объединения на [0.5,1] */

float Sqroot (float x)

{

int expo, i;

float a, b;

if (x <= 0.F) return (0.F);

/* разложение x в мантиссу на промежутке [0.5,1) и экспонент.

Машинно-зависимые операции представлены вызовами функций. */

x = frexp (x, &expo);

/* нечетный экспонент: умножаем мантиссу на 2 и уменьшаем экспонент, делая его четным.

Теперь мантисса в промежутке [0.5,2.) */

if (expo & 1)

{

x *= 2.F;

expo--;

}

/* начальное приближение */

a = 1.F;

for (i = ITNUM; i > 0; i--)

{

 

b = x / a;

a += b;

a *= 0.5F;

}

/* делим экспонент на 2 и объединяем результат.

Фукнция ldexp() противоположна frexp. */

a = ldexp(a, expo / 2);

return (a);

}

 

/* Вариант без использования библиотек. Промежуток уменьшен до [1,16].

Используется 16 повторяющихся делений. */

float Sqroot1 (float x)

{

int sp = 0, i, inv = 0;

float a, b;

if (x <= 0.F) return (0.F);

/* аргумент меньше 1: инвертируем его */

if (x < 1.F)

{

x = 1.F / x;

inv = 1;

}

/* последовательно делим на 16 пока аргумент не станет <16 */

while (x > 16.F)

{

sp++;

x /= 16.F;

}

/* начальное приближение */

a = 2.F;

/* Алгоритм Ньютона */

for (i = ITNUM; i > 0; i--)

{

b = x / a;

a += b;

a *= 0.5F;

}

while (sp > 0)

{

sp--;

a *= 4.F;

}

/* инвертируем результат для инвертированнго аргумента */

if (inv) a = 1.F / a;

return (a);

}

 

 

int main()

{

float x;

printf ("Введите число: ");

scanf ("%f", &x);

printf ("\nРезультат с использованием стандартных библиотек: %f\n", Sqroot(x));

printf ("Результат без использования стандартных библиотек: %f\n", Sqroot1(x));

system ("pause");

}

 

Результат выполнения программы

Введите число: 35

 

Результат с использованием стандартных библиотек: 5.916080

Результат без использования стандартных библиотек: 5.916080

 





Поделиться с друзьями:


Дата добавления: 2016-10-22; Мы поможем в написании ваших работ!; просмотров: 826 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Студент может не знать в двух случаях: не знал, или забыл. © Неизвестно
==> читать все изречения...

2741 - | 2304 -


© 2015-2024 lektsii.org - Контакты - Последнее добавление

Ген: 0.012 с.