數據結構中的高級排序算法

希爾排序

你可以將希爾排序理解成——先通過幾次分組的、較小的組間插入排序將原數組變得有序,最后再進行一次序列基本有序的完整插入排序。

#include <stdio.h>#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0]))void print_arr(int arr[], int len) {for (int i = 0; i < len; i++) {printf("%d ", arr[i]);}printf("\n");
}// 希爾排序: 縮小增量排序, 其實就是多人摸牌, 逐漸減少摸牌人數
// 希爾排序中, 增量序列的設計非常重要,這里采取簡單的gap序列: 長度減半..一直減半,直到為1
// gap為1時就是一個在數組元素基本有序情況下的,插入排序
void shell_sort(int arr[], int len) {// 第一個元素是第一個人的初始手牌,一直到第gap個元素都是初始手牌int gap = len >> 1;while (gap > 0) {// 外層for的i仍然代表新摸到的手牌的下標,i從gap開始,直到摸完整個數組元素for (int i = gap; i < len; i++) {// 先記錄一下新手牌的值, 便于后續的插入操作int tmp = arr[i];int j = i - gap;    // 代表每一個人舊手牌的最后一張牌for (; j >= 0; j -= gap) {// 內層for代表 每個人每摸到一張新手牌,都會和原本的舊手牌比較,但由于gap存在,所以需要減去gapif (arr[j] > tmp) { // 注意:不能加=,加了就不穩定了arr[j + gap] = arr[j];  // 將舊手牌中大于新手牌的所有牌都向后移}else{break;  // 只要發現一張舊手牌更小或相等, 就說明已經找到新手牌的插入位置了}}arr[j + gap] = tmp;}print_arr(arr, len);    // 每一輪希爾排序后查看數組排序結果gap >>= 1; // 每一輪希爾排序,增量都減半}
}int main(void) {int arr[] = { 16, 1, 45, 23, 99, 2, 18, 67, 42, 10 };int arr_len = ARR_LEN(arr);shell_sort(arr, arr_len);return 0;
}

時間復雜度:

希爾排序的時間復雜度,和選擇的增量序列有密切的關聯:

若使用希爾本人提出的減半序列,時間復雜度通常會小于O(n2),但在最壞情況也會接近O(n2)

空間復雜度分析:

希爾排序是一種原地排序算法,不需要占用額外內存空間。空間復雜度是O(1)

穩定性分析:

希爾排序顯然不是一種穩定的排序算法,因為它先分組再插入排序的方式,使得相同元素可能會由于分組不同改變位置。很明顯這不是穩定的排序算法。

?歸并排序

歸并排序的分治策略思路大體上如下:

  1. 分解大問題:將一個大數組分解成兩個或更多的子數組,直到每個子數組足夠小,通常是直到每個子數組只包含一個元素或者是空數組。
  2. 解決子問題:數組小到只有一個元素或者沒有元素,那自然是"有序數組",所以這些子問題可以視為被解決了。
  3. 合并:歸并排序的核心在于合并步驟,也可以叫"歸并"操作,它會將兩個有序的子數組合并成一個大的有序數組。這個過程通常需要反復比較元素,比較復雜。

?遞歸分解:

遞歸分解的過程會不停地將大數組分解成兩個小的子數組,這個分解的過程會根據大數組的左右界限求一個中間索引,然后將大數組盡量等分為兩份。所以,遞歸分解的函數,至少需要三個參數:

  1. 遞歸分解的數組arr
  2. 數組分解的左下標left
  3. 數組分解的右下標right

此遞歸分解的函數會將arr數組的[left, right]區間分解成兩個小數組。

于是遞歸的出口就很明顯了是:left >= right,這表示子數組縮小到只包含一個元素或沒有元素時,遞歸將停止。

在計算中索引時,我們將采用一種優化的方案:

  1. 一般情況下,可以直接使用 "(left + right) >> 1" 來直接求中間索引。
  2. 但C語言中int類型可能只占2個字節,此時int類型取值范圍較小,上面的表達式可能會出現數據溢出失真。為避免這種情況發生,我們可以采用表達式"left + (right - left >> 1)"去求中間索引。

