1. 程式人生 > >網路上搜集的面試題

網路上搜集的面試題

原文:http://blog.csdn.net/he_haiqiang/article/details/7914983 

 假設需要將N個任務分配給N個工人同時去完成,每個人都能承擔這N個任務,但費用不

  同.下面的程式用回溯法計算總費用最小的一種工作分配方案,在該方案中,為每個人分配

  1個不同的任務.

  程式中,N個任務從0開始依次編號,N個工人也從0開始依次編號,主要的變數說明如下:

  c[i][j]:將任務i分配給工人j的費用;

  task[i]:值為0表示任務i未分配,值為j表示任務i分配給工人j;

  worker[k]:值為0表示工人k未分配任務,值為1表示工人k已分配任務;

  mincost:最小總費用.

  */

  #include <stdio.h>

  #include <stdlib.h>

  #define N 8 //N表示任務數和工人數

  int c[N][N];

  unsigned int mincost = 65535; //設定的初始值,大於可能的費用

  int task[N],temp[N],worker[N];

  void Plan(int k,unsigned int cost){

  int i;

  if(k>=N && cost<mincost){

  mincost = cost;

  for(i=0;i<N;i++){

  temp[i] = task[i];

  }

  }else{

  for(i=0;i<N;i++){ //分配任務k

  if(worker[i]==0 && cost+c[k][i]){

  worker[i] = 1; task[k] = i;

  Plan(k+1,cost+c[k][i]);

  worker[i] = 0; task[k] = 0;

  }

  }

  }

  }

即x+2y+5z=100,並且條件為x<=100,y<=50,z<=20

程式就如下:

int number=0;

for (x=0; x<=100; x++) for (y=0; y<=50; y++) for (z=0; z<=20; z++) if ((x+2*y+5*z)==100) number++;

 3 void main()
 4   {
 5 int i,j,n=0;
 6 for(i=0;i<=20;i++)
 7    {
 8 for(j=0;j<=(100-i*5)/2;j++)
 9    {
10    n++;
11    }
12    }

用異或運算實現交換兩個變數的值

void swap(int a , int b)

{

   a = a^b;

   b = a^b;

   a = a^b;

}

Winsock建立套接字的步驟

伺服器端:socket()建立套接字,繫結bind()並監聽listen(),用accept()等待客戶端連線.

客戶端:socket()建立套接字,連線connect()伺服器,連線上後使用send()和recv(),在套接字上讀寫資料,直至資料交換完畢,closesocket()關閉套按字.

伺服器端:accept()發現有客戶端連線,建立一個新的套接字,自身重新開始等待連線.該新產生的套接字使用send()和recv()讀寫資料,直至資料交換完畢,closesocket()關閉套接字.

MFC訊息對映機制

訊息對映就是建立一個訊息和函式的對應表,當收到訊息時查詢表,如果表中有相應的訊息,就將訊息交給相應的函式處理。通俗點講,訊息對映表就是一個記錄了訊息號和相應處理函式的陣列。當然表中還有其他資訊,這裡先說矛盾的主要方面了。其中訊息對映表中的每個元素都是一個結構體變數,他的成員很多,最主要的就是訊息號和相對應的訊息處理函式。關於訊息對映表的查詢,是通過虛擬函式實現的,通過父類的虛擬函式查詢父類及其層層子類定義的訊息對映表。如果找不到,就交給預設的視窗處理函式處理。 如果一個類的訊息對映表中定義了一個訊息處理,那麼就不再繼續查詢子類或者子類的子類,從而實現了覆蓋。

    部落格分類:
  • C++
【轉】 
程序間的通訊方式: 

1.管道(pipe)及有名管道(named pipe): 

管道可用於具有親緣關係的父子程序間的通訊,有名管道除了具有管道所具有的功能外,它還允許無親緣關係程序間的通訊。 

2.訊號(signal): 

訊號是在軟體層次上對中斷機制的一種模擬,它是比較複雜的通訊方式,用於通知程序有某事件發生,一個程序收到一個訊號與處理器收到一箇中斷請求效果上可以說是一致的。 

3.訊息佇列(message queue): 

訊息佇列是訊息的連結表,它克服了上兩種通訊方式中訊號量有限的缺點,具有寫許可權得程序可以按照一定得規則向訊息佇列中新增新資訊;對訊息佇列有讀許可權得程序則可以從訊息佇列中讀取資訊。

4.共享記憶體(shared memory): 

可以說這是最有用的程序間通訊方式。它使得多個程序可以訪問同一塊記憶體空間,不同程序可以及時看到對方程序中對共享記憶體中資料得更新。這種方式需要依靠某種同步操作,如互斥鎖和訊號量等。

5.訊號量(semaphore): 

主要作為程序之間及同一種程序的不同執行緒之間得同步和互斥手段。 

6.套接字(socket); 

這是一種更為一般得程序間通訊機制,它可用於網路中不同機器之間的程序間通訊,應用非常廣泛。 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 

執行緒之間的同步通訊: 

1.訊號量 二進位制訊號量 互斥訊號量 整數型訊號量 記錄型訊號量 

2.訊息     訊息佇列 訊息郵箱 

3.事件event 
  1. 請用標準C語言實現下列標準庫函式,設計中不得使用其他庫函式。
    char *strstr(char *str1,char *str2);

    在字串str1中,尋找字串str2,若找到返回找到的位置,否則返回NULL

    const char* StrStr(const char *str1, const char *str2)  
  2. {  
  3.       assert(NULL != str1 && NULL != str2);  
  4.       while(*str1 != '\0')  
  5.       {  
  6.           const char *p = str1;  
  7.           const char *q = str2;  
  8.           const char *res = NULL;  
  9.           if(*p == *q)  
  10.           {  
  11.                 res = p;  
  12.                 while(*p && *q && *p++ == *q++)  
  13.                 ;  
  14.                 if(*q == '\0')  
  15.                       return res;                      
  16.           }  
  17.           str1++;  
  18.       }  
  19.       return NULL;  
  20. }  
編寫strcat函式 
已知strcat函式的原型是
char *strcat (char *strDest, const char *strSrc);
其中strDest 是目的字串,strSrc 是源字串。
char* Strcat(char *str1,char *str2)
{
    char* tempt = str1;
    
    while(*str1!='\0')
    {
        str1++;
    }
    
    while(*str2!='\0')
    {
        *str1 = *str2;
        str1++;
        str2++;
    }
    
    *str1 = '\0';
    return tempt;
} 求質數的程式 bool NotRrime(int num)     bool bRim=false;     i f (num<3)     { returnfalse;     } while (num>=3)      {          for (int index=2;index<num;index++ )          { if (num%index==0)               { bRim =true;break; }         }  } return bRim; }

