[數據結構——lesson10.堆及堆的調整算法]

引言

上節我們學習完二叉樹后[數據結構——lesson9.二叉樹],這節我們將學習數據結構——

學習目標

1.堆的概念及結構

堆是一種特殊的完全二叉樹結構在計算機科學和數據結構中廣泛應用,特別是在堆排序算法和優先隊列的實現中,堆可以通過數組來表示,這種表示方式利用了完全二叉樹的性質,即除了最后一層外,每一層都被完全填滿,并且所有節點都盡可能地向左對齊。

1.1堆的概念

堆通常是一個可以被看作一棵完全二叉樹的數組對象,若滿足:

(1)任意節點的值>=其子節點的值。則稱為大根堆

(2)任意節點的值<=其子節點的值。則稱為小根堆

1.2堆的結構

  • 邏輯結構:堆是一棵完全二叉樹,除了最后一層外,每一層都被完全填滿,并且所有節點都盡可能地向左對齊
  • 物理結構:堆在物理上通常采用順序存儲,即使用數組來表示。根節點位于數組的第一個位置(下標通常為 0 或 1)。對于下標從 1 開始的數組,任意節點 i 的左子節點為 2i,右子節點為 2i+1對于下標從 0 開始的數組,左子節點為 2i+1,右子節點為 2i+2。通過這種方式,可以利用數組的隨機訪問特性高效地模擬樹的結構。

如圖所示:

?操作系統和數據結構這兩門學科中都有棧和堆的概念,如何區分 ?

數據結構角度

  • :是一種運算受限的線性表2。只允許在表的一端進行插入和刪除操作,遵循后進先出(LIFO)原則。主要操作有入棧(Push)出棧(Pop)。可使用數組或鏈表作為底層數據結構,分別稱為順序棧和鏈式棧。
  • :是一種特殊的樹形數據結構。通常可分為最大堆和最小堆,最大堆中父節點的值大于等于子節點的值,最小堆中父節點的值小于等于子節點的值。主要操作包括插入元素和刪除堆頂元素等,并需重新調整堆的結構。常用于實現優先隊列、堆排序等。

操作系統角度

  • :是進程地址空間中的一塊區域,用于存儲函數調用時的局部變量、函數參數和返回地址等信息。其內存由操作系統自動分配和釋放,函數調用時壓棧,函數返回時彈棧。棧的大小有限,一般每個進程的棧大小相對較小,如 64 位 Linux 系統默認棧大小通常為 10MB。內存地址生長方向是從高地址向低地址增長。
  • :也是進程地址空間中的一部分,用于動態內存分配。程序運行時通過 malloc 或 new 等函數分配內存,大小可變。內存由程序員手動管理,需調用相應函數釋放內存,否則易引發內存泄漏。堆的內存地址通常從低地址向高地址增長,由于是通過分散的內存塊管理,其內存空間在物理上不連續,頻繁操作可能產生內存碎片。

二者聯系

1.3堆的調整算法

在實現堆的結構之前我們先要了解一下堆的調整算法

1.堆的數值交換

思路:堆的交換還是比較簡單的,跟之前寫的沒什么區別,記得傳地址。

void swap(HPDatatype* x, HPDatatype* y)
{int temp = 0;temp = *x;*x = *y;*y = temp;
}