合并 操作:

  1. 從左到右輪流比較待合并子數組中的元素,把比較過程中的較小元素存入臨時數組中,直到某個子數組元素為空。
  2. 然后再將存在剩余元素的子數組中的所有元素,輪流放入臨時數組中。
  3. 最后把臨時數組中的元素,復制回原數組。

注:臨時數組的長度和原數組是一致的,且合并過程共有同一套下標索引。

#include <stdio.h>
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))// 打印數組的函數
void print_arr(int arr[], int left, int right) {for (int i = left; i <= right; i++) {printf("%d ", arr[i]);}printf("\n");
}/*
* 合并的思路:
* 1.把左右子數組中元素按照順序合并到臨時數組中,過程類似"穿針引線"
* 2.將排好序的臨時數組元素按照下標賦值給原數組
* 注:臨時數組和原數組共有一套下標
* 傳入函數邏輯上的左右子數組是有序的,相當于合并兩個有序的左右子數組
*/
static void merge(int arr[], int left, int mid, int right, int *tmp) {/** tmp_idx: 用于存放合并結果的臨時數組的開始下標* left_idx: 左子數組的開始下標* right_idx: 右子數組的開始下標*/int tmp_idx = left, left_idx = left, right_idx = mid + 1;// 只要左右子數組同時還有元素while (left_idx <= mid && right_idx <= right) {// 捉對比較左右子數組的元素,按照從小到大放入臨時數組// <=判斷不會改變相同元素的相對位置,是穩定算法。反之則不是穩定算法if (arr[left_idx] <= arr[right_idx]) {tmp[tmp_idx++] = arr[left_idx++];}else {tmp[tmp_idx++] = arr[right_idx++];}}// while結束時,左右子數組必然有一個沒有元素了,此時另一個數組必然還有元素// 也就是說只會有一個數組是空的// 但我們無法確定是哪個數組沒有元素了// 所以我們都判斷一下將左右子數組還剩余的元素取出來while (left_idx <= mid) {// 說明左數組還有元素tmp[tmp_idx++] = arr[left_idx++];}while (right_idx <= right) {// 說明右數組還有元素tmp[tmp_idx++] = arr[right_idx++];}// 將臨時數組中已排序好的元素復制到原始數組中for (int i = left; i <= right; i++) {arr[i] = tmp[i];}// 打印此一輪歸并排序的元素print_arr(arr, left, right);
}/*
* 輔助函數,實現對[left, right]范圍內的數組遞歸分解合并
* left表示遞歸分解的區間起點,right表示遞歸分解區間的終點,是一個閉區間
* 遞歸分解的思路是:
* 對[left, right]區間元素的排序,可以分解成:
* [left, mid]區間,和[mid + 1, right]區間的排序合并
* 遞歸的出口是:
* 如果區間僅有一個元素或沒有元素,遞歸結束
*/
static void divide_merge(int arr[], int left, int right, int *tmp) {// 遞歸的出口if (left >= right) {return;}// 遞歸體// 計算中間索引int mid = left + (right - left >> 1);// 分解,規定左數組是[left, mid]// 右數組是[mid + 1, right]divide_merge(arr, left, mid, tmp);divide_merge(arr, mid + 1, right, tmp);/** 歸并,歸并排序的核心操作* 需要一個臨時數組完成此操作* 這個臨時數組至少要和原先的數組一般大* 有兩種方案:* 1.用全局變量數組或局部變量,該方式簡潔易實現,無需考慮內存回收*   但缺點是*   a.必須編譯時期確定數組長度,無法運行時期動態分配*   b.棧區和數據段都無法創建長數組,在大數據集下容易產生溢出錯誤* 為了解決這兩個缺點,我們可以在堆上動態分配數組*   但同樣也有缺點:*   a.內存管理風險*   b.動態分配數組會帶來額外性能開銷*/merge(arr, left, mid, right, tmp);}void merge_sort(int arr[], int len) {// 臨時數組int *tmp = calloc(len, sizeof(int));if (tmp == NULL) {printf("calloc failed in merge_sort.\n");return;}// 將整個數組進行遞歸分解合并,即完成歸并排序divide_merge(arr, 0, len - 1, tmp);// 不要忘記free釋放資源free(tmp);
}// 測試歸并排序
int main() {int arr[] = { 8, 3, 2, 6, 9, 7, 1, 0, 4, 5 };int arr_size = ARR_SIZE(arr);merge_sort(arr, arr_size);return 0;
}