大端格式:

在這種格式中,字資料的高位元組儲存在低地址中,而字資料的低位元組則存放在高地址中,如圖2.1所示:

小端格式:

與大端儲存格式相反,在小端儲存格式中,低地址中存放的是字資料的低位元組,高地址存放的是字資料的高位元組。如圖2.2所示:

 請寫一個C函式,若處理器是Big_endian的,則返回0;若是Little_endian的,則返回1

解答:

int checkCPU( )

{

{

union w

{  

int a;

char b;

} c;

c.a = 1;

           return(c.b ==1);

}

}

 39人閱讀 評論(0)收藏舉報

庫函式原型:

#inclue <stdlib.h>

int atoi(const char *nptr);

用法:將字串裡的數字字元轉化為整形數,返回整形值。

注意:轉化時跳過前面的空格字元,直到遇上數字或正負符號才開始做轉換,而再遇到非數字或字串結束符號時('/0')才結束轉換,並將結果返回

例:

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

int main()
{

    char *ptr1 = "-12345.12";
    char *ptr2 = "+1234w34";
    char *ptr3 = "   456er12";
    char *ptr4 = "789 123";
    int a,b,c,d;

    a = atoi(ptr1);
    b = atoi(ptr2);

    c = atoi(ptr3);
    d = atoi(ptr4);

    printf("a = %d, b = %d, c = %d, d = %d/n", a,b,c,d);

    return 0;
}

輸出結果:a = 12345 b = 1234 c = 456 d = 789

***************************************************

不呼叫庫函式用C語言實現atoi函式的功能:

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

int my_atoi(const char *str);

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

    char *ptr = " 1234 3455";
    int n;

    n = my_atoi(ptr);
    printf("myAtoi:%d/n", n);

    n = atoi(ptr);
    printf("atoi:%d/n", n);

    return 0;
}

