2013年1月31日 星期四

[C/Java] Armstrong數

當一個三位的正整數符合a^3+b^3+c^3 = abc的條件,
即各位數本身的立方和等於這個三位數時,
我們將其稱之為Armstrong數。(三位的Armstrong數)
試寫一個程式求出所有的三位Armstrong數。

解:
之前在讀Java的時候有習題出過,
就試解了一下,
Code如下:
public class Armstrong{
	public static void main(String[] args){
		int h,t,o,sum;
		for(int i = 100; i <= 999; i++){
			h = i / 100; 
			t = (i / 10) % 10;
			o = i % 10;
			sum = h*h*h + t*t*t + o*o*o;
			if(sum == i) System.out.println(i);
		}
	
	}
}
這樣寫當然是沒有問題,
根本概念是一個一個check每一個數是否等同於各位數的立方和,
但,每一次換位都要重新計算,不覺得太浪費時間了嗎?
按照書上給的概念的話,解法如下:(C語言)
int main(int argc, char *argv[])
{
	int p,q,r,p_cube,q_cube,r_cube;
	int p100,q10;
	int number,sum;
	int count = 0;
	for(p = 1, p100 = 100; p <= 9; p++, p100+=100)
	{
		p_cube = p*p*p;
		for(q = q10 = 0; q <= 9; q++, q10+=10)
		{
			q_cube = q*q*q;
			for(r = 0; r <= 9; r++)
			{
				r_cube = r*r*r;
				number = p100 + q10 + r;
				sum = p_cube + q_cube + r_cube;
				if(number == sum)
					printf("\n%3d%9d", ++count, number);
			}
		}
	
	}
	printf("\n\nTotal Count: %d\n", count);
	system("PAUSE");
	return 0;
}
這個解法同樣有相同的問題存在。
它在換位的時候依然會重算,只有在高位不動的狀況下不會重計。
如果是以速度考量的話,可以擔負10個int大小來存放0~9的三次方。
更改如下:
int main(int argc, char *argv[])
{
	int p,q,r;
	int p100,q10;
	int number,sum;
	int count;
	int* cube = (int*)malloc(sizeof(int)*10);
	cube[0] = 0; cube[1] = 1;
	for(count = 2; count <= 9; count++)
		cube[count] = count*count*count;
	count = 0;
	for(p = 1, p100 = 100; p <= 9; p++, p100+=100)
		for(q = q10 = 0; q <= 9; q++, q10+=10)
			for(r = 0; r <= 9; r++)
			{
				number = p100 + q10 + r;
				sum = cube[p] + cube[q] + cube[r];
				if(number == sum)
					printf("\n%3d%9d", ++count, number);
			}
	printf("\n\nTotal Count: %d\n", count);
	system("PAUSE");
	return 0;
}
不但省下了每次的重新計算,同時實際增加的單位只有7個int而已。
(因為p_cube~r_cube省掉了)

2013年1月30日 星期三

[C Programming] 等值首尾和-延伸

1. 這個程式要求所有元素都大於零,是否有此必要?
    如果沒有這個條件,請證明它;如果不行,請改寫程式。
2. 請改寫程式,將造成前段和與後段和相同的指標,
    連同這個相等的值一併顯示出來。

個人解法:
1. 很明顯有必要,因為原本的寫法是假設:
  「往下多加一個元素會得到更大的前段/後段和」。
    如果元素是負的,甚至可能會令前段或後段和等同於更之前已經碰到過的前後段和。
    不說其他,就算只限制不小於零,也可能造成平台的問題。
    比如2,3,0,0,3,2,兩個零的部分會造成4種組合。
    改寫程式的話,可能就要比較全體的和了。
    可以藉由迴圈不斷將上一個加總到下一個去,免去從f[]的開頭加到後面,
    這也是一種空間換取時間的做法。
    經過sorting的話,可以利用之前做的等值數目延伸問題的code,
    來計算確切的組數。
    由於出去之後並沒有回傳pointer,所以多花點功夫作free的動作,
    算是養成好習慣吧XD
 