時間復雜度:

無論原始數組處在什么狀態,歸并排序都會按照既定步驟分解、合并。所以在最好,最壞,平均情況下,歸并排序的時間復雜度都是一樣的,都是O(nlogn)。

歸并排序的時間復雜度分析需要考慮它的兩個主要操作,分解和合并:

  1. 分解過程也就是遞歸調用的過程,這個過程大概分解了log2n次(每次都將數組折半,也就是遞歸的深度)
  2. 在合并的過程中,需要遍歷并比較子數組的元素,然后將它們按順序復制回原數組。每次合并操作的時間復雜度都是O(n),因為它涉及到整個數組的遍歷。合并的次數和分解的次數是一樣的,都是log2n次,所以對于合并操作,總的時間復雜度是O(nlogn)

?空間復雜度:

歸并排序顯然不是一個原地算法。它需要額外的內存空間:

  1. 需要一個與原始數組大小相同的,長度是n的輔助數組來進行合并操作。
  2. 遞歸調用,占用額外的棧空間。因為每次遞歸調用都會將數組分為兩個大致相等的部分,所以每次都將問題的規模減半。遞歸深度大致是log2n

所以空間復雜度是O(n)。

穩定性:

歸并排序是穩定的排序算法。這是因為如果兩個元素相等,歸并排序不會改變它們的相對順序。

快速排序

單向分區

所謂單向分區,指的是快速排序算法在分區的過程中,元素比較和交換的操作是單向進行的,也就是從數組的一端進行到另外一端。

單向分區快速排序算法,具體而言,它的思路是:

  1. 選擇一個基準值(pivot),可以是隨機選擇,也可以是直接選首尾元素。選定基準值后,一般會將pivot交換到數組末尾,這樣做可以簡化分區操作。
  2. 設置一個索引(比如叫idx)來追蹤小于基準值的元素應該插入的位置,一開始idx索引指向數組的首元素。
  3. 遍歷數組進行分區操作:

    1. 從數組首元素開始遍歷整個數組
    2. 如果元素小于基準值,則將該元素與idx位置的元素交換,idx索引加1。
    3. 如果元素大于或等于基準值,則不做任何操作,繼續遍歷下一個元素。
  4. 當遍歷到最后一個元素,也就是pivot時,遍歷結束:

    1. 最后將pivot元素和此時idx索引元素進行交換,完成這一輪分區操作。
    2. 此時pivot左側的元素一定都是小于基準值的。
    3. pivot右側的元素一定都是大于等于基準值的。
  5. 對基準值左右兩邊的子數組遞歸地執行以上步驟,直到每個子數組的大小減少到1或0,此時數組就被完全排序了。

?