int my_atoi(const char *str)
{

    int value = 0;
    int flag = 1; //判斷符號

    while (*str == ' ')  //跳過字串前面的空格
    {

        str++;
    }

    if (*str == '-')  //第一個字元若是‘-’,說明可能是負數
    {

        flag = 0;
        str++;
    }
    else if (*str == '+') //第一個字元若是‘+’,說明可能是正數

    {

        flag = 1;
        str++;
    }//第一個字元若不是‘+’‘-’也不是數字字元,直接返回0

    else if (*str >= '9' || *str <= '0')
 
    {
        return 0;    
    }

    //當遇到非數字字元或遇到‘/0’時,結束轉化
    while (*str != '/0' && *str <= '9' && *str >= '0')

    {
        value = value * 10 + *str - '0'; //將數字字元轉為對應的整形數

        str++;

    }

    if (flag == 0) //負數的情況
    {

        value = -value;
    }

    return value;
}


五大演算法:貪心法,動態規劃法,分治法

http://blog.sina.com.cn/s/blog_3f135d4601010lsk.html

字串逆序

中文需要單獨處理的,一箇中文佔兩個位元組,反轉時順序不變。

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

void reverse(char* s)

{

int len = strlen(s);

char* pNewStr = (char*)malloc(len + 1) ;

char* pNewMove = pNewStr;

char* pStr = s + len - 1;

while(pStr >= s)

{

unsigned char ch = *pStr;

if(ch > 127) //中文判斷 不太確定,這個條件是否嚴謹,在本機測試沒問題

{

*pNewMove = *(pStr - 1);

pNewMove ++;

*pNewMove= *pStr;

pNewMove ++;

pStr -= 2;

}else

{

*pNewMove =*pStr;

pNewMove ++;

pStr--;

}

}

pNewStr[len] = '\0';

strcpy(s,pNewStr);

free(pNewStr);

}

int main()

{  

char str[201];

printf("輸入要反轉的字串\n");

scanf("%s",str);

reverse(str);

printf("反轉後字元變為:\n %s \n",str);

system("pause");

return 0;

}

top命令是Linux下常用的效能分析工具,能夠實時顯示系統中各個程序的資源佔用狀況,類似於Windows的工作管理員。

Linux下2種定時執行任務方法

2011-10-08 00:00中國IT實驗室佚名 關鍵字:Linux

  (1)at命令

  假如我們只是想 要讓特定任務執行一次,那麼,這時候就要用到at監控程式了。

  設定at命令很簡單,指示定執行的時間,那麼就會在哪個時候執行。at類似列印 程序,會把任務放到/var/spool/at目錄中,到指定時間執行它 。at命令相當於另一個shell,執行at time命令時,它傳送一個個命令,可以輸入任意命令或者程式。at now + time命令可以在指示任務。

  假設處理一個大型資料庫,要在別人不用系統時去處理資料,比如凌晨3點10分。那麼我們就應該先建立/home/kyle/do_job指令碼管理資料庫,計劃處理/home/kyle/do_job檔案中的結果。正常方式是這樣啟動下列命令:

  # at 2:05 tomorrow

  at>/home/kyle/do_job

  at> Ctrl+D

  AT Time中的時間表示方法

  -----------------------------------------------------------------------

  時 間 例子 說明

  -----------------------------------------------------------------------

  Minute at now + 5 minutes 任務在5分鐘後執行

  Hour at now + 1 hour 任務在1小時後執行

  Days at now + 3 days 任務在3天后執行

  Weeks at now + 2 weeks 任務在兩週後執行

  Fixed at midnight 任務在午夜執行

  Fixed at 10:30pm 任務在晚上10點30分

  注意:一定要檢查一下atq的服務是否啟 動,有些作業系統未必是預設啟動的, linux預設為不啟動,而ubuntu預設為啟動的。檢查是否啟動,用service atd檢查語法,用service atd status檢查atd的狀態,用service atd start啟動atd服務。

  檢視at執行的具體內容:一般位於/var/spool/at目錄下面, 用vi開啟,在最後一部分就是你的執行程式

  (2)crontab

  cron是一個linux下 的定時執行工具,可以在無需人工干預的情況下執行作業。由於Cron 是Linux的內建服務,但它不自動起來,可以用以下的方法啟動、關閉這個服務:

  /sbin/service crond start //啟動服務

  /sbin/service crond stop //關閉服務

  /sbin/service crond restart //重啟服務

  /sbin/service crond reload //重新載入配置

  /sbin/service crond status //檢視服務狀態

  你也可以將這個服務在系統啟 動的時候自動啟動:

  在/etc/rc.d/rc.local這個指令碼的末尾加上:

  /sbin/service crond start

  現在Cron這個服務已經在程序裡面了,我們就可以用這個服務了,Cron服務提供以下幾種介面供大家使用:

  1、直接用crontab命 令編輯

  cron服務提供 crontab命令來設定cron服務的,以下是這個命令的一些引數與說明:

  crontab -u //設定某個使用者的cron服務,一般root使用者在執行這個命令的時候需要此引數

  crontab -l //列出某個使用者cron服務的詳細內容

  crontab -r //刪除某個使用者的cron服務

  crontab -e //編輯某個使用者的cron服務

  比如說root檢視自己的cron設定:crontab -u root -l

  再例 如,root想刪除fred的cron設定:crontab -u fred -r

  基本格式 :

  *  *  *  *  *  command

  分  時  日  月  周  命令

  第1列表示分鐘1~59 每分鐘用*或者 */1表示

  第2列表示小時1~23(0表示0點)

  第3列表示日期1~31

  第4列表示月份1~12

  第5列標識號星期0~6(0表示星期天)

  第6列要執行的命令

  crontab檔案的一些例子:

  #每晚的21:30重啟apache。

  30 21 * * * /usr/local/etc/rc.d/lighttpd restart

  #每月1、10、22日

  45 4 1,10,22 * * /usr/local/etc/rc.d/lighttpd restart

  #每天早上6點10分

  10 6 * * * date

  #每兩個小時

  0 */2 * * * date

  #晚上11點到早上8點之間每兩個小時,早上8點

  0 23-7/2,8 * * * date

  #每個月的4號和每個禮拜的禮拜一到禮拜三的早上11點

  0 11 4 * mon-wed date

  #1月份日早上4點

  0 4 1 jan * date


