1. 程式人生 > >線性表——連結串列、佇列、堆疊

線性表——連結串列、佇列、堆疊

主要內容

 連結串列

       連結串列相加/連結串列部分翻轉/連結串列去重

       連結串列劃分/連結串列公共結點

 佇列

        拓撲排序

        最短路徑條數

 堆疊

        括號是否匹配

        最長括號匹配

        計算逆波蘭表示式

        出棧入棧問題


 連結串列

  連結串列相加
  

給定兩個連結串列,分別表示兩個非負整數。它 們的數字逆序儲存在連結串列中,且每個結點只 儲存一個數字,計算兩個數的和,並且返回 和的連結串列頭指標。

 如:輸入:2→4→3、5→6→4,輸出:7→0→8

問題分析
   輸入: 2→4→3、5→6→4  

   輸出:7→0→8

   因為兩個數都是逆序儲存,正好可以從頭向 後依次相加,完成“兩個數的豎式計算”。

   注意考慮兩個數的位數不相同的情況。


typedef struct tagSNode
{
	int value;
    tagSNode* pNext;

    tagSNode(int v):value(v),pNext(NULL){};

}SNode;


SNode*add(SNode*pHead1,SNode*pHead2)
{
    SNode* pSum=new SNode(0);
    SNode* pTail=pSum;        //新結點插入到pTail的後面
    SNode*p1=pHead1->pNext;
    SNode*p2=pHead2->pNext;

    SNode*pCur;

    int carry=0;    //進位
    int value=0;

    while(p1&&p2)
    {
        value=(p1->value+p2->value+carry)%10;
        carry=(p1->value+p2->value+carry)/10;   
        
        pCur=new SNode(value);
        pTail->pNext=pCur;   //新結點連結到pTail的後面
        pTail=pCur;

        p1=p1->pNext;  //處理下一位
        p2=p2->pNext;

    }

    //處理較長的鏈
    SNode*p=p1? p1:p2;

    while(p)
    {
    	value=(p->value+carry)%10;
    	carry=(p->value+carry)/10;
         
        pCur=new SNode(value);
        pTail->pNext=pCur;
        pTail=pCur; 

        p=p->pNext;

    }
    //處理可能存在的進位   
    if(carry)
    {
    	pTail->pNext=new SNode(carry);
    }

    return pSum;
   
}

連結串列的部分翻轉
 

 給定一個連結串列,翻轉該連結串列從m到n的位置。 要求直接翻轉而非申請新空間。

 如:給定1→2→3→4→5,m=2,n=4,返回 1→4→3→2→5。

 假定給出的引數滿足:1≤m≤n≤連結串列長度。

 

分析
 空轉m-1次,找到第m-1個結點,即開始翻轉 的第一個結點的前驅,記做head;

 以head為起始結點遍歷n-m次,將第i次時, 將找到的結點插入到head的next中即可。

 即頭插法


typedef struct tagSNode
{
	int value;
    tagSNode* pNext;

    tagSNode(int v):value(v),pNext(NULL){};

}SNode;


void reverse(SNode*pHead,int from,int to)
{
   SNode* pCur=pHead->pNext;
   for (int i = 0; i < from-1; ++i)
   {
     pHead=pCur;
     pCur=pCur->pNext;
   }

   SNode*pPre=pCur;
   pCur=pCur->pNext;
   to--;

   SNode*pNext;
   for (int i = 0; i < to; ++i)
   {
     pNext=pCur->pNext;
     pCur->pNext=pHead->pNext;
     pHead->pNext=pCur;
     pPre->pNext=pNext;
     pCur=pNext;
   }


}

 排序連結串列中去重

 給定排序的連結串列,刪除重複元素,只保留重 復元素第一次出現的結點。

 如:  給定:2→3→3→5→7→8→8→8→9→9→10

             返回:2→3→5→7→8→9→10

問題分析
 若p->next的值和p的值相等,則將p->next>next賦值給p,刪除p->next;重複上述過 程,直至連結串列尾端。

