1. 程式人生 > 其它 >資料結構與演算法 - 陣列

資料結構與演算法 - 陣列

資料結構與演算法 - 陣列

資料結構

一、定義
陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空間,來儲存一組具有相同型別的資料。

線性表(Linear List)就是資料排成像一條線一樣的結構。每個線性表上的資料最多隻有兩個方向。除了陣列,連結串列、佇列、棧也是線性表結構。

與線性表對立的是非線性表,比如二叉樹、堆、圖等。之所以叫非線性,是因為,在非線性表中,資料之間並不是簡單的前後關係。

二、操作

操作 時間複雜度
查詢 \(O(1)\)
插入 開頭\(O(1)\),末尾\(O(n)\),平均\(O(n)\)
刪除 開頭\(O(1)\),末尾\(O(n)\)
,平均\(O(n)\)

三、細節

  1. 為什麼資料編號從0開始?

從陣列的儲存模型上來看,“下標”最確切的定義應該是“偏移量(offset)”。
如果用a代表陣列的首地址,a[0]就是偏移量為0的位置,也就是首地址,a[k]就表示偏移量k個 type_size 的位置,所以計算 a[k] 的記憶體地址,以1開始,則多一次減法運算:

\[\begin{array}{l} \begin{array}{l} a[k]\_address = base\_address + k * type\_size \\ a[k]\_address = base\_address + (k-1)*type\_size \end{array} \end{array} \]
  1. 陣列型別與大小如何選擇?
    1. 陣列型別根據實際的情況,預先估算資料量大小、儲存大小等需求;
    2. 如果資料大小事先已知,並且對資料的操作非常簡單,可以定義靜態陣列;
    3. 如果資料大小需要動態新增,並且需要反覆操作,可以定義動態陣列vector

靜態與動態陣列

一、 定義

1.靜態陣列:不可以更改陣列長度的

2.動態陣列:動態陣列本質上就是陣列,是由靜態陣列封裝的一些擴容能力。

靜態陣列在記憶體中位於棧區,是在定義時就已經在棧上分配了固定大小,在執行時這個大小不能改變,在函式執行完以後,系統自動銷燬;

動態陣列是使用者自己創建出來的,位於記憶體的堆區,它的大小是在執行時給定,並且可以改變其大小。同時,使用完必須由程式設計師自己釋放,否則嚴重會引起記憶體洩露。

二、 動態擴容機制

vector(c++):

面試題:C++vector的動態擴容,為何是1.5倍或者是2倍

  1. 擴容原理?當向vector中插入元素時,如果元素有效個數size與空間容量capacity相等時,vector內部會觸發擴容機制。拷貝元素和釋放舊空間,可以通過&vector[0]的方式來檢視資料首地址改變情況。

  2. 擴容大小?每次擴容新空間不能太大,也不能太小,太大容易造成空間浪費,太小則會導致頻繁擴容而影響程式效率。不同的的編譯器實現方式不同,vs中以1.5倍擴容,GCC以2倍擴容。

  3. 如何避免擴容導致效率低?如果在插入之前,可以預估vector儲存元素的個數,提前將底層容量開闢好即可。如果插入之前進行reserve,只要空間給足,則插入時不會擴容,如果沒有reserve,則會邊插入邊擴容,效率極其低下。

  4. 為什麼選擇以倍數方式擴容?以等長個數k進行擴容,向vector插入n個元素,需要插入元素操作和搬移元素操作的總和:\(n + \sum_{i=1}^{\frac{n}{k}}ik= n + \frac{(1+n/k)*n}{2}\),平攤下來每次操作時間\((n + \frac{(1+n/k)*n}{2})/n=3/2+n/2*k = O(N)\)。以倍數方式m進行擴容,向vector插入n個元素,需要插入元素操作和搬移元素操作的總和:\(n + \sum_{i=1}^{\log_{m}{n}}m^i= n + \frac{m(n-1)}{m-1}=n+\frac{mn}{m-1}\),平攤每次操作的時間\((n+\frac{mn}{m-1})/n = O(\frac{m}{m-1}))=O(1)\),m為常量。

  5. 為什麼選擇1.5倍或者2倍方式擴容,而不是3倍、4倍?(斐波那契數,1.618)理想的分配方案是在第N次擴容時如果能複用之前N-1次釋放的空間,因此按照小於2倍方式擴容,多次擴容之後就可以複用之前釋放空間。如果倍數超過2倍(包含2倍)方式擴容會存在:(1)空間浪費可能會比較高,比如:擴容後申請了64個空間,但只存了33個元素,有接近一半的空間沒有使用。(2)無法使用到前面已釋放的記憶體。

List(python):list是以兩倍進行擴容,並且會建立新的陣列,然後拷貝,系統再回收久陣列。

二維陣列

一、定義

二維陣列就是在一維陣列上,多加一個維度;

二、宣告與初始化

  1. c++ (靜態陣列)