malloc,free和new,delete有區別嗎?如果有,是什麼?

1,malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。
2, 對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。
3,因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。
4,C++程式經常要呼叫C函式,而C程式只能用malloc/free管理動態記憶體

程序是程式的一次執行,執行緒可以理解為程序中的執行的一段程式片段。在一個多工環境中下面的概念可以幫助我們理解兩者間的差別:   
      程序間是獨立的,這表現在記憶體空間,上下文環境;執行緒執行在程序空間內。   
      一般來講(不使用特殊技術)程序是無法突破程序邊界存取其他程序內的儲存空間;而執行緒由於處於程序空間內,所以同一程序所產生的執行緒共享同一記憶體空間。 
      同一程序中的兩段程式碼不能夠同時執行,除非引入執行緒。   
執行緒是屬於程序的,當程序退出時該程序所產生的執行緒都會被強制退出並清除。   
      執行緒佔用的資源要少於程序所佔用的資源。   
      程序和執行緒都可以有優先順序。   
      線上程系統中程序也是一個執行緒。可以將程序理解為一個程式的第一個執行緒。

以下程式的輸出結果是:0120;

#include <stdio.h>
void eit(int n);
int main(void)
{
    int a = 3;
    eit(a);
    return 0;
}
void eit(int n)
{
    if (n > 0)
    {
        eit(--n);
        printf("%d", n);
        eit(--n);
    }
}

一道思考題:

寫一個“標準”巨集MIN,這個巨集輸入兩個引數並返回較小的一個。另外,當你執行”least = MIN(*p++, b); “程式碼時會發生什麼事?

解答: #define MIN(A,B)  ( (A) <= (B) ?  (A) : (B) )     MIN(*p++, b)會產生巨集的副作用。 
剖析: 這道題考察對巨集定義的使用,巨集定義可以實現類似於函式的功能,但是它終歸不是函式,而巨集定義中括弧中的“引數”也不是真的引數,在巨集展開的時候對“引數”進行的是一對一的替換。             程式設計師對巨集定義的使用要非常小心,特別要注意兩個問題:
(1)謹慎地將巨集定義中的“引數”和整個巨集用用括弧括起來。所以,嚴格地講,下
          述解答: #define MIN(A,B) (A) <= (B) ? (A) : (B)       #define MIN(A,B) (A <= B ? A : B )  都是錯誤的。
