資料結構與演算法分析 讀書筆記(連結串列 棧 佇列)

myxs發表於2017-04-11

資料結構與演算法分析 讀書筆記(連結串列 棧 佇列)

標籤: Data-Structures


不直接寫連結串列,棧,佇列的實現,直接把做課後題目以及想到的或遇到的問題實現了一下,只給出關鍵程式碼

1 逆轉單連結串列

這裡採用從第二個元素到最後一個元素逐個插入到頭節點和第一個節點之間的方式。習題給出的參考程式碼用的是3個指標,改變當前元素的指標值指向前一個後,向後移動,直到最後一個元素。

void ReverseList(List L){
    Position P,Q,R;
    P = L->Next;//第一個元素
    Q = P->Next;//第二個元素
    while(Q){
        R = Q->Next;//第三個元素開始,暫時儲存
        Q->Next = L->Next;//第二個元素插入到頭節點和第一個元素之間
        L->Next = Q;
        Q = R;//指標移動到下一個元素,即第三個元素
    }
    P->Next = NULL;
}

2 通過調整指標的方式交換某兩個相鄰的元素

  • 單連結串列

    //交換P和AfterP
    void SwapWithNext( Position BeforeP, LinkList L ) {
        Position P, AfterP;
        P = BeforeP->Next;
        AfterP = P->Next;
        P->Next = AfterP->Next;
        BeforeP->Next = AfterP;
        AfterP->Next = P;
    }
    
  • 雙向連結串列

    void SwapWithNext( Position P, List L ){
        Position BeforeP, AfterP;
        BeforeP = P->Prev;
        AfterP = P->Next;
    
    
    
    P->Next = AfterP->Next;
    BeforeP->Next = AfterP;
    AfterP->Next = P;
    P->Next->Prev = P;
    P->Prev = AfterP;
    AfterP->Prev = BeforeP;
    
    }

3 已排序的2個連結串列,求它們的交集

void intersection(LinkList L,LinkList P){
    Position T1,T2;
    T1 = L->Next;
    T2 = P->Next;

    while(T1 != NULL && T2 != NULL){
        if (T1->Elem < T2->Elem){
            T1 = T1->Next;
        }else if(T1->Elem > T2->Elem){
            T2 = T2->Next;
        }else{
            printf("%d ",T1->Elem);
            T1 = T1->Next;
            T2 = T2->Next;
        }
    }
}

4 已排序的2個連結串列,求並集

LinkList Union(LinkList L,LinkList P){
    Position T1,T2;
    int X;
    LinkList R;
    R = malloc(sizeof(struct Node));
    R ->Next = NULL;
    T1 = L->Next;
    T2 = P->Next;

    while(T1 != NULL && T2 != NULL){
        if(T1->Elem < T2->Elem){
            X = T1->Elem;
            T1 = T1->Next;
        }else if(T1->Elem > T2->Elem){
            X = T2->Elem;
            T2 = T2->Next;
        }else{
            X = T1->Elem;
            T1 = T1->Next;
            T2 = T2->Next;
        }
        insert(X,R);
    }
    if (T1 != NULL){
        while(T1){
            insert(T1->Elem,R);
            T1 = T1->Next;
        }
    }
    if (T2 != NULL){
        while(T2){
            insert(T2->Elem,R);
            T2 = T2->Next;
        }
    }
    return R;
}

5 多項式加法

List add(List L1, List L2){
    Position P1,P2;
    int E,C;
    P1 = L1->Next;
    P2 = L2->Next;
    List result;
    result = malloc(sizeof(struct Node));
    result ->Next = NULL;

    while(P1 != NULL && P2!= NULL){
        if(P1->Exp < P2->Exp){
            C = P2->Cof;
            E = P2->Exp;
            P2 = P2->Next;
        }else if(P1->Exp > P2->Exp){
            C = P1->Cof;
            E = P1->Exp;
            P1 = P1->Next;
        }else{
            C = P1->Cof+P2->Cof;
            E = P2->Exp;
            P1 = P1->Next;
            P2 = P2->Next;
         }
        insert(C,E,result);
    }
    if(P1!=NULL){
        while(P1){
            insert(P1->Cof,P1->Exp,result);
                P1 = P1->Next;
        }
    }
    if(P2!=NULL){
        while(P2){
            insert(P2->Cof,P2->Exp,result);
                P2 = P2->Next;
        }
    }
    return result;
}

6 多項式乘法

