Optymalizacja poprawnego rozwiązania zadania

0

Cześć,

Mam do zrealizowana zadanie, którego treść wkleiłem poniżej w postaci obrazka. Jeszcze niżej znajduje się kod programu który napisałem. Kod działa (dla poprawnych danych wejściowych daje odpowiednie rezultaty), wydaje mi się też, że nie jest najgorszy, ale nie jest niestety wystarczająco optymalny. Zrealizowany jest w oparciu o "zapętloną" listę jednokierunkową. Pomóżcie i powiedzcie, co mógłbym zmienić, aby ten program wykonywał się szybciej?

user image

#include <stdio.h>
#include <stdlib.h>

typedef struct n {
    int value;
    struct n *next;
} node;

typedef struct {
    node *begin;
    node *end;
    node *pos;
    int size;
} list;

list *addToList(list *l, int i);

void processList(list *l, int numberOfOperations);

void printList(list *l);

void movePOSRight(list *l, int i);

void makeOperR(list *l);

void makeOperX(list *l);

int main(int argc, char **argv) {

    int numberOfOperations = 0;
    int x = 0;
    list *l = malloc(sizeof(*l));
    l->size = 0;
    l->begin = l->end = l->pos = NULL;

    if (scanf("%d", &x) == 1) {
        numberOfOperations = x;
        while (scanf("%d", &x) == 1) {
            l = addToList(l, x);
        }
        if (l->size != 0) {
            processList(l, numberOfOperations);
        }
    }

    printList(l);

    return 0;

}

list *addToList(list *l, int i) {
    if (l->begin == NULL) {
        l->begin = malloc(sizeof(l->begin));
        l->begin->value = i;
        l->pos = l->end = l->begin->next = l->begin;
    } else {
        l->end->next = malloc(sizeof(l->end->next));
        l->end->next->value = i;
        l->end->next->next = l->begin;
        l->end = l->end->next;
    }
    l->size++;
    return l;
}


void processList(list *l, int numberOfOperations) {
    while (numberOfOperations--) {
        if (l->pos->value % 2 == 0) {
            makeOperR(l);
        } else {
            makeOperX(l);
        }
    }
}

void printList(list *l) {
    int i;
    node *n;
    if (l == NULL || l->size == 0) {
        printf("-1");
        return;
    }
    n = l->pos;
    printf("%d", n->value);
    n = n->next;
    for (i = 1; i < l->size; i++) {
        printf(" %d", n->value);
        n = n->next;
    }
}


void makeOperX(list *l) {
    node *tmp = l->pos->next;
    l->pos->next = malloc(sizeof(l->pos->next));
    l->pos->next->next = tmp;
    l->pos->next->value = l->pos->value - 1;
    l->size++;
    movePOSRight(l, l->pos->value);
}

void makeOperR(list *l) {
    int i = l->pos->next->value;
    l->pos->next = l->pos->next->next;
    l->size--;
    movePOSRight(l, i);
}

void movePOSRight(list *l, int i) {
    i = i % l->size;
    while (i--) {
        l->pos = l->pos->next;
    }
}
0

Odświeżam. Znacznie poprawiłem kod dodając mu więcej czytelności.

0

Wywalić i napisać z użyciem drzewa binarnego.

0
_13th_Dragon napisał(a):

Wywalić i napisać z użyciem drzewa binarnego.

Mógłbyś wytłumaczyć dlaczego BT przyśpieszy wykonywane operacje? Wydaje mi się , że skasowanie i wstawienie do BT ma taką samą złożoność jak do listy jednokierunkowej, przy czym po liście szybciej się łazi.

1

Dla tego że cofanie się o C dla listy to koszt O(C)=O(109) zaś dla BT to koszt O(log(N))=O(log(107))=O(23) czyli jakieś 4*107 szybciej

0
_13th_Dragon napisał(a):

Dla tego że cofanie się o C dla listy to koszt O(C)=O(109) zaś dla BT to koszt O(log(N))=O(log(107))=O(23) czyli jakieś 4*107 szybciej

