1. 程式人生 > >演算法設計與分析——第二篇,論演算法與資料結構的使用方法及技巧

演算法設計與分析——第二篇,論演算法與資料結構的使用方法及技巧

寫在前面的話——

這篇的主體內容其實就是抄書,不過個人覺得我選的還是比較有意義的內容,書上也沒有程式碼,所以程式碼是我自己寫的,比較簡單的題目了,演算法前面的題目或者說知識點都比較初級

本片全文摘抄自我的課本《演算法設計與分析》,呂國英主編,有興趣的小夥伴可以去看看這本書!

第二篇——

論演算法與資料結構的使用方法及技巧

現代計算機可以解決的問題種類繁多,計算機解決問題的實質是對“資料”進行加工處理,計算機處理的問題型別,粗略地可以分成數值計算問題和非數值性問題,隨著計算機應用領域的擴大和軟、硬體的發展,“非數值型問題”越來越顯得重要。這類問題設計的資料結構就較為複雜,資料元素之間的相互關係往往無法用數學方程式加以描述。因此,解決此類問題的關鍵不僅僅是問題分析、數學建模和演算法設計,還必須設計出合適的資料結構,才能有效地解決問題。

演算法設計的實質是對實際問題要處理的資料選擇一種恰當的儲存結構,並在選定的儲存結構上設計一個好的演算法,實現對資料的處理。演算法中的操作是以確定的儲存結構為前提的,所以,在演算法設計中選擇好資料結構是非常重要的,選擇了資料結構,演算法才隨之確定。好的演算法在很大程式上取決於問題中資料所採用的資料結構。

常用的儲存結構可以分為連續儲存和鏈式儲存。連續儲存又分為靜態分配和動態分配兩種,對連續儲存和鏈式離散儲存兩種儲存結構各有長短,選擇哪一種有具體的問題決定。

下面討論一下資訊儲存中的一些技巧。

1、原始資訊與處理結果的對應儲存

解決一個問題時,往往存在多方面的資訊。就演算法而言,一般有輸入資訊、輸出資訊和資訊加工處理過程中的中間資訊。那麼將哪些資料用陣列儲存,陣列元素下標與資訊怎麼樣對應等問題的確定,在很大程度上影響著演算法的編寫效率和執行效率

下面的例子恰當的選擇了用陣列儲存的資訊,並且把題目中的有關資訊作為下標使用,使演算法的實現過程大大簡化。

【例】 程式設計統計身高(單位為釐米)。統計分150~154、155~159、160~164、165~169、170~174、175~179、低於150和高於179,共8檔次進行。

演算法設計:很明顯該題目不需要將所有身高資料都儲存入陣列,僅需要8個檔次即可,此時需要分析這8個檔次之間的規律來合理的儲存資料並且輸出相應值,定義一個含有8個元素的陣列,此時陣列的下標需要和8個檔次形成對應關係,觀察每個檔次的第一個數字,如果都執行/5-29的操作之後,正好分別為1、2、3、4、5、6,再將0和7分別作為低於150和高於179檔的下標,如此正好將下標分配完成,找到這個規律之後通過一個迴圈進行儲存,該問題即可解決。

程式程式碼見附錄1.

演算法說明:該演算法的精華部分就在於找到各個檔次之間的規律,身高/5-29這個運算很重要,通過這樣計算之後需要聯絡到陣列下標從而提高演算法效率。

從這個例子中可以認識到,演算法與資料結構是密切相關的,好的儲存方式可以大大提高演算法的效率。

2、陣列使資訊有序化

一個問題的提出,其中的資料可能一時間找不到規律或者沒有規律,很難把重複的工作抽象成迴圈不變式來完成,但是如果通過先用陣列儲存這些資訊之後,就變得有序從而可以解決問題。

【例】 編寫演算法將數字編號“翻譯”成英文編號。

例如:將編號35706“翻譯”成英文編號three-five-seven-zero-six。

演算法設計:可以看出任意寫一個數字編號,其各位之間不一定有規律可以找,所以關鍵在於找到如何將數字編號和英文編號對應起來,由此可以現將陣列下標和英文編號對應起來,此時如果再將數字編號各位分別找出來,然後和陣列下標對應,可以進行“翻譯”工作了。

程式程式碼見附錄2.

演算法說明:該演算法的關鍵在於想出將每個數字的英文存進陣列,然後就可以將陣列代替英文,從而出現規律可以通過迴圈來解決“翻譯”問題,至於將如何數字和陣列對應,可以通過迴圈或者遞迴法來取出各位的數,在找出以這個數作為下標的英文,即完成“翻譯”,同時如果改用字串型別來儲存該數字編號,則該字串的每一個元素正好對應該數字的各位的值,從而可以避免需要通過取餘和整除來計算每位的數字。

3、陣列記錄狀態資訊

有的問題會限定現有資料每個資料只能被使用一次,那麼在c語言中,要想判斷資料是否重複使用,比較樸素的想法是用陣列儲存已經使用過的資料,然後每處理一個新的資料就與前面的資料逐一比較看是否重複,但是當資料量比較大時判斷是否重複的工作效率就會越來越低,所以此時,可以開闢一個狀態陣列,專門記錄資料使用情況,並且將資料資訊與狀態陣列下標對應,就能較好的完成判斷是否重複使用的操作。