#include <stdio.h>
#include <time.h>
#include <stdlib.h>#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define SWAP(arr, i, j) {   \int tmp = arr[i];       \arr[i] = arr[j];        \arr[j] = tmp;           \
}// 打印數組的函數
void print_arr(int arr[], int left, int right) {for (int i = left; i <= right; i++) {printf("%d ", arr[i]);}printf("\n");
}// 分區核心操作實現,返回一輪快排選擇的pivot的下標
int partition(int arr[], int left, int right) {// 1.隨機選擇一個基準值,然后把它先放到數組末尾int pivot_idx = left + rand() % (right - left + 1); // 得到一個[left, right]范圍內的隨機索引int pivot = arr[pivot_idx];SWAP(arr, pivot_idx, right);// 2.設置一個partition_idx索引,指示小于pivot的元素應該插入的位置// 同時該索引最終表示分區的界限索引,所以命名為partition_idxint partition_idx = left;// 3.遍歷整個數組,當元素小于pivot時,將它和partition_idx位置元素交換,partition_idx加1// 希望遍歷結束時,i指向數組末尾的pivot,所以i < rightfor (int i = left; i < right; i++) {if (arr[i] < pivot) {SWAP(arr, i, partition_idx);partition_idx++;}}// 4.遍歷結束后,將pivot元素(最后一個元素)交換到partition_idx位置SWAP(arr, right, partition_idx);printf("此一輪分區操作,選擇的pivot是: %d\n分區結束后的數組是: ", pivot);print_arr(arr, left, right);// 5.返回基準值的位置索引return partition_idx;
}/*
* 輔助函數
* 用于對對[left, right]區間中的元素進行遞歸分區操作
*/
void partition_recursion(int arr[], int left, int right) {// 遞歸出口if (left >= right) {return;}// 遞歸體int idx = partition(arr, left, right);  // 分區操作,找到pivot元素的下標位置partition_recursion(arr, left, idx - 1);partition_recursion(arr, idx + 1, right);
}void quick_sort_one_way(int arr[], int len) {// 初始化隨機數生成器,time(NULL)獲取當前時間戳// 用于生成隨機索引srand(time(NULL));// 調用輔助函數進行遞歸分解partition_recursion(arr, 0, len - 1);
}int main(void) {// 測試單向分區快速排序int arr[] = { 8,3,2,6,9,5 };int len = ARR_SIZE(arr);quick_sort_one_way(arr, len);return 0;
}

時間復雜度分析:平均時間復雜度就是O(nlogn)

空間復雜度分析:在最佳和平均情況下,遞歸深度大約是log2n,空間復雜度是O(logn)。但如果是在最壞情況下,遞歸深度接近n,此時空間復雜度為O(n)

穩定性:快速排序是一種不穩定的排序算法

雙向分區

比起單向分區,雙向分區是更常用的快排分區策略,一般而言當我們提起快速排序,指的都是雙向分區策略的快速排序。

所謂雙向分區,指的是在分區過程中,元素比較和交換操作的方向是,同時從數組的兩端向中間逼近的。

雙向分區快速排序算法,具體而言,它的思路是:

  1. 選擇基準值pivot,基準值可以是一個隨機元素,也可以選擇一個固定元素。然后將基準值元素和首元素交換,這樣做的目的是為了將交換元素操作優化成一個賦值操作。并且要將基準值存儲起來。
  2. 設置兩個索引 low 和 high :

    1. 索引 low 一開始指向數組首元素,它的含義是指示小于基準值的元素應該置于的位置。
    2. 索引 high 一開始指向數組尾元素,它的含義是指示大于等于基準值的元素應該置于的位置。
  3. 率先移動索引high,它從尾元素開始向左移動,目標是找到第一個小于基準值的元素:

    1. 找到該元素后,直接將該元素賦值給low索引位置,也就是覆蓋掉基準值。
    2. 賦值結束后,low索引和high索引都不需要移動。
  4. 然后向右移動索引 low,找到第一個大于等于基準值的元素:

    1. 找到該元素后,直接將該元素賦值給high索引位置
    2. 賦值結束后,low索引和high索引都不需要移動。
  5. 重復過程3和4,直到索引high和low相遇。最后將基準值放入它們相遇的位置。
  6. 于是分區就結束了,基準值到達了排序的最終位置,基準值左邊都是小于基準值的元素,右邊都是大于等于基準值的元素。
  7. 對基準值左右兩邊的子數組遞歸地執行以上步驟,直到每個子數組的大小減少到1或0,此時數組就被完全排序了。