Code部分如下:
int headtail_neg(int f[], int n)
{
 int* presum = (int*)malloc(sizeof(int)*n);
 int* sufsum = (int*)malloc(sizeof(int)*n);
 int pre = 0, count = 0;
 presum[0] = f[pre]; sufsum[0] = f[n-1];
 // Calculate each prefix-sum and suffix-sum.
 for(pre = 1; pre < n; pre++)
 {
  presum[pre] = f[pre] + presum[pre-1];
  sufsum[pre] = f[n-1-pre] + sufsum[pre-1];
 }
 qsort(presum, n, sizeof(int), sort);
 qsort(sufsum, n, sizeof(int), sort);
 
 count = eq_cnt(presum, sufsum, n, n); // 見等值數目-延伸
 free(presum); presum = 0;
 free(sufsum); sufsum = 0;
 return count;
}
2. 只要在搜尋到的時候輸出printf就可以了。

在原程式中count++底下加入該行:
printf("\nCount: %2d (prefix,suffix):(%2d,%2d) sum = %2d",count,pre,suf,presum);
範例輸出結果:
Count:  1 (prefix,suffix):( 0,10) sum =  0
Count:  2 (prefix,suffix):( 1, 9) sum =  1
Count:  3 (prefix,suffix):( 2, 8) sum =  3
Count:  4 (prefix,suffix):( 3, 7) sum =  6
Count:  5 (prefix,suffix):( 4, 6) sum = 11
Count:  6 (prefix,suffix):( 5, 5) sum = 18
Count:  7 (prefix,suffix):( 6, 4) sum = 26
Count:  8 (prefix,suffix):( 7, 3) sum = 33
Count:  9 (prefix,suffix):( 8, 2) sum = 38
Count: 10 (prefix,suffix):( 9, 1) sum = 41
Count: 11 (prefix,suffix):(10, 0) sum = 43
Total Count: 11

[C Programming] 等值首尾和

假設有一個陣列x[],當中有n個元素,每個皆大於0,
定義x[0]+x[1]...+x[i]為前段和(所以前段和有n種),
x[j]+x[j+1]...+x[n-1]為後段和(所以後段和也有n種)。
請寫一個程式,求出x[]中有多少組相同的前段和和後段和。

解:
其實也跟前面的題目大同小異。
讓兩個index分別從前後段開始,若前段和比後段和大,
則後段再多取一個值,反之亦然。
遇到相等的時候增加count,最後回傳即達到要求。
為了程式方便起見,一開始的起點是prefix=0,suffix=n-1,
按照程式跑法,第一次就會增加count,
但由於最後一次會剛好先跳出迴圈,所以一來一往恰巧互補了。

Code大概長這樣:
int headtail(int f[], int n)
{
 int pre = 0, suf = n-1, presum = 0, sufsum = 0, count = 0;
 while( pre < n && suf >= 0)
  if(presum > sufsum)
   sufsum += f[suf--];
  else if(sufsum > presum)
   presum += f[pre++];
  else
  {
   count++;
   sufsum += f[suf--];
   presum += f[pre++];
  }
 return count;
}

2013年1月29日 星期二

[C Programming] 兩陣列最短距離-延伸

1. 將程式改寫成函數來解下一題,雖然解法不會很好。(等值首尾和問題)
2. 其中有一個陣列沒排好時,還能正常運作嗎?兩個都沒排好的話呢?
    請克服之。
3. 請修改程式,報告出造成最短距離的那兩個元素的位置與它們的值。
4. 將兩個++的敘述都移到"minimum=..."之前的話,會有相同的結果嗎?

個人解法:
1. 留待下一題吧XD
2. 陣列沒排好的時候,可以進行sorting來回到適合本解法的狀態。
    如果不作排序的話,對單一陣列而言,比如x[]未排序而y[]已排序時,
    拿每一個x[i]去跟y[]中的值比較,找到y[j-1]<x[i]<y[j]的時候,
    就可以算出x[i]和y[]的最小距離,如此一來,
    每個x[]中的值都比過後就可以得到答案。
    但這個方法的worst case還是n^2(兩者長度皆為n的話),
    所以可以的話,先排序好來作比較有效率。
    沒排好的狀況只要有斷層就會令程式出錯,無法正確輸出。
