Wieczorowe Studia Licencjackie Wrocław, 4..2006 Wstęp do programowania Wykład nr 7 (w oparciu o notatki K. Lorysia, z modyfikacjami) Obliczanie współczynnika dwumianowego Newtona. Definicja. n = m n! m!( n m)! Algorytm korzystający wprost z definicji byłby mało efektywny, ponieważ w trakcie obliczeń powstawałyby bardzo duże liczby. Można by co prawda dokonywać skracania przez wspólne dzielniki licznika i mianownika, ale jest to kłopotliwe. Własność. n n n = + m m m W oparciu o tę własność obliczany jest trójkąt Pascala, w którym i-ty wiersz zawiera wszystkie współczynniki dla k=0,,...,i. i k 2 3 3 4 6 4
Program nierekurencyjny Wersja 0 Obliczamy trójkąt Pascala i umieszczamy go w dwuwymiarowej tablicy a, jak pokazano poniżej 0 0 0 0 0.. 0 0 0 0.. 2 0 0 0.. 3 3 0 0.. 4 6 4 0................ Jako wynik zwracamy k-ty element n-tego wiersza. Uwaga: Z podobnych względów co poprzednio zmienne i, j, a są lokalne w funkcji. Tym razem dochodzi jeszcze argument ekonomiczny gdyby tablica a była globalna, zajmowałaby pamięć przez cały czas wykonywania programu. Zmienne lokalne zajmują pamięć tylko na czas wykonywania funkcji. #define m 30 long newton(int n, int k) { int a[m][m]; int i, j; for (i=0; i<m; i++) for (j=0; j<m; j++) a[i][j]=0; a[0][0]=; for (i=; i<=n; i++) { a[i][0]=; for (j=; j<i; j++) a[i][j]=a[i-][j-]+a[i-][j]; a[i][i]=; return a[n][k];
Wersja Zauważamy, że do obliczenia kolejnego wiersza wystarczy pamiętać tylko wiersz poprzedni. Używamy wobec tego dwóch tablic: st do pamiętania poprzedniego wiersza oraz nowy do pamiętania aktualnie obliczanego wiersza. Po obliczeniu nowego wiersza, jest on kopiowany do tablicy st. #define k 200 { int i, j; long st[k],nowy[k]; st[0]=; for (i=0;i<n;i++) { nowy[0]=; for (j=; j<=i; j++) nowy[j]=st[j-]+st[j]; nowy[i+]=; for (j=0;j<=i+;j++) { st[j]=nowy[j]; nowy[j]=0; return(st[m]); Uwaga: W pętli for (i=0;...) używamy dwóch pętli wewnętrznych for (j=...). Pierwsza z nich oblicza i-ty wiersz trójkąta Pascala i umieszcza go w tablicy nowy; druga kopiuje go z tablicy nowy do tablicy st i zeruje tablicę nowy. Ten fragment zaprogramowany jest nieco nieporadnie. W szczególności zerowanie tablicy nowy nie jest konieczne. Wersja 2 Dokonujemy drobnych poprawek:. nie zerujemy tablicy nowy; 2. kopiujemy do st tylko elementy od -ego do (i-)-ego, 3. nie używamy nowy[i+]. { int i, j; long st[k],nowy[k]; st[0]=; nowy[0]=; for (i=0;i<n;i++) { for (j=; j<=i; j++) nowy[j]=st[j-]+st[j]; for (j=;j<=i+;j++) st[j]=nowy[j]; st[i+]=; return(st[m]);
Wersja 3 Zauważamy, że obliczenia można wykonać używając tylko jednej tablicy. Musimy jednak zmodyfikować sposób jej wypełniania teraz robimy to od prawej do lewej strony. Poniższa ilustracja pokazuje jak zmieniać się będzie zawartość tablicy st podczas obliczania piątego wiersza trójkąta Pascala. 4 6 4 0 0 0 0 0 4 6 4 5 0 0 0 0 4 6 0 5 0 0 0 0 4 0 0 5 0 0 0 0 5 0 0 5 0 0 0 0 { int i, j; long st[k]; st[0]=; for (i=0;i<n;i++) { st[i+]=; for (j=i; j>0; j--) st[j]=st[j-]+st[j]; return(st[m]); Komentarz do instrukcji: Zamiast st[j]=st[j-]+st[j]; możemy napisać st[j]+=st[j-]; Program rekurencyjny: { if ((m==0) (m==n)) return ; else return newton(n-,m-)+newton(n-,m); Podobnie jak w przypadku liczb Fibonacciego, wersja ta jest nieefektywna nawet dla niezbyt dużych wartości n i m. Przyczyną jest wielokrotne wywoływanie funkcji dla tych samych wartości parametrów.
Jednoczesne szukanie minimum i maksimum W ciągu liczb umieszczonym w tablicy a[0..n-] chcemy znaleźć element najmniejszy i największy: - Najprostsze rozwiązanie polegałoby na zastosowaniu standardowej metody wyszukiwania minimum oraz standardowej metody wyszukiwania maksimum void maxmin(int *a, int n, int *min, int *max) { int k; *max=*min=a[0]; for (k=; k<n; k++) if (*min>a[k]) *min=a[k]; if (*max<a[k]) *max=a[k]; Uwaga: Wynikiem działania funkcji mają być dwie liczby. Dlatego wprowadziliśmy parametry, które nie są elementami typu int lecz int *. Oznacza to, że zmienne min i max są wskaźnikami na elementy typu int. Analogicznie do adresowania pośredniego w kodzie RAM, *min i *max to elementy typu int, na które wskazują zmienne min i max. Poniżej znajduje się przykład programu głównego wywołującego funkcję maxmin: main() { int b[]={, 2,,-3,7,-4; int mx,mn; maxmin(b,6,&mn,&mx); printf("\n%d %d\n",mn,mx); Jak widać, wskazanie na zmienne mx, mn typu int uzyskujemy za pomocą operatora &: &mn, &mx. - Drobne usprawnienie możemy uzyskać w oparciu o obserwację, że element mniejszy od dotychczasowego minimum na pewno nie jest kandydatem na maksimum void maxmin(int *a, int n, int *min, int *max) { int k; *max=*min=a[0]; for (k=; k<n; k++) if (*min>a[k]) *min=a[k]; else if (*max<a[k]) *max=a[k];
- Oba powyższe rozwiązania wymagają w najgorszym przypadku 2n-2 porównań. Możemy jednak rozwiązać ten problem inaczej: o Dla ciągu o długości jeden, minimum i maksimum równe są jedynemu elementowi ciągu. o Dla ciągu o długości n>, dzielimy ten ciąg na dwa podciągi (mniej więcej) równej długości. Niech M i m oznaczają odpowiednio maksimum i minimum pierwszego podciągu, a M2 i m2 maksimum i minimum drugiego podciągu. Wówczas maksimum całego ciągu jest równe większej z liczb M i M2 a minimum mniejszej z liczb m i m2 maxmin(int *a, int l,int p, int *min, int *max) { int mi,mi2,ma,ma2; if (l==p) *min=*max=a[l]; else if (p-l==){ if (a[l]<a[p]) {*max=a[p]; *min=a[l]; else {*max=a[l]; *min=a[p]; else { maxmin(a,l,(l+p)/2, &mi,&ma); maxmin(a,(l+p)/2+,p, &mi2,&ma2); *min=mi<mi2? mi :mi2; *max=ma>ma2? ma :ma2; Oznaczmy przez T(n) liczbę porównań wykonywanych przez powyższą funkcję dla danych o rozmiarze n. Wówczas zachodzi następująca zależność rekurencyjna: T ( n) = T ( 0 n = n / 2 ) + T ( n / 2 ) + 2 n > Dla n będących potęgami dwójki (n=2, 4, 8, 6, 32, 64,...) zależność powyższa przyjmuje postać T(n)=2T(n/2)+2 dla n>, której rozwiązaniem jest postać T(n)=3n/2-2. Liczba porównań dla n nie będących potęgą dwójki jest również istotnie mniejsza od 2n (wynosi nie więcej niż 5n/3-2).