(2)防止巨集的副作用。巨集定義#define MIN(A,B) ((A) <= (B) ? (A) : (B))對MIN(*p++, b)的作用結果是: ((*p++) <= (b) ? (*p++) : (b)) 這個表示式會產生副作用,指標p會作二次++自增操作。
          除此之外,另一個典型的錯誤解答是: #define MIN(A,B) ((A) <= (B) ? (A) : (B)); 這個解答在巨集定義的後面加“;”,顯示編寫者對巨集的概念模糊不清。

使用巨集定義定義MAX,不用if,也不用?:#define MAX(a,b) ((a)+(b)+(abs((a)-(b)))/2)

搜尋引擎是指根據一定的策略、運用特定的計算機程式從網際網路上搜集資訊,在對資訊進行組織和處理後,為使用者提供檢索服務,將使用者檢索相關的資訊展示給使用者的系統。搜尋引擎包括全文索引、目錄索引、元搜尋引擎、垂直搜尋引擎、集合式搜尋引擎、門戶搜尋引擎與免費連結列表等。百度和谷歌等是搜尋引擎的代表。

就是對地圖中的特徵點如何獲取

1、給定一個數組a[N],我們希望構造陣列b[N],其中b[i]=a[0]*a[1]*...*a[N-1]/a[i]。在構造過程:
不允許使用除法;
要求O(1)空間複雜度和O(n)時間複雜度;
除遍歷計數器與a[N] b[N]外,不可使用新的變數(包括棧臨時變數、對空間和全域性靜態變數等);
請用程式實現並簡單描述。
解答:陣列分割

10 void output(long long* a, int len)
20 /*
21 題目要求的演算法
22 */
23 /************************************************************************/
24 void problem(int* a, long long* b, int N)
25 {
26     b[0] = 1;
27     for(int i = 1; i < N; ++i)
28     {
29         b[i] = b[i-1] * a[i-1]; // 分水嶺的前半段乘積
30     }
31 
32     b[0] = a[N - 1];
33     for(int i = N - 2; i >= 1; --i)
34     {
35         b[i] *= b[0];
36         b[0] *= a[i]; // 分水嶺的後半段乘積,是從陣列尾部向前迴圈的
37     }
38 }
1.一個正確的二分法。
   
二分查詢用於在多條記錄中快速找到待查詢的記錄。它的思想是:每次將查詢的範圍縮小一半,直到最後找到記錄或者找不到記錄返回。
   
二分查詢的時間複雜度為O(logn)
   二分法的遞迴和非遞迴演算法如下
 i.非遞迴演算法。



[cpp] view plaincopyprint?


1. int binary_search(int arr[],int n ,int key){  

2.     int mid;  

3.     int low = 0,high = n-1;  

4.     while(low <= high){  //防止死迴圈

5.         mid =  low + (high - low)/2;   //中間元素,防止溢位   

6.         if(key == arr[mid]) return mid;//找到時返回 
7.   

8.         else if(key > arr[mid]){  

9.             low = mid + 1;//在更高的區間搜尋   

10.         }else{  

11.             high = mid - 1;//在更低的區間搜尋   

12.         }  

13.     }  

14.     return -1;//沒有找到元素,返回-1 
15.   

16. }  


   ii.遞迴演算法:



[cpp] view plaincopyprint?


1. int binary_search(int arr[],int low,int high,int key){  

2.     if(low > high) return -1;  

3.     int mid =  low + (high - low)/2   //中間元素,防止溢位   

4.     if(key == arr[mid])return mid;//找到時返回 
5.   

6.     else if(key > arr[mid]){  

7.         return binary_search(arr,mid + 1,high,key);/在更高的區間搜尋  

8.     }else{  

9.         return binary_search(arr,low,mid - 1,key);/在更低的區間搜尋  

10.     }  

11.    }  
12. 
經典書籍推薦,主要是linux C方面的,我把我看過或者瞭解的簡單說一下。
C語言:
C程式設計語言 -- 
沒有太細的看,而且修為不夠,所以沒啥感覺
C和指標 -- 感覺這本書倒很適合做大一的教材,比較經典。
C陷阱與缺陷 -- 
兩天就能看完吧,比較簡單,只要瞭解一些變態語法就行。
C專家程式設計 -- 
我沒看。但九度貌似有word版總結這幾本書的,那個word看完了。確實總結的很不錯。
個人重點推薦C和指標 + 
C陷阱缺陷