//資料型別 陣列名[行數][列數];
int a1[3][3];
//資料型別 陣列名[行數][列數] = {{資料1,資料2,資料3},{資料4,資料5}};
int a2[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
//資料型別 陣列名[行數][列數] ={資料1,資料2,資料3,資料4}
int a2[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
//資料型別 陣列名[  ] [列數] = {資料1,資料2,資料3,資料4};
int a3[][3] = { 1,2,3,4,5,6 };
//查詢二維陣列所佔記憶體空間
cout << "二維陣列佔用記憶體空間為:" << sizeof(arr) << endl;
cout << "二維陣列第一行佔用記憶體為:" << sizeof(arr[0]) << endl;
cout << "二維陣列第一個元素所佔記憶體空間" << sizeof(arr[0][0]) << endl;
// 行列數
cout << "二維陣列行數為:" << sizeof(arr) / sizeof(arr[0]) << endl;
cout << "二維陣列列為:" << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
//可以檢視二維陣列的首地址
cout << "二維陣列首地址為:" << (int)arr << endl;
cout << "二維陣列第一行首地址為:" << (int)arr[0] << endl;
cout << "二維陣列第二行首地址為:" << (int)arr[1] << endl;

cout << "二維陣列第一個元素首地址:" << (int)&arr[0][0] << endl;
  1. c++ (動態陣列)
vector<vector<int>> a(m, vecotr<int>(n, 0));
  1. python (list)
li = [[0 for i in range(m)] for j in range(n)]

山脈陣列

一、 定義

山脈陣列:\(arr.length >= 3\),在 \(0 < i < arr.length - 1\)條件下,存在\(i\)使得:

  • \(arr[0] < arr[1] < ... arr[i-1] < arr[i]\)
  • \(arr[i] > arr[i+1] > ... > arr[arr.length - 1]\)

二、 題型

序號 題目 難度
—— ———————————————————————————————————————————————— —————
0845 845. 陣列中的最長山脈 中等
0852 852. 山脈陣列的峰頂索引 簡單
0941 941. 有效的山脈陣列 簡單
1095 1095. 山脈陣列中查詢目標值 困難

旋轉陣列

一、 定義

旋轉陣列:nums在預先未知的某個下標 \(k(0 <= k < nums.length)\)上進行了 旋轉,使陣列變為\([nums[k], ..., nums[n-1], nums[0], ..., nums[k-1]]\)(下標 從 0 開始 計數)。例如, \([0,1,2,4,5,6,7]\) 在下標 3 處經旋轉後可能變為 \([4,5,6,7,0,1,2]\)

注意:

1.旋轉陣列,無論選擇多少次都是二分有序。

2.元素可以重複出現, 因此旋轉點一定是最小值, 但最小值不一定是旋轉點,如\([2,0,2,2,2]\)。因此需要判斷左右端點,當左右端點與中點相等,左遞增1右遞減1,然後再根據單調性來判斷。最小值可以根據右端點來比較。

二、 題型

序號 題目 難度
—— ———————————————————————————————————————————————— —————
189.輪轉陣列
33. 搜尋旋轉排序陣列 中等
81. 搜尋旋轉排序陣列 II 中等
153. 尋找旋轉排序陣列中的最小值 中等
154. 尋找旋轉排序陣列中的最小值 II 困難
劍指 Offer 11. 旋轉陣列的最小數字 中等
面試題 10.03. 搜尋旋轉陣列 中等

環形陣列

一、 定義

陣列是 環形 的,所以可以假設從最後一個元素向前移動一步會到達第一個元素,而第一個元素向後移動一步會到達最後一個元素。

二、 題型

序號 題目 難度
—— ———————————————————————————————————————————————— —————
457. 環形陣列是否存在迴圈
劍指 Offer II 090. 環形房屋偷盜

子陣列

一、 定義

子陣列 是陣列的連續子序列。

思路:滑動視窗(可變滑窗)和動態規劃。

二、 題型

序號 題目 難度
—— ———————————————————————————————————————————————— —————
152. 乘積最大子陣列
209. 長度最小的子陣列
525. 連續陣列
560. 和為 K 的子陣列
581. 最短無序連續子陣列
643. 子陣列最大平均數 I
644. 子陣列最大平均數 II
659. 分割陣列為連續子序列
713. 乘積小於K的子陣列
718. 最長重複子陣列
795. 區間子陣列個數
1423. 可獲得的最大點數
1248. 統計「優美子陣列」
795. 區間子陣列個數
1630. 等差子陣列
2104. 子陣列範圍和
978. 最長湍流子陣列
1246. 刪除迴文子陣列
1310. 子陣列異或查詢
LCP 14. 切分陣列
898. 子陣列按位或操作
1574. 刪除最短的子陣列使剩餘陣列有序
992. K 個不同整數的子陣列
1524. 和為奇數的子陣列數目
2090. 半徑為 k 的子陣列平均值
930. 和相同的二元子陣列
1157. 子陣列中佔絕大多數的元素
974. 和可被 K 整除的子陣列
1186. 刪除一次得到子陣列最大和
1856. 子陣列最小乘積的最大值
862. 和至少為 K 的最短子陣列
1191. K 次串聯後最大子陣列之和
915. 分割陣列
劍指 Offer II 010. 和為 k 的子陣列
劍指 Offer II 009. 乘積小於 K 的子陣列
劍指 Offer II 011. 0 和 1 個數相同的子陣列
劍指 Offer II 011. 0 和 1 個數相同的子陣列
劍指 Offer 42. 連續子陣列的最大和
劍指 Offer II 012. 左右兩邊子陣列的和相等