《數據結構》代碼--第一章:概述

遞歸與線性對比

當n的值從1萬增加到10萬時,第二個程序會由於開闢的內存過大,導致內存溢出,程序異常終止。空間複雜度過高。
內存

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
    int a=10;
    PrintN1(a);
    printf("\n");
    PrintN2(a);

    return 0;
}
void PrintN1(int n)//線性執行
{
    int i;
    for(i=1;i<=n;i++)
    {
        printf("%d\t",i);
    }
}
void PrintN2(int n)//遞歸執行
{
    if(n>0)
    {
        PrintN2(n-1);
        printf("%d\t",n);
    }
}

秦九昭算法和普通算法

明顯少了一個量級,秦九昭算法的速度是:O(n),一般的多項式計算速度是O(n^2)。速度變快!我國數學家真厲害!!

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
    int n=10000;
    int a[3]={1,2,3};
    int x=2;
    printf("%d\t%d",f1(n,a,x),f2(n,a,x));

    return 0;
}

//計算一元多項式
int f1(int n,int a[],int x)
{
    //計算階數爲n,係數爲a[]....a[n]的多項式在x點的值
    int i;
    int p=a[0];
    for(i=1;i<=n;i++)
    {
        p+=a[i]*pow(x,i);
    }
    return p;
}
//秦九韶算法:f(x)=a0+x(a1+x(...(an-1+x(an))))
int  f2(int n,int  a[],int  x)
{
    int i;
    int  p=a[n];
    for(i=n;i>0;i--)

        p=a[i-1]+x*p;
    return p;
}

算法速度對比:

在這裏插入圖片描述
在計算機內只需要關注乘的次數,不必在意加法,可知log(n)運行速度最快!我們在使用時,最好將算法的速度改進到log(n)!

選擇排序:

void SelectSort(int a[],int n)//需要排序的數組和其中存的數字個數
{
    int i,j,mix,temp;
    for(i=0;i<n-1;i++)//循環n-1次即可排序完成
    {
        mix=i;//假設最小元的下標
        for(j=i+1;j<n;j++)
        {
            if(a[j]<a[mix])//找到比mix還小的數字的話,記住其下標
                mix=j;
        }
        if(i!=mix)//如果有就就交換,找到本輪i值對應的最小值,並進入下一i的確定
        {
            temp=a[i];
            a[i]=a[mix];
            a[mix]=temp;
        }
    }
}

算法複雜度:

(1)空間複雜度S(n)——根據算法寫成的程序在執行時所佔用存儲單元的長度。這個長度往往和輸入的規模n有關。空間複雜度過高的算法可能導致使用的內存超限,造成程序非正常中斷。
(2)時間複雜度T(n)——根據算法寫的程序在執行時所花費時間的長度,這個長度往往也與輸入數據的規模n有關。時間複雜度過高的低效率算法可能導致我們在有生之年都等不到程序運行結果

應用實例:最大子列和問題

給定n個整數序列{a1,a2,a3…an},求函數f(i,j)=max{0,Σk<-i to j ak}的最大值。
例如:{-2,11,-4,13,-5,-2},其最大子列位{11,-4,13},和爲20。
代碼一:

int MaxSubseqSuml(int List[],int N)
{
    int i,j,k;
    int ThisSum,MaxSum=0;
    for(i=0;i<N;i++)//i是子列最左端位置
    {
        for(j=i;j<N;j++)//j是子列最右端位置
        {
            ThisSum=0;//從List[i]-->List[j]的子列和
            for(k=i;k<=j;k++)
                ThisSum+=List[k];//從i加到j
            if(ThisSum>MaxSum)//如果這個子列和比目前最大值大,就更新最大值
                MaxSum=ThisSum;
        }//j層循環結束
    }//i層循環結束
    return MaxSum;
}

這個代碼的時間複雜度是由3層for()循環所決定。
T(N)=ΣΣΣ1=O(N^3)
不難發現最內層有大量重複的代碼,是最大的浪費點。因爲當固定i,當j增大了1以後,k循環要重新從i加到j。事實上第j-1步的計算完全可以存下來,第j步只要在此基礎上累加一個List[ j ]就可以了。沒有必要再從頭加起!
下面我們將裏面部分中間值採用窮舉。得到代碼二。
代碼二:

int MaxSubseqSum2(int List[],int N)
{
    int i,j;
    int ThisSum,MaxSum=0;
    for(i=0;i<N;i++)//左端
    {
        ThisSum=0;
        for(j=i;j<N;j++)//右端
        {//對於相同的i,不同的j,只要在j-1次循環的基礎之上累加1項即可
            ThisSum+=List[j];
            if(ThisSum>MaxSum)
                MaxSum=ThisSum;
        }//j循環結束
    }//i循環結束
    return MaxSum;
    
    
}

