【數據結構】堆(Heap)

一、堆的概念及結構

1、概念

堆(Heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵 完全二叉樹數組對象。 堆是非線性數據結構,相當于一維數組,有兩個直接后繼。
如果有一個關鍵碼的集合K = { k?,k?,k? ,k? ,…,k??? ?},把它的所有元素按完全二叉樹的順序存儲方式存儲,在一個一維數組中,并滿足:K? ?<= K? *??? ?且 K? ?<= K? *??? ?(K? ?>= K? *?? ??且 K? ?>= K? *??? ) i = 0,1,2…,則稱為小堆 (或大堆)。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。

大根堆和小根堆】:

根結點最大的堆叫做大根堆,樹中所有父親都大于或等于孩子。

根結點最小的堆叫做小根堆,樹中所有父親都小于或等于孩子。

這個大根堆和小根堆有什么特點呢?

最值總在 0 號位,根據這個特點我們可以做很多事情。比如 TopK 問題 (在一堆數據里面找到前 K 個最大 / 最小的數)。生活中也有很多實例,比如某外賣軟件中有上千家店鋪,我想選出當地好評最多的十家烤肉店,這時我們不用對所有數據進行排序,只需要取出前 K 個最大 / 最小數據。使用堆排序效率也更高。?


2、性質

  • 堆中某個節點的值總是不大于或不小于其父節點的值;
  • 堆總是一棵完全二叉樹


二、堆的實現

1、堆向下調整算法

給出一個數組,邏輯上看做一顆完全二叉樹。我們通過從根節點開始的 向下調整算法 可以把它調整成一個 小堆 向下調整算法有一個前提:左右子樹必須是一個堆包括大堆和小堆) ,才能調整
  • 從根節點開始,不斷往下調。
  • 選出根節點的左右孩子中小的那個孩子,再與父親進行比較。
  • (1)如果父親小于孩子,就不需處理了,整個樹已經是小堆了。
  • (2) 如果父親大于孩子,就跟父親交換位置,并將原來小的孩子的位置當成父親繼續向下進行調整,直到調整到葉子結點為止。
int array[] = {27,15,19,18,28,34,65,49,25,37}; // 根節點的左右子樹都是小堆

