系統程式設計師成長計畫 動態陣列(二)

2021-04-26 01:18:02 字數 2017 閱讀 3338

對比雙向鍊錶和動態陣列

在c語言中,陣列的長度是事先確定的,不能在執行時動態調整。所謂動態陣列就是它的長度可以根據儲存資料多少自動調整,這需要我們用程式來實現。對比雙向鍊錶和動態陣列,我們會發現:

o 動態陣列本身占用一塊連續的記憶體,而雙向鍊錶的每個結點要占用一塊記憶體。在頻繁增刪資料的情況下,雙向鍊錶更容易造成記憶體碎片,具體影響與記憶體管理器的好壞有關。

o 動態陣列的增刪操作需要移動後面的元素,而雙向鍊錶只需要修改前後元素的指標。在儲存大量資料的情況下,這種差異更為明顯。

o 動態陣列支援多種高效的排序演算法,像快速排序、歸併排序和堆排序等等,而這些演算法在雙向鍊錶中的表現並不好,甚至不如氣泡排序來得快。

o 排序好的動態陣列可以使用二分查詢,而排序好的雙向鍊錶仍然只能使用順序查詢。主要原因是雙向鍊錶不支援隨機定位,定位中間結點時只能乙個乙個的移動指標。

o 對於小量資料,使用動態陣列還是雙向鍊錶沒有多大區別,使用哪個只看個人的喜好了。

實現動態陣列

在考慮存值還是指標時,我們同樣選擇存指標,所以這裡我們實現的是指標陣列。動態陣列的功能和雙向鍊錶非常類似,所以它對外的介面也是類似的:

動態陣列的動態性如何實現了呢?其實很簡單,借助標準c的記憶體管理函式realloc,我們可以輕易改變陣列的長度。函式realloc是比較費時 的,如果每插入/刪除乙個元素就要realloc一次,不但會帶來效能的下降,而且可能造成記憶體碎片。為了解決這個問題,需要使用乙個稱為預先分配的慣用 手法,預先分配實際上是用空間換時間的典型應用,下面我們看看它的實現:

擴充套件空間

在擴充套件陣列時,不是一次擴充套件乙個元素,而是一次擴充套件多個元素。至於應該擴充套件多少個,經驗資料是擴充套件為現有元素個數的1.5倍。

#define min_pre_allocate_nr 10

static ret darray_expand(darray* thiz, size_t need)

}return ((thiz->size + need) <= thiz->alloc_size) ? ret_ok : ret_fail;

}ret darray_insert(darray* thiz, size_t index, void* data)

thiz->data[cursor] = data;

thiz->size++;

ret = ret_ok;

}return ret;

}

擴充套件的大小由下列公式得出:

size_t alloc_size = (thiz->alloc_size + thiz->alloc_size>>1) + min_pre_allocate_nr;

計算1.5*thiz->alloc_size時,我們不使用1.5 * thiz->alloc_size,因為這樣存在浮點數計算,在大多數嵌入式平台中,都沒有硬體浮點數計算的支援,浮點數的計算比定點數的計算要慢上百倍。

我們也不使用thiz->alloc_size+ thiz->alloc_size/2,如果編譯器不做優化,除法指令也是比較慢的操作,特別是像在arm這種沒有除法指令的晶元中,需要很多條指令才能實現除法的計算。

這裡我們使用(thiz->alloc_size + thiz->alloc_size>>1),這是最快的方法。後面加上min_pre_allocate_nr的原因是避免thiz->alloc_size為0時存在的錯誤。

減小空間

在刪除元素時也不是馬上釋放空閒空間,而是等到空閒空間高於某個值時才釋放它們。這裡我們的做法時,空閒空間多於有效空間一倍時,將總空間調整為有效空間的1.5倍。

static ret darray_shrink(darray* thiz)

}return ret_ok;

}ret darray_delete(darray* thiz, size_t index)

thiz->size--;

darray_shrink(thiz);

return ret_ok;

}

為了避免極端情況下的出現頻繁resize的情況,在總空間小於等於min_pre_allocate_nr時,我們不減少空間的大小。

系統程式設計師成長計畫 動態陣列(一)

雙向鍊錶和動態陣列是最簡單的兩種資料結構,在研讀大量源 之後,我發現正是這兩種最簡單的資料結構有著最廣泛的應用。像平衡二叉樹這樣的複雜資料結構,除了學習之外,你永遠都不會寫第二遍,甚至連使用的機會都沒有,而雙向鍊錶和動態陣列則會在開發中反覆的運用。也正是由於這個原因,我們才選擇雙向鍊錶和動態陣列作為...

系統程式設計師成長計畫 動態陣列(四)

有序陣列的兩個應用 前面我們學習了陣列的排序方法,通常我們對陣列排序不是為了排序而排序,而是為了其它的用途才排序的,這裡了解一下有序陣列的兩個常見應用。二分查詢 二分查詢也稱為折半查詢,它的前提是陣列中的元素是有序的。演算法過程如下 假定陣列為公升序 先拿要查詢的元素與陣列中間位置的元素相比較,如果...

系統程式設計師成長計畫 動態陣列(三)(上)

作者 李先靜 排序大多數高階排序演算法都是針對陣列實現的,接下來我們一起學習一下幾種排序演算法,學習演算法本身只是我們的目標之一,最重要的是要從中學習一些思考問題的方法。對比不同演算法的特點,也有助於我們在設計時做出正確的選擇。這裡我們請讀者實現氣泡排序 快速排序和歸併排序。要求如下 o 演算法同時...