#include <stdio.h>
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))// 打印數組的函數
void print_arr(int arr[], int left, int right) {for (int i = left; i <= right; i++) {printf("%d ", arr[i]);}printf("\n");
}// 快速排序的核心操作: 雙向分區, 也就是確定pivot的最終位置
// 挑選一個基準值,通過雙向分區操作,決定最終的位置,最終位置就是基準值排好序的位置
static int partition(int arr[], int left, int right) {// 1.為了簡化實現,直接挑選首元素為基準值(因為基準值要交換到開頭,所以直接挑選首元素作為基準值,可以減少一步交換)int pivot = arr[left];// 2.初始化兩個索引low和high,分別指向數組兩端int low = left, high = right;// 3.循環遍歷這個數組區間while (low < high) {    // 兩個索引沒有相遇就繼續循環// 在兩個索引沒有相遇的情況下,high索引用于尋找比基準值小的元素while (low < high && arr[high] >= pivot) {high--;}   // while循環結束時,要么兩個索引相遇了,要么high索引已經找到了一個比基準值小的元素arr[low] = arr[high];   // 將這個比基準值小的元素覆蓋到low位置//low++;    該行語句不能加,因為若此時兩個索引相遇結束while,low++將導致相遇的索引不再相遇// 在兩個索引沒有相遇的情況下,low索引用于尋找比基準值大和相等的元素while (low < high && arr[low] < pivot) {low++;}   // while循環結束時,要么兩個索引相遇了,要么low索引已經找到了一個比基準值大或相等的元素arr[high] = arr[low];   // 將這個比基準值大或相等的元素覆蓋到high位置//high--;   該行語句不能加,因為若此時兩個索引相遇結束while,high--將導致相遇的索引不再相遇}   // while循環結束時,說明low和high索引相遇,此時該位置就是pivot應該放置的位置arr[low] = pivot;printf("此一輪分區操作選擇的pivot = %d\n", pivot);print_arr(arr, left, right);return low;
}// 對[left, right]區間進行遞歸分區操作
void partition_recursion(int arr[], int left, int right) {// 遞歸出口if (left >= right) {return;}// 遞歸體int idx = partition(arr, left, right);  // 分區操作,找到pivot下標位置partition_recursion(arr, left, idx - 1);partition_recursion(arr, idx + 1, right);
}void quick_sort_two_way(int arr[], int len) {partition_recursion(arr, 0, len - 1);
}int main(void) {int arr[] = { 8,3,2,6,9,5 };int len = ARR_SIZE(arr);// 測試雙向分區-快速排序quick_sort_two_way(arr, len);return 0;
}

時間復雜度分析:平均時間復雜度就是O(nlogn)

空間復雜度分析:在最佳和平均情況下,遞歸深度大約是log2n,空間復雜度是O(logn)。但如果是在最壞情況下,遞歸深度接近n,此時空間復雜度為O(n)

穩定性:快速排序是一種不穩定的排序算法

?堆排序

上述堆排序算法在具體實現時,要分為兩個步驟:

  1. 將待排序的原始數組,在邏輯上進行第一次堆化的操作
  2. 將大頂堆的根結點元素移到數組末尾,交換首尾元素,邏輯上堆大小減1,以新的根結點進行堆化操作。

這兩個步驟中的核心操作邏輯都是——堆化。

堆化的過程,其實就是自父結點開始,向下檢查左右子樹和這個父結點大小關系的過程:

  1. 如果左子樹大于父結點,那么交換左子樹和父結點
  2. 如果右子樹大于父結點,那么交換右子樹和父結點
  3. 如果出現了交換,那么被交換的左子樹或右子樹就要重新進行堆化操作。
  4. 如果根結點已經是最大值(相等的最大值也算),沒有交換,那么堆化結束。

?