3. 修改時就變成要記住是誰造成最短距離了。
Code如下:
int min_dist(int x[], int y[], int m, int n)
{
 int minimum = INT_MAX, _xi = 0, _yi = 0;
 int _xmin = 0, _ymin = 0;
 while( (_xi < m) && (_yi < n) )
  if(x[_xi] >= y[_yi]) 
  { 
   if(x[_xi]-y[_yi] <= minimum)
   {
    minimum = x[_xi] - y[_yi];
    _xmin = _xi; _ymin = _yi;
   }
   _yi++; 
  }
  else
  { 
   if(y[_yi]-x[_xi] <= minimum)
   {
    minimum = y[_yi]-x[_xi];
    _xmin = _xi; _ymin = _yi;
   }
   _xi++;  
  } 
 printf("\nFrom x[%d] and y[%d],",_xmin,_ymin);  
 return minimum;
}


4. 不會,因為移動後的index不一定比較符合,且第一次的值比較會被跳過。
    再者,index會移動到陣列範圍外,會有什麼值在那沒人知道的XD

[C Programming] 兩陣列最短距離

已知兩個元素自小到大排好的陣列x[]和y[],請寫一個程式,
算出兩個陣列元素彼此之間差的絕對值中最小的一個,這叫作陣列的距離。

解:
跟前面的幾題有異曲同工之妙,
當x[i]比y[j]大的時候就j++,
反之則i++,每次尋找更小的值即可。

Code大概長這樣:
#include <limits.h>

int min(int x, int y)
{
 if(x<y) return x;
 return y;
}

int min_dist(int x[], int y[], int m, int n)
{
 // INT_MAX是limits.h裡的常數,表int的最大值。
 int minimum = INT_MAX, _xi = 0, _yi = 0; 
 while( (_xi < m) && (_yi < n) )
  if(x[_xi] >= y[_yi]) 
  { 
   minimum = min(minimum, x[_xi]-y[_yi]);
   _yi++; 
  }
  else
  { 
   minimum = min(minimum, y[_yi]-x[_xi]);
   _xi++;  
  }   
 return minimum;
}
另外,其實可以加入相等的情況,直接return 0;出來,
但並不一定會比較快,相對還可能每一次迴圈都要判斷兩次。

2013年1月27日 星期日

[C Programming] 等值數目-延伸

延伸問題:
1. 請修改程式,將相等元素的足碼(index)顯示出來。
2. 如果f[]和g[]中有相同的元素,但仍然是自小到大排好了,
    這個程式還能正常工作嗎?問題何在?請嘗試修改之。
    舉例來說,如果f[]是1,3,3,5,7而g[]是1,3,3,8,9,就會產生四對3是相同的。
    修改後的程式效率還能一樣好嗎?
3. 為什麼一邊的元素用完就可以停止工作呢?
4. 試證明這個程式的比較次數絕對不會超過2n次,n是f[]與g[]中的元素個數。

個人解法:
1. 只要加入在相等時輸出的函式或者以陣列記錄下來即可。
    或者也可以開二維陣列記錄下來,不過比較麻煩。

將原Code處加入printf:
else if(f[i] == g[j])
 {printf("\n(f[%d],g[%d]),"); i++;j++;cnt++; }
2. 如果f[]和g[]中有相同的元素的話,程式就無法正常計算了。
    因為在i++和j++的過程中會漏算組合。
    修改的方法是當發現相等時,嘗試去尋找兩邊平台長度,
    相乘後得到組合的數目。當兩邊長度為1時(單個),
    組數自然也是1,合乎邏輯。


將原Code的else...if處更改如下:
else if(f[i] == g[j])
{
 len_f = 1; len_g = 1; tmp = f[i]; // 在進入函式時宣告之
 while(i+1<m)
  if(f[i+1] == tmp){i++;len_f++;}
  else break; // 搜尋到最後一個相等的f[i]為止
 while(j+1<n)
  if(g[j+1] == tmp){j++;len_g++;}
  else break;
 cnt += len_f * len_g;   
 i++;j++; // 讓index遞增移出平台範圍
}
注意如果還要達到1的要求的話就要在這中間跑迴圈完成,在此不再重複。

3. 因為在前題是同陣列中的各元素皆不相等的條件下,
    當發生一個陣列的元素被用完,
    要嘛是f[]的最後一個比g[j]小或相等,
    這樣後面的g[j+1]、g[j+2]...等更大,所以也都不用比了;
    不然就是g[]的最後一個比f[i]小或相等,
    這樣後面的f[i+1]、f[i+2]...更大,所以同樣也都不用比了。
4. 這程式會繼續跑的條件就是(i < n) && (j < n)。
    每一次判斷式中,i或j之中最少有一個數會遞增,
    最壞的狀況就是i跑到n-1,j也跑到n-1,如此,
    亦不會超過2*( (n-1)-0 +1) = 2n次。