2.堆的向上調整算法?(應用于堆的數據插入

此算法是為了確保插入數據后的堆依然是符合堆的性質而單獨封裝出來的函數,就好比如我們后續要插入的數字10(小堆

  • 基本思路:將新增元素插入到堆數組的末尾,然后將該元素與其父結點進行比較若該元素的值小于父結點的值(小根堆)或大于父結點的值(大根堆),則交換兩者的位置,之后將原父結點當作新的目標結點,繼續向上進行比較和交換操作直到該元素的值不小于父結點的值(小根堆)或不大于父結點的值(大根堆),或者到達根節點為止

為了確保在插入數字10后依然是個小根堆,所以要將10和28交換,依次比較父結點parent和子結點child的大小,當父小于子結點的時候,就返回,反之就一直交換,直到根部while(child>0)截止。

:由前文的得知的規律,parent = (child - 1) / 2,我們操控的是數組,但要把它想象成二叉樹。畫圖演示調整過程:

代碼實現:

// 向上調整函數(沒有改變動態數組的首地址,不需要用指針傳輸)
// 時間復雜度O(logN)
// 傳入了 動態數組  和  最后一個位置的下標(也就是孩子的下標)
// 向上調整的條件;上面的數據是堆
void AdjustUp(HPDatatype* a, int child)
{// 計算父親的小標int parent = (child - 1) / 2;// 當 child 的小標大于 0 就繼續 (也就小于是根節點位置)while (child > 0){// 小堆 <// 大堆 >if (a[child] < a[parent]){swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

核心邏輯:

  • 計算父節點下標:parent = (child - 1) / 2
  • 循環比較子節點與父節點的值
  • 如果子節點值小于父節點值(符合小堆特性),則交換兩者位置
  • 更新子節點和父節點下標,繼續向上比較
  • 當子節點成為根節點(child > 0不成立)或滿足堆特性時,循環結束

3.堆的向下調整算法(應用于堆的數據刪除)

此算法是為了確保刪除數據的堆依然是符合堆的性質而單獨封裝出來的函數,就好比如我們后續要刪除數字10小堆

  • 基本思路首先將堆頂元素與堆中最后一個元素進行交換,然后刪除最后一個元素此時堆頂元素為原來的堆尾元素)。接著從堆頂開始,將堆頂元素與它的子節點進行比較,如果子節點中有比它更小(小根堆)或更大(大根堆)的元素,則將堆頂元素與該子節點交換位置,之后繼續以交換后的位置為新的 “堆頂”,重復上述比較和交換操作,直到滿足堆的性質為止。

如下圖:

(我們這里首先將堆頂元素(10)與堆中最后一個元素進行交換(28),然后刪除最后一個元素(10)

此時我們看到,這個二叉樹整體上不符合堆的性質,但是其根部的左子樹和右子樹均滿足堆的性質。?接下來,就要進行向下調整,確保其最終是個堆。只需三部曲。

?:找出左右孩子中最小的那個

?:跟父親比較,如果比父親小,就交換

?:再從交換的孩子位置繼續往下調整

代碼實現:

// 向下調整
// 向下調整的前提:后面的數據是堆
void AdjustDown(HPDatatype* a, int n, int parent)
{// 左孩子int child = parent * 2 + 1;// 孩子可能會超出數組范圍while (child < n){// 如果右孩子小于左孩子// 找出小的那個孩子// 保證右孩子存在且不越界// 大堆 >// 小堆 <if (child + 1 < n && a[child+1] < a[child]){//跳轉到有孩子那里child++;}// 開始向下調整// 大堆 >// 小堆 <if (a[child] < a[parent]){swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

核心邏輯:

  • 先計算左孩子下標:child = parent * 2 + 1
  • 循環條件:孩子下標小于總元素個數(未超出邊界)
  • 首先判斷是否存在右孩子,并且比較左右孩子大小,選擇較小的那個孩子(小堆特性)
  • 比較父節點與選中的孩子節點值,如果孩子值更小,則交換兩者位置
  • 更新父節點和孩子節點下標,繼續向下比較
  • 當父節點滿足堆特性或沒有孩子節點時,循環結束

2.堆的實現方式

雖然堆是一種特殊的二叉樹,它既可以用數組存儲也可以用鏈式存儲。但是考慮到其完全二叉樹的特性,我們在這里采用數組存儲的方式,因為這樣既方便訪問,也并不會浪費格外的空間。

數組與堆的映射關系(這里實現根的下標為0):

1.若某節點在數組中的下標為i(i從0開始),則其左子節點(若存在)的下標為2i+1,右子節點(若存在)的下標為2i+2,其父節點(若存在)的下標為(i-1)/2。

2.堆的根節點在數組的下標為0。

2.1堆的聲明

由于我們使用數組來實現堆,因此堆的聲明與順序表類似。

2.2堆的功能實現

注意:我們這里實現的是小根堆(大根堆實現原理一樣)

我們要實現的堆的基本功能:

1.堆的初始化

2.堆的插入

3.堆的刪除

4.獲取堆頂的元素

5.堆的元素個數

6.堆的判空

7.銷毀堆

2.2.1堆的初始化
(1)代碼實現
// 堆的初始化
void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}

🧐🧐🧐堆的核心功能

2.2.2堆的插入
(1)代碼實現

思路堆的插入不像先前順序表一般,可以頭插,任意位置插入等等,因為是堆,要符合大根堆或小根堆的性質,不能改變堆原本的結構,所以尾插才是最適合的,并且尾插后還要檢查是否符合堆的性質。

  • 尾插的原因:堆一般使用數組存儲,完全二叉樹的結點從上到下、從左到右依次連續,用數組存儲不會浪費空間。在數組尾部插入數據的時間復雜度是 O (1),效率較高。若采用頭插或其他任意位置插入,會破壞堆的完全二叉樹結構,導致難以高效地維護堆的性質,所以尾插是最適合堆的插入方式。

比如我們有一串數組,該數組是按照小根堆的性質存儲的。現在想在數組尾部插入一個數字10,如圖:

這顆樹在沒插入數字10之前是個小堆,在插入后就不是了,改變了小根堆的性質了。因為子結點10小于其父結點28

首先最基本的,在插入之前就要先判斷該堆的容量是否還夠插入數據,先檢查要不要擴容,擴容完畢后。我們可以發現,插入的10只會影響到從自己本身開始到根,也就是祖先,只要這條路上符合堆的性質,插入即成功

核心思想:向上調整算法。當我們看到插入的10比父親28小時,此時交換數字,但是此時10又要比18小,再次交換,最終發現10比15還小,再次交換,此時結束,到根部了。當然這是最壞的情況,如果在中間換的過程中滿足了堆的性質,那么就不需要再換了,直接返回即可。這就叫向上調整算法,直接套用上面的函數即可。

代碼如下:

// 交換
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 堆向上調整
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
// 堆插入數據
void HPPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}
2.2.3堆的刪除

思路:在上文堆的插入中,我們明確插完依舊是堆,而這里堆的刪除同樣也要確保刪除后依舊是堆,注意:這里堆的刪除是刪除堆頂的數據。以小根堆為例,刪除堆頂的數據,也就是把最小的數據刪掉,那么還要保證依舊是堆

如果我們直接刪除堆頂元素并往前覆蓋就可能打亂原有的親緣關系。所以我們可以先將堆頂的元素與末尾元素交換,然后再進行向下調整·。

  • 首先,把第一個數據和最后一個數據進行交換
  • 交換后,此時的堆就不符合其性質了,因為原先最后一個數據肯定是比第一個數據大的,現在最后一個數據到了堆頂,就不是堆了,但是根結點的左子樹和右子樹不受影響,單獨看它們依舊是堆
  • 接著,--size,確保刪除堆頂數據
  • 因為此時堆頂的數據已經到了堆尾,只需要像順序表那樣--size,確保有效數據減1也就是確保了堆頂的刪除
  • 最后,運用向下調整算法,確保其是堆結構

(1)代碼實現
// 堆向下調整
void AdjustDown(HPDataType* a, int n, int parent)
{// 假設左孩子比右孩子小int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}// 刪除堆頂數據
void HPPop(HP* php)
{assert(php);assert(php->size > 0);// 將堆頂元素(即php->a[0])與堆的最后一個元素交換Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}


5.4.獲取堆頂的元素

思路:直接返回堆頂即可。前提是得斷言size>0

(1)代碼實現
// 讀取堆頂數據
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}
5.6.堆的判空

思路:堆的判空很簡單,跟之前棧順序表一樣,若size為0,直接返回即可。

(1)代碼實現
// 判斷堆是否為空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}
5.7.銷毀堆
(1)代碼實現
// 堆的銷毀
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

5.8堆的構建

對于堆的創建這一塊,有兩種方法,一種是直接利用我們上面所寫的【Init】和【Push】聯合向上調整建堆;另一種則是利用數據拷貝進行向下調整建堆

方法1
  • 首先我們來看第一種。很簡單,就是利用【Init】和【Push】聯合向上調整進行建堆
/*建堆*/
void HeapCreate1(Hp* hp, HpDataType* a, int n)
{assert(hp);HeapInit(hp);for (int i = 0; i < n; ++i){HeapPush(hp, a[i]);}
}

方法2?

  • 接著是第二種,比較復雜一些,不會像【向上調整算法】一樣插入一個調整一個,而是為這個堆的存放數據的地方單獨開辟出一塊空間,然后將數組中的內容拷貝過來,這里使用到了memcpy,不懂的小伙伴可以先去了解一下它的用法

當把這些數據都拿過來之后,我們去整體性地做一個調整,那就不可以做向上調整了,需要去進行一個【向下調整】,我們通過圖解來看看
?

HeapInit(hp);
HpDataType* tmp = (HpDataType*)malloc(sizeof(HpDataType) * n);	//首先申請n個空間用來存放原來的堆
if (tmp == NULL)
{perror("fail malloc");exit(-1);
}
hp->a = tmp;//void * memcpy ( void * destination, const void * source, size_t num );
memcpy(hp->a, a, sizeof(HpDataType) * n);	//將數組a中n個數據拷貝到堆中的數組
hp->size = n;
hp->capacity = n;

  • 可以看到,對于即將要調整的根結點,首先我們要回憶一下向下調整算法的先決條件,就是當要調整的結點的左右子樹均為大堆或者小堆,只有待調整的結點不滿足時,才可以使用這個算法,但是可以看到,【4】下面的兩個子樹均不是大堆(我這里默認建大堆),那有同學說這該怎么辦呢?此時我們應該先去調整其左右子樹,使他們先符合條件才行
  • 然后可以看到左子樹這一邊,當【47】作為要調整的結點時,它的左右子樹依舊不是一個大堆,此時我們需要做的就是再去調整其左右子樹,直到其符合條件為止,那此時我們應該去調整【3】【14】,那還需要再去調整其左右子樹嗎?可以看到【1】和【36】確實也是不符合,但是呢對于葉子結點來說是沒有孩子的,所以調不調整都一個樣,因此我們只需要從倒數第二層開始調整就行,也就是最后一個非葉子結點,即【14】
  • 那要如何去找到和這個【14】呢,這個好辦,我們可以知道它的孩子,就是堆底的末梢元素,那對于數組來說最后一個數據的下標為【n - 1】,在上面有說到過已知孩子結點去求結點其父親結點【(child - 1)/2】,那這里的【child】我們使用【n - 1】帶入即可,然后通過循環來一直進行調整,但是在調整完【14】這棵子樹后要怎么越位到【3】這棵子樹呢,上面說到了,堆存放在一個數組中,因此我們直接將這個【parent - 1】就可以繼續往前調整了。最后直到根節點為止就是我們上面講解【向下調整算法】時的樣子
    //向下調整		
    /*
    * (n - 1)代表取到數組最后一個數據,不可以訪問n
    *  (x - 1)/2 代表通過孩子找父親
    */
    for (int i = ((n - 1) - 1) / 2; i >= 0; --i)
    {Adjust_Down(hp->a, n, i);
    }
    
  • 下面是【向下調整算法建堆】執行的全過程

完整代碼

// 堆結構體定義(底層是動態數組)
typedef int HpDataType;
typedef struct Heap {HpDataType* a;    // 存儲堆元素的數組int size;         // 當前堆中元素個數int capacity;     // 堆的最大容量
} Hp;// Way2:基于數組拷貝+向下調整的建堆函數
void HeapCreate2(Hp* hp, HpDataType* a, int n) {assert(hp);       // 斷言堆指針非空,避免非法訪問HeapInit(hp);     // 先初始化堆(將a置NULL,size和capacity置0)// 步驟1:為堆數組開辟內存(大小為n個HpDataType)HpDataType* tmp = (HpDataType*)malloc(sizeof(HpDataType) * n);if (tmp == NULL) {  // 檢查內存開辟是否成功perror("fail malloc");  // 打印錯誤信息exit(-1);       // 終止程序(內存開辟失敗無法繼續)}hp->a = tmp;        // 將開辟的內存地址賦值給堆的數組指針// 步驟2:拷貝原數組a的n個元素到堆數組hp->a中memcpy(hp->a, a, sizeof(HpDataType) * n);  // 說明:memcpy(dest, src, num),num=元素大小*個數,確保拷貝所有元素// 步驟3:更新堆的size和capacity(此時堆已有n個元素,容量為n)hp->size = n;hp->capacity = n;// 步驟4:從最后一個非葉子節點開始,逐個向下調整// 計算最后一個非葉子節點的下標:(n-2)/2for (int i = ((n - 1) - 1) / 2; i >= 0; --i) {Adjust_Down(hp->a, hp->size, i);  // 對每個節點i執行向下調整}
}// 向下調整算法(核心依賴,建大堆為例)
void Adjust_Down(int* a, int n, int parent) {int child = parent * 2 + 1;  // 先假設左孩子是較大的子節點while (child < n) {  // 循環條件:孩子節點存在(未越界)// 1. 找到左右孩子中較大的那個(需先判斷右孩子是否存在)if (child + 1 < n && a[child + 1] > a[child]) {++child;  // 右孩子存在且更大,切換到右孩子}// 2. 比較父節點與較大孩子:若父節點小,則交換并繼續向下調整if (a[child] > a[parent]) {swap(&a[child], &a[parent]);  // 交換父子節點(swap是自定義交換函數)parent = child;               // 父節點下移到交換后的孩子位置child = parent * 2 + 1;       // 重新計算新的左孩子位置} else {break;  // 父節點 >= 孩子,當前子樹已符合堆性質,退出循環}}
}

Way2 的關鍵優勢:效率遠高于 Way1

Way2 的核心優勢體現在時間復雜度上:

建堆方式時間復雜度核心原因
Way1(向上調整)O(NlogN)每個元素插入時需向上調整,平均調整次數為樹的高度(logN),N 個元素總復雜度為 N*logN
Way2(向下調整)O(N)從最后一個非葉子節點開始調整,底層節點(數量多)調整次數少(1 次),頂層節點(數量少)調整次數多(logN 次),通過錯位相減計算總次數約為 N

完整代碼

Heap.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>typedef int HPDataType;typedef struct Heap 
{HPDataType* a;int size;int capacity;
}HP;void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);
int HeapSize(HP* php);
Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"// 交換
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 堆向上調整
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//堆向下調整
void AdjustDown(HPDataType* a, int n, int parent)
{//先假設左孩子比右孩子小int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//堆的初始化
void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}//堆的銷毀
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}void HPPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}// 刪除堆頂數據
void HPPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}//讀取堆頂數據
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//判斷堆是否為空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}// 獲取堆的個數
int HeapSize(HP* php)
{assert(php);return php->size;
}

