線性表——連結串列、佇列、堆疊
主要內容
連結串列
連結串列相加/連結串列部分翻轉/連結串列去重
連結串列劃分/連結串列公共結點
佇列
拓撲排序
最短路徑條數
堆疊
括號是否匹配
最長括號匹配
計算逆波蘭表示式
出棧入棧問題
連結串列
連結串列相加
給定兩個連結串列,分別表示兩個非負整數。它 們的數字逆序儲存在連結串列中,且每個結點只 儲存一個數字,計算兩個數的和,並且返回 和的連結串列頭指標。
如:輸入: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。