文章標題

算法:解決特定問題求解步驟的描述
好的算法:時間效率高和存儲量低

判斷一個算法的效率時,函數中的常數和其他次要項常常可以忽略,而更應該關注最高階項的階數:輸入規模越大,次要項影響越來越小

算法事件複雜度:隨着問題規模n的增大,算法執行時間的增長率和f(n)的增長率相同;O(n) ;(n就是程序語句執行多少次)

對數階:2的x次方等n,x=O(logn)

int count = 1;
while(count < n)
    count = count * 2;

順序表,在存、讀數據時,不管哪個位置,時間複雜度都是O(1);而插入或刪除時,時間複雜度都是O(n)

鏈表的每個節點中只包含一個指針域:單鏈表

頭指針:鏈表指向第一個節點的指針,若鏈表有頭節點,則是指向頭節點的指針;無論鏈表是否爲空,頭指針均不爲空;頭指針是鏈表的必要元素
頭結點:不一定是鏈表的必須要素;爲了操作的統一和方便而設立的

typedef struct Node{
    ElemType data;
    struct Node* next;
}Node;
typedef  struct Node*  LinkList;

單鏈表的讀取(第i個數據)

鏈表插入:注意要重新申請一個節點的內存空間
鏈表刪除:主要要釋放刪除節點的內存

頭插法:p =(LinkList*)malloc(sizeof(Node)); p -> next = head->next;
head -> next = p;

尾插法: p = (LinkList*) malloc(sizeof(Node)); head -> next = p; head = p;

整表刪除:p = head->next; while(p) { q = p->next; free(p); p = q;}

單鏈表和順序表

  • 存儲方式:用一段連續的存儲單元依次存儲線性表的數據元素;單鏈表用一組任意的存儲單元存放線性表的元素
  • 時間性能:查找:O(1)、O(n);插入和刪除:O(n)、O(1)
  • 空間性能:順序存儲結構需要預分配空間

靜態鏈表:用數組描述的鏈表;數組的元素由兩個數據域組成:data存放數據元素,cur存放該元素的後繼在數組中的下表

#define MAX_SIZE 1000
typedef struct
{
    ElemType data;
    int cur;    
}Component,StaticLinkList[MAXSIZE];

數組第一個元素,即下標爲0的元素的cur就存放數組空閒空間的第一個元素的下標
數組最後一個元素的cur存放第一個有數值的元素的下表(相當於頭結點)

循環鏈表:將單鏈表中終端節點指針由空指針改爲指向頭節點,就使整個單鏈表形成一個環,這種頭尾銜接的單鏈表稱爲循環鏈表

循環鏈表和單鏈表的主要差異就在於循環的判斷條件上,原來是判斷p->next 是否爲空,現在則是p->next不等於頭結點,則循環未結束

單鏈表有了頭節點,我們可以用O(1)的時間訪問第一個節點,但對於要訪問最後一個節點,卻需要O(n)時間

我們用指向終端結點的尾指針來表示循環鏈表,此時查找開始節點和終端節點都很方便了:終端節點用read指針指示,查找終端節點:rear;查找開始節點rear->next->next

將兩個鏈表合併成一個表時,有了尾指針也非常簡單:已知rearA和rearB

p = rearA->next;
rearA ->next = rearB->next->next;
rearB->next = p;
free(rearB->next);

雙向鏈表:在單鏈表的每個節點中,再設置一個指向其前驅節點的指針域

單鏈表根據next指針要查找上一節點的話,最壞的時間複雜度就是O(n),因爲每次都要從頭開始遍歷查找

插入操作:將s節點插入到節點p和p->next之間

s->prior = p;
s->next = p->next;
p->next ->prior = s;
p->next  = s;

刪除操作:刪除節點p

p->prior->next = p->next;
p->next->prior = p->prior;

:限定僅在表尾進行插入和刪除操作的線性表;棧頂和棧底;後進先出