這次將3個for()循環降成2個for()循環,時間複雜度得到一定改善。
T(n)=O(n^2)。
但這仍然不是最快的算法,下面我們將使用分而治之的方法來將原問題進行拆解成若干個小問題,然後再將其結果進行合併,使用遞歸的話十分方便。
將原始序列一分爲二,那麼最大子列或者在左半邊,或者在右半邊,或者是橫跨中分線的一段。
最後將三段的最大值進行合併,即:Smax=max{ S左,S中,S右 }。
代碼三:

int Max3(int A, int B, int C)
{
    return A>B ? (A>C ? A:C):(B >C ? B:C);//嵌套版三目運算求最大值
}
int DivideAndConquer(int List[], int left, int right)
{//分治法求List[left]--->List[right]的最大子列和
    int MaxLeftSum,MaxRightSum;//存放左右子問題的解
    int MaxLeftBorderSum,MaxRightBorderSum;//存放跨分界的結果

    int LeftBorderSum,RightBorderSum;
    int center,i;
    if(left==right)//遞歸終止條件,子列只有一個數字
    {
        if(List[left]>0) return List[left];
        else return 0;
    }
    //下面是“分”的過程
    center=(left+right)/2;//找到中分點
    //遞歸求的兩邊子列的最大和
    MaxLeftSum=DivideAndConquer(List,left,center);
    MaxRightSum=DivideAndConquer(List,center+1,right);

    //下面求跨分界的最大子列和
    MaxLeftBorderSum=0;LeftBorderSum=0;
    for(i=center;i>=left;i--)
    {
        LeftBorderSum+=List[i];
        if(LeftBorderSum>MaxLeftBorderSum)
            MaxLeftBorderSum=LeftBorderSum;
    }//左邊掃描結束

    MaxRightBorderSum=0;RightBorderSum=0;
    for(i=center+1;i<right;i++)//從中線向右掃描
    {
        RightBorderSum+=List[i];
        if(RightBorderSum>MaxRightBorderSum)
            MaxRightBorderSum=RightBorderSum;
    }//右邊掃描結束
    //下面返回“治”的結果
    return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);

}
int MaxSubseqSum3(int List[],int N)
{//保持與前兩種算法相同的函數接口
    return DivideAndConquer(List,0,N-1);

}

解決問題的核心函數是DivideAndConquer——分而治之,但是作爲專業的程序員,還要在擁有相同功能的同是,不改變原有的接口,若記這個代碼的時間複雜度位:T(N),則函數進行分的複雜度爲:2T(N/2),因爲我們解決了兩個長度減半的子問題,所以在O(N)時間完成。其他步驟都只需要O(1)時間。
綜上分析得遞推式:

T(1)=O(1);
T(N)=2T(N/2)+O(N)
=2[2T(N/2)/2+O(N/2)]+O(N)=22T(N/22)+2O(N)
=…=2kT(N/2k)+kO(N)

  當我們不斷對分,直到N/2^k^=1,即2^k^=N時,T(N)=N*T(1)+logN*O(N)=O(NlogN)。
  由圖可知其速度將大於O(n^2^).

但這仍然不是最快的算法!下面講一種在線處理的算法,在線就是每輸入一個數據經行即時處理,得到的結果對於已讀入的所有數據都成立。即在任何一個地方終止程序,算法都能正確給出當前的解。
前面三種給出的都是必須等所有的N個整數讀取並存儲後纔可以進行計算!下面將進行的時不用存儲,就可以即時計算任何時刻的最大子列和。
該算法的核心是基於下面的事實:如果整數序列{a1,a2,…an}的最大和子列是{ai,ai+a,…aj},那麼必定有Σi->l ak>=0對於任意的i<=l<=j成立。因此,一旦發現當前子列和爲負數,則重新開始考察一個新的子列。
代碼四:

int MaxSubseqSum4(int List[],int N)
{
    int i;
    int ThisSum,MaxSum;

    ThisSum=MaxSum=0;
    for(i=0;i<N;i++)
    {
        ThisSum+=List[i];
        if(ThisSum>MaxSum)
            MaxSum=ThisSum;
        else if(ThisSum<0)
            ThisSum=0;
    }
    return MaxSum;
}

程序運行流程圖:
在線處理
這種在線處理的方式,不需要將數據存儲起來,我們只需要一個一個讀入,同時,一個一個處理即可,處理過後的數據也沒有必要存起來。整個算法只把數據掃描了一遍,應該是最快的算法了!!

本章小結:

本章介紹了兩個重要概念:“數據結構”和“算法”。
數據結構:包括數據對象集以及它們在計算機中的組織方式,即他們的邏輯結構和物理結構,同時還包括數據對象集相關聯的操作集,以及實現這些操作的高效算法。抽象數據型是用來描述數據結構的重要類型。
算法:是解決問題步驟的有限集合,通常用某一種計算機語言進行僞碼描述。我們用時間複雜度和空間複雜度來衡量算法的優劣,用漸進表示法分析算法複雜度的增長趨勢。