[C Programming] 等值數目

已知兩個整數數列f[]與g[],它們的元素都已自小到大排列好,
且兩個陣列中的元素都各自不相同(Ex. f[]:1,3,5,7,9  g[]:3,5,7,8,10)
請寫一個程式,算出兩個陣列彼此之間有多少組相同的資料,
以範例來說有3 5 7共3組。

但請不要使用暴力法,這樣會是O(n^2)。
提示:善用已排序的特性。


解:
因為同陣列的元素都各自不相同且排序,
所以當:
1. f[i] < g[j] → i++;
2. f[i]==g[j] → i++; j++; cnt++; (因為這組相等不會再重複出現了)
3. f[i] > g[j] → j++;
一直到其中一個index先跑到底為止。
若兩陣列長度皆為n,最多只要比對2n次就可以結束了!

Code大概長這樣:
int eq_cnt(int f[], int g[], int m, int n)
{
 int cnt = 0, i = 0, j = 0;
 while( (i < m) && (j < n) )
  if(f[i] < g[j]) 
   i++;
  else if(f[i] == g[j])
  { i++;j++;cnt++; }
  else
   j++;
 return cnt;
}

[C Programming] 支配值數目-延伸

延伸問題:
1.若f[]和g[]都有n個元素,試證明這個程式最多只會比較2n次。
2.如果f[]與g[]中有相同元素時,這個程式還正確嗎?為什麼?
3.請修改程式,把在g[]中小於每一個f[i]的元素都顯示出來。

個人解法:
1. 在程式中,每次比較的結果必定是f[]的index++,
    或者g[]的index++。
    所以最多可能產生的比較次數的狀況就是兩者都跑到底,也就是2n次。
    可能成立嗎?可以的XD
    只要考慮g[n-1]<f[n-1]<g[n]<f[n]這樣的情況,
    就可以保證兩邊都會跑到尾了。
2. f[]中有相同元素的時候程式不會影響,
   因為由小到大排序,當f[]如像11,12,12,12,13,13,g[]像8,12,13,14,15,
   我們找到第一個12去比較8時,仍然可以保證其後的值仍大於8。
   g[]的狀況也是一樣,因為比較都是一個一個去比的,
   f[]不夠大會找下一個來比,g[]不夠大也會找自己的下一個來比,
   所以不會影響程式的正確性。
3.我們可以另外產生一個陣列h[]來記錄。
   h[i]的值表f[i]是從g[]的第幾個值以前比較大。
   注意要控制迴圈的條件,如果因為g[]的序列先跑完的話,
   h[]要把後面的值補滿。(皆為n)

Code大概長這樣:
int gt_cnt(int f[], int g[], int h[], int m, int n)
{
 int cnt = 0, _fi = 0, _gi = 0, _hi = 0;
 while( (_fi < m) && (_gi < n) )
  if(f[_fi] <= g[_gi]) 
  { h[_fi] = _gi; _fi++; }
  else
  { cnt += m-_fi; _gi++; } 
 while( _fi < m ) 
  h[_fi++] = n;
 
 printf("For all elements in g[] ");
 printf("which are less than f[i]:");
 while( _hi < m )
 {
  _gi = 0;
  printf("\nf[%2d]: ", _hi);
  while( _gi < h[_hi])
   printf("%d ", g[_gi++]);
  _hi++;
 }
 return cnt;
}

執行結果像這樣子:
測資:
f[] = {1,3,5,7,9,10,11,12,13,15,17,18,19,20,21}
g[] = {5,6,7,8,9,10,12,13,18,19,20,21,22,23,24,25,26}
// Output
For all elements in g[] which are less than f[i]:
f[ 0]:
f[ 1]:
f[ 2]:
f[ 3]: 5 6
f[ 4]: 5 6 7 8
f[ 5]: 5 6 7 8 9
f[ 6]: 5 6 7 8 9 10
f[ 7]: 5 6 7 8 9 10
f[ 8]: 5 6 7 8 9 10 12
f[ 9]: 5 6 7 8 9 10 12 13
f[10]: 5 6 7 8 9 10 12 13
f[11]: 5 6 7 8 9 10 12 13
f[12]: 5 6 7 8 9 10 12 13 18
f[13]: 5 6 7 8 9 10 12 13 18 19
f[14]: 5 6 7 8 9 10 12 13 18 19 20
Count: 84