// 向下調整算法 -- 調成小堆,把大的節點往下調整
void AdjustDown(int* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 判斷右孩子是否存在 選出左右孩子中小的那個if (child + 1 < size && a[child + 1] < a[child]){++child;}// 孩子跟父親比較if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

【時間復雜度】我們以滿二叉樹計算,最壞情況下,向下調整算法最多進行滿二叉樹的高度減 1 次比較,則說明向下調整算法最多調整滿二叉樹的高度減 1 次,n 個節點的滿二叉樹高度為 log?(n+1),估算后所以時間復雜度為 O(log?n)。?


2、堆的創建

給出一個數組,這個數組邏輯上可以看做一顆完全二叉樹,但還不是一個堆,現在我們可以通過算法,把它構建成一個。根節點左右子樹不是堆,我們怎么調整呢?我們從倒數的第一個非葉子節點的子樹開始調整,一直調整到根節點的樹,就可以調整成堆

為什么要倒著調整呢?

因為這樣我們可以倒數第一個非葉子節點的子樹的左右子樹看成是一個 (大 / 小) 堆,滿足向下調整的前提條件,這時才能去使用向下調整算法。?

// 建大堆
int a[] = {1,5,3,8,7,6};

建堆過程演示(以建大堆為例)從下到上,依次遍歷完所有非葉子節點,分別對每個子樹進行向下調整。依次進行每一步,方框內的樹進行向下調整為一個大堆

調換 1 和 8 的位置時,8 的其左子樹構成的遞歸結構被破壞。因此,在每一次發生元素交換時,都需要遞歸調用重新構造堆的結構。

#include <stdio.h>
#include <stdlib.h>void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下調整算法 -- 建大堆,把小的節點往下調整
void AdjustDown(int* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 判斷右孩子是否存在 選出左右孩子中大的那個if (child + 1 < size && 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 HeapSort(int* a, int n)
{// O(N)// botto-top(自底向上),依次遍歷完所有子樹,分別對其進行調整for (int i=((n-1)-1)/2; i>=0; i--) // 從最后一個葉子節點的父親的下標開始{AdjustDown(a, n, i);}// 升序// O(N*logN)int end = n - 1; // 記錄堆中最后一個元素的下標while (end > 0){Swap(&a[0], &a[end]); // 將堆頂元素和堆中最后一個元素交換,把最大的數(堆頂)放到最后AdjustDown(a, end, 0);--end;}
}

【拓展】堆的創建(采用向上調整法)

下面給出一個數組,這個數組邏輯上可以看做一顆完全二叉樹,但不是一個堆,我們需要通過向上調整算法把它構建成一個堆。如果根節點左右子樹不是一個 (大 / 小) 堆,我們應該怎么調整呢?

我們從上到下第一個節點(也就是根節點)的左孩子開始,依次遍歷完所有節點,分別對每個節點進行向上調整,一直到最后一個節點,就可以建成一個 (大 / 小) 堆

我們把數組中的第一個元素看作是一個剩余的元素依次插入到這個。這跟堆的插入接口原理相同,就是向上調整。

如果堆的創建過程使用向上調整算法,那么每次插入一個新元素時都需要進行一次向上調整操作,以確保新插入的元素能夠滿足堆的性質。

【時間復雜度】

????????假設堆中已經有 n 個元素,那么堆的高度 h = log?(n+1),在插入一個新元素的過程中,需要進行的向上調整操作次數為?h-1,則?h = log?(n+1) -?1,該操作的時間復雜度為O(log?n)。需要注意的是,這個時間復雜度是針對單次 “向上建堆” 操作而言的。如果需要對一個無序數組進行完整的堆排序,則需要進行 n/2 次 “向上建堆”?操作,那么整個堆排序的時間復雜度為 O(nlog n)。所以,如果使用向上調整算法來創建堆排序,那么堆的創建過程的時間復雜度為 O(n*log?n)

? ? ? ?結論:?使用向上調整算法創建堆需要進行多次調整操作,而使用向下調整算法只需要進行一次調整操作。因此,從實際操作的角度來看,使用向下調整算法創建堆更為高效。同時,向下調整算法也更為直觀,容易理解和實現。因此,在實際應用中,一般會選擇使用向下調整算法來創建堆


【堆排序】

利用 堆刪除 思想來進行排序(后文有介紹)
建堆和堆刪除中都用到了 向下調整 ,因此掌握了向下調整,就可以完成堆排序。
(1)升序 -- 建大堆

【思考】排升序,建小堆可以嗎?-- 可以(但不推薦)。
?首先對 n 個數建小堆,選出最小的數,接著對剩下的 n-1 個數建小堆,選出第2小的數,不斷重復上述過程。

【時間復雜度】建 n 個數的堆時間復雜度是 O(N),所以上述操作時間復雜度為 O(N2),效率太低,尤其是當數據量大的時候,效率就更低。同時堆的價值也沒有被體現出來,這樣不如用直接排序。

?【最佳方法】排升序,因為數字依次遞增,需要找到最大的數字,得建大堆。

首先對 n 個數建大堆。將最大的數(堆頂)和最后一個數交換,把最大的數放到最后。前面 n-1 個數的堆結構沒有被破壞(最后一個數不看作在堆里面的),根節點的左右子樹依然是大堆,所以我們進行一次向下調整成大堆即可選出第 2 大的數,放到倒數第二個位置,然后重復上述步驟。

【時間復雜度】:建堆時間復雜度為 O(N),向下調整時間復雜度為 O(log?N),這里我們最多進行N-2 次向下調整,所以堆排序時間復雜度為 O(N*log?N),效率相較而言是很高的。


(2)降序 -- 建小堆(與建大堆同理)

【最佳方法】排降序,因為數字越來越小,需要找到最小的數字,得建小堆。

首先對 n 個數建小堆。將最小的數(堆頂)和最后一個數交換,把最小的數放到最后。前面 n-1 個數的堆結構沒有被破壞(最后一個數不看做堆里面的),根節點的左右子樹依舊是小堆,所以我們進行一次向下調整成小堆即可選出第2小的數,放到倒數第二個位置,然后重復上述步驟。
【時間復雜度】:建堆時間復雜度為 O(N),向下調整時間復雜度為 O(log?N),這里我們最多進行N-2 次向下調整,所以堆排序時間復雜度為O(N*log?N),效率相較而言是很高的。


3、建堆時間復雜度

因為堆是完全二叉樹,而滿二叉樹也是完全二叉樹,此處為了簡化使用滿二叉樹來證明(時間復雜度本來看的就是近似值,多幾個節點不影響最終結果):
等比數列求和公式:
建堆要從倒數第一個非葉子節點開始調整,也即是從倒數第二層開始調,可得出時間復雜度公式:
????????????????????????T ( n ) = ∑ ( 每 層 節 點 數 ? ( 堆 的 高 度 ? 當 前 層 數 ) )?
建堆的時間復雜度為O(N)。(向下調整算法)

4、堆的插入

插入一個 10 到數組的尾上,再進行 向上調整算法 ,直到滿足堆。

5、堆的刪除

刪除堆是刪除堆頂的數據 將堆頂的數據根最后一個數據一換,然后刪除數組最后一個數據,再進行 向下調整算法
  • 將堆頂元素和最后一個元素交換(這樣就變成尾刪了)。
  • 刪除堆中最后一個元素。
  • 從根節點開始,對剩下元素進行向下調整,調成(大 / 小)堆。


6、堆的代碼實現

(1)文件的創建

  • test.c(主函數、測試堆各個接口功能)
  • Heap.c(堆接口函數的實現)
  • Heap.h(堆的類型定義、接口函數聲明、引用的頭文件)

(2)Heap.h 頭文件代碼

// Heap.h
#pragma once#include <stdio.h>
#include<stdlib.h>  // malloc, free
#include <assert.h> // assert
#include <stdbool.h> // bool
#include<string.h>  // memcpytypedef int HPDataType;typedef struct Heap
{HPDataType* a; // 指向動態開辟的數組int size; // 數組中有效元素個數int capacity; // d容量
}Heap;// 交換函數
void Swap(HPDataType* p, HPDataType* q);
// 向下調整函數(調成大堆,把小的往下調)
void AdjustDown(HPDataType* a, int size, int parent);
// 向上調整函數(調成大堆,把大的往上調)
void AdjustUp(HPDataType* a, int child);
// 堆的打印
void HeapPrint(Heap* hp);// 堆的構建
void HeapCreate(Heap* hp, HPDataType* arr, int n);
// 堆的銷毀
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的刪除
void HeapPop(Heap* hp);
// 取堆頂的數據
HPDataType HeapTop(Heap* hp);
// 堆的數據個數
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

(3)接口的實現(以大堆為例)

I.堆的創建(初始化)

堆的初始化一般是使用數組進行初始化的,還需要先實現一個向下調整算法

//交換
void Swap(HPDataType* p, HPDataType* q)
{HPDataType tmp = *p;*p = *q;*q = tmp;
}// 向下調整(調成大堆,把小的往下調)
void AdjustDown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//選出左右孩子中大的那個if (child + 1 < size && 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 HeapCreate(Heap* hp, HPDataType* arr, int n)
{assert(hp);//斷言hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//動態開辟n個空間if (hp->a == NULL){printf("malloc fail\n");exit(-1);}memcpy(hp->a, arr, sizeof(HPDataType) * n);//把給定數組的各元素值拷貝過去hp->size = hp->capacity = n;//建堆(建大堆)int parent = ((hp->size - 1) - 1) / 2; //倒數第一個非葉子節點下標for (int i = parent; i >= 0; i--){AdjustDown(hp->a, hp->size, i);}
}

II.堆的打印?
// 打印
void HeapPrint(Heap* hp)
{assert(hp);for (int i = 0; i < hp->size; i++){printf("%d ", hp->a[i]);}printf("\n");
}

III.堆的銷毀
// 堆的銷毀
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a); // 釋放動態開辟的空間hp->a = NULL;hp->size = hp->capacity = 0;
}

IV.堆的插入

堆的插入數據不分頭插、尾插。將數據插入后,原來堆的屬性不變。先放在數組的最后一個位置,再進行向上調整。堆的插入,首先需要實現一個向上調整算法

//向上調整(調成大堆,把大的往上調)
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 HeapPush(Heap* hp, HPDataType x)
{assert(hp);if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->_capacity = newcapacity;}// 插入元素hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1); // 從插入的元素開始,進行向上調整,保持它依然是堆
}