C++
C++ Primer -- 看了兩遍吧;實習生面試前一遍;暑假一遍;
高質量程式設計指南C/C++ -- 
6月初看的一遍,這本書很不錯,很多黑體重要結論,引經據典,回答C++的問題能夠拎上的話加分不少。
深度搜索C++物件模型 -- 
6月份看的,有點小難,而且意義不是很大,瞭解一個邏輯模型就可以了,而且裡面本身就有很多錯誤。
STL原始碼剖析 -- 
暑假看的更是掃描的看的。重原理,輕細節,糾結詳盡的模板語法對菜鳥來說估計會死。
Effective C++ -- 
每天整理兩三個條款,我覺得這種條款類的書很適合閒暇時間看。
More Effective C++ -- 
就挑了幾個常考的條款看了看,挺好的。
Effective STL -- 
僅看了幾個條款。

軟體基礎知識,個人認為最好都通曉點:
資料結構 & 演算法設計分析 -- 
演算法導論對我這菜鳥實在啃不動。就整了考研時李春葆的課本 + 清華那本計算機演算法設計與分析。 
作業系統原理 -- 湯子瀛的課本 整了整程序排程 + 
記憶體那塊。
計算機網路 -- 謝希仁的課本 整了整網路層 + 傳輸層。
資料庫系統實現 -- 
結合pg原始碼看的。同樣,也是看到編譯執行,併發事務沒看。
搜尋引擎-資訊檢索實踐 -- 
9月中旬才買的書,忽悠搜尋引擎用的,但整天在面試,基本沒看。但看看挺好的。忽悠百度、搜狗、有道啥的有用。
大話設計模式 -- 
就看了幾個模式。本來就一個暑假,不可能樣樣都知道,實驗室老闆還逼著看Totem原始碼(實驗室基於PostgreSQL自己開發的擴充套件版資料庫,程式碼更改了近三分之一啊感覺,也就不奇怪當年開發實驗室自己資料庫那幫人很多去搞Oracle
DB2了,武大最後三年制變兩年制的最後一屆)
個人推薦:資料結構和演算法最重要啊還是,另外,建議大家買的專業課考研資料不要賣啊。看重點很有用。

Linux/Unix程式設計部分
Linux程式設計,過年開學正月十五去光谷玩時在華科買的,5月份差不多主要部分就看完了。瞭解了這麼些系統呼叫。啥的。
UNIX環境高階程式設計 
6月18號 - 7月30號 看了兩遍,並做了筆記。挺好的。
POSIX多執行緒程式設計 
第二遍看APUE時附帶看的,這本書很早就絕版了,電子版貌似也不多。
TCP/IP Sockets程式設計(C語言實現) 
簡單的入門書。200頁很薄。
TCP/IP高效程式設計 
真本書是條款的,44個條款。大概也就看了前十多個條款。挺好的,有時間的話這兩本加起來基本可以了,UNIX網路程式設計那兩卷加起來都可以鎮宅用了,能看?
重點推薦UNIX環境高階程式設計 

TCP/IP高效程式設計啊。後邊那本書44個條款對網路程式設計絕對是一個很好的總結。另外shell/python啥的,反正找工作時候自學了一點,基本不會。也沒重點去看,所以沒啥推薦的。

應試啊,應試啊:
程式設計之美--至少今年很多題還出自這裡面,必不可少。。。
程式設計師面試寶典 
-- 三天就能看完,真要淪落到看這本書,那。。。除非技術正的大牛。。。
程式設計師求職成功路:技術、求職技巧與軟實力培養 -- 
就算看應試的書,個人推薦還是看這本吧,講的很多都比較有深度。尤其前幾章C記憶體的部分。
程式設計師面試攻略 -- 
題目比較老,但是看看有助於思維發散。
《程式設計之美: 
求二叉樹中節點的最大距離》的另一個解法
昨天花了一個晚上為《程式設計之美》,在豆瓣寫了一篇書評《遲來的書評和感想──給喜愛程式設計的朋友》。書評就不轉載到這裡了,取而代之,在這裡介紹書裡其中一條問題的另一個解法。這個解法比較簡短易讀及降低了空間複雜度,或者可以說覺得比較「美」吧。

問題定義

如果我們把二叉樹看成一個圖,父子節點之間的連線看成是雙向的,我們姑且定義"距離"為兩節點之間邊的個數。寫一個程式求一棵二叉樹中相距最遠的兩個節點之間的距離。

我也想不到更好的分析方法。

書上的解法