棧的順序存儲結構(需要事先確定數組存儲空間大小)

  • 數組下標爲0的一端作爲棧底;
  • top棧頂元素在數組中的位置,top必須小於StackSize
  • 空棧:top = -1

    typedef int SElemType;
    typedef struct
    {
        SElemType data[MAXSIZE];
        int top;  //用於棧頂指針
    }SqStack;
    
    
    Status Push(SqStack* s,SElemType e)
    {
        if(s->top == MAXSIZE-1)
            return ERROR;
        s->top++;
        s->data[s->top]  = e;
        return OK;
    }
    
    Status Pop(SqStack* s, SElemType e)
    {
        if(s->top == -1)
            return ERROR;
        e = s->data[s->top];
        s->top--;
        return OK;
    }
    

棧的鏈式存儲結構:棧頂指針就是指向鏈表的頭部(第一個節點),對於鏈棧來說,是不需要頭節點的。

typedef struct StackNode
{
    ElemType data;
    struct StackNode* next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
    LinkStackPtr top;
    int count;
}LinkStack;(鏈棧)

Status Push(LinkStack* s,SElemType e)
{
    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    s->data = e;
    s->next = s->top;
    s->top = s;
    s->count++;
    return OK;
}

Status Pop(LinkStack* s,SElemType* e)
{
    LinkStackPtr p;
    if(StackEmpty(*s))
        return ERROR;
    *e = s->top->data;
    p = s->top;
    s->top = s->top->next;
    free(p);
    s->count--;
    return OK;
}

如果棧的使用過程元素變化不可預料,有時大有時小,那麼最好用鏈棧;反之,棧的大小變化範圍在可控範圍內,建議使用順序棧

棧的應用–遞歸:在前行階段,對於每一層遞歸,函數的局部變量、參數值以及返回地址都被壓入棧中,在退回階段,位於棧頂的局部變量、參數值和返回地址被彈出,恢復調用的狀態

棧的應用–四則運算表達式

  • 後綴表達式:所有的符號都是在要運算數字的後面出現
    • “9+(3-1)X3+10/2”:“931-3*+102/+”
  • 規則:從左到右遍歷表達式的每個數字和符號,遇到數字就進棧,遇到符號,就將處於棧頂兩個數字出棧,進行運算,運算結果進棧,已知到最終獲得結果

中綴表達式轉後綴表達式:從左到右遍歷中綴表達式的每個數字和符號,若是數字就輸出,即稱爲後綴表達式的一部分;若是符號,則判斷其與棧頂符號的優先級,是右括號或優先級低於棧頂符號,則棧頂元素依次出棧並輸出

隊列:先進先出;只允許在一端進行插入操作,而在另一端進行刪除操作的線性表

爲了避免當只有一個元素時,隊頭和隊尾重合使處理變得麻煩,引入兩個指針,front指針指向隊頭元素,rear指針指向隊尾元素的下一個位置;當front等於rear時,空隊列

循環隊列:隊列的頭尾相接的順序存儲結構

typedef struct
{
    QElemType data[MAXSIZE];
    int front;
    int rear;   
}SqQueue;

隊列的鏈式存儲結構:其實就是單鏈表,只不過是尾進頭出

  • 隊頭指針指向鏈隊列的頭結點,隊尾指針指向終端節點

    typedef struct QNode
    {
        QElemType data;
        struct QNode* next;
    }QNode,*QueuePtr;
    

    typedef struct
    {
    QueuePtr front, rear;
    }LinkQueue;

:由零個或多個字符組成的有限序列

查找子串位置

int Index(String s,String t,int pos)
{
    int n = strlen(s);
    int m = strlen(t);  
    int i = pos;
    String sub; 
    while(i <= n-m+1)
    {
        SubStr(sub,s,i,m);
        if(StrCompare(sub,t) != 0)
        {
            ++i;            
        }       
        else
        {
            return i;           
        }       
    }   
    return 0;   
}

串的模式匹配

從主串“goodgoogle”中找到”google”這個子串的位置:對主串的每一個字符作爲子串的開頭,與要匹配的字符串進行匹配