【例】 12個小朋友手拉手站成一個圓圈,從某個小朋友開始報數,報到7的那個小朋友退到圈外,然後他的下一位重新報1。這樣繼續下去,直到最後只剩下一個小朋友。求解這個小朋友原來站在什麼位置上。

演算法設計:很明顯這個問題需要開闢一個數組表示每個小朋友的圈內圈外的狀態,以所在位置為下標,可以定義陣列的值大於0,則為站在圈內,值等於0則為退到圈外,依次報數則實質上是通過迴圈計數來確認陣列哪一個該被置為0,當迴圈到置為0的陣列下標的時候,此時應該跳過直接對下一位進行計數,同時因為是迴圈計數,當到了陣列最後一位的時候應該要回到陣列第一位,迴圈退出的條件應該是通過定義一個變數來記錄退出圈外的次數恰好比總人數少一個的時候,迴圈退出,輸出結果,得到陣列的下標。

程式程式碼見附錄3.

演算法說明:該演算法一旦清楚瞭如何高效的表示圈內圈外的狀態之後,問題就可以迎刃而解。

通過以上3點,可以明確在演算法設計中選擇好資料結構是非常重要的,靈活使用陣列的儲存的方式,對於程式的執行效率和執行效率都是極大的提升。


附錄:

1.

#include <stdio.h>
 
int main()
{
       int i_height;
       int a_level[8] = {0};
       printf("請輸入身高資料(整數):");
       scanf("%d",&i_height);
 
       while(1) {
              if(i_height < 0) {//設定退出條件
                     break;
              }
 
              if(i_height > 179) {
                     a_level[7]++;
              }else if(i_height < 150) {
                     a_level[0]++;
              }else {
                     a_level[i_height/5- 29]++;
              }
              printf("請輸入下一個:");
              scanf("%d",&i_height);
       }
 
       printf("\n錄入完畢,正在輸出結果!\n");
 
       int i = 0;
       printf("低於150有學生%d人\n",a_level[0]);
       for(i = 1; i < 7; i++) {
              printf("%d~%d檔有學生%d人\n",(i+29)*5,(i+29)*5+4,a_level[i]);
 
       }
       printf("高於179有學生%d人\n",a_level[7]);
       return 0;
}

2.

#include <stdio.h>
#include <string.h>
 
char * cap_english[10] = {"zero","one","two","three","four","five","six","seven","eight","nine"};
 
int main()
{
       char numberid[40];
       printf("請輸入待翻譯編號:");
       scanf("%s",numberid);
 
       int n =strlen(numberid);
       if(0 == n) {
              printf("error\n");
       }
       else {
              printf("%s翻譯為%s",numberid, cap_english[numberid[0] - 48]);
              int i = 0;
              for(i = 1; i <= n - 1; i++) {
                     printf("-%s",cap_english[numberid[i] - 48]);
              }
              printf("\n");
       }
 
       return 0;
}


3.

#include <stdio.h>
#include <string.h>
 
int main()
{
       int i_number, i_beginIndex, i_point;
       printf("輸入遊戲人數,開始報數的編號和退出圈外的報數點:");
       scanf("%d%d %d",&i_number, &i_beginIndex, &i_point);
 
       while(i_number < 1 || i_beginIndex < 1 || i_point < 1) {
              printf("輸入錯誤,重新輸入遊戲人數,開始報數的編號和退出圈外的報數點:");
              scanf("%d%d %d",&i_number, &i_beginIndex, &i_point);
       }
 
       char i_status[i_number + 1];
       memset(i_status, 1,sizeof(i_status));//設定初始值為1,即都在圈內
 
       int i_number_exit = 0;//退出圈外的人數
       int i_index = i_beginIndex;//正在報數的人的陣列下標
       int i_sum = 0;//正在報的數字
       while(1) {
              if(i_number == (i_number_exit + 1)) {//設定退出條件,即此時還剩一個人沒有退出
                     printf("遊戲結束!\n");
                     break ;
              }
 
              while(0 == i_status[i_index]) {
                     i_index++;
                     if(i_index > i_number) {
                            i_index= 1;//陣列到最後一個則回到第一個
                     }
              }
 
              i_sum++;
              if(i_sum == i_point) {//此時到了退出圈外的報數點
                     i_status[i_index]= 0;
                     i_number_exit++;
                     i_sum= 0;
                     printf("%d號退出圈外\n",i_index);
              }
 
              i_index++;
              if(i_index > i_number) {
                     i_index= 1;//陣列到最後一個則回到第一個
              }
       }
 
       for(i_index = 1; i_index <= i_number; i_index++) {
              if(0 != i_status[i_index]) {
                     break;
              }
       }
       printf("%d個人站成一個圈,從%d號開始報數1,報到%d的人退出圈外,從下一位開始繼續從1開始報,最後留在圈內的是%d號\n", i_number, i_beginIndex, i_point, i_index);
 
       return 0;
}


這個題,個人覺得比較有意思,而且在我看來真的是一個數學題,我簡直想在紙上算一算了,不過最後還是覺的作為數學學渣,還是不要了,所以我沒有沒有辦法驗證我的方法是不是正確的,也沒有用書上的虛擬碼寫的程式碼,書上的比較有意思,啟發了我一些思維,但是實際使用中我覺得我自己的話,第一時間並不會想到,只會在後期優化的時候優化出來,所以大概這就是學習演算法的意義吧,第一時間就能在腦海中想到最有效率的方法吧!我還是貼出我自己寫的程式碼。