排序連結串列中去重2
 

 若題目變成:若發現重複元素,則重複元素 全部刪除,程式碼應該怎麼實現呢?

 如:  給定:2→3→3→5→7→8→8→8→9→9→10

             返回:2→5→7→10

 

 

連結串列劃分

 

 給定一個連結串列和一個值x,將連結串列劃分成兩 部分,使得劃分後小於x的結點在前,大於 等於x的結點在後。在這兩部分中要保持原 連結串列中的出現順序。

 如:給定連結串列1→4→3→2→5→2和x = 3,返回 1→2→2→4→3→5

問題分析
 分別申請兩個指標p1和p2,小於x的新增到 p1中,大於等於x的新增到p2中;最後,將 p2連結到p1的末端即可。

 時間複雜度是O(N),空間複雜度為O(1);該 問題其實說明:快速排序對於單鏈表儲存結 構仍然適用。

 注:不是所有排序都方便使用連結串列儲存,如堆 排序,將不斷的查詢陣列的n/2和n的位置,用鏈 表做儲存結構會不太方便。

 

 單鏈公共結點問題

 

 給定兩個單向連結串列,計算兩個連結串列的第一個 公共結點,若沒有公共節點,返回空。

問題分析

 令兩連結串列的長度為m、n,不妨認為m≥n,由 於兩個連結串列從第一個公共結點到連結串列的尾結 點是完全重合的。所以前面的(m-n)個結點一 定沒有公共結點。

 演算法:先分別遍歷兩個連結串列得到它們的長度 m,n。長連結串列空轉|m-n|次,同步遍歷兩鏈 表,直到找到相同結點或到連結串列結束。  時間複雜度為O(m+n)。

 

佇列

 

拓撲排序


拓撲排序的方法
 從有向圖中選擇一個沒有前驅(即入度為0)的 頂點並且輸出它;

 從網中刪去該頂點,並且刪去從該頂點發出 的全部有向邊;

 重複上述兩步,直到剩餘的網中不再存在沒 有前驅的頂點為止

 

拓撲排序的進一步思考
 拓撲排序的本質是不斷輸出入度為0的點,該演算法可 用於判斷圖中是否存在環;

 可以用佇列(或者棧)儲存入度為0的點,避免每次遍 歷所有點;

 每次更新連線點的入度即可。

 拓撲排序其實是給定了結點的一組偏序關係。

 “拓撲”的涵義不限於此,在GIS中,它往往指點、 線、面、體之間的相互鄰接關係,即“橡皮泥集 合” 。儲存這些關係,往往能夠對某些演算法帶來好 處。

 計算不自交的空間曲面是否能夠圍成三維體  提示:任意三維邊都鄰接兩個三維曲面

 最短路徑條數

給定如圖所示的無向連通圖,假定圖中所有 邊的權值都為1,顯然,從源點A到終點T的 最短路徑有多條,求不同的最短路徑的數目

資料結構的選擇

 權值相同的最短路徑問題,則單源點Dijkstra 演算法退化成BFS廣度優先搜尋,假定起點為 0,終點為N:

 結點步數step[0…N-1]初始化為0

 路徑數目pathNum[0…N-1]初始化為0  pathNum[0] = 1

演算法分析
 若從當前結點i擴充套件到鄰接點j時:

 若step[j]為0,則

      step[j]=step[i]+1,pathN[j] = pathN[i]

 若step[j]==step[i]+1,則

      pathN[j] += pathN[i]

 可考慮擴充套件到結點N,則提前終止演算法。

 

堆疊 

括號是否匹配

 給定字串,僅由"()[]{}"六個字元組成。設 計演算法,判斷該字串是否有效。

 括號必須以正確的順序配對,如:“()”、“()[]” 是有效的,但“([)]”無效

