資料結構與演算法整理
基本上耳熟能詳的2個概念了,之前總是趕到招聘的時候,才會折騰翻一下,總覺得沒什麼用,但是隨著工作以及網上的閱讀,發現,其實,用處真的是太大了。
好早之前就買了《演算法導論》這部鉅著,然而,現在依然束之高閣,最近想著從簡單點的看起,於是翻了翻《大話資料結構》《啊哈!演算法》,因為也還沒看完,下面就想到哪說到哪咯。
佇列:先進先出的一種資料結構(FIFO),在樹的層序遍歷裡以及廣度搜索演算法中,都有用到,再簡單點,像陣列啦、vector啦,都是類似的東東,在這裡,有必要分享一個憂桑的故事:話說有一天,廢柴君興致勃勃的去參加面試,一路各種low爆,終於,面試官忍無可忍的問了廢柴君一個基礎問題:佇列說從頭進還是從尾進?那一刻,廢柴君內心是崩潰的,是迷茫的,只記得先進先出啊,頭進尾出或者是尾進頭處,不都一樣麼……好吧,後來記住了,原來佇列是尾進
棧:後進先出(LIFO),一般比較熟悉的應該就是壓棧出棧之類的操作,在函式呼叫之類的地方,總會被各種壓來出去的,所以這裡好像有個說法,太多的遞迴呼叫,是不好的。以前對棧什麼感覺,一直到前一陣做一個模仿資料夾“後退前進”操作的時候,當時是各種煎熬啊,生生了折騰了1天,各種陣列、各種flag,雖然當時是弄出來了,但是回頭再看的時候,連自己都蒙圈。直到前幾天翻到棧這裡,原來折騰半天,一個現成的資料結構擺在那裡,我卻沒有去珍惜。回去優化了一下,很好,自己看著真心舒服很多。
陣列和連結串列:這兩種基本上就很常用了,資料呢,是一段連續的記憶體,隨機訪問速度很快,但是針對陣列中間資料進行操作的話,就會十分麻煩,而連結串列則不然,對連結串列的中間資料操作起來倒是比較方便,可是說到隨機訪問,就不行了,必須要一個個的遍歷過去。
基本上典型點的代表,就是STL中的vector以及list了,而針對前面說過的佇列和棧,其實用陣列或連結串列,都可以分別實現的,再專業點的說法叫做:順序儲存結構和鏈式儲存結構。
樹:樹就是不包含迴路的連通無向圖,二叉樹裡面最全的,叫滿二叉樹(一個點都不少呦),可以少幾個樹葉(從右往左)的,叫完全二叉樹。其他紅黑樹、B樹什麼的,真心沒看過,下次再說。樹這裡,暫時能想到的,也就2個:1是遍歷:前序、中序、後序,層序;2是堆:最小堆、最大堆。
這裡說到的前序、中序、後序,主要是遍歷的時候,讀取節點的位置,讀取節點,之後左子節點,之後右子節點,這就是前序,其他類似。
至於堆,主要就是用樹來進行排序。最小堆就是父節點的值小於所有子節點的值,反之,就是最大堆了。
針對最小堆或者最大堆,主要有2種維護方式,分別是向下維護以及向上維護,程式碼像下面這樣:
// 向下維護最小堆
void siftDown(int i)
{
// 假設heap已經是最小堆了
int t = 0;
while (i*2<n && !bFlag)
{
if (heap[i] < heap[i*2])
{
t = i;
}
else
{
t = i*2;
}
if (i*2+1<n)
{
if (head[t] > heap[i*2+1])
{
t = i*2+1;
}
}
if (i == t)
{
bFlag = true;
}
else
{
swap(i, t);
i = t;
}
}
}
// 向上維護最小堆
void siftUp(int i)
{
// 假設heap已經是最小堆了
if (i == 1)
{
return;
}
if (heap[i] > head[i/2])
{
return;
}
else
{
swap(i, i/2);
siftUp(i/2);
}
}
至於堆的應用,比如想從小到大排序,可以先建立最小堆,然後每次把根節點拿出來,之後將最後的葉子節點放上去,維護一下最小堆,再次重複拿出根節點,就可以啦。
堆排序還有一種更好的方法,從小到大排序的時候,不建立最小堆而建立最大堆,之後每次講根節點,與最後的節點互換,這樣還不多佔用空間了呦。
堆還經常被用來求一個數列中第k大的數:只需要先將數列中前k個元素拿出來,組成一個最大堆,之後後面的元素依次比較就可以了。
最後還有圖:好吧,這裡除了Folyd演算法,其他的真心沒看懂……
以上部分內容節選自:《大話資料結構》《啊哈!演算法》感謝前人栽樹。