書中對這個問題的分析是很清楚的,我嘗試用自己的方式簡短覆述。

計算一個二叉樹的最大距離有兩個情況:


情況A: 路徑經過左子樹的最深節點,通過根節點,再到右子樹的最深節點。 

情況B: 路徑不穿過根節點,而是左子樹或右子樹的最大距離路徑,取其大者。 

只需要計算這兩個情況的路徑距離,並取其大者,就是該二叉樹的最大距離。



我也想不到更好的分析方法。

但接著,原文的實現就不如上面的清楚 (原始碼可從這裡下載):



?




// 資料結構定義
struct NODE
{
    NODE* pLeft;        // 左子樹
    NODE* pRight;       // 右子樹
    int nMaxLeft;       // 左子樹中的最長距離
    int nMaxRight;      // 右子樹中的最長距離
    char chValue;       // 該節點的值
};
  
int nMaxLen = 0;
  
// 尋找樹中最長的兩段距離
void FindMaxLen(NODE* pRoot)
{
    // 遍歷到葉子節點,返回
    if(pRoot == NULL)
    {
        return;
    }
  
    // 如果左子樹為空,那麼該節點的左邊最長距離為0
    if(pRoot -> pLeft == NULL)
    {
        pRoot -> nMaxLeft = 0; 
    }
  
    // 如果右子樹為空,那麼該節點的右邊最長距離為0
    if(pRoot -> pRight == NULL)
    {
        pRoot -> nMaxRight = 0;
    }
  
    // 如果左子樹不為空,遞迴尋找左子樹最長距離
    if(pRoot -> pLeft != NULL)
    {
        FindMaxLen(pRoot -> pLeft);
    }
  
    // 如果右子樹不為空,遞迴尋找右子樹最長距離
    if(pRoot -> pRight != NULL)
    {
        FindMaxLen(pRoot -> pRight);
    }
  
    // 計算左子樹最長節點距離
    if(pRoot -> pLeft != NULL)
    {
        int nTempMax = 0;
        if(pRoot -> pLeft -> nMaxLeft > pRoot -> pLeft -> nMaxRight)
        {
            nTempMax = pRoot -> pLeft -> nMaxLeft;
        }
        else
        {
            nTempMax = pRoot -> pLeft -> nMaxRight;
        }
        pRoot -> nMaxLeft = nTempMax + 1;
    }
  
    // 計算右子樹最長節點距離
    if(pRoot -> pRight != NULL)
    {
        int nTempMax = 0;
        if(pRoot -> pRight -> nMaxLeft > pRoot -> pRight -> nMaxRight)
        {
            nTempMax = pRoot -> pRight -> nMaxLeft;
        }
        else
        {
            nTempMax = pRoot -> pRight -> nMaxRight;
        }
        pRoot -> nMaxRight = nTempMax + 1;
    }
  
    // 更新最長距離
    if(pRoot -> nMaxLeft + pRoot -> nMaxRight > nMaxLen)
    {
        nMaxLen = pRoot -> nMaxLeft + pRoot -> nMaxRight;
    }
}

有一個數組,該陣列的特點是:前一部分遞增有序,後一部分遞減有序,求陣列的峰值位置
我們發現,給出的陣列可能有如下三種形態:

其中後兩種情況是屬於特殊的,是有序陣列,峰值的位置就是陣列的開始或結束索引。我們單單考慮第一種情況。
思考1:線性掃描,思路最簡單,也最容易實現,只需掃描 一遍陣列,比較當前元素與前一個元素的大小關係,一旦碰到當前元素比前一元素小,前一元素就是峰值。或者比較當前元素與後一元素,一旦後一元素比當前元素小,當前元素就是峰值。此方法的缺點是需要線性時間O(n)
思考2:考慮二分搜尋,如果不考慮情況2和情況3.對於情況一而言,mid中間元素的位置不外乎三個情況:

是不是跟二分搜尋很類似?
我們可以根據中間元素與兩邊元素的大小關係,判斷搜尋左邊還是右邊。據此,不難寫出如下程式碼(未經嚴格測試):
[cpp] view plaincopyprint?
1. #include <stdio.h>   
2.   
3. //陣列是先增後減陣列。   
4. int findMax(int *a ,int n){  
5.     int low = 0,high = n-1,mid;  
6.     while(low <= high){  
7.         mid = low + ((high-low)>>1);  
8.         if((mid + 1)<=