- •Ответы по дисциплине «Основы алгоритмизации и программирования»
- •Запись алгоритма Евклида на языке с
- •Int main() {
- •Эвристический алгоритм «ближайшего соседа»
- •Эвристический алгоритм «ближайших пар»
- •«Правильный» алгоритм поиска маршрута
- •Эволюция языка с bcpl → b → c → k&r c → ansi c → c99 → c1x
- •#Define имя текст_для_подстановки
- •123, 67543, 037, 07777, 0Xabf7, 0xffff, …
- •123456789L, 0xful (это просто число 15).
- •Определение символических констант в limits.H
- •Int lower, upper, step;
- •Int main() {
- •Int main() {
- •Int main() {
- •Всего операций: 47
- •If (условие) оператор
- •If (условие) оператор1 else оператор2
- •Int main() {
- •Int main() {
- •Int main() {
- •Int main() {
- •If (found)
- •Адресация памяти
- •Адреса объектов программы
- •Int fact(int n) {
- •О размерах участков памяти, выделяемых объектам
- •Правила адресной арифметики
- •Никакие другие операции к адресам неприменимы, т.Е. Адреса нельзя умножать, делить, складывать между собой и пр.
- •Имя массива – это константный указатель на его начало.
- •T X[] эквивалентно t *X
- •Int main() {
- •Void *calloc(size_t n, size_t r)
- •Void free(void *p)
- •Int main() {
- •Void *p;
- •Void swaps(char** a, char** b) {
- •Int main(void) {
- •Int main() {
- •Правило «право-лево»
- •Int pt_in_rect(struct point p, struct rect r) {
- •Int main() {
- •Int main() {
- •Int ival;
- •Void init(Vector*);
- •Void resize(Vector*, int);
- •Void push_back(Vector*, double);
- •Void push_s(Stack *st, double d) {
- •Void init_q(Queue *q) {
- •Void enqueue(Queue *q, double d) {
- •Int dequeue(Queue *q, double *d) {
- •Typedef struct Heap {Vector V;} Heap;
- •Void init_h(Heap *hp) {
- •Int Heap_Maximum(Heap *hp, double *z) {
- •Void Max_Heap_Insert(Heap *hp, double X){
- •Void Max_Heapify(Heap *hp, int I) {
- •Int l, r, largest;
- •Int Heap_Extract_Max(Heap *hp, double *z) {
- •Void Build_Max_Heap(Heap *hp) {
- •Void Insert_head_l1(List1 *l, double z) {
- •Void Insert_back_l1(List1 *l, double z) {
- •Int Extract_head_l1(List1 *l, double *z) {
- •Int Extract_back_l1(List1 *l, double *z) {
- •Void reverse_l1(List1 *l) {
- •Исходный код функции sort_l1
- •Void sort_l1(List1 *l) {
- •Void visit(List1* l) {
- •Void traverse(List1* l) {
- •Void Print_l1(List1 *l) {
- •Void Insert_l2(List2 *l, double z, int direction) {
- •Прямой обход (сверху вниз), при котором мы посещаем узел, а затем левое и правое поддеревья
- •Поперечный обход (слева направо), при котором мы посещаем левое поддерево, затем узел, а затем правое поддерево
- •Обратный обход (снизу вверх), при котором мы посещаем левое и правое поддеревья, а затем узел.
- •Простой метод сортировки массива
- •Задача о взвешивании монет
- •1) Очевидно, что на последнем шаге процедуры взвешивания мы должны иметь дело максимум с 3 монетами, чтобы в при любом исходе взвешивания получить результат.
- •2) Задача предпоследнего шага – отобрать группу из 3-х монет. Это можно сделать, если в нашем распоряжении будет не более 9 монет (3 группы по 3 монеты).
- •3) Наконец, если у нас будет от 10 до 27 монет, мы сможем отобрать из них не более 9
- •Void mov(int n, char a, char c, char b) {
- •Int main() {
Запись алгоритма Евклида на языке с
/* Алгоритм Евклида */
#include <stdio.h>
#include <stdlib.h>
Int main() {
int m, n, r;
printf("Input m: "); scanf("%d", &m);
printf("Input n: "); scanf("%d", &n);
/* Е0. [Гарантировать, что m>n.] Если m<n, то m <-> n */
if (m<n) {r=m; m=n; n=r;}
while (1) {
/* E1. [Нахождение остатка.] Разделим m на n, и пусть остаток от деления будет равен r */
r=m%n;
/* Е2. [Сравнение с нулем.] Если r = 0, то завершить; n — НОД */
if (r==0) break;
/* ЕЗ. [Замещение.] Присвоить m <- n, n <- r и вернуться к E1 */
m=n; n=r;
}
printf("Greatest common divisor = %d \n", n);
exit(EXIT_SUCCESS);
}
Вопрос №3. Что такое «анализ алгоритмов»?
На практике нужны не просто алгоритмы, а хорошие алгоритмы в широком смысле этого слова. Одним из критериев качества алгоритма является время, необходимое для его выполнения; данную характеристику можно оценить по тому, сколько раз выполняется каждый шаг. Другими критериями являются адаптируемость алгоритма к различным компьютерам, его простота, изящество и т. д.
Часто решить одну и ту же проблему можно с помощью нескольких алгоритмов и нужно выбрать наилучший из них. Этими вопросами занимается важная область теоретического программирования - анализ алгоритмов. Предмет этой области состоит в том, чтобы для заданного алгоритма определить его рабочие характеристики.
Вопрос №4. Какие вопросы рассматривает теория алгоритмов?
Теория алгоритмов — это более широкая область, которая включает не только вопросы анализа алгоритмов, но и рассматривает формальные модели алгоритмов, вопросы существования или не существования эффективных алгоритмов решения определенных задач (алгоритмически неразрешимые проблемы), эквивалентность алгоритмов и др.
Вопрос №5. Сравнение эвристических и точных алгоритмов (на примере задачи коммивояжера).
Эвристический алгоритм «ближайшего соседа»
Начиная с произвольной точки p0, идем к ближайшей к ней точке (соседу) p1. От нее – к ее ближайшему еще не посещенному соседу (при этом точка p0 исключается из рассмотрения). Процесс повторяется до тех пор, пока не останется ни одной не посещенной точки, после чего мы возвращаемся в исходную точку p0, завершая маршрут.
Этот алгоритм прост в понимании, легко реализуется и достаточно эффективен, в общем, удовлетворяет всем свойствам алгоритма, но он совершенно неправилен! Т.е. получаемый с его помощью маршрут не обязательно будет самым коротким.
L=64
L=84
Эвристический алгоритм «ближайших пар»
Еще одна эвристика – соединять пары самых близких точек, если такое соединение не вызывает никаких проблем (досрочное завершение цикла или три пути из одной точки). Каждая вершина, еще не вошедшая в цепочку, рассматривается как самостоятельная «одновершинная» цепочка. Таким образом, на любом этапе выполнения этого алгоритма имеется множество отдельных вершин и множество не имеющих общих вершин цепочек, которые нужно соединить.
Общее количество искомых звеньев полного маршрута = n-1, поэтому алгоритм имеет вид цикла for i=1 to n-1 do, т.е. на каждом шаге отыскивается ровно одно звено, которое либо соединяет две отдельных вершины, либо «подключает» отдельную вершину к цепочке, либо соединяет две цепочки. За пределами цикла формируется последнее звено, соединяющее начало и конец единственной цепочки.
Этот алгоритм правильно работает на предыдущем примере:
Однако на другом примере алгоритм ближайших пар работает неверно!
L=5-e+√(5+6e+5e2)
→ 7.24
L=6+2e
→ 6