1. 程式人生 > >啊哈!演算法--第04節--小哼買書

啊哈!演算法--第04節--小哼買書

    本文主要參考:啊哈磊的《啊哈!演算法》,特此說明。

    排序演算法還有很多,例如我在《啊哈C!思考快你一步》一書中講過的選擇排序,另外還有計數排序、基數排序、插入排序、歸併排序和堆排序等等。堆排序是基於二叉樹的排序,我會在後面的章節講到。現在來看一個具體的例子“小哼買書”(根據全國青少年資訊學奧林匹克聯賽NOIP2006 普及組第一題改編),來實踐一下本章所學的三種排序演算法。


這裡寫圖片描述

    小哼的學校要建立一個圖書角,老師派小哼去找一些同學做調查,看看同學們都喜歡讀哪些書。小哼讓每個同學寫出一個自己最想讀的書的ISBN 號(你知道嗎?每本書都有唯一的ISBN 號,不信的話你去找本書翻到背面看看)。當然有一些好書會有很多同學都喜歡,這樣就會收集到很多重複的ISBN 號。小哼需要去掉其中重複的ISBN 號,即每個ISBN 號只保留一個,也就說同樣的書只買一本(學校真是夠摳門的)。然後再把這些ISBN 號從小到大排序,小哼將按照排序好的ISBN 號去書店買書。請你協助小哼完成“去重”與“排序”的工作。
    輸入有2 行,第1 行為一個正整數,表示有n 個同學參與調查(n≤100)。第2 行有n個用空格隔開的正整數,為每本圖書的ISBN 號(假設圖書的ISBN 號在1~1000 之間)。
    輸出也是2 行,第1 行為一個正整數k,表示需要買多少本書。第2 行為k 個用空格隔開的正整數,為從小到大已排好序的需要購買的圖書的ISBN 號。
    例如輸入:

10
20 40 32 67 40 20 89 300 400 15

    則輸出:

8
15 20 32 40 67 89 300 400

    最後,程式執行的時間限制為1 秒。
    決這個問題的方法大致有兩種。第一種方法:先將這n 個圖書的ISBN 號去重,再進行從小到大排序並輸出;第二種方法:先從小到大排序,輸出的時候再去重。這兩種方法都可以。
    先來看第一種方法。通過第一節的學習我們發現,桶排序稍加改動正好可以起到去重的效果,因此我們可以使用桶排序的方法來解決此問題。

#include <stdio.h>
int main()
{
    int a[1001],n,i,t;
    for
(i=1;i<=1000;i++) a[i]=0; //初始化 scanf("%d",&n); //讀入n for(i=1;i<=n;i++) //迴圈讀入n個圖書的ISBN號 { scanf("%d",&t); //把每一個ISBN號讀到變數t中 a[t]=1; //標記出現過的ISBN號 } for(i=1;i<=1000;i++) //依次判斷1~1000這個1000個桶 { if(a[i]==1)//如果這個ISBN號出現過則打印出來 printf("%d ",i); } getchar();getchar(); return
0;

    這種方法的時間複雜度就是桶排序的時間複雜度,為O(N+M)。
    第二種方法我們需要先排序再去重。排序我們可以用氣泡排序或者快速排序。

20 40 32 67 40 20 89 300 400 15

    將這10 個數從小到大排序之後為 15 20 20 32 40 40 67 89 300 400。
    接下來,要在輸出的時候去掉重複的。因為我們已經排好序,所以相同的數都會緊挨在一起。只要在輸出的時候,預先判斷一下當前這個數a[i]與前面一個數a[i1]是否相同。如果相同則表示這個數之前已經輸出過了,不用再次輸出;不同則表示這個數是第一次出現,需要輸出這個數。

#include <stdio.h>
int main()
{
    int a[101],n,i,j,t;
    scanf("%d",&n); //讀入n
    for(i=1;i<=n;i++) //迴圈讀入n個圖書ISBN號
    {
        scanf("%d",&a[i]);
    }
    //開始氣泡排序
    for(i=1;i<=n-1;i++)
    {
        for(j=1;j<=n-i;j++)
        {
            if(a[j]>a[j+1])
            { t=a[j]; a[j]=a[j+1]; a[j+1]=t; }
        }
    }
    printf("%d ",a[1]); //輸出第1個數
    for(i=2;i<=n;i++) //從2迴圈到n
    {
        if( a[i] != a[i-1] ) //如果當前這個數是第一次出現則輸出
        printf("%d ",a[i]);
    }
    getchar();getchar();
    return 0;
}

    這種方法的時間複雜度由兩部分組成,一部分是氣泡排序的時間複雜度,是N (N2),另一部分是讀入和輸出,都是O(N),因此整個演算法的時間複雜度是O(2*N+N 2)。相對於N2 來說,2*N 可以忽略(我們通常忽略低階),最終該方法的時間複雜度是O(N2)。
    接下來我們還需要看下資料範圍。每個圖書ISBN 號都是1~1000 之間的整數,並且參加調查的同學人數不超過100,即n≤100。之前已經說過,在粗略計算時間複雜度的時候,我們通常認為計算機每秒鐘大約執行10 億次(當然實際情況要更快)。因此以上兩種方法都可以在1 秒鐘內計算出解。如果題目中圖書的ISBN 號範圍不是在1~1000 之間,而是-2147483648~2147483647 之間的話,那麼第一種方法就不可行了,因為你無法申請出這麼大的陣列來標記每一個ISBN 號是否出現過。另外如果n 的範圍不是小於等於100,而是小於等於10 萬,那麼第二種方法的排序部分也不能使用氣泡排序。因為題目要求的時間限制是1 秒,使用氣泡排序對10 萬個數進行排序,計算機要執行100 億次,需要10 秒鐘,因此要替換為快速排序,快速排序只需要100000×log2100000≈100000×17≈170 萬次,這還不到
0.0017 秒。是不是很神奇?同樣的問題使用不同的演算法竟然有如此之大的時間差距,這就是演算法的魅力!
    我們來回顧一下本章三種排序演算法的時間複雜度。桶排序是最快的,它的時間複雜度是O(N+M);氣泡排序是O(N 2);快速排序是O(NlogN)。
    最後,你可以到“添柴程式設計學習網”提交本題的程式碼,來驗證一下你的解答是否完全正確。《小哼買書》題目的地址如下:
www.tianchai.org/problem-12001.html
    接下來,本書中的所有演算法都可以去“添柴程式設計學習網”一一驗證。如果你從來沒有使用過類似“添柴程式設計學習網”這樣的線上自動評測系統(online judge),那麼我推薦你可以先嚐試提交下這道題:A+B=? 地址如下:
www.tianchai.org/problem-10000.html