2013年1月26日 星期六

[C Programming] 支配值數目

已知f[]和g[]兩個整數數列(已由小到大排列且各數列中無重複元素),
請寫一個程式,輸出f[]中的每一個元素比g[]中的每一個元素大的個數的總數。
也就是:
f[0]比g[]中的多少個還大,
f[1]比g[]中的多少個還大,
f[2]比g[]中的多少個還大,
......的加總。

解法:
因為是排序過的數列,
所以只要從小的index往上比較,
當f[x]>g[y]的時候,f[x+1]及其後也會大於g[y]。
依此特性來搜尋,碰到f[x]≦g[y]時,遞增x值,
f[x]>g[y]時,遞增y值並把計數加上相對應的個數就可以了。


Code大概長這樣:
int gt_cnt(int f[], int g[], int m, int n)
{
 int cnt = 0, _fi = 0, _gi = 0;
 while( (_fi < m) && (_gi < n) )
  if(f[_fi] <= g[_gi]) 
   _fi++;
  else
  { cnt += m-_fi; _gi++; } 
 return cnt;
}

2013年1月25日 星期五

[C Programming] 最長平台問題-延伸

針對最長平台問題,達成新的要求:
1. 請改寫程式,指出最長平台的位置。
2. 請寫一個程式,接收一個已經自小到大排好的陣列,
    把所有平台與它的位置都找出來。
3. 如果給定的陣列沒有依序排好,
    請寫一個程式可以找出所有平台與它們的位置,
    但給定的陣列卻不一定要排列好。
    (程式還可以只比n次就找到答案嗎?)
4.寫成遞迴的模式。

個人解法:
1. 我們插入一個變數來記錄一個平台的結束點,
    按照原方法,當找到一個更長的平台時,
    就把結束點再次變更到該索引值。

Code:
// Use *index to remember the end point.
int longest_pl(int x[], int n, int* index, int* len)
{
 *len = 1;
 int i;
 for(i = 1; i < n; i++)
  if( x[i] == x[i-*len] )
  {
   *len = *len + 1; 
   *index = i;
  }
 return *len;
}

2&3. 改寫大概就不能用原來那套的方法了,
因為必定要加入變數來記錄位置,所以就乾脆換方法吧?

Code:
// Complexity is O(n)
void all_pldata(int x[], int n, int rec[][3])
{
 int i;
 int tmp = 0;
 int cnt = 0;
 for(i = 1; i < n; i++) 
 {
  if( x[i] != x[tmp] )
  {
   rec[cnt][0] = x[tmp]; rec[cnt][1] = tmp; rec[cnt][2] = i-tmp;
   // printf("%d : Index %d ~ %d\n", x[tmp], tmp, i-1);
   tmp = i; cnt++;
  }
  else if( i + 1 == n ) // Last index
  {
   rec[cnt][0] = x[tmp]; rec[cnt][1] = tmp; rec[cnt][2] = i-tmp+1;
   // printf("%d : Index %d ~ %d\n", x[tmp], tmp, i);
   cnt++; rec[cnt][0] = -1;
  }
 }
 for(i=0; i<cnt; i++)
 {
  printf("%d : Index %d ~ %d\n", 
    rec[i][0], rec[i][1], rec[i][1]+rec[i][2]-1); 
 } 
}

int rec[][3]的一維長度設為n。每一組rec[]中存的依序為:
平台的數字、平台起始點、平台長度。
當中注意要處理最後一組的狀況,會有一點點差異。
如果不是全部平台長度為1,則rec不會被用完,在尾端補上-1,
可以方便外界想使用的時候,能有迴圈的終止條件。(否則會一路跑下去)
當然如果覺得多花了記憶體空間的話,也可以先找出共有幾組平台,
再進行宣告並把資料丟進去,只是就會多出先跑一次的執行時間。

比較的次數依舊為n次,因為開頭從i=1開始,
但結束的時候可能會跑過if以及else if兩個判斷式。

其實tricky一點來說,還要考慮n==1的狀況XD

// 把它放在函式的開頭!
if(n==1)
{
 printf("%d : Index %d ~ %d\n", x[0], 0, 0); 
 return;
}

4.其實跟原本的樣子差不多,寫成遞迴不會比較快就是了@@"