Dzięki, rzeczywiście!

Jak więc budować takie drzewo? Nadawać elementom indeksy, czy tylko dodając elementy dbać o to, żeby zawsze po obu stronach każdego wierzchołka było tyle samo elementów? Jeśli będę budował BST wstawiając elementy i je indeksując to co jeśli będę musiał wstawić gdzieś element pomiędzy np 10, a 11 -jaki indeks wtedy nadać? Przydałoby się jakoś rozpoznawać "kolejność" wstawiania która decydowałaby o pozycji w drzewie, ale w taki sposób, żeby można było coś wstawiać pomiędzy...
Jak szybko przesuwać się po takim drzewie o n pozycji? Wyszukiwać, porównując elementy?

2

@MStef94, nie trzeba numerować. Wstawiasz element wg pozycji.
@stryku,

  • Przy każdym węźle trzymamy ilość liści pod nim (czyli defakto ilość NULL'i, pusty ma wartość 2)
  • Powyższe pozwala łatwo upgradować ilość liści węzła nadrzędnego (np po wstawianiu/usuwaniu) = suma liści lewego i prawego.
  1. Dla POS numerowanego od 1
  2. Zaczynamy szukanie od korzenia (od razu możemy dla cyklicznego wyszukiwania zapodać resztę z dzielenia POS=(POS-1)%(IlośćLiści-1)+1)
  3. Jeżeli POS<Lewy->IlośćLiści szukamy POS w lewym, przechodzimy do 3.
  4. Jeżeli POS>Lewy->IlośćLiści szukamy POS-Lewy->IlośćLiści w prawym, przechodzimy do 3.
  5. Jeżeli POS=Lewy->IlośćLiści to ten aktualny to nasz węzeł o numerze POS.

Jeżeli możemy znaleźć POS w czasie O(log(N)) to możemy również znaleźć POS+C w tym samym czasie.

0

@_13th_Dragon
Nadal nie czaję co ma być tym wyznacznikiem determinującym położenie węzła tak, aby w kolejności in-order wychodziła poprawna kolejność. Nie wiem jak przekształcać drzewo bez takiego wyznacznika, aby go nie "popsuć", w sensie nie naruszyć odpowiedniej kolejności. Chyba że się mylę i nie musi to być drzewo AVL lub zbalansowane BST (choć przyśpieszyło by to chyba działanie). Mógłbyś, pokrótce, wyjaśnić jak widzisz kwestię inicjowania, czyli wstawiania do takiego drzewa kolejnych elementów ciągu oraz wstawiania elementu "gdzieś w środku"? :)

1
  • Takim wyznacznikiem kolejności jest kolejność inorder.
  • Drzewo nie ma żadnego klasycznego klucza.
  • Algorytm w poprzednim poście pozwala odnaleźć węzeł o numerze POS (numerowane od 1) w porządku inorder.
  • Jak coś chcesz wstawić w numer POS to znajdujesz ten węzeł (tym samym algorytmem) i wstawiasz po lewej, jeżeli po lewej coś jest to od tego lewego na prawo do oporu i wstawiasz po prawej od tego "oporu".
  • Równoważenie drzewa nie psuje kolejności inorder, więc może to być AVL, R-B, Squize lub dowolna inna zasada równoważenia np wagowa.
0

@_13th_Dragon
Dzięki, wiele się wyjaśniło!

Przy budowaniu drzewa BST, AVL itp zazwyczaj patrzy się na dane w węźle. Jeśli wstawiany element jest mniejszy od bieżącego to idzie na lewo a jak odwrotnie to w prawo i dalej etc. Tutaj przy wstawianiu sprawdzalibyśmy tylko wg tego:

_13th_Dragon napisał(a):
  • Jak coś chcesz wstawić w numer POS to znajdujesz ten węzeł (tym samym algorytmem) i wstawiasz po lewej, jeżeli po lewej coś jest to od tego lewego na prawo do oporu i wstawiasz po prawej od tego "oporu".

?

1 użytkowników online, w tym zalogowanych: 0, gości: 1