測試用例:

void TestHeap()
{HP hp;HPInit(&hp);if (HPEmpty(&hp)){printf("堆空\n");}else{printf("堆非空\n");}int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };for (size_t i = 0; i < sizeof(a) / sizeof(int); i++){HPPush(&hp, a[i]);}if (HPEmpty(&hp)){printf("堆空\n");}else{printf("堆非空\n");}printf("堆的數據個數:%d\n", HeapSize(&hp));while (!HPEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}printf("\n");printf("堆的數據個數:%d\n", HeapSize(&hp));HPDestroy(&hp);
}

輸出結果:

結束語:

本節我們學習了新的二叉樹——堆,接下來還會繼續更新對數據結構-------堆的應用(排序,TopK問題)

感謝您的三連支持!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/99184.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/99184.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/99184.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

九識智能與北控北斗合作研發的L4級燃氣超微量高精準泄漏檢測無人車閃耀服貿會,守護城市安全

2025年9月10日至14日&#xff0c;2025年中國國際服務貿易交易會將于北京首鋼園舉辦。在這場國際盛會上&#xff0c;九識智能與北京北控北斗科技投資有限公司&#xff08;以下簡稱“北控北斗”&#xff09;合作研發的L4級燃氣超微量高精準泄漏檢測無人車及相關系統解決方案&…