Code:
// 丟出來的時候不要忘了用*來取值。
int* recur_pl(int x[], int n, int *i, int* len)
{
 // Let *len = 1, i = 1 when first time calling this function.
 if(*i == n) return len;
 if( x[*i] == x[*i-*len] ) *len = *len + 1; 
 *i = *i + 1;
 return recur_pl(x, n, i, len);
}

[C Programming] 最長平台問題


在看一個很基本的最長平台問題。
自己想用比較能跳的方法來做的話,
會變成要時不時的判斷N-n是否大於0......
(N為陣列長度,n為index)
Somehow在C裡面去取超出範圍的index也是會回傳給你一個阿哩布達的東西,
它用指標來看直接吐給你也不會出錯= =......
Java和C++的話可以try-catch,但C沒辦法,
自己實作的話就變成花時間做判斷式,
不過這樣看起來的話try-catch其實也是隱性的判斷式?


回過頭來想字串要留'\0'作結尾真的很有用。

明天整理過後再將寫出來的Code和原版解答PO上來。


自己想的方法有些不理想,就把原版的解法寫出來好了。
Code大概長這樣:

// Complexity is O(n)
int longest_pl(int x[], int n)
{
 int len = 1;
 int i;
 for(i = 1; i < n; i++)
  if( x[i] == x[i-len] )
   len++;
 return len;
}

簡單說起來,就是從目前的index來往回推最大長度+1(i-len),
如果說看到的數字跟目前的一樣,代表說這組長度已經比最大長度還長了。
這樣每個數字掃過一遍,就可以得到最長平台的長度。

2013年1月24日 星期四

[C Programming] 三角形最大周長問題

題目:
有n支棒子,棒子i的長度是ai
您想從這些棒子中選出三支來做出周長最長的三角形。
請求出能夠做出的最大周長。如果無法做出三角形時請輸出0。

限制條件:
3 ≦ n ≦ 100, 1 ≦ a≦ 10^6

暴力法要花O(n^3)的時間。程式碼大概長這樣:(ary是存放的array)

// O(n^3) algorithm.
int ai,aj,ak,aans;
aans = 0;
for(ai = 0; ai < n; ai++){
 for(aj = ai + 1; aj < n; aj++){
  for(ak = aj + 1; ak < n; ak++){
   int len = ary[ai] + ary[aj] + ary[ak];
   int ma = max(ary[ai], max(ary[aj], ary[ak]));
   int rest = len - ma;
   if(ma < rest) aans = max(aans, len);
  }
 }
}
printf("最大周長:%d\n", aans);

其實有O(n log n)的解法,但書裡面叫我自己想。(靠= =)
在繞了大圈子以後總算想到了。
解法:
1. 先由小到大排序(正常的演算法可以輕鬆達到O(n log n))
2. 設三根分別是第c、b、a根。當c被選定時,
    若存在b、a使三角形成立,
    那麼c、c-1、c-2的組合一定是這當中能達到最大周長的。
    反過來說,如果c、c-1、c-2的組合無法構成三角形,
    那更小的b、a也不可能構成三角形。
    所以對每個c來說考慮c、c-1、c-2的組合就好了。
3. ∵  a(c) + a(c-1) + a(c-2) ≧ a(c-1) + a(c-2) + a(c-3)
    ∴ 讓c從最大的n開始往下,那麼最先找到的三角形必定是周長最大的。
 
4. 一旦找到三角形成立就可以直接輸出。
    如果找到第三根都沒有構成三角形,就輸出0。
5. 這個迴圈只要O(n),所以整體複雜度決定在排序法的複雜度。

Code大概長這樣:

// Complexity is O(n)
void searchC(int* ary){
    pmeter = 0;
    for(c = n - 1; c >= 2; c--){
        // b=c-1; a=c-2;
        if( (ary[c] - ary[c-1]) < ary[c-2]){
           pmeter = ary[c] + ary[c-1] + ary[c-2]; return;
        }
    }
}

呼叫前不要忘記要先排序array:(當然認真來說quicksort的worst case是O(n^2)...... )

// comparator
int sort(const void *x, const void *y){
 return (*(int*)x - *(int*)y); 
}

// 程式中使用C內建的quicksort
// 請記得#include <stdlib.h>
qsort(ary, n, sizeof(int), sort);

2013年1月21日 星期一

[Algorithm] 以空間換取時間(以Fibonacci為例)