注意:這里的 while 括號里的條件不能寫成 (parent >= 0),因為 parent 在這里不會小于 0。(假設child 為 0,則 parent = (0-1) / 2 = 0)。


V.堆的刪除

刪除堆頂元素,刪除后保持它依然是堆。

// 堆的刪除
void HeapPop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能為空Swap(&(hp->a[0]), &(hp->a[hp->size - 1])); // 將堆頂元素和最后一個元素交換hp->size--; // 刪除堆中最后一個元素// 從根節點開始,對剩下元素進行向下調整成大堆,保持它依然是堆AdjustDown(hp->a, hp->size, 0); 
}

VI.取堆頂元素
// 取堆頂的數據(最值)
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能為空return hp->a[0];
}

VII.堆的數據個數
// 堆的數據個數
int HeapSize(Heap* hp)
{assert(hp);return hp->size;
}

VIII.堆的判空

判斷堆是否為空,為空返回true,不為空返回false。

// 堆的判空
bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}

(4)代碼整合

// Heap.c
#include "Heap.h"//交換
void Swap(HPDataType* p, HPDataType* q)
{HPDataType tmp = *p;*p = *q;*q = tmp;
}// 打印
void HeapPrint(Heap* hp)
{assert(hp);for (int i = 0; i < hp->size; i++){printf("%d ", hp->a[i]);}printf("\n");
}// 向下調整(調成大堆,把小的往下調)
void AdjustDown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//選出左右孩子中大的那個if (child + 1 < size && 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 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 HeapCreate(Heap* hp, HPDataType* arr, int n)
{assert(hp);//斷言hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//動態開辟n個空間if (hp->a == NULL){printf("malloc fail\n");exit(-1);}memcpy(hp->a, arr, sizeof(HPDataType) * n);//把給定數組的各元素值拷貝過去hp->size = hp->capacity = n;//建堆(建大堆)int parent = ((hp->size - 1) - 1) / 2; //倒數第一個非葉子節點下標for (int i = parent; i >= 0; i--){AdjustDown(hp->a, hp->size, i);}
}// 堆的銷毀
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a); // 釋放動態開辟的空間hp->a = NULL;hp->size = hp->capacity = 0;
}// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->_capacity = newcapacity;}// 插入元素hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1); // 從插入的元素開始,進行向上調整,保持它依然是堆
}// 堆的刪除
void HeapPop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能為空Swap(&(hp->a[0]), &(hp->a[hp->size - 1])); // 將堆頂元素和最后一個元素交換hp->size--; // 刪除堆中最后一個元素// 從根節點開始,對剩下元素進行向下調整成大堆,保持它依然是堆AdjustDown(hp->a, hp->size, 0); 
}// 取堆頂的數據(最值)
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能為空return hp->a[0];
}// 堆的數據個數
int HeapSize(Heap* hp)
{assert(hp);return hp->size;
}// 堆的判空
bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}