有不同的解法

  • 暴力的話時間複雜度很高(M^2 N^2)。2個連結串列的每個項相乘,需要MN,而得到相乘結果後需要在一個連結串列中搜尋,最壞情況也是MN
  • 將每一項與一個多項式相乘,並通過維持一個指向當前結果連結串列位置的指標把結果插入進來,時間複雜度為 (M^2 N)
  • O(MN log(MN))時間複雜度:計算所有的MN結果,藉助排序演算法按照指數排序併合並

給出一個自己的完整實現,其中在指定位置後插入元素的函式未放出來。參考了別人,地址在這裡

    List mulp(List L1,List L2){
        Position P1,P2,P3;
        int cof,exp;
        P1 = L1->Next;
        P2 = L2->Next;
        List Result;//結果連結串列
        Result = malloc(sizeof(struct Node));
        Result ->Next = NULL;

        for (; P1; P1 = P1->Next){
            P3 = Result;
            for (; P2; P2 = P2->Next) {
                cof = P1->Cof * P2->Cof;//係數相乘
                exp = P1->Exp + P2->Exp;//指數相加
                //在結果連結串列中查詢某個位置,滿足指數的遞減規律,即介於2者之間
                while(P3->Next != NULL && P3->Next->Exp > exp){
                    P3 = P3->Next;
                }
                if (P3->Next==NULL || P3->Next->Exp < exp)
                    insertforPoly(cof,exp,Result,P3);
                else if(P3->Next->Exp == exp)
                    P3->Next->Cof += cof;
                P3 = P3->Next;
            }
            P2 = L2->Next;//P2迴圈完後重新指回L2
        }
        return Result;
    }

7 約瑟夫環問題

寫約瑟夫環時,不小心判斷錯了,開始寫的先是判斷下一個節點是否到達頭結點,但是在處理M=0時,遇到問題。注意,先判斷是否count已經為M,是則刪除當前節點,並指向下一個節點從新開始計數;否則指標移動,注意,移動時需要判斷是否為頭結點,是則跳過。

Position delete(int E, List L){
    Position P,Pre,Tmp,Next;
    P = L->Next;
    Pre = FindPrevious(E,L);
    if (!IsLast(P,L) ){
        Tmp = Pre->Next;//刪除Tmp節點
        printf("delete %d \n",Tmp->Elem);
        Pre->Next = Tmp->Next;
        free(Tmp);
    }
    Next = Pre->Next;
    if (Next==L)
        Next = Next->Next;
    return Next;
}

List Josephus(List L){
    int count;
    Position P,Next;
    P = L->Next;
    count = 0;
    while(P->Next->Next != P){
        if(count == M){//滿足刪除節點條件,則刪除
            Next = delete(P->Elem,L);//刪除當前節點,返回下一個節點
            P = Next;//指向下一個節點
            count = 0;//重置count
        }
        else{//不滿足刪除節點條件,則指標向後移動,並計算count值。
            if (P->Next == L)
                P = P->Next->Next;
            else
                P = P->Next;
            count++;
        }
    }
    return L;//返回刪除節點後的連結串列
}

8 檢測平衡符號

原題給的是 Pascal語言(begin/end, (), {}, []) 筆者實現的省去了begin/end的檢測,C語言的比較沒有Java的方便,可以自行實現

Stack S;
char ch[N]={'[','{','}','(','a','b','c',')'};
S = CreateStack(20);
for (int i = 0; i < N; ++i) {
    char c = ch[i];
    if (c=='(' || c=='[' || c=='{')
        Push(S,c);
    if(c == ')' || c==']'|| c=='}'){
        if(!match(c, S)){
            printf("error: %c\n",c);
            break;
        }
    }
}
if (!IsEmpty(S))
    printf("error");
else
    printf("success");


int match(char c,Stack S){//判斷是否匹配
    char s = Pop(S);
    if (c==')')
        return s=='(';
    else if(c==']')
        return s=='[';
    else if (c=='}')
        return s=='{';
}

9 計算字尾表示式

Stack S;
char ch[N]={'6','5','2','3','+','8','*','+','3','+','*'};
S = CreateStack(50);
for (int i = 0; i < N; ++i) {
    char c = ch[i];
    int result = IsDight(c);
    if (result)//當前讀取的字元是數字,入棧
        Push(S,c-'0');
    else{//出棧2個數,計算過結果併入棧
        int num1,num2;
        num1 = Pop(S);
        num2 = Pop(S);
        switch(c){
            case '+':
                Push(S,num2+num1);
                break;
            case '-':
                Push(S,num2-num1);
                break;
            case '*':
                Push(S,num2*num1);
                break;
            case '/':
                Push(S,num2/num1);
                break;
            default:
                break;
        }
    }
}
printf("result: %d ",Pop(S));


