(考研)數據結構中的重難點算法

轉載自原文博客,感謝原博主~ https://blog.csdn.net/sinat_33871437/article/details/51425241
本文會把複習中所遇到的所有算法記錄並分析,以供以後查閱,【並在原博主基礎上進行了修正和補充】

KMP



KMP算法在考研中主要考察Next NextVal數組

KMP算法的名字是三個發明者名字所命名的,沒有別的特殊含義
KMP類似動態規劃思想,利用已知信息,讓已經計算過的值或者無意義的值不再計算

KMP的思路是,創建一個字典保存模板串中共有元素長度

A B C D A B D中,只有倒數第三個值開始,A B與開頭匹配
則保存字典值爲0 0 0 0 1 2 0

最終的計算方法爲:移動位數 = 已匹配的字符數 - 對應的部分匹配值

void makeNext(const char P[],int next[])
{
    int q,k;//q:模版字符串下標;k:最大前後綴長度
    int m = strlen(P);//模版字符串長度
    next[0] = 0;//模版字符串的第一個字符的最大前後綴長度爲0

    ///for循環,從第二個字符開始,依次計算每一個字符對應的next值
    for (q = 1,k = 0; q < ml; ++q){
        while(k > 0 && P[q] != P[k])
            k = next[k-1];
        if (P[q] == P[k]){
            k++;
        }
        next[q] = k;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面爲代碼,較難理解部分爲while部分,實際上,當匹配共有元素時,假如我們匹配到A B C D A B C D時,最後一個D的值不匹配(P[q] != P[k]),則查找之前匹配值是否包含此,如模板開頭爲A B C D A B C E,當匹配到E時候與D不匹配,則返回查找之前是否有存在其它局部匹配,如模板開頭的ABCD,可能會不理解爲什麼是k=next(k-1),next保存的是匹配長度,則查找E上個字符C的匹配長度,也就是C的next的值代表開頭從0至此值長度的字符串是匹配的,則就跳到此值長度字符串的下一個字符進行匹配,因爲我們是從0開始下標,所以不用加1

A B C D A B C D
A B C D A B C E
    A B C D A B C E

二叉樹


二叉樹的性質
1、在二叉樹的第 i 層上至多有 2i+1(i1)2i+1(i≥1)

1.如果 i=1i=1

代碼實現

c版本

//二叉樹結點
typedef struct BiTNode{
    //數據
    char data;
    //左右孩子指針
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

c# 版本(面向對象)

public class BinNode
{
    public int Element;
    public BinNode Left;
    public BinNode Right;
    public BinNode(int element, BinNode left, BinNode right)
    {
        this.Element = element;
        this.Left = left;
        this.Right = right;
    }

    public bool IsLeaf()
    {
        return this.Left == null && this.Right == null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

遍歷方法

遍歷遞歸版本

//先序遍歷  
void PreOrder(BiTree T){  
    if(T != NULL){  
        //訪問根節點  即是訪問T本身
        Visit(T);  
        //訪問左子結點  
        PreOrder(T->lchild);  
        //訪問右子結點  
        PreOrder(T->rchild);  
    }  
}  
//中序遍歷  
void InOrder(BiTree T){  
    if(T != NULL){  
        //訪問左子結點  
        InOrder(T->lchild);  
        //訪問根節點  
        Visit(T);  
        //訪問右子結點  
        InOrder(T->rchild);  
    }  
}  
//後序遍歷  
void PostOrder(BiTree T){  
    if(T != NULL){  
        //訪問左子結點  
        PostOrder(T->lchild);  
        //訪問右子結點  
        PostOrder(T->rchild);  
        //訪問根節點  
        Visit(T);  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

遍歷非遞歸版本

/* 先序遍歷(非遞歸)
   思路:訪問T->data後,將T入棧,遍歷左子樹;遍歷完左子樹返回時,棧頂元素應爲T,出棧,再先序遍歷T的右子樹。
*/
void PreOrder2(BiTree T){
    stack<BiTree> stack;
    //p是遍歷指針
    BiTree p = T;
    //棧不空或者p不空時循環
    while(p || !stack.empty()){
        if(p != NULL){
            //存入棧中
            stack.push(p);
            //訪問根節點
            printf("%c ",p->data);
            //遍歷左子樹
            p = p->lchild;
        }
        else{
            //退棧
            p = stack.top();
            stack.pop();
            //訪問右子樹
            p = p->rchild;
        }
    }//while
}

void InOrder2(BiTree T){
    stack<BiTree> stack;
    //p是遍歷指針
    BiTree p = T;
    //棧不空或者p不空時循環
    while(p || !stack.empty()){
        if(p != NULL){
            //存入棧中
            stack.push(p);
            //遍歷左子樹
            p = p->lchild;
        }
        else{
            //退棧,訪問根節點
            p = stack.top();
            printf("%c ",p->data);
            stack.pop();
            //訪問右子樹
            p = p->rchild;
        }
    }//while
}

//後序遍歷(非遞歸)
typedef struct BiTNodePost{
    BiTree biTree;
    char tag;
}BiTNodePost,*BiTreePost;

void PostOrder2(BiTree T){
    stack<BiTreePost> stack;
    //p是遍歷指針
    BiTree p = T;
    BiTreePost BT;
    //棧不空或者p不空時循環
    while(p != NULL || !stack.empty()){
        //遍歷左子樹
        while(p != NULL){
            BT = (BiTreePost)malloc(sizeof(BiTNodePost));
            BT->biTree = p;
            //訪問過左子樹
            BT->tag = 'L';
            stack.push(BT);
            p = p->lchild;
        }
        //左右子樹訪問完畢訪問根節點
        while(!stack.empty() && (stack.top())->tag == 'R'){
            BT = stack.top();
            //退棧
            stack.pop();
            BT->biTree;
            printf("%c ",BT->biTree->data);
        }
        //遍歷右子樹
        if(!stack.empty()){
            BT = stack.top();
            //訪問過右子樹
            BT->tag = 'R';
            p = BT->biTree;
            p = p->rchild;
        }
    }//while
}

//層次遍歷
void LevelOrder(BiTree T){
    BiTree p = T;
    //隊列
    queue<BiTree> queue;
    //根節點入隊
    queue.push(p);
    //隊列不空循環
    while(!queue.empty()){
        //對頭元素出隊
        p = queue.front();
        //訪問p指向的結點
        printf("%c ",p->data);
        //退出隊列
        queue.pop();
        //左子樹不空,將左子樹入隊
        if(p->lchild != NULL){
            queue.push(p->lchild);
        }
        //右子樹不空,將右子樹入隊
        if(p->rchild != NULL){
            queue.push(p->rchild);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

前序遍歷是深度優先遍歷的一種
但二叉樹深度優先遍歷還包括中序遍歷、後續遍歷
兩者並不是一個概念

哈夫曼樹


路徑: 樹中一個結點到另一個結點之間的分支構成這兩個結點之間的路徑
路徑長度:路徑上的分枝數目稱作路徑長度。
樹的路徑長度:從樹根到每一個結點的路徑長度之和
結點的帶權路徑長度:在一棵樹中,如果其結點上附帶有一個權值,通常把該結點的路徑長度與該結點上的權值之積稱爲該結點的帶權路徑長度(weighted path length)
樹的帶權路徑長度:如果樹中每個葉子上都帶有一個權值,則把樹中所有葉子的帶權路徑長度之和稱爲樹的帶權路徑長度

帶權路徑長度最小的二叉樹就稱爲哈夫曼樹或最優二叉樹

步驟

img

應用

在電文傳輸中,需要將電文中出現的每個字符進行二進制編碼。在設計編碼時需要遵守兩個原則:
(1)發送方傳輸的二進制編碼,到接收方解碼後必須具有唯一性,即解碼結果與發送方發送的電文完全一樣;
(2)發送的二進制編碼儘可能地短。下面我們介紹兩種編碼的方式。

  1. 等長編碼
    這種編碼方式的特點是每個字符的編碼長度相同(編碼長度就是每個編碼所含的二進制位數)。假設字符集只含有4個字符A,B,C,D,用二進制兩位表示的編碼分別爲00,01,10,11。若現在有一段電文爲:ABACCDA,則應發送二進制序列:00010010101100,總長度爲14位。當接收方接收到這段電文後,將按兩位一段進行譯碼。這種編碼的特點是譯碼簡單且具有唯一性,但編碼長度並不是最短的。

  2. 不等長編碼
    在傳送電文時,爲了使其二進制位數儘可能地少,可以將每個字符的編碼設計爲不等長的,使用頻度較高的字符分配一個相對比較短的編碼,使用頻度較低的字符分配一個比較長的編碼。例如,可以爲A,B,C,D四個字符分別分配0,00,1,01,並可將上述電文用二進制序列:000011010發送,其長度只有9個二進制位,但隨之帶來了一個問題,接收方接到這段電文後無法進行譯碼,因爲無法斷定前面4個0是4個A,1個B、2個A,還是2個B,即譯碼不唯一,因此這種編碼方法不可使用。

因此,爲了設計長短不等的編碼,以便減少電文的總長,還必須考慮編碼的唯一性,即在建立不等長編碼時必須使任何一個字符的編碼都不是另一個字符的前綴,這宗編碼稱爲前綴編碼(prefix code)

(1)利用字符集中每個字符的使用頻率作爲權值構造一個哈夫曼樹;
(2)從根結點開始,爲到每個葉子結點路徑上的左分支賦予0,右分支賦予1,並從根到葉子方向形成該葉子結點的編碼

AVL&RB Tree


AVL樹又稱高度平衡的二叉搜索樹
紅黑樹是一種很有意思的平衡檢索樹,它的統計性能要好於平衡二叉樹
紅黑樹和AVL樹的區別在於它使用顏色來標識結點的高度,它所追求的是局部平衡而不是AVL樹中的非常嚴格的平衡
C++ STL中,很多部分(目前包括 set, multiset, map, multimap)應用了紅黑樹的變體(SGI STL中的紅黑樹有一些變化,這些修改提供了更好的性能,以及對set操作的支持)
紅黑樹是真正的變態級數據結構


// TODO

二叉搜索樹

二叉排序樹(Binary Sort Tree)又稱二叉查找樹(Binary Search Tree),亦稱二叉搜索樹

12n(n1)12n(n−1) 條弧的有向圖成爲有向完全圖

Prim算法

1、輸入:一個加權連通圖,其中頂點集合爲V,邊集合爲E;
2、初始化:Vnew = {x},其中x爲集合V中的任一節點(起始點),Enew = {},爲空;
3、重複下列操作,直到Vnew = V:

(1). 在集合E中選取權值最小的邊 < u, v >,其中u爲集合Vnew中的元素,而v不在Vnew集合當中,並且v∈V(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);
(2). 將v加入集合Vnew中,將< u, v >邊加入集合Enew中;

4、 輸出:使用集合Vnew和Enew來描述所得到的最小生成樹。

複雜度:鄰接矩陣:O(v2) 鄰接表:O(elog2v)

#include<algorithm>
#define INF INT_MAX
#define MAX_V 1000
int cost[MAX_V][MAX_V];
int min_cost[MAX_V];
bool vis[MAX_V];
//vert表示的頂點的個數
int prim(int vert)
{
    fill(vis,vis+vert,false);
    fill(min_cost,min_cost+vert,INF);
    min_cost[0] = 0 ;
    int res = 0 ;
    while(true)
    {
        int v = -1 ;
        for(int i=0;i!=vert;++i)
            if(!vis[i] && (v==-1 || min_cost[i] < min_cost[v]))
                v = i ;
        if(v==-1)
            break;
        res +=min_cost[v];
        vis[v] = true;
        for(int i=0;i!=vert;++i)
            //此處的寫法只是一種簡便的寫法,對於已加入vis[i]=true的頂點
            //即使更新到它也不會出錯,請注意上面循環中的!vis[i]這個條件
            min_cost[i] = min(min_cost[i],cost[v][i]);
    }
    return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

Kruskal算法

1、記Graph中有v個頂點,e個邊
2、新建圖Graphnew,Graphnew中擁有原圖中相同的e個頂點,但沒有邊
3、將原圖Graph中所有e個邊按權值從小到大排序
4、循環:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中
如果 這條邊連接的兩個節點於圖Graphnew中不在同一個連通分量中
則 添加這條邊到圖Graphnew中

#include<iostream>
#include<algorithm>
#define MAX_E 10000
#define MAX_V 110
using namespace std;
struct Edge
{
    int from,to,cost;
    void set(int f,int t,int c)
    {
        from = f , to = t , cost = c ;
    }
    bool operator<(const Edge& t) const
    {
        return cost < t.cost;
    }
}e[MAX_E];
int f[MAX_V];
int Find(int x)
{
    return x==f[x] ? x : f[x] = Find(f[x]) ;
}
bool Union(int x,int y)
{
    int fx = Find(x),fy = Find(y);
    if(fx==fy)
        return false;
    f[fx] = fy ;
    return true;
}
//vert表示的是頂點的個數
inline void init_find_union(int vert)
{
    for(int i=0;i!=vert;++i)
        f[i] = i ;
}
//vert:頂點的個數
//e_num:邊的條數
int Kruskal(int vert,int e_num)
{
    int cnt = vert - 1 ,res = 0 ;
    init_find_union(vert);
    sort(e,e+e_num);
    for(int i=0;cnt && i!=e_num;++i)
        if(Union(e[i].from,e[i].to))
            res +=e[i].cost;
    return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
					<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-7f770a53f2.css" rel="stylesheet">
            </div>
								
				<script>
					(function(){
						function setArticleH(btnReadmore,posi){
							var winH = $(window).height();
							var articleBox = $("div.article_content");
							var artH = articleBox.height();
							if(artH > winH*posi){
								articleBox.css({
									'height':winH*posi+'px',
									'overflow':'hidden'
								})
								btnReadmore.click(function(){
									articleBox.removeAttr("style");
									$(this).parent().remove();
								})
							}else{
								btnReadmore.parent().remove();
							}
						}
						var btnReadmore = $("#btn-readmore");
						if(btnReadmore.length>0){
							if(currentUserName){
								setArticleH(btnReadmore,3);
							}else{
								setArticleH(btnReadmore,1.2);
							}
						}
					})()
				</script>
				</article>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章