(5)程序運行效果


三、堆的應用

【TOP-K問題】?

TOP-K 問題:即求數據結合中前 K 個最大的元素或者最小的元素,一般情況下數據量都比較大。
在生活中,也有不少例子:班級前 10 名、世界 500 強、富豪榜等。
方法一:對于 Top-K 問題,能想到的最簡單直接的方式就是排序,但是:如果數據量非常大,排序就不太可取了(可能數據都不能一下子全部加載到內存中)。
排序 --【時間復雜度為】:O(N*logN)
方法二:建一個 N 個數的堆(優先級隊列),不斷選數,選出前 K 個。
【時間復雜度】:?O(N+K*log(N))
?假設 N 是十億,顯然前兩個方法都不適用。
【最佳方法】-- 方法三:最佳的方式就是用堆來解決,基本思路如下:
1、用數據集合中前 K 個元素來建堆。
  • 前k個最大的元素,則建小堆
  • 前k個最小的元素,則建大堆
2、用剩余的 N-K 個元素依次與堆頂元素來比較,不滿足則替換堆頂元素。將剩余 N-K 個元素依次與堆頂元素比完之后,堆中剩余的 K 個元素就是所求的前 K 個最小或者最大的元素。
// test.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>void PrintTopK(int* a, int n, int k)
{// 1、建堆 -- 用a中前k個元素建堆(0~k-1)int* kMinHeap = (int*)malloc(sizeof(int) * k);assert(kMinHeap);for (int i = 0; i < k; ++i){kMinHeap[i] = a[i];}for (int i = (k - 1 - 1) / 2; i >= 0; --i){AdjustDown(kMinHeap, k, i);}// 2、將剩余n-k個元素依次與堆頂元素交換,不滿則則替換(k~n)for (int j = k; j < n; ++j){if (a[j] > kMinHeap[0]){kMinHeap[0] = a[j];AdjustDown(kMinHeap, k, 0);}}for (int i = 0; i < k; ++i){printf("%d ", kMinHeap[i]);}printf("\n");
}void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);// 創建隨機數種子srand((unsigned int)time(0));for (int i = 0; i < n; ++i){a[i] = rand() % 1000000; // 生成隨機數}// 如果找出這10個數,說明TOP-K算法是正確的a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[531] = 1000000 + 3;a[5121] = 1000000 + 4;a[120] = 1000000 + 5;a[99] = 1000000 + 6;a[0] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}

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

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