【C語言入門】手把手教你實現順序棧

棧是計算機科學中最基礎且重要的數據結構之一&#xff0c;它遵循"后進先出"&#xff08;LIFO&#xff09;的原則。想象一下一疊盤子&#xff0c;你只能從最上面取放&#xff0c;這就是棧的直觀體現。本文將用C語言帶你一步步實現一個順序棧&#xff0c;即使你是編程小…

北斗導航 | ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程

要詳細說明ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程,需結合ARAIM的核心邏輯(多星座融合、多假設解分離、風險優化分配)與LPV-200的嚴格要求(垂直保護級VPL≤35米、垂直告警限VAL=35米、有效監測門限EMT≤15米等),以下是 step-by-step 的…

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展 引言 一、快速上手 1.1 安裝AIPex擴展 1.2 首次配置 1.3 界面介紹 第二章:30+工具詳解 2.1 標簽頁管理工具集 ??? **get_all_tabs - 全局標簽頁概覽** ?? **switch_to_tab - 智能標簽頁切換** ?? **標簽頁批量操作** ?? …

機器學習模型可信度與交叉驗證:通俗講解

先從一個故事說起&#xff1a;農場里的火雞科學家&#xff0c;觀察了一年發現“每天上午11點必有食物”&#xff0c;結果感恩節當天&#xff0c;它沒等到食物&#xff0c;反而成了人類的食物。這個故事告訴我們&#xff1a;只靠過去的經驗下結論&#xff0c;很可能出錯——機器…