演算法分析
 在考察第i位字元c與前面的括號是否匹配時:

 如果c為左括號,開闢緩衝區記錄下來,希望c能夠 與後面出現的同類型最近右括號匹配。

 如果c為右括號,考察它能否與緩衝區中的左括號 匹配。

 這個匹配過程,是檢查緩衝區最後出現的同類型左括號

 即:後進先出——棧

括號匹配演算法流程
 從前向後掃描字串:

 遇到左括號x,就壓棧x;

 遇到右括號y:

      如果發現棧頂元素x和該括號y匹配,則棧頂元素出棧, 繼續判斷下一個字元。

      如果棧頂元素x和該括號y不匹配,字串不匹配;

      如果棧為空,字串不匹配;

 掃描完成後,如果棧恰好為空,則字串匹配,否 則,字串不匹配。 

最長括號匹配

 給定字串,僅包含左括號‘(’和右括號 ‘)’,它可能不是括號匹配的,設計演算法, 找出最長匹配的括號子串,返回該子串的長 度。

 如:

   (():2

   ()():4

   ()(()):6

   (()()):6
 

演算法分析
 記起始匹配位置start=-1;最大匹配長度ml=0:

 考察第i位字元c:

 如果c為左括號,壓棧;

 如果c為右括號,它一定與棧頂左括號匹配;

      如果棧為空,表示沒有匹配的左括號,start=i,為下一次可能 的匹配做準備

      如果棧不空,出棧(因為和c匹配了);

            如果棧為空,i-start即為當前找到的匹配長度,檢查i-start是否比 ml更大,使得ml得以更新;

            如果棧不空,則當前棧頂元素t是上次匹配的最後位置,檢查i-t是 否比ml更大,使得ml得以更新。

 注:因為入棧的一定是左括號,顯然沒有必要將它們本身入棧, 應該入棧的是該字元在字串中的索引。 

 

逆波蘭表示式RPN
 

 Reverse Polish Notation,即字尾表示式。

 習慣上,二元運算子總是置於與之相關的兩 個運算物件之間,即中綴表達方法。波蘭邏 輯學家J.Lukasiewicz於1929年提出了運算子 都置於其運算物件之後,故稱為字尾表示。

 如:

      中綴表示式:a+(b-c)*d

      字尾表示式:abc-d*+

運算與二叉樹
  事實上,二元運算的前提下,中綴表示式可 以對應一顆二叉樹;逆波蘭表示式即該二叉 樹後序遍歷的結果。

  中綴表示式:a+(b-c)*d

  字尾表示式:abc-d*+
  該結論對多元運算也成立, 如“非運算”等

計算逆波蘭表示式

 計算給定的逆波蘭表示式的值。有效操作只 有+-*/,每個運算元都是整數。

 如:

     "2", "1", "+", "3", "*":9——(2+1)*3

     "4", "13", "5", "/", "+":6——4+(13/5)

逆波蘭表示式的計算方法
 abc-d*+

 若當前字元是運算元,則壓棧

 若當前字元是操作符,則彈出棧中的兩個操 作數,計算後仍然壓入棧中

     若某次操作,棧內無法彈出兩個運算元,則表 達式有誤。

 

入棧出棧問題
 

 給定無重複元素的兩個等長陣列,分別表述 入棧序列和出棧序列,請問:這樣的出棧序 列是否可行。

 如:入棧序列為“ABCDEFG”、出棧序列為 “BAEDFGC”,則可行。

 入棧序列“ABCD”、出棧序列“BDAC”,不可 行。

問題分析
 使用一個堆疊S來模擬壓棧出棧的操作。記入棧序 列為A,出棧序列為B

 遍歷B的每個元素b:

 情形1:若b等於棧頂元素s,恰好匹配,則檢查B的 下一個元素,棧頂元素s出棧;

 情形2:若b不等於棧頂元素s,則將A的當前元素入 棧,目的是希望在A的剩餘元素中找到b。

      在情形1中,若棧S為空,則認為b無法與棧內元素匹配, 則呼叫情形2。