相關文章

“深入解析JVM:探索Java虛擬機的內部工作原理“

標題&#xff1a;深入解析JVM&#xff1a;探索Java虛擬機的內部工作原理 摘要&#xff1a;本文將深入解析Java虛擬機&#xff08;JVM&#xff09;的內部工作原理&#xff0c;包括類加載、內存管理、垃圾回收、即時編譯等關鍵概念。通過對這些概念的詳細講解和示例代碼的演示&a…

關于openfeign調用時content-type的問題

問題1描述&#xff1a; 今天在A服務使用openfeign調用B服務的時候&#xff0c;發現經常會偶發性報錯。錯誤如下&#xff1a; 情況為偶發&#xff0c;很讓人頭疼。 兩個接口如下&#xff1a; A服務接口&#xff1a; delayReasonApi.test(student);就是使用openfeign調用B服務的…

K8S常用命令

1.1 查看集群信息&#xff1a; kubectl cluster-info: 顯示集群信息。 kubectl config view: 顯示當前kubectl配置信息。1.2 查看資源狀態&#xff1a; kubectl get pods: 查看所有Pod的狀態。 kubectl get deployments: 查看所有部署的狀態。 kubectl get services: 查看所有…

Php“牽手”shopee商品詳情頁數據采集方法,shopeeAPI接口申請指南

shopee詳情接口 API 是開放平臺提供的一種 API 接口&#xff0c;它可以幫助開發者獲取商品的詳細信息&#xff0c;包括商品的標題、描述、圖片等信息。在電商平臺的開發中&#xff0c;詳情接口API是非常常用的 API&#xff0c;因此本文將詳細介紹詳情接口 API 的使用。 一、sh…

Python接口自動化之request請求封裝

我們在做自動化測試的時候&#xff0c;大家都是希望自己寫的代碼越簡潔越好&#xff0c;代碼重復量越少越好。那么&#xff0c;我們可以考慮將request的請求類型&#xff08;如&#xff1a;Get、Post、Delect請求&#xff09;都封裝起來。這樣&#xff0c;我們在編寫用例的時候…

Python文件操作教程,Python文件操作筆記

文件的打開與關閉 想一想&#xff1a; 如果想用word編寫一份簡歷&#xff0c;應該有哪些流程呢&#xff1f; 打開word軟件&#xff0c;新建一個word文件寫入個人簡歷信息保存文件關閉word軟件 同樣&#xff0c;在操作文件的整體過程與使用word編寫一份簡歷的過程是很相似的…

爬蟲逆向實戰(十三)--某課網登錄

一、數據接口分析 主頁地址&#xff1a;某課網 1、抓包 通過抓包可以發現登錄接口是user/login 2、判斷是否有加密參數 請求參數是否加密&#xff1f; 通過查看“載荷”模塊可以發現有一個password加密參數&#xff0c;還有一個browser_key這個可以寫死不需要關心 請求頭…

【11】Redis學習筆記 (微軟windows版本)【Redis】

注意:官redis方不支持windows版本 只支持linux 此筆記是依托微軟開發windows版本學習 一、前言 Redis簡介&#xff1a; Redis&#xff08;Remote Dictionary Server&#xff09;是一個開源的內存數據結構存儲系統&#xff0c;它也被稱為數據結構服務器。Redis以鍵值對&am…

取證的學習

Volatility命令語法 1.判斷鏡像信息&#xff0c;獲取操作系統類型 Volatility -f xxx.vmem imageinfo 在查到操作系統后如果不確定可以使用以下命令查看 volatility - f xxx.vmem --profile [操作系統] volshell 2.知道操作系統類型后&#xff0c;用–profile指定 volat…