HTML5和CSS3新增的一些屬性

1、HTML5新增特性這些新特性都有兼容性問題&#xff0c;基本是IE9以上版本瀏覽器才支持1&#xff09;新增語義化標簽2&#xff09;新增多媒體標簽音頻&#xff1a;<audio>視頻&#xff1a;<video>&#xff08;1&#xff09;視頻<video>---盡量使用mp4格式<…

Redis的RedLock

RedLock算法深度解析RedLock是Redis作者針對分布式環境設計的多節點鎖算法&#xff0c;核心目標是解決單點Redis在分布式鎖場景中的可靠性缺陷。傳統方案的局限性單節點Redis鎖的問題單點故障&#xff1a;單個Redis實例宕機導致所有鎖服務不可用可靠性不足&#xff1a;無法保證…

SpringMVC @RequestMapping的使用演示和細節 詳解

目錄 一、RequestMapping是什么&#xff1f; 二、RequestMapping 的使用演示 1.RequestMapping在方法上的使用&#xff1a; 2.RequestMapping同時在類和方法上使用&#xff1a; 3.RequestMapping指定請求參數&#xff1a; 4.RequestMapping使用Ant風格URL&#xff1a; 5.Requ…

flutter項目 -- 換logo、名稱 、簽名、打包

1、換logo, 透明底&#xff0c;下面5個尺寸&#xff0c;需要UI設計2、換名沒配置型的改名方式如下 打開app/src/main/AndroidManifest.xml3、簽名 運行 flutter doctor -vD:\project\Apk\keystore 自己建立的keystore文件夾&#xff0c; 注意命令后是 megoai-release-key(自…

