堆(heap)與棧(stack)是開發人員必須面對的兩個概念,在理解這兩個概念時,需要放到具體的場景下,因為不同場景下,堆與棧代表不同的含義。一般情況下,有兩層含義:
(1)程式記憶體布局場景下,堆與棧表示兩種記憶體管理方式;
(2)資料結構場景下,堆與棧表示兩種常用的資料結構。
int main()
其中函式中定義的區域性變數按照先後定義的順序依次壓入棧中,也就是說相鄰變數的位址之間不會存在其它變數。棧的記憶體位址生長方向與堆相反,由高到底,所以後定義的變數位址低於先定義的變數,比如上面**中變數 s 的位址小於變數 b 的位址,p2 位址小於 s 的位址。棧中儲存的資料的生命週期隨著函式的執行完成而結束。
int main()
;//棧初始化,成功返回棧物件指標,失敗返回空指標null
seqstack* initseqstack()
else
}//判斷棧是否為空
bool isemptyseqstack(seqstack* s)
//入棧,返回-1失敗,0成功
int pushseqstack(seqstack* s, datatype x)
else
}//出棧,返回-1失敗,0成功
int popseqstack(seqstack* s, datatype* x)
else
}//取棧頂元素,返回-1失敗,0成功
int topseqstack(seqstack* s,datatype* x)
}//列印棧中元素
int printseqstack(seqstack* s)
//test
int main()
//將棧頂元素出棧
ret=popseqstack(seqstack,&x);
if(0==ret)
}return 0;
}執行上面的程式,輸出結果:
當前棧中的元素:
7 5 4
top element is 7
pop top element is 7
2.2 堆簡介
2.2.1 堆的性質
堆是一種常用的樹形結構,是一種特殊的完全二叉樹,當且僅當滿足所有節點的值總是不大於或不小於其父節點的值的完全二叉樹被稱之為堆。堆的這一特性稱之為堆序性。因此,在乙個堆中,根節點是最大(或最小)節點。如果根節點最小,稱之為小頂堆(或小根堆),如果根節點最大,稱之為大頂堆(或大根堆)。堆的左右孩子沒有大小的順序。下面是乙個小頂堆示例:
堆的儲存一般都用陣列來儲存堆,i節點的父節點下標就為(i–1)/2(i – 1) / 2(i–1)/2。它的左右子節點下標分別為 2∗i+12 * i + 12∗i+1 和 2∗i+22 * i + 22∗i+2。如第0個節點左右子節點下標分別為1和2。
2.2.2 堆的基本操作
(1)建立
以最小堆為例,如果以陣列儲存元素時,乙個陣列具有對應的樹表示形式,但樹並不滿足堆的條件,需要重新排列元素,可以建立「堆化」的樹。
(2)插入
將乙個新元素插入到表尾,即陣列末尾時,如果新構成的二叉樹不滿足堆的性質,需要重新排列元素,下圖演示了插入15時,堆的調整。
(3)刪除。
堆排序中,刪除乙個元素總是發生在堆頂,因為堆頂的元素是最小的(小頂堆中)。表中最後乙個元素用來填補空缺位置,結果樹被更新以滿足堆條件。
2.2.3 堆操作實現
(1)插入**實現
每次插入都是將新資料放在陣列最後。可以發現從這個新資料的父節點到根節點必然為乙個有序的數列,現在的任務是將這個新資料插入到這個有序資料中,這就類似於直接插入排序中將乙個資料併入到有序區間中,這是節點「上浮」調整。不難寫出插入乙個新資料時堆的調整**:
//新加入i節點,其父節點為(i-1)/2
//引數:a:陣列,i:新插入元素在陣列中的下標
void minheapfixup(int a, int i)
a[i] = temp;
} 因此,插入資料到最小堆時:
//在最小堆中加入新的資料data
//a:陣列,index:插入的下標,
void minheapaddnumber(int a, int index, int data)
(2)刪除**實現
按照堆刪除的說明,堆中每次都只能刪除第0個資料。為了便於重建堆,實際的操作是將陣列最後乙個資料與根節點交換,然後再從根節點開始進行一次從上向下的調整。
調整時先在左右兒子節點中找最小的,如果父節點不大於這個最小的子節點說明不需要調整了,反之將最小的子節點換到父節點的位置。此時父節點實際上並不需要換到最小子節點的位置,因為這不是父節點的最終位置。但邏輯上父節點替換了最小的子節點,然後再考慮父節點對後面的節點的影響。堆元素的刪除導致的堆調整,其整個過程就是將根節點進行「下沉」處理。下面給出**:
//a為陣列,len為節點總數;從index節點開始調整,index從0開始計算index其子節點為 2*index+1, 2*index+2;len/2-1為最後乙個非葉子節點
void minheapfixdown(int a,int len,int index)
2.2.4 堆的具體應用——堆排序
堆排序(heapsort)是堆的乙個經典應用,有了上面對堆的了解,不難實現堆排序。由於堆也是用陣列來儲存的,故對陣列進行堆化後,第一次將a[0]與a[n - 1]交換,再對a[0…n-2]重新恢復堆。第二次將a[0]與a[n – 2]交換,再對a[0…n - 3]重新恢復堆,重複這樣的操作直到a[0]與a[1]交換。由於每次都是將最小的資料併入到後面的有序區間,故操作完成後整個陣列就有序了。有點類似於直接選擇排序。
因此,完成堆排序並沒有用到前面說明的插入操作,只用到了建堆和節點向下調整的操作,堆排序的操作如下:
//array:待排序陣列,len:陣列長度
void heapsort(int array,int len)
} (1)穩定性。堆排序是不穩定排序。
(2)堆排序效能分析。由於每次重新恢復堆的時間複雜度為o(logn),共n-1次堆調整操作,再加上前面建立堆時n/2次向下調整,每次調整時間複雜度也為o(logn)。兩次操作時間複雜度相加還是o(nlogn),故堆排序的時間複雜度為o(nlogn)。
最壞情況:如果待排序陣列是有序的,仍然需要o(nlogn)複雜度的比較操作,只是少了移動的操作;
最好情況:如果待排序陣列是逆序的,不僅需要o(nlogn)複雜度的比較操作,而且需要o(nlogn)複雜度的交換操作,總的時間複雜度還是o(nlogn)。
因此,堆排序和快速排序在效率上是差不多的,但是堆排序一般優於快速排序的重要一點是資料的初始分布情況對堆排序的效率沒有大的影響。
堆與棧區別
檢視文章 關於記憶體中棧和堆的區別 2008 09 02 17 12 1 記憶體分配方面 堆 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os 注意它與資料結構中的堆是兩回事,分配方式是類似於鍊錶。可能用到的關鍵字如下 new malloc delete free等等。棧 由編譯器...
棧 與 堆 的區別
預備知識 堆與棧有什麼區別?一 預備知識 程式的記憶體分配 乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束...
堆與棧的區別
堆與棧有什麼區別?一 預備知識 程式的記憶體分配 乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由o...