本章算法程序原碼:

#include <stdio.h>
void  SelectSort(int a[],int n);//選擇排序
int MaxSubseqSuml(int List[],int N);//求最大子列和
int MaxSubseqSum2(int List[],int N);
int MaxSubseqSum3(int List[],int N);
int MaxSubseqSum4(int List[],int N);
int Max3(int A, int B, int C);
int DivideAndConquer(int List[], int left, int rigth);
int main()
{
    int n,i;
    int a[10];
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    //求最大子列和問題
    int maxsub=MaxSubseqSuml(a,n);
    int maxsub2=MaxSubseqSum2(a,n);
    int maxsub3=MaxSubseqSum3(a,n);
    int maxsub4=MaxSubseqSum4(a,n);
    printf("%d\n%d\n%d\n%d\n",maxsub,maxsub2,maxsub3,maxsub4);

    SelectSort(a,n);
    for(i=0;i<n;i++)
    {
        printf("%d\t",a[i]);
    }

    return 0;

}
void SelectSort(int a[],int n)//需要排序的數組和其中存的數字個數
{
    int i,j,mix,temp;
    for(i=0;i<n-1;i++)//循環n-1次即可排序完成
    {
        mix=i;//假設最小元的下標
        for(j=i+1;j<n;j++)
        {
            if(a[j]<a[mix])//找到比mix還小的數字的話,記住其下標
                mix=j;
        }
        if(i!=mix)//如果有就就交換,找到本輪i值對應的最小值,並進入下一i的確定
        {
            temp=a[i];
            a[i]=a[mix];
            a[mix]=temp;
        }
    }
}

int MaxSubseqSuml(int List[],int N)
{
    int i,j,k;
    int ThisSum,MaxSum=0;
    for(i=0;i<N;i++)//i是子列最左端位置
    {
        for(j=i;j<N;j++)//j是子列最右端位置
        {
            ThisSum=0;//從List[i]-->List[j]的子列和
            for(k=i;k<=j;k++)
                ThisSum+=List[k];//從i加到j
            if(ThisSum>MaxSum)//如果這個子列和比目前最大值大,就更新最大值
                MaxSum=ThisSum;
        }//j層循環結束
    }//i層循環結束
    return MaxSum;
}

int MaxSubseqSum2(int List[],int N)
{
    int i,j;
    int ThisSum,MaxSum=0;
    for(i=0;i<N;i++)//左端
    {
        ThisSum=0;
        for(j=i;j<N;j++)//右端
        {//對於相同的i,不同的j,只要在j-1次循環的基礎之上累加1項即可
            ThisSum+=List[j];
            if(ThisSum>MaxSum)
                MaxSum=ThisSum;
        }//j循環結束
    }//i循環結束
    return MaxSum;


}
int Max3(int A, int B, int C)
{
    return A>B ? (A>C ? A:C):(B >C ? B:C);//嵌套版三目運算求最大值
}
int DivideAndConquer(int List[], int left, int right)
{//分治法求List[left]--->List[right]的最大子列和
    int MaxLeftSum,MaxRightSum;//存放左右子問題的解
    int MaxLeftBorderSum,MaxRightBorderSum;//存放跨分界的結果

    int LeftBorderSum,RightBorderSum;
    int center,i;
    if(left==right)//遞歸終止條件,子列只有一個數字
    {
        if(List[left]>0) return List[left];
        else return 0;
    }
    //下面是“分”的過程
    center=(left+right)/2;//找到中分點
    //遞歸求的兩邊子列的最大和
    MaxLeftSum=DivideAndConquer(List,left,center);
    MaxRightSum=DivideAndConquer(List,center+1,right);

    //下面求跨分界的最大子列和
    MaxLeftBorderSum=0;LeftBorderSum=0;
    for(i=center;i>=left;i--)
    {
        LeftBorderSum+=List[i];
        if(LeftBorderSum>MaxLeftBorderSum)
            MaxLeftBorderSum=LeftBorderSum;
    }//左邊掃描結束

    MaxRightBorderSum=0;RightBorderSum=0;
    for(i=center+1;i<right;i++)//從中線向右掃描
    {
        RightBorderSum+=List[i];
        if(RightBorderSum>MaxRightBorderSum)
            MaxRightBorderSum=RightBorderSum;
    }//右邊掃描結束
    //下面返回“治”的結果
    return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);

}
int MaxSubseqSum3(int List[],int N)
{//保持與前兩種算法相同的函數接口
    return DivideAndConquer(List,0,N-1);

}

int MaxSubseqSum4(int List[],int N)
{
    int i;
    int ThisSum,MaxSum;

    ThisSum=MaxSum=0;
    for(i=0;i<N;i++)
    {
        ThisSum+=List[i];
        if(ThisSum>MaxSum)
            MaxSum=ThisSum;
        else if(ThisSum<0)
            ThisSum=0;
    }
    return MaxSum;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章