【貪心算法】day9

&#x1f4dd;前言說明&#xff1a; 本專欄主要記錄本人的貪心算法學習以及LeetCode刷題記錄&#xff0c;按專題劃分每題主要記錄&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代碼&#xff1b;&#xff08;2&#xff09;優質解法 優質代碼&#xff1b;&#xff…

linux C 語言開發 (八) 進程基礎

文章的目的為了記錄使用C語言進行linux 開發學習的經歷。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; linux C 語言開發 (一) Window下用gcc編譯和gdb調試 linux C 語言開發 (二) VsCode遠程開發 linux linux C 語言開發 (…

從零學算法1094

1094.拼車 車上最初有 capacity 個空座位。車 只能 向一個方向行駛&#xff08;也就是說&#xff0c;不允許掉頭或改變方向&#xff09; 給定整數 capacity 和一個數組 trips , trips[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客&#xff0c;接他…

B2B企業營銷型AI Agent服務商推薦:誰更專業?如何選型?

一、引言&#xff1a;為什么B2B企業需要營銷型AI Agent&#xff1f;在當前競爭激烈的B2B市場中&#xff0c;企業普遍面臨幾大挑戰&#xff1a;線索獲取難&#xff1a;獲客成本持續上升&#xff0c;高質量線索難以篩選。銷售周期長&#xff1a;從初步接觸到簽單&#xff0c;往往…

算法-雙指針5.6

目錄 &#x1f33f;力扣611-有效三角形得個數 &#x1f9ca;題目鏈接&#xff1a;https://leetcode.cn/problems/valid-triangle-number/description/ &#x1f9ca;題目描述&#xff1a;?編輯 &#x1f9ca;解題思路&#xff1a; &#x1f9ca;解題代碼&#xff1a; &a…

超參數自動化調優指南:Optuna vs. Ray Tune 對比評測

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;注冊即送-H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;從"手動煉丹"到"自動化…

軟考-局域網基礎考點總結

這篇文章用于整理軟考網絡相關的知識點&#xff0c;囊括了詳細的局域網基礎的考點&#xff0c;能夠讓你認真備考&#xff0c;基礎知識一網打盡&#xff0c;讓后續的學習更加通暢~ 第一部分&#xff1a;OSI七層參考模型 OSI(Open System Interconnection)模型是一個理論框架&am…

Node.js核心模塊介紹

1. fs 模塊fs&#xff08;File System&#xff09;模塊允許對文件系統進行操作&#xff0c;提供了文件讀寫、文件夾操作等功能。fs 支持同步和異步兩種 API。1.1. 常用方法讀取文件&#xff1a;異步: fs.readFile()同步: fs.readFileSync()寫入文件&#xff1a;異步: fs.writeF…

緩存三大劫攻防戰:穿透、擊穿、雪崩的Java實戰防御體系(二)

第二部分&#xff1a;緩存擊穿——熱點key過期引發的“DB瞬間高壓” 緩存擊穿的本質是“某個熱點key&#xff08;高并發訪問&#xff09;突然過期”&#xff0c;導致大量請求在同一時間穿透緩存&#xff0c;集中沖擊DB&#xff0c;形成“瞬間高壓”。 案例3&#xff1a;電商秒殺…

Linux相關概念和易錯知識點(45)(網絡層、網段劃分)

目錄1.網絡層&#xff08;1&#xff09;IP協議頭格式&#xff08;2&#xff09;工作流程2.網段劃分&#xff08;1&#xff09;五類地址&#xff08;2&#xff09;回環地址&#xff08;3&#xff09;網段的特殊地址&#xff08;4&#xff09;網絡建設我們前面暫時跳過了網絡層&a…

transition(過渡)和animation(動畫)——CSS

1.transition過渡可以為一個元素在不同狀態之間進行切換時添加過渡效果&#xff0c;實現不同狀態間的變化效果。通過觸發事件(鼠標懸停、點擊等)&#xff0c;在兩個狀態間切換。1.1 使用語法&#xff1a;transition: [property] [duration] [timing-function] [delay];property…