一般在寫程式會在意的效率的決定因素,在於花費時間以及記憶體用量
以Fibonacci數列來說,大部分的書寫遞迴的方式會像這樣:

int fib(int n)
{
  if(n <= 1) return n;
  return fib(n-1) + fib(n-2);
}

在這種狀況下,同樣的值會被重複的運算,
比如fib(8) = fib(7) + fib(6),fib(7) = fib(6) + fib(5),
fib(6)就變成重複計算了。

當然,有些compiler據說會很聰明地自己去列表來記錄,
這樣碰上有記錄的就直接把值丟上去而不用重算;
但如果不想把效率給不知道會不會幫忙的compiler決定的話,
自己修改一下程式碼,其實也可以達到效果的!

比如把程式改成以下這樣:

int fibs[n+1]; // 宣告一個陣列來記已經算過的數值

int fib(int n)
{
  if(n <= 1) return n;
  if(fibs[n] != 0) return fibs[n]; // 已經算過的就直接回傳值
  return fibs[n] = fib(n-1) + fib(n-2);
}


2013年1月17日 星期四

[C Programming] 反轉字串實作

認真說起來,這其實也並不算困難的實作,
只是如果沒有事先想過和處理,
實作的時候就會弄出一大堆bug。

如果條件限定函式傳入char*,傳出也是char*,
但不可以覆蓋過原先傳入的值,就可以使用上一篇提到的malloc。

函式實作如下:
char* strReverse(char *s){
 int len = 0; int i;
 char *ptr;  ptr = s;
 while(ptr[len]!='\0') 
  len++;
 printf("Total length: %d\n",len);
 ptr = (char *)malloc((len+1)*sizeof(char));
 for(i=0;i<len;i++)
  ptr[i] = s[len-i-1];
 ptr[len] = '\0';
 return ptr; 
}

你可以用或者不用(char *)來指定malloc的東西,但主動寫出來cast會好一點。
常犯的錯誤會在:char array的結尾為'\0'。
由於是特殊字元,所以要加上"\"作為跳脫用。
且使用malloc()來宣告char array的時候,電腦不會好心幫你加上'\0'......
你要自己記得加,切記切記。
最後,用malloc()的話,記得自己要使用free()來釋放記憶體。

一個搭配主程式的範例如下:

int main(int argc, char *argv[])
{
  char str[50];
  printf("Enter a string: ");
  scanf("%s", str);
  char* revPtr = strReverse(str);
  printf("After strReverse: %s\n",revPtr);
  free(revPtr); revPtr = 0;
  system("PAUSE"); 
  return 0;
}

[C Programming] data segment、heap以及stack

在C語言中存放資料的地方有三種:
  1. 資料區 (Data segment)︰ 全域變數, static 變數,常數。
  2. 堆疊區 (Stack)︰ 區域變數 (Auto variable), 函式參數,暫時變數。
  3. Heap 區︰ 動態配置的記憶體。
當程式開始時,全域變數以及static變數、
常數皆會於宣告完成的時候放到data segment裡,從此你儂我儂科科與程式共存亡。

值得注意的是static變數的可見範圍只在宣告的scope內,
離開所宣告的層級(比如function的大括號)後,就存取不到它了。
常數則是指被宣告為const的char array。
有人說const int就照舊放在stack,但我所學不精無法驗證。
以上這些資料都會放在data segment。

stack中擺的則是大部分會變動的東西。
任何"暫時性"的東西大概都擺在這。

heap中擺的只有一種,就是你用malloc來配置的記憶體空間。
好的使用習慣是配合sizeof()來指定大小,比如說:

 int *ptr;
 ptr = (int *)malloc(sizeof(int)*5);
 int i,j;
 for(i=0;i<5;i++)
    ptr[i]=i;
 for(j=0;j<5;j++)
    printf("%d\n", ptr[j]);
上面的範例配置了長度為5個int大小的int array,並且使用ptr來存取。
使用malloc()後盡量記得以free()來釋放記憶體,
因為malloc()就是叫系統別管你用它做什麼,直到天荒地老。
也因此可以拿來在函式中存放想回傳的資料而不會被清掉,相當方便。
free()的使用方式:

 
 free(ptr); 
 ptr = 0; // 習慣上在釋放後自行將ptr的指向去除,
          // 因為這時再度存取那個位址肯定會出錯。