這一篇筆記主要記錄總結了線性表
數據結構中的數組
概念以及相關的算法
。程序員
線性表
是數據排成像一條線同樣的結構
,每一個線性表上的數據最多有向前和向後兩個方向。除了數組
外,鏈表、隊列、棧
也是線性表
數據結構。算法
和線性表相對立,數據之間不是簡單的先後關係
,這樣的結構稱爲非線性表
,如圖、樹、堆
等數據結構。編程
數組是一種線性表
數據結構,是用一組連續的內存空間
,來存儲一組具備相同數據類型
的數據。幾乎在全部的編程語言都存在數組這中最基本的數據結構類型。在 Objective-C 語言中是 NSArray
,固然 Objective-C 是 C 的超集,因此也徹底可使用 C 語言的數組類型。數組
正式由於數組是用一組連續的內存空間
來存儲數據的,因此數組支持下標隨機訪問
,複雜度是 O(1)
。數據結構
int[] a = new int[10]
// 數組a 首地址
base_address = 1000
// 尋址公式 data_type_size:數組中元素的數據類型長度
a[i]_address = base_address + i * data_type_size
複製代碼
相比於複雜度是 O(1)
的隨機訪問操做,對於數組而言,插入和刪除
操做的複雜度都爲O(n)
,由於每次要在數組的第 k 個位置插入或者刪除一個元素的話,都須要移動 k ~ n 個元素的位置。架構
優化技巧:編程語言
插入操做:若是不須要追求數組中元素的
有序
,則能夠考慮直接將第 k 個位置的元素移到數組的末尾,而後把要插入的新元素放到第 k 個位置就行,這樣,複雜度也就是O(1)。 刪除操做:在一些場景下,並不追求數組中數據的連續性,能夠將屢次刪除操做集中在一塊兒執行
。先記錄下已經刪除的元素,並不真的刪除,當數組沒有更多空間時,再觸發真正的刪除操做,這樣能夠省下大量重複的數據移動操做。佈局
看一段 C 語言代碼:性能
int main(int argc, char* argv[]){
int i = 0;
int arr[3] = {0};
for(; i<=3; i++){
arr[i] = 0;
printf("hello world\n");
}
return 0;
}
複製代碼
這裏就會出現數組越界問題
,C語言的執行結果是未決
,就是沒有規定數組訪問越界時編譯器應該如何處理。若是該內存是一塊能夠訪問的不受限內存
,在x86架構機器下,那麼執行結果是會無線循環打印hello world。緣由是內存分配從棧的高位到低位
開始,i 變量實際上與數組元素 arr[2] 相鄰。數組下標從 0 開始,當執行到循環最後一次 i = 3 時,根據根據以前的尋址公式 a[i]_address = base_address + i * data_type_size
,實際上 arr[3] 訪問的會是變量 i 的地址,賦值給 i = 0,而後死循環。學習
這裏還和使用的編譯器
內存分配以及字節對齊
有關係,有些編譯器會默認開啓堆棧保護
,當若是變量訪問一塊不屬於本身的內存時,會出現編譯錯誤。 爲了避免讓程序出現這種不肯定的錯誤,致使 debug 難度大,還有就是容易被黑客利用攻擊,因此寫代碼時要特別警戒數組越界
。不過不少高級語言的都會默認作越界檢查
,如 Objective-C 裏面的數組,若是越界訪問就會下面這種經典錯誤。
動態擴容
,若是插入數據的時候發現數組的空間不夠,就須要從新申請一塊更大的內存空間,並把原來的數據都複製進新的數組,在將新的數據插入。可是若是事先知道數據的大小,能夠建立的時候就制定好數據的大小,這樣能夠避免沒必要要的動態擴容
操做。平常業務開發,使用高級語言提供的數組容器就行,如 Objective-C 的 NSArray
,損失一點性能,但寫起來方便簡單。若是是作比較注重性能的底層開發,能夠考慮使用數組。
a[i]_address = base_address + (i - 1 * data_type_size)
,轉成彙編指令,對於 CPU 而言,就多了一條要執行的減法指令
,而這種數組的操做是很頻繁很底層的操做,爲了優化,因此數組的下標都設計從 0 開始。前面我基於數組的原理引出 JVM 的標記清除垃圾回收算法的核心理念,我不知道你是否使用 Java 語言,理解 JVM,若是你熟悉回顧下你理解的標記清除垃圾回收算法。
前面咱們講到一維數組的內存尋址公式,那你能夠思考一下,類比一二維數組的尋址公式是怎樣?
a[i][j]_address = base_address + (i * n + j data_type_size)
,其中 i < m,j < n。內存佈局以下:// 頭文件 ————————————————————————————————————————————————————————
#ifndef DynamicExpansionArray_h
#define DynamicExpansionArray_h
#include <stdio.h>
typedef struct {
int *array;// 指針數組
int size; // 數組大小
}Array;
// 建立數組
Array array_creat(int init_size);
// 釋放數組
void array_free(Array *a);
// 獲取數組大小
int array_size(const Array *a);
// 根據下標獲取數組
int* array_at(Array *a, int index);
// 根據下標獲取值
int array_get(const Array *a, int index);
// 根據下標設置值
void array_set(Array *a, int index, int value);
// 數組擴容
void array_inflate(Array *a, int more_size);
#endif /* DynamicExpansionArray_h */
// 實現文件 ————————————————————————————————————————————————————
#include "DynamicExpansionArray.h"
#include <stdlib.h>
#include <string.h>
//typedef struct {
// int *array;
// int size;
//}Array;
const int BLOCK_SIZE = 20;
// 建立數組
Array array_creat(int init_size) {
Array a;
a.size = init_size;
a.array = (int *)malloc(sizeof(int) * a.size);
return a;
}
// 釋放數組
void array_free(Array *a) {
free(a->array);
a->size = 0;
// 防止外界重複free致使崩潰,free(NULL) 是沒問題的。
a->array = NULL;
}
// 獲取數組大小
int array_size(const Array *a) {
return a->size;
}
// 返回對應index的內存地址
int* array_at(Array *a, int index) {
if (index < 0) {
printf("下標不合法!!!!");
}
// 若是下標大於等於當前最大的size,則數組須要擴容
if (index >= a->size) {
array_inflate(a, (index / BLOCK_SIZE + 1) * BLOCK_SIZE - a->size);
}
// array[index] :若是分配的是連續的內存空間,指針array能夠像數組同樣使用
return &(a->array[index]);
}
// 根據下標獲取值
int array_get(const Array *a, int index) {
return a->array[index];
}
// 根據下標設置值
void array_set(Array *a, int index, int value) {
a->array[index] = value;
}
// 數組擴容
void array_inflate(Array *a, int more_size) {
int *p = (int *)malloc((a->size + more_size) * sizeof(int));
// memcoy,將a->array內存拷貝到p
memcpy(p, a->array, sizeof(int) * a->size);
// for (int i = 0; i < a->size; i++)
// {
// p[i] = a->array[i];
// }
// free(a->array);
a->array = p;
a->size += more_size;
}
// 測試 ——————————————————————————————————————————————————————————
#include <stdio.h>
#include "DynamicExpansionArray.h"
int main(int argc, const char * argv[]) {
// 建立一個大小 100 數組結構
Array a = array_creat(10);
int b[] = {0,1,2,3,4,5,6,7,8,9};
a.array = b;
printf("size = %d\n", array_size(&a));
// 根據索引下標設置值
*array_at(&a, 0) = 10;
*array_at(&a, 1) = 12;
// 根據索引下標取值
int index_0_Value = *array_at(&a, 0);
int index_1_Value = *array_at(&a, 1);
printf("index_0_Value = %d\nindex_1_Value = %d\n",index_0_Value, index_1_Value);
// 設置值
array_set(&a, 2, 20);
array_set(&a, 3, 21);
// 測試超出數組下標出插入,動態擴容數組,原來數組空間爲 10,如今是120
*array_at(&a, 101) = 101;
int index_101_Value = *array_at(&a, 101);
printf("index_101_Value = %d\n", index_101_Value);
// 打印原來的值
for (int i = 0; i < 10; i ++) {
printf("---index_%d_Value %d\n", i, array_get(&a, i));
}
// 釋放內存空間
array_free(&a);
return 0;
}
// 打印日誌 ——————————————————————————————————————————————————————————
size = 10
index_0_Value = 10
index_1_Value = 12
index_101_Value = 101
---index_0_Value 10
---index_1_Value 12
---index_2_Value 20
---index_3_Value 21
---index_4_Value 4
---index_5_Value 5
---index_6_Value 6
---index_7_Value 7
---index_8_Value 8
---index_9_Value 9
Program ended with exit code: 0
複製代碼
// 頭文件 -----------------------------------------------------
#ifndef SortArray_h
#define SortArray_h
#include <stdio.h>
typedef struct {
int size;// 數組大小
int used; // 數組已經使用了多少
int *array; // 指針
}Array;
// 根據數組大小初始化一個數組
Array arrayCreat(int init_size);
// 釋放空間
void arrayFree(Array *a);
// 增,在數組末尾插入新數據
void arrayAdd(Array *a, int value);
// 刪
void arrayDelete(Array *a, int index);
// 改,修改指定下標位置的值
void arrayUpdate(Array *a, int index, int value);
#endif /* SortArray_h */
// 實現文件 --------------------------------------------------------
#include "SortArray.h"
#include <stdlib.h>
//typedef struct {
// int size;// 數組大小
// int *array; // 指針
//}Array;
// 建立固定大小數組
Array arrayCreat(int init_size) {
Array a;
a.array = (int *)malloc(init_size*sizeof(int));
a.size = init_size;
a.used = 0;
return a;
}
// 釋放空間
void arrayFree(Array *a) {
free(a->array);
a->size = 0;
a->used = 0;
// 防止外界重複free致使崩潰,free(NULL) 是沒問題的。
a->array = NULL;
}
// 增,在數組末尾插入新數據
void arrayAdd(Array *a, int value) {
// 先判斷數組空間是否滿了
if (a->used == a->size) {
printf("添加失敗,數組空間已滿!!!");
} else {
// 若是數組爲空
if (a->used == 0) {
a->array[a->used] = value;
} else if (value >= a->array[a->used - 1]) {
// 比數組中最大的還大
a->array[a->used] = value;
} else {
// 循環遍歷數組中的元素,比較新加入的值是否比原來每個元素大,大的話就往前再比
for (int i = a->used - 1; i >= 0; i--) {
// 將 i ~ used -1 下標都要日後移動一位
a->array[i+1] = a->array[i];
if (value >= a->array[i]) {
a->array[i + 1] = value;
break;
} else {
if (i == 0) {
a->array[i] = value;
}
}
}
}
// 加入元素成功,更新used
a->used += 1;
}
}
// 刪,根據下標刪除一個元素
void arrayDelete(Array *a, int index) {
// 判斷下標是否合法
if (index >= a->size || index < 0) {
printf("下標不合法!!!");
} else {
// 從 index + 1 ~ used 位置的元素都須要向前移動
for (int i = index + 1; i < a->used; i ++) {
a->array[i - 1] = a->array[i];
}
// 更新used
a->used -= 1;
}
}
// 改,修改指定下標位置的值
void arrayUpdate(Array *a, int index, int value) {
// 判斷下標是否合法
if (index >= a->used || index < 0) {
printf("下標 = %d 不合法!!!", index);
} else {
if (value != a->array[index]) {
// 先刪掉index位置的元素
arrayDelete(a, index);
// 從新把value加入進來
arrayAdd(a, value);
}
}
}
// 測試 ------------------------------------------------------------------
// 1. 建立一個 10 大小的固定數組
Array a = arrayCreat(10);
// 2.添加元素
printf("-----插入\n");
arrayAdd(&a, -4);
arrayAdd(&a, 8);
arrayAdd(&a, 2);
arrayAdd(&a, 19);
arrayAdd(&a, 4);
arrayAdd(&a, 78);
arrayAdd(&a, 100);
arrayAdd(&a, 11);
arrayAdd(&a, 12);
arrayAdd(&a, 5);
// 插入失敗
arrayAdd(&a, 10);
printf("\n");
// 打印數組元素
for (int i = 0; i < a.used; i++) {
printf("a[%d] = %d\n", i, a.array[i]);
}
// 3. 刪除
printf("-----刪除\n");
arrayDelete(&a, 0);
arrayDelete(&a, 1);
arrayDelete(&a, 2);
arrayDelete(&a, 3);
arrayDelete(&a, 4);
printf("\n");
for (int i = 0; i < a.used; i++) {
printf("a[%d] = %d\n", i, a.array[i]);
}
// 4. 修改
printf("-----修改\n");
arrayUpdate(&a, 0, 10);
arrayUpdate(&a, 5, 50);
printf("\n");
for (int i = 0; i < a.used; i++) {
printf("a[%d] = %d\n", i, a.array[i]);
}
// 釋放空間
arrayFree(&a);
// 打印日誌 --------------------------------------------------------------
-----插入
添加失敗,數組空間已滿!!!
a[0] = -4
a[1] = 2
a[2] = 4
a[3] = 5
a[4] = 8
a[5] = 11
a[6] = 12
a[7] = 19
a[8] = 78
a[9] = 100
-----刪除
a[0] = 2
a[1] = 5
a[2] = 11
a[3] = 19
a[4] = 100
-----修改
下標 = 5 不合法!!!
a[0] = 5
a[1] = 10
a[2] = 11
a[3] = 19
a[4] = 100
Program ended with exit code: 0
複製代碼
// 合併兩個有序數組
Array mergeSortArray(const Array *a, const Array *b) {
Array p;
// 申請內存空間
p.array = (int *)malloc(sizeof(int) * (a->used + b->used));
p.size = a->used + b->used;
p.used = a->used + b->used;
// 長短數組同時遍歷,若是短數組遍歷完了,剩下的就是長數組裏面的數據,直接加上去就行,前面的數據都已經排好序了
int i = 0, j = 0, k = 0;
while (i < a->used && j < b->used) {
if (a->array[i] <= b->array[j]) {
p.array[k++] = a->array[i++];
} else {
p.array[k++] = b->array[j++];
}
}
while (i < a->used) {
p.array[k++] = a->array[i++];
}
while (j < b->used) {
p.array[k++] = b->array[j++];
}
return p;
}
//測試 --------------------------------------------------------
Array a = arrayCreat(10);
Array b = arrayCreat(10);
printf("-----a數組插入數據\n");
arrayAdd(&a, 20);
arrayAdd(&a, 93);
arrayAdd(&a, 3);
arrayAdd(&a, 43);
arrayAdd(&a, 65);
for (int i = 0; i < a.used; i++) {
printf("a[%d] = %d\n", i, a.array[i]);
}
printf("-----b數組插入數據\n");
arrayAdd(&b, 100);
arrayAdd(&b, 125);
arrayAdd(&b, 34);
arrayAdd(&b, 2);
arrayAdd(&b, 11);
arrayAdd(&b, 19);
arrayAdd(&b, 78);
arrayAdd(&b, 89);
for (int i = 0; i < b.used; i++) {
printf("b[%d] = %d\n", i, b.array[i]);
}
printf("數組合並\n");
Array c = mergeSortArray(&a, &b);
for (int i = 0; i < c.used; i++) {
printf("c[%d] = %d\n", i, c.array[i]);
}
arrayFree(&a);
arrayFree(&b);
// 打印日誌 ----------------------------------------------------
-----a數組插入數據
a[0] = 3
a[1] = 20
a[2] = 43
a[3] = 65
a[4] = 93
-----b數組插入數據
b[0] = 2
b[1] = 11
b[2] = 19
b[3] = 34
b[4] = 78
b[5] = 89
b[6] = 100
b[7] = 125
數組合並
c[0] = 2
c[1] = 3
c[2] = 11
c[3] = 19
c[4] = 20
c[5] = 34
c[6] = 43
c[7] = 65
c[8] = 78
c[9] = 89
c[10] = 93
c[11] = 100
c[12] = 125
Program ended with exit code: 0
複製代碼
分享我的技術學習記錄和跑步馬拉松訓練比賽、讀書筆記等內容,感興趣的朋友能夠關注個人公衆號「青爭哥哥」。