int IsDight(char c){
    int num = c-'0';
    if (num>=0 && num<=9)
        return 1;
    else
        return 0;
}

10 中綴到字尾的轉換

關鍵在於處理括號,以及操作符的優先順序。 當前讀取操作符優先順序低於或等於棧頂操作符的優先順序時,需要出棧直到遇到一個優先順序比它低的元素。 在處理 '('時,優先順序最高 ')',出棧直到'(',注意括號不輸出 筆者沒有實現冪操作符以及從字尾轉字首,有興趣的可以自行實現。

//實現過程中呼叫的函式
int IsDight(char c){
    int result = c-'0';
    if (result>=0 && result<=9)
        return 1;
    return 0;
}

int higherorequal(char c, char t){
    if ((c== '*' || c =='/') && (t=='+'|| t=='-' || t== '*' || t=='/')){//讀入* / 棧頂為+/-/*/ /,優先順序高或相同
        return 1;
    }
    if((c== '+' || c =='+') && (t=='+'|| t=='-'))//優先順序相同
        return 1;
    return 0;
}


//main函式
char ch[15] = {'1','+','2','*','3','+','(','4','*','5','+','6',')','*','7'};//中綴 1 + 2 * 3 + ( 4 * 5 + 6 ) * 7
//字尾 1 2 3 * + 4 5 * 6 + 7 * +
Stack S;
S = CreateStack(20);
for (int i = 0; i < 15; ++i) {
    char c = ch[i];
    if (IsDight(c)){//數字,入棧
        printf("%c ",c);
    }else{//操作符
        if (c =='+' || c  == '-' ){
            while (higherorequal(Top(S),c)){//判斷棧頂元素優先順序和讀入的操作符的優先順序,棧中優先順序高或相同時,出棧直到優先順序低於當前讀取的操作符優先順序
                printf("%c ",Pop(S));
            }
            //棧中彈出優先順序高或相等的操作符後,再把當前操作符入棧
            Push(S,c);
        }
        if (c=='*' || c=='/'){
            if (Top(S)=='('){//如果棧頂是 '(' 優先順序最高,不彈出,繼續入棧
                Push(S,c);
            }else if(higherorequal(c,Top(S))) {//棧頂不是 '(',直接入棧
                Push(S, c);
            }
        }
        if (c=='('){
            Push(S,c);
        }
        if ( c==')'){//彈出操作符直到 '(',注意處理符號,操作符才輸出,括號不輸出
            char top = Top(S);
            while(top){
                if (top=='('){
                    Pop(S);
                    break;
                }
                printf("%c ",Pop(S));
                top = Top(S);
            }
        }
    }
}
while(!IsEmpty(S))
    printf("%c ",Pop(S));

11 佇列的實現

佇列的陣列實現比較簡單,這裡給出的是連結串列。筆者在實現的時候,出現了許多錯誤,沒有決定要不要採用頭結點,後來選擇沒有頭結點。 元素入隊時,先malloc一個節點,作為下一次插入,而當前元素放在Rear節點中,然後Rear指標移動。 注意要統一方式,先建一個未儲存元素的節點,如建立佇列時的Front和Rear指標都指向一個節點;入隊時就能採用新建節點,加入元素,Rear指標移動。

標頭檔案 queue.h

typedef struct QueueNode * Node;
typedef Node Position;
typedef struct QueueRecord *Queue;

struct QueueNode{
    int Elem;
    struct QueueNode * Next;
};

struct QueueRecord{
    Position Front, Rear;
    int Size;
};

實現

Queue CreateQueue(){
    Queue Q;
    Q = malloc(sizeof(struct QueueRecord));
    Q->Rear = malloc(sizeof(struct QueueNode));
    Q->Front= Q->Rear ;
    Q->Rear->Next = NULL;
    Q->Size = 0;
    return Q;
}

void Enqueue(int X, Queue Q){
    Node P;
    P = malloc(sizeof(struct QueueNode));
    P->Next = NULL;
    Q->Rear->Elem = X;
    Q->Rear->Next = P;
    Q->Rear= P;
    Q->Size+1;
  }

int Dequeue(Queue Q){
    int result;
    Node T;
    T = Q->Front->Next;
    result = Q->Front->Elem;
    free(Q->Front);
    Q->Front = T;
    return result;
}
int main() {
    Queue Q;
    Q = CreateQueue();
    for (int i = 0; i < 5; ++i) {
        Enqueue(i+1,Q);
    }
    for (int j = 0; j < 5; ++j) {
        printf("%d ",Dequeue(Q));
    }
    return 0;
} 

相關文章