#include <stdio.h>
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define SWAP_ELEMENT(arr, i, j){    \int tmp = arr[i];       \arr[i] = arr[j];        \arr[j] = tmp;       \
}void print_arr(int arr[], int n) {for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}// 該函數會把以root_idx索引元素為根結點的
// 邏輯長度是tree_len的一棵完全二叉樹arr,構建成一個大頂堆
static void heapify(int arr[], int tree_len, int root_idx) {/*堆化操作必然是需要循環來完成的如果對于某個循環,既不清楚循環的次數,循環結束的條件也不太好找到那么可以先寫一個死循環, 然后具體到代碼中再用break,return等結束循環*/while (1) {// 根據根節點的下標,先計算出左右子樹的下標int lchild_idx = (root_idx << 1) + 1;int rchild_idx = (root_idx << 1) + 2;int max_idx = root_idx;     // 先假設根節點就是最大值if (lchild_idx < tree_len && arr[lchild_idx] > arr[max_idx]) {// 如果左子樹存在且左子樹值比假設的最大值要大,那么左子樹下標就是新的最大值下標max_idx = lchild_idx;}if (rchild_idx < tree_len && arr[rchild_idx] > arr[max_idx]) {// 如果右子樹存在且右子樹值比假設的最大值要大,那么右子樹下標就是新的最大值下標max_idx = rchild_idx;}if (max_idx != root_idx) {// 交換左右子樹較大者和根節點的值SWAP_ELEMENT(arr, max_idx, root_idx);// 此時max_idx結點的值就是以前根節點的值,此時由于數據發生了改變,max_idx結點的樹就不一定是大頂堆了// 所以接下來要以max_idx為根節點,繼續構建大頂堆root_idx = max_idx;}else {// 不需要交換了,說明以root_idx為根節點的樹已經是大頂堆了break;}}
}// 第一次將數組構建成大頂堆,自下而上將每一個非葉子結點構建大頂堆
static void first_build_heap(int arr[], int len) {int last_idx = len - 2 >> 1;    //最后一個非葉子結點的下標for (int i = last_idx; i >= 0; i--) {heapify(arr, len, i);}printf("第一次堆化后數組為: \n");print_arr(arr, len);
}
void heap_sort(int arr[], int len) {// 1.將原arr數組構建成大頂堆,第一次構建大頂堆first_build_heap(arr, len);// 2.反復移除根結點元素,然后再重建大頂堆int heap_len = len; // 堆邏輯上的長度,一開始就是數組長度,隨著反復移除重建大頂堆,這個長度會一直減少1while (heap_len > 1) {  // 只要堆還有兩個元素就需要繼續構建移除SWAP_ELEMENT(arr, 0, heap_len - 1);heap_len--;/*堆排序的核心操作:重新構建大頂堆*/heapify(arr, heap_len, 0);  // 堆排序核心操作:堆化printf("重新構建大頂堆后: \n");print_arr(arr, heap_len);}
}int main(void) {int arr[] = { 4, 10, 3, 5, 1 };int len = ARR_SIZE(arr);heap_sort(arr, len);return 0;
}

時間復雜度:堆排序在任何情況下,時間復雜度都是O(nlogn)

空間復雜度:堆排序顯然是一個原地算法,不需要任何額外內存空間,空間復雜度是O(1)

穩定性:堆排序也是一種不穩定的排序算法,在堆化的過程需要交換父節點和左右子樹結點,這個過程非常容易出現改變相同元素位置的情況。

幾種排序算法的應用場景

  1. 選擇排序:建議任何情況都不用。
  2. 冒泡排序:建議任何情況都不用。
  3. 插入排序:適合小數據集,尤其當數據已基本有序時非常好用。
  4. 希爾排序:一般不使用。
  5. 歸并排序:大數據集的場景下,需要穩定排序算法時使用。
  6. 快速排序:大數據集的場景下,通用的排序算法,效率高,但不穩定。
  7. 堆排序:大數據集的場景下,性能均衡的不穩定排序算法,優點是不占用額外內存空間。

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

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

相關文章

計算機視覺最不卷的方向:三維重建學習路線梳理

提到計算機視覺&#xff08;CV&#xff09;&#xff0c;大多數人腦海中會立馬浮現出一個字&#xff1a;“卷”。卷到什么程度呢&#xff1f;2022年秋招CV工程師崗位數下降了16%&#xff0c;但求職人數增加了23%&#xff0c;求職人數與招聘崗位的比例達到了恐怖的15:1&#xff0…

【Docker】Docker環境下快速部署Ollama與Open-WebUI:詳細指南

Docker環境下快速部署Ollama與Open-WebUI&#xff1a;詳細指南 在本篇文章中&#xff0c;我們將深入探討如何在Docker中高效部署 Ollama 和 Open-WebUI&#xff0c;并解決在實際使用中常見的問題&#xff0c;確保你的模型服務穩定高效地運行。 一、Ollama 和 Open-WebUI 快速部…

Vue3學習(組合式API——Watch偵聽器詳解)

目錄 一、Watch偵聽器。 &#xff08;1&#xff09;偵聽單個數據。 &#xff08;2&#xff09;偵聽多個數據。&#xff08;數組寫法&#xff1f;&#xff01;&#xff09; &#xff08;3&#xff09;immediate參數。(立即執行回調) &#xff08;3&#xff09;deep參數。(深層監…

SARIMA-LSTM融合模型對太陽黑子數量預測分析|附智能體數據代碼

全文智能體鏈接&#xff1a;https://tecdat.cn/?p41969 分析師&#xff1a;Peng Fan 本研究以太陽黑子活動數據為研究對象&#xff0c;旨在幫助客戶探索其未來走勢并提供預測分析。首先&#xff0c;通過對數據的清洗和處理&#xff0c;包括離群值的識別與處理以及時間序列的建…

簡單易懂的JavaScript中的this指針

文章目錄 默認綁定 / 隱式綁定如何調整this1.用變量固定this2.箭頭函數2.bind3.call/apply&#xff08;一次性&#xff09; 默認綁定 / 隱式綁定 要找this指針指向誰&#xff0c;我們首先要做的是&#xff1a;找到一個明確的對象&#xff0c;這個對象調用了含有this指針的函數…

Spring MVC數據綁定和響應 你了解多少?

數據綁定的概念 在程序運行時&#xff0c;Spring MVC接收到客戶端的請求后&#xff0c;會根據客戶端請求的參數和請求頭等數據信息&#xff0c;將參數以特定的方式轉換并綁定到處理器的形參中。Spring MVC中將請求消息數據與處理器的形參建立連接的過程就是Spring MVC的數據綁…

BMS工具箱用來執行貝葉斯模型平均(BMA)計算模塊

貝葉斯模型平均&#xff08;Bayesian Model Averaging&#xff0c;BMA&#xff09;是一種用于處理模型不確定性的統計方法&#xff0c;通過結合多個模型的預測結果來提高預測的準確性和魯棒性。在 MATLAB 中&#xff0c;可以使用專門的工具箱&#xff08;如 BMS 工具箱&#xf…

Java內存馬的檢測與發現

【網絡安全】Java內存馬的檢測與發現 一、Java內存馬的現象二、檢測思路三、重點關注類四、檢測方法1. 檢查方法&#xff08;FindShell&#xff09;2. 檢查方法&#xff08;sa-jdi&#xff09;3. 檢查方法&#xff08;arthas-boot&#xff09;4. 檢查方法&#xff08;cop.jar&a…

ISP有感自發

一、黑電平 由于傳感器&#xff0c;即便在無光的情況下&#xff0c;依然會產生微小的暗電流&#xff0c;這些暗電流可能是噪點會影響后期的調試。因此&#xff0c;我們便將這些電流處理為0&#xff0c;成為純黑的顏色。可以在源頭消除這些誤差。 如何矯正黑電平&#xff1a; …

數字信號處理-大實驗1.1

MATLAB仿真實驗目錄 驗證實驗&#xff1a;常見離散信號產生和實現驗證實驗&#xff1a;離散系統的時域分析應用實驗&#xff1a;語音信號的基音周期&#xff08;頻率&#xff09;測定 目錄 一、常見離散信號產生和實現 1.1 實驗目的 1.2 實驗要求與內容 1.3 實驗…

【SSL證書系列】https雙向認證中客戶端認證的原理

HTTPS雙向認證&#xff08;也稱為雙向SSL/TLS認證&#xff09;是一種增強安全性的機制&#xff0c;其中客戶端和服務器都需要驗證彼此的數字證書&#xff0c;以確保雙方身份的真實性。以下是其核心原理和步驟的詳細解析&#xff1a; 一、雙向認證的核心目標 雙向身份驗證&#…

Linux系統編程——fork函數的使用方法

在 Linux 系統編程 中&#xff0c;fork() 函數是創建新進程的關鍵系統調用。fork() 在當前進程&#xff08;父進程&#xff09;中創建一個幾乎完全相同的子進程。子進程和父進程從調用 fork() 的位置繼續執行&#xff0c;但它們是兩個獨立的進程&#xff0c;每個進程都有自己的…

LLMs之ChatGPT:《Connecting GitHub to ChatGPT deep research》翻譯與解讀

LLMs之ChatGPT&#xff1a;《Connecting GitHub to ChatGPT deep research》翻譯與解讀 導讀&#xff1a;這篇OpenAI幫助文檔全面介紹了將GitHub連接到ChatGPT進行深度代碼研究的方法、優勢和注意事項。通過連接GitHub&#xff0c;用戶可以充分利用ChatGPT強大的代碼理解和生成…

flutter 視頻通話flutter_webrtc

flutter 比較熱門的庫 flutter_webrtc | Flutter package agora_rtc_engine | Flutter package 我使用的是flutter_webrtc 下面是官方推薦的demo庫 GitHub - flutter-webrtc/flutter-webrtc-demo: Demo for flutter-webrtc 其中 https://demo.cloudwebrtc.com:8086/ 已經停…

同設備訪問php的多個接口會有先后等待問題

同設備訪問php的多個接口會有先后等待問題 這個現象的核心原因通常與 PHP 的 Session 鎖機制 有關&#xff0c;即使兩個接口表面上無關聯&#xff0c;也可能因共享 Session 導致請求排隊。以下是詳細分析&#xff1a; 關鍵背景&#xff1a;PHP 的 Session 鎖機制 PHP 的 Sessi…

【免殺】C2免殺技術(三)shellcode加密

前言 shellcode加密是shellcode混淆的一種手段。shellcode混淆手段有多種&#xff1a;加密&#xff08;編碼&#xff09;、偏移量混淆、UUID混淆、IPv4混淆、MAC混淆等。 隨著殺毒軟件的不斷進化&#xff0c;其檢測方式早已超越傳統的靜態特征分析。現代殺軟往往會在受控的虛…

【論文閱讀】Dip-based Deep Embedded Clustering with k-Estimation

摘要 近年來,聚類與深度學習的結合受到了廣泛關注。無監督神經網絡,如自編碼器,能夠自主學習數據集中的關鍵結構。這一思想可以與聚類目標結合,實現對相關特征的自動學習。然而,這類方法通常基于 k-means 框架,因此繼承了諸如聚類呈球形分布等各種假設。另一項常見假設(…

.NET8關于ORM的一次思考

文章目錄 前言一、思路二、實現ODBC>SqlHelper.cs三、數據對象實體化四、SQL生成SqlBuilder.cs五、參數注入 SqlParameters.cs六、反射 SqlOrm.cs七、自定義數據查詢八、總結 前言 琢磨著在.NET8找一個ORM&#xff0c;對比了最新的框架和性能。 框架批量操作性能SQL控制粒…

CVE-2025-31258 macOS遠程視圖服務沙箱逃逸漏洞PoC已公開

蘋果公司近日針對macOS系統中新披露的CVE-2025-31258漏洞發布補丁&#xff0c;該漏洞可能允許惡意應用程序突破沙箱限制&#xff0c;獲取未授權的系統資源訪問權限。在安全研究員Seo Hyun-gyu公開概念驗證&#xff08;PoC&#xff09;利用代碼后&#xff0c;該漏洞已在macOS Se…

21.第二階段x64游戲實戰-分析采集物偏移

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 本次游戲沒法給 內容參考于&#xff1a;微塵網絡安全 上一個內容&#xff1a;20.第二階段x64游戲實戰-代碼實現遍歷周圍 上一個內容里把遍歷周圍的npc和玩家…