IO和文件系統性能分析工具

以下內容來自于RHEL 官方文檔。以下工具可以用來分析磁盤 IO 和文件系統性能瓶頸。 分析方法見 《性能分析方法-《性能之巔》筆記》&#xff0c;USE 法必須要使用相關性能分析工具。 影響 IO 和文件系統性能的主要因素&#xff1a; 數據寫入或讀取特征 順序或隨機 buffered 或…

基于ssm+mysql智能圖書館導航系統設計與實現

摘 要 電腦的出現是一個時代的進步&#xff0c;不僅僅幫助人們解決了一些數學上的難題&#xff0c;如今電腦的出現&#xff0c;更加方便了人們在工作和生活中對于一些事物的處理。應用的越來越廣泛&#xff0c;通過互聯網我們可以更方便地進行辦公&#xff0c;也能夠在網上就能…

【Oracle 數據庫 SQL 語句 】積累1

Oracle 數據庫 SQL 語句 1、分組之后再合計2、顯示不為空的值 1、分組之后再合計 關鍵字&#xff1a; grouping sets &#xff08;&#xff08;分組字段1&#xff0c;分組字段2&#xff09;&#xff0c;&#xff08;&#xff09;&#xff09; select sylbdm ,count(sylbmc) a…

神經網絡基礎-神經網絡補充概念-20-激活函數

概念 激活函數是神經網絡中的一個重要組成部分&#xff0c;它引入了非線性性質&#xff0c;使得神經網絡可以學習和表示更復雜的函數關系。激活函數對于將輸入信號轉換為輸出信號起到了關鍵作用&#xff0c;它在神經元的計算過程中引入了非線性變換。 幾種常見的激活函數及其…

DR模式 LVS負載均衡群集

數據包流向分析&#xff1a; &#xff08;1&#xff09;客戶端發送請求到 Director Server&#xff08;負載均衡器&#xff09;&#xff0c;請求的數據報文&#xff08;源 IP 是 CIP,目標 IP 是 VIP&#xff09;到達內核空間。 &#xff08;2&#xff09;Director Server 和 Re…

Docker 網絡

目錄 Docker 網絡實現原理 Docker 的網絡模式&#xff1a; 網絡模式詳解&#xff1a; 1&#xff0e;host模式 2&#xff0e;container模式 3&#xff0e;none模式 4&#xff0e;bridge模式 5&#xff0e;自定義網絡 Docker 網絡實現原理 Docker使用Linux橋接&#x…

Linux下如何修改CPU 電源工作模式

最近處理一起歷史遺留問題&#xff0c;感覺很爽。 現象&#xff1a; 背景&#xff1a;設備采用ARM&#xff0c;即rk3568處理器&#xff0c;采用Linux系統&#xff1b;主要用于視覺后端處理 現象&#xff1a;當軟件運行一段時間&#xff0c;大概1個小時&#xff08;也不是很固定…

考研算法第46天: 字符串轉換整數 【字符串,模擬】

題目前置知識 c中的string判空 string Count; Count.empty(); //正確 Count ! null; //錯誤c中最大最小宏 #include <limits.h>INT_MAX INT_MIN 字符串使用發運算將字符加到字符串末尾 string Count; string str "liuda"; Count str[i]; 題目概況 AC代碼…

國內的PMP有多少含金量?

1.PMP是什么 PMP&#xff08;Project Management Professional&#xff09;指項目管理專業人士資格認證。它是由美國項目管理協會&#xff08;PMI&#xff09;舉辦的項目管理專業人員&#xff08;PMP&#xff09;認證考試&#xff0c;在全球190多個國家和地區推廣&#xff0c;…

vue 數字遞增(滾動從0到)

使用 html <Incremental :startVal"0" :endVal"1000" :duration"500" />js&#xff1a; import Incremental from /utils/num/numViewjs let lastTime 0 const prefixes webkit moz ms o.split( ) // 各瀏覽器前綴let requestAnimatio…

[C++] string類的介紹與構造的模擬實現,進來看吧,里面有空調

文章目錄 1、string類的出現1.1 C語言中的字符串 2、標準庫中的string類2.1 string類 3、string類的常見接口說明及模擬實現3.1 string的常見構造3.2 string的構造函數3.3 string的拷貝構造3.4 string的賦值構造 4、完整代碼 1、string類的出現 1.1 C語言中的字符串 C語言中&…