int Index(String s,String t,int pos)
{
    int n = strlen(s);
    int m = strlen(t);
    int i = pos;
    int j = 0;  
    while(i < n && j < m)
    {
        if(s[i] == t[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i-j+1; 
            j = 0;  
        }   
    }   
    if(j>m)
        return i-m;
    else
        return 0;   
}

:n個節點的有限集;n=0時稱爲空樹;

在任意一顆非空樹中:

  • 有且僅有一個特定的稱爲根的節點
  • 當n>1時,其餘節點可分爲m個互不相交的有限集T1, T2, T3…..Tm,其中每一個集合本身又是一棵樹,並稱爲根的子樹

  • 節點擁有的子樹數稱爲節點的度;度爲0的節點稱爲葉節點;樹的度就是樹內各節點的度的最大值

  • 節點的祖先是從根到該節點所經分支上的所有節點。
  • 樹中節點的最大層次稱爲樹的深度或高度

如果將樹中節點的各子樹看成從左到右是有次序的,不能互換的,則稱該樹爲有序樹

雙親表示法:每個節點:data(數據域)和parent(指針域:存儲該節點的雙親在數組中的下標)

孩子表示法:每個節點有多個指針域,其中每個指針指向一顆子樹的根節點

孩子兄弟表示法:設置兩個指針:分別指向該節點的第一個孩子和此節點的右兄弟

二叉樹:n個結點的有限集合,該集合或者爲空集,或者由一個根節點和兩顆互不相交的、分別稱爲根節點的左子樹和右子樹的二叉樹組成

  • 每個結點最多有兩顆子樹,二叉樹不存在度大於2的結點
  • 左子樹和右子樹是有順序的,次序不能顛倒

斜樹:所有的結點都只有左子樹的二叉樹叫左斜樹;所有的結點只有右子樹的二叉樹叫右斜樹

滿二叉樹:在一棵二叉樹中,如果所有分支結點都存在左子樹和右子樹,並且所有葉子都在同一層上

完全二叉樹:對一棵具有n個結點的二叉樹按層序編號,如果編號爲i(1<=i<=n)的結點與同樣深度的滿二叉樹編號爲i的結點在二叉樹中位置完全相同

滿二叉樹一定是一棵完全二叉樹,但完全二叉樹不一定是滿的

二叉樹的性質

  • 在二叉樹的第i層上至多有2的i-1次方個結點
  • 深度爲k的二叉樹至多有2的k次方-1個結點
  • 對任何一顆二叉樹T,如果其終端節點數爲N0,度爲2的節點數N2,N0 = N2+1
  • 具有n個結點的完全二叉樹的深度爲[log2n]+1([x]表示不大於x的最大整數)

二叉樹的順序存儲結構

  • 順序存儲結構一般只用於完全二叉樹

二叉樹的鏈式存儲結構

data、lchild和rchild

typedef struct BiTNode
{
    TElemType data;
    struct BiTNode* lchild,*rchild; 
}BiTNode,*BiTree;

二叉樹的遍歷:從根節點出發,按照某種次序依次訪問二叉樹中所有節點,使得每個結點被訪問一次且僅被訪問一次

  • 前序遍歷:若二叉樹爲空,則空操作返回,否則先訪問根節點,然後前序遍歷左子樹,再前序遍歷右子樹
  • 中序遍歷:若二叉樹爲空,則空操作返回,否則從根節點開始(注意不是先訪問根節點),中序遍歷根節點的左子樹,然後是訪問根節點,最後中序遍歷右子樹;
  • 後序遍歷:若二叉樹爲空,則空操作返回;從左到右先葉子後節點的方式遍歷左右子樹,最後是訪問根節點

前序遍歷算法:

void PreOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    printf("%c\n",T->data);
    PreOrderTraverse(T->lchild);
    PreOrderTraverse(T->rchild);    
}

中序遍歷算法:

void InOrderTraverse(BiTree T)
{
    if(T==UNLL)
        return NULL;
    InOrderTraverse(T->lchild);
    printf("%c",T->data);
    InOrderTraverse(T->rchild); 
}

後序遍歷算法

void PostOrderTraverse(BiTree T)
{
    if(T==NULL)
        return;
    PostOrderTraverse(T->lchild);
    PostOrderTraverse(T->rchild);
    printf("%c",T->data);   
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章