《算法導論》第 16 章 - 貪心算法

?????????大家好!今天我們來深入探討《算法導論》第 16 章的核心內容 —— 貪心算法。貪心算法是一種在每一步選擇中都采取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是全局最好或最優的算法。它在許多優化問題中都有廣泛應用,如活動安排、哈夫曼編碼、最小生成樹等。

????????接下來,我們將按照教材的結構,逐一講解各個知識點,并提供完整的 C++ 代碼實現,幫助大家更好地理解和應用貪心算法。

16.1 活動選擇問題

????????活動選擇問題是貪心算法的經典應用案例,問題描述如下:

????????問題:?假設有 n 個活動,這些活動共享同一個資源(如會議室),而這個資源在同一時間只能供一個活動使用。每個活動 i 都有一個開始時間 s [i] 和結束時間 f [i],其中 0 ≤ s [i] < f [i] < ∞。如果兩個活動的時間區間不重疊,則它們是兼容的。活動選擇問題就是要選擇出一個最大的兼容活動集。

貪心策略分析

對于活動選擇問題,有多種可能的貪心策略:

  • 選擇最早開始的活動
  • 選擇最短持續時間的活動
  • 選擇最晚開始的活動
  • 選擇最早結束的活動

????????經過分析,選擇最早結束的活動這一策略能夠得到最優解。其核心思想是:盡早結束當前活動,以便為后續活動留出更多時間。

代碼實現

下面是活動選擇問題的 C++ 實現,包括遞歸版本和迭代版本:

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 活動結構體:開始時間、結束時間、活動編號
struct Activity {int start;int finish;int index;
};// 按結束時間升序排序的比較函數
bool compareFinish(const Activity& a, const Activity& b) {return a.finish < b.finish;
}/*** 迭代版本的活動選擇算法* @param activities 活動列表* @return 選中的活動索引列表*/
vector<int> selectActivitiesIterative(vector<Activity>& activities) {// 按結束時間排序sort(activities.begin(), activities.end(), compareFinish);vector<int> selected;int n = activities.size();// 選擇第一個活動selected.push_back(activities[0].index);int lastSelected = 0;// 依次檢查后續活動for (int i = 1; i < n; i++) {// 如果當前活動的開始時間 >= 最后選中活動的結束時間,則選擇該活動if (activities[i].start >= activities[lastSelected].finish) {selected.push_back(activities[i].index);lastSelected = i;}}return selected;
}/*** 遞歸版本的活動選擇算法* @param activities 活動列表(已排序)* @param index 當前檢查的索引* @param n 活動總數* @param selected 已選中的活動索引列表*/
void selectActivitiesRecursive(const vector<Activity>& activities, int index, int n, vector<int>& selected) {// 找到第一個與最后選中活動兼容的活動int i;for (i = index; i < n; i++) {if (i == 0 || activities[i].start >= activities[selected.back()].finish) {break;}}if (i < n) {// 選擇該活動selected.push_back(activities[i].index);// 遞歸選擇剩余活動selectActivitiesRecursive(activities, i + 1, n, selected);}
}int main() {// 示例活動數據vector<Activity> activities = {{1, 4, 1},   // 活動1: 1-4{3, 5, 2},   // 活動2: 3-5{0, 6, 3},   // 活動3: 0-6{5, 7, 4},   // 活動4: 5-7{3, 9, 5},   // 活動5: 3-9{5, 9, 6},   // 活動6: 5-9{6, 10, 7},  // 活動7: 6-10{8, 11, 8},  // 活動8: 8-11{8, 12, 9},  // 活動9: 8-12{2, 14, 10}, // 活動10: 2-14{12, 16, 11} // 活動11: 12-16};cout << "所有活動(索引: 開始時間-結束時間):" << endl;for (const auto& act : activities) {cout << "活動" << act.index << ": " << act.start << "-" << act.finish << endl;}// 測試迭代版本vector<int> selectedIter = selectActivitiesIterative(activities);cout << "\n迭代版本選擇的活動索引:";for (int idx : selectedIter) {cout << idx << " ";}cout << endl;// 測試遞歸版本(需要先排序)sort(activities.begin(), activities.end(), compareFinish);vector<int> selectedRecur;selectActivitiesRecursive(activities, 0, activities.size(), selectedRecur);cout << "遞歸版本選擇的活動索引:";for (int idx : selectedRecur) {cout << idx << " ";}cout << endl;return 0;
}

代碼說明

  1. 首先定義了Activity結構體來存儲活動的開始時間、結束時間和索引
  2. 實現了按結束時間排序的比較函數compareFinish
  3. 迭代版本算法:
    • 先對活動按結束時間排序
    • 選擇第一個活動,然后依次選擇與最后選中活動兼容的活動
  4. 遞歸版本算法:
    • 在已排序的活動列表中,找到第一個與最后選中活動兼容的活動
    • 遞歸處理剩余活動
  5. 主函數中提供了示例數據,并測試了兩種版本的算法

????????運行上述代碼,會輸出選中的活動索引,兩種方法得到的結果是一致的。這驗證了貪心算法在活動選擇問題上的有效性。

16.2 貪心算法原理

????????貪心算法通過一系列選擇來構造問題的解。在每一步,它選擇在當前看來最好的選項,而不考慮過去的選擇,也不擔心未來的后果。這種 "短視" 的選擇策略在某些問題上能夠得到最優解。

貪心算法的核心要素

要使用貪心算法解決問題,通常需要滿足以下兩個關鍵性質:

  1. 貪心選擇性質:全局最優解可以通過一系列局部最優選擇(即貪心選擇)來得到。也就是說,在做出選擇時,我們只需要考慮當前狀態下的最優選擇,而不需要考慮子問題的解。

  2. 最優子結構性質:問題的最優解包含其子問題的最優解。如果我們做出了一個貪心選擇,那么剩下的問題可以轉化為一個更小的子問題,而這個子問題的最優解可以與我們的貪心選擇結合,形成原問題的最優解。

貪心算法與動態規劃的對比

貪心算法和動態規劃都用于解決具有最優子結構的問題,但它們的策略不同:

特性貪心算法動態規劃
決策方式每次選擇局部最優,不回溯自底向上或自頂向下計算,保存子問題解
適用問題具有貪心選擇性質的問題所有具有最優子結構的問題
時間復雜度通常較低(O (n log n) 或 O (n))較高(通常為多項式時間)
例子活動選擇、哈夫曼編碼最長公共子序列、背包問題

綜合案例:找零問題

????????問題描述:假設貨幣系統有面額為 25、10、5、1 的硬幣,如何用最少的硬幣數找給顧客一定金額的零錢?

貪心策略:每次選擇不超過剩余金額的最大面額硬幣,直到找零完成。

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;/*** 貪心算法解決找零問題* @param denominations 硬幣面額(從大到小排序)* @param amount 需要找零的金額* @return 每種硬幣的使用數量*/
vector<int> makeChange(const vector<int>& denominations, int amount) {vector<int> counts(denominations.size(), 0);int remaining = amount;for (int i = 0; i < denominations.size() && remaining > 0; i++) {// 選擇當前最大面額的硬幣counts[i] = remaining / denominations[i];// 更新剩余金額remaining -= counts[i] * denominations[i];}// 如果還有剩余金額,說明無法用給定面額找零if (remaining > 0) {return {}; // 返回空向量表示無法找零}return counts;
}int main() {// 硬幣面額(從大到小排序)vector<int> denominations = {25, 10, 5, 1};int amount;cout << "請輸入需要找零的金額:";cin >> amount;vector<int> counts = makeChange(denominations, amount);if (counts.empty()) {cout << "無法用給定的硬幣面額完成找零" << endl;} else {cout << "找零 " << amount << " 需要的硬幣數量:" << endl;int total = 0;for (int i = 0; i < denominations.size(); i++) {if (counts[i] > 0) {cout << denominations[i] << "分:" << counts[i] << "枚" << endl;total += counts[i];}}cout << "總共需要:" << total << "枚硬幣" << endl;}return 0;
}

代碼說明

  1. 算法先對硬幣面額從大到小排序(示例中已預先排序)
  2. 對于每種面額,計算最多能使用多少枚而不超過剩余金額
  3. 更新剩余金額,繼續處理下一面額
  4. 如果最終剩余金額為 0,則返回各面額的使用數量;否則表示無法找零

????????注意:貪心算法并非在所有貨幣系統中都能得到最優解。例如,如果貨幣面額為 25、10、1,要找 30 元,貪心算法會選擇 25+1+1+1+1+1(6 枚),但最優解是 10+10+10(3 枚)。因此,貪心算法的適用性取決于問題本身的特性。

16.3 赫夫曼編碼

????????赫夫曼編碼(Huffman Coding)是一種用于數據壓縮的貪心算法,由 David A. Huffman 于 1952 年提出。它通過為出現頻率較高的字符分配較短的編碼,為出現頻率較低的字符分配較長的編碼,從而實現數據的高效壓縮。

赫夫曼編碼的基本概念

  1. 前綴碼:一種編碼方式,其中任何一個字符的編碼都不是另一個字符編碼的前綴。這種特性使得我們可以無需分隔符就能解碼。

  2. 二叉樹表示:赫夫曼編碼通常用二叉樹表示,左分支表示 0,右分支表示 1,樹葉表示字符。

  3. 構建過程:基于字符出現的頻率構建赫夫曼樹,頻率高的字符離根節點近(編碼短),頻率低的字符離根節點遠(編碼長)。

代碼實現

下面是赫夫曼編碼的完整 C++ 實現:

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <string>
#include <memory>using namespace std;// 赫夫曼樹節點
struct HuffmanNode {char data;           // 字符int freq;            // 頻率shared_ptr<HuffmanNode> left, right; // 左右孩子HuffmanNode(char data, int freq) : data(data), freq(freq), left(nullptr), right(nullptr) {}
};// 優先隊列的比較函數(最小堆)
struct CompareNodes {bool operator()(const shared_ptr<HuffmanNode>& a, const shared_ptr<HuffmanNode>& b) {return a->freq > b->freq; // 頻率小的節點優先級高}
};/*** 構建赫夫曼樹* @param freq 字符頻率映射* @return 赫夫曼樹的根節點*/
shared_ptr<HuffmanNode> buildHuffmanTree(const unordered_map<char, int>& freq) {// 創建優先隊列(最小堆)priority_queue<shared_ptr<HuffmanNode>, vector<shared_ptr<HuffmanNode>>, CompareNodes> pq;// 為每個字符創建葉節點并加入隊列for (const auto& pair : freq) {pq.push(make_shared<HuffmanNode>(pair.first, pair.second));}// 構建赫夫曼樹while (pq.size() > 1) {// 提取兩個頻率最小的節點auto left = pq.top();pq.pop();auto right = pq.top();pq.pop();// 創建新的內部節點auto internal = make_shared<HuffmanNode>('$', left->freq + right->freq);internal->left = left;internal->right = right;// 將新節點加入隊列pq.push(internal);}// 剩下的節點是根節點return pq.top();
}/*** 遞歸生成赫夫曼編碼* @param root 赫夫曼樹的根節點* @param currentCode 當前編碼* @param huffmanCodes 存儲生成的編碼*/
void generateCodes(const shared_ptr<HuffmanNode>& root, string currentCode, unordered_map<char, string>& huffmanCodes) {if (!root) return;// 如果是葉節點,保存編碼if (root->data != '$') {huffmanCodes[root->data] = currentCode.empty() ? "0" : currentCode;return;}// 遞歸處理左右子樹generateCodes(root->left, currentCode + "0", huffmanCodes);generateCodes(root->right, currentCode + "1", huffmanCodes);
}/*** 編碼字符串* @param str 原始字符串* @param huffmanCodes 赫夫曼編碼映射* @return 編碼后的二進制字符串*/
string encodeString(const string& str, const unordered_map<char, string>& huffmanCodes) {string encoded;for (char c : str) {encoded += huffmanCodes.at(c);}return encoded;
}/*** 解碼二進制字符串* @param encoded 編碼后的二進制字符串* @param root 赫夫曼樹的根節點* @return 解碼后的原始字符串*/
string decodeString(const string& encoded, const shared_ptr<HuffmanNode>& root) {string decoded;auto current = root;for (char bit : encoded) {if (bit == '0') {current = current->left;} else {current = current->right;}// 如果到達葉節點,添加字符并重置當前節點if (!current->left && !current->right) {decoded += current->data;current = root;}}return decoded;
}/*** 計算字符頻率* @param str 輸入字符串* @return 字符頻率映射*/
unordered_map<char, int> calculateFrequency(const string& str) {unordered_map<char, int> freq;for (char c : str) {freq[c]++;}return freq;
}int main() {string str = "this is an example for huffman encoding";cout << "原始字符串: " << str << endl;// 計算字符頻率auto freq = calculateFrequency(str);cout << "\n字符頻率:" << endl;for (const auto& pair : freq) {cout << "'" << pair.first << "': " << pair.second << endl;}// 構建赫夫曼樹auto root = buildHuffmanTree(freq);// 生成赫夫曼編碼unordered_map<char, string> huffmanCodes;generateCodes(root, "", huffmanCodes);cout << "\n赫夫曼編碼:" << endl;for (const auto& pair : huffmanCodes) {cout << "'" << pair.first << "': " << pair.second << endl;}// 編碼字符串string encoded = encodeString(str, huffmanCodes);cout << "\n編碼后的字符串: " << encoded << endl;// 解碼字符串string decoded = decodeString(encoded, root);cout << "解碼后的字符串: " << decoded << endl;// 計算壓縮率double originalSize = str.size() * 8; // 假設每個字符8位double compressedSize = encoded.size();double compressionRatio = 100 - (compressedSize / originalSize * 100);cout << "\n壓縮率: " << compressionRatio << "%" << endl;return 0;
}

代碼說明

  1. HuffmanNode 結構體:表示赫夫曼樹的節點,包含字符、頻率和左右孩子指針
  2. 構建赫夫曼樹
    • 使用優先隊列(最小堆)存儲節點
    • 反復提取兩個頻率最小的節點,創建新的內部節點,直到只剩下一個節點(根節點)
  3. 生成編碼:通過遞歸遍歷赫夫曼樹,為每個字符生成二進制編碼(左 0 右 1)
  4. 編碼與解碼
    • 編碼:將原始字符串轉換為二進制編碼字符串
    • 解碼:將二進制編碼字符串還原為原始字符串
  5. 輔助功能:計算字符頻率、計算壓縮率等

????????運行上述代碼,可以看到赫夫曼編碼如何為頻率高的字符分配較短的編碼,從而實現數據壓縮。

16.4 擬陣和貪心算法

????????擬陣(Matroid)是一種組合結構,它為我們提供了一個理解貪心算法有效性的通用框架。許多可以用貪心算法解決的問題都可以建模為擬陣上的優化問題。

代碼實現(基于擬陣的活動選擇)

我們可以用擬陣理論來重新實現活動選擇問題,展示擬陣與貪心算法的關系:

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_set>using namespace std;// 活動結構體
struct Activity {int start;int finish;int index;int weight; // 活動權重,用于加權活動選擇問題
};// 按權重降序排序
bool compareWeight(const Activity& a, const Activity& b) {return a.weight > b.weight;
}// 檢查活動集是否獨立(即是否存在沖突)
bool isIndependent(const vector<Activity>& activities, const unordered_set<int>& selectedIndices) {vector<Activity> selected;for (int idx : selectedIndices) {selected.push_back(activities[idx]);}// 檢查所有選中的活動是否兩兩不沖突for (int i = 0; i < selected.size(); i++) {for (int j = i + 1; j < selected.size(); j++) {// 如果兩個活動時間重疊,則不獨立if (!(selected[i].finish <= selected[j].start || selected[j].finish <= selected[i].start)) {return false;}}}return true;
}/*** 基于擬陣的貪心算法解決加權活動選擇問題* @param activities 活動列表* @return 選中的活動索引集合*/
unordered_set<int> maxWeightIndependentSet(vector<Activity>& activities) {// 按權重降序排序sort(activities.begin(), activities.end(), compareWeight);unordered_set<int> selected;// 依次考慮每個活動for (int i = 0; i < activities.size(); i++) {// 嘗試加入當前活動selected.insert(i);// 檢查是否保持獨立性if (!isIndependent(activities, selected)) {// 如果不獨立,移除該活動selected.erase(i);}}return selected;
}int main() {// 帶權重的示例活動數據vector<Activity> activities = {{1, 4, 1, 3},   // 活動1: 1-4, 權重3{3, 5, 2, 2},   // 活動2: 3-5, 權重2{0, 6, 3, 4},   // 活動3: 0-6, 權重4{5, 7, 4, 5},   // 活動4: 5-7, 權重5{3, 9, 5, 1},   // 活動5: 3-9, 權重1{5, 9, 6, 6},   // 活動6: 5-9, 權重6{6, 10, 7, 7},  // 活動7: 6-10, 權重7{8, 11, 8, 8},  // 活動8: 8-11, 權重8{8, 12, 9, 9},  // 活動9: 8-12, 權重9{2, 14, 10, 10}, // 活動10: 2-14, 權重10{12, 16, 11, 11} // 活動11: 12-16, 權重11};// 應用基于擬陣的貪心算法auto selected = maxWeightIndependentSet(activities);// 計算總權重int totalWeight = 0;for (int idx : selected) {totalWeight += activities[idx].weight;}cout << "選中的活動(索引):";for (int idx : selected) {cout << activities[idx].index << " ";}cout << endl;cout << "選中的活動詳情:" << endl;for (int idx : selected) {const auto& act = activities[idx];cout << "活動" << act.index << ": " << act.start << "-" << act.finish << ", 權重: " << act.weight << endl;}cout << "總權重:" << totalWeight << endl;return 0;
}

代碼說明

  1. 這個例子實現了加權活動選擇問題,與 16.1 節的問題不同,這里每個活動都有一個權重,我們的目標是選擇總權重最大的兼容活動集。

  2. 我們將這個問題建模為擬陣問題:

    • 集合?S?是所有活動的集合
    • 獨立集?\(\mathcal{I}\)?是所有不沖突的活動子集(即任意兩個活動時間不重疊)
  3. 算法步驟:

    • 將活動按權重降序排序
    • 依次考慮每個活動,如果加入后仍保持獨立性(即與已選活動不沖突),則選中該活動
    • 最終得到的集合是總權重最大的獨立集
  4. isIndependent函數用于檢查一個活動集合是否是獨立集(即不包含沖突的活動)

????????這個例子展示了擬陣理論如何為貪心算法提供理論基礎,以及如何將實際問題建模為擬陣上的優化問題。

16.5 用擬陣求解任務調度問題

????????任務調度是計算機科學中的一個重要問題,我們可以利用擬陣理論和貪心算法來求解某些類型的任務調度問題。

問題描述

考慮一個單處理器的任務調度問題:

代碼實現

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_set>using namespace std;// 任務結構體
struct Task {int id;         // 任務IDint t;          // 處理時間int w;          // 權重int d;          // 截止時間
};// 按權重/處理時間比值降序排序
bool compareRatio(const Task& a, const Task& b) {// 避免浮點數運算,使用交叉相乘比較return (long long)a.w * b.t > (long long)b.w * a.t;
}// 按截止時間升序排序
bool compareDeadline(const Task& a, const Task& b) {return a.d < b.d;
}/*** 檢查任務集是否可以被可行調度* @param tasks 要檢查的任務集* @return 如果可以被可行調度,返回true;否則返回false*/
bool isFeasible(vector<Task> tasks) {// 按截止時間升序排序sort(tasks.begin(), tasks.end(), compareDeadline);int totalTime = 0;for (const auto& task : tasks) {totalTime += task.t;// 檢查前綴和是否超過當前任務的截止時間if (totalTime > task.d) {return false;}}return true;
}/*** 用擬陣和貪心算法求解任務調度問題* @param tasks 所有任務的列表* @return 選中的任務集合*/
vector<Task> scheduleTasks(vector<Task>& tasks) {// 按權重/處理時間比值降序排序sort(tasks.begin(), tasks.end(), compareRatio);vector<Task> selected;// 依次考慮每個任務for (const auto& task : tasks) {// 嘗試加入當前任務selected.push_back(task);// 檢查是否仍能可行調度if (!isFeasible(selected)) {// 如果不可行,移除該任務selected.pop_back();}}return selected;
}int main() {// 示例任務數據vector<Task> tasks = {{1, 3, 10, 6},   // 任務1: t=3, w=10, d=6{2, 2, 20, 4},   // 任務2: t=2, w=20, d=4{3, 1, 5, 3},    // 任務3: t=1, w=5, d=3{4, 4, 40, 10},  // 任務4: t=4, w=40, d=10{5, 2, 15, 5},   // 任務5: t=2, w=15, d=5{6, 5, 25, 15}   // 任務6: t=5, w=25, d=15};// 應用調度算法vector<Task> scheduled = scheduleTasks(tasks);// 計算總權重int totalWeight = 0;for (const auto& task : scheduled) {totalWeight += task.w;}// 按截止時間排序以顯示調度順序sort(scheduled.begin(), scheduled.end(), compareDeadline);cout << "選中的任務(按調度順序):" << endl;int currentTime = 0;for (const auto& task : scheduled) {cout << "任務" << task.id << ": 開始時間=" << currentTime << ", 結束時間=" << currentTime + task.t << ", 截止時間=" << task.d << ", 權重=" << task.w << endl;currentTime += task.t;}cout << "總權重:" << totalWeight << endl;return 0;
}

代碼說明

  1. 這個實現解決了帶截止時間和權重的單處理器任務調度問題,目標是最大化總權重同時滿足所有截止時間約束。

  2. 算法核心步驟:

    • 按權重 / 處理時間比值降序排序任務(優先考慮 "單位時間價值" 高的任務)
    • 依次嘗試添加任務,每次添加后檢查是否仍能找到可行的調度方案
    • 可行調度檢查:按截止時間排序任務,確保所有前綴和(累計處理時間)不超過對應任務的截止時間
  3. 最終輸出的是按截止時間排序的任務,這代表了一種可行的調度順序。

????????這個例子展示了如何將實際問題建模為擬陣問題,并應用貪心算法求解,體現了擬陣理論在貪心算法中的基礎作用。

思考題

  1. 活動選擇問題中,如果活動有不同的權重,我們希望選擇總權重最大的兼容活動集,此時 16.1 節的貪心算法是否仍然適用?為什么?

  2. 證明赫夫曼編碼產生的是最優前綴碼。

  3. 考慮 0-1 背包問題:有 n 個物品,每個物品有重量 w_i 和價值 v_i,背包容量為 W,如何選擇物品使得總價值最大且總重量不超過 W。為什么貪心算法(如按價值 / 重量比排序)不能保證得到最優解?這個問題是否可以建模為擬陣問題?

  4. 設計一個基于貪心算法的算法,用于求解區間圖的最小頂點覆蓋問題。

  5. 考慮一個有向圖的最短路徑問題,從源點到其他所有頂點的最短路徑。Dijkstra 算法是一種貪心算法,它的貪心策略是什么?為什么在存在負權邊的情況下 Dijkstra 算法可能失效?

本章注記

????????貪心算法是一種強大而簡潔的算法設計技術,它通過一系列局部最優選擇來構建全局最優解。本章介紹了貪心算法的基本原理、經典應用以及理論基礎(擬陣)。

????????貪心算法的歷史可以追溯到 20 世紀 50 年代,赫夫曼編碼算法是早期著名的貪心算法之一。擬陣理論則是由 Whitney 于 1935 年提出,后來被 Edmonds 等學者應用于貪心算法的理論分析。

除了本章介紹的應用外,貪心算法還廣泛應用于:

  • 最小生成樹算法(Kruskal 算法和 Prim 算法)
  • 單源最短路徑算法(Dijkstra 算法)
  • 圖的匹配問題
  • 資源分配問題
  • 調度問題

????????貪心算法的優勢在于其簡潔性和高效性,但它只適用于具有貪心選擇性質和最優子結構性質的問題。在實際應用中,我們需要先證明問題具有這些性質,才能確保貪心算法得到最優解。

????????

????????擬陣理論為我們提供了一個判斷貪心算法是否適用的通用框架,許多可以用貪心算法解決的問題都可以建模為擬陣上的優化問題。

????????希望本章的內容能幫助你深入理解貪心算法的原理和應用,在實際問題中靈活運用這一強大的算法設計技術。

????????以上就是《算法導論》第 16 章貪心算法的詳細講解,包含了各個知識點的理論分析和完整的 C++ 代碼實現。通過這些內容,相信你已經對貪心算法有了更深入的理解。如果有任何問題或疑問,歡迎在評論區留言討論!

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

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

相關文章

Redis面試精講 Day 18:Redis網絡優化與連接管理

【Redis面試精講 Day 18】Redis網絡優化與連接管理 開篇 歡迎來到"Redis面試精講"系列第18天&#xff0c;今天我們將深入探討Redis網絡優化與連接管理技術。在分布式系統中&#xff0c;Redis的網絡性能和連接管理直接影響整個系統的響應速度和穩定性。掌握這些優化…

Centos8系統在安裝Git包時,報錯:“沒有任何匹配: git”

報錯類型&#xff1a; sudo dnf install git Repository AppStream is listed more than once in the configuration Repository BaseOS is listed more than once in the configuration Repository extras is listed more than once in the configuration Repository fasttrac…

glide緩存策略和緩存命中

一 緩存策略 1 Glide 的 diskCacheStrategy() 一共有 5 種枚舉值&#xff08;DiskCacheStrategy&#xff09;&#xff0c;每種的作用和區別如下&#xff1a;1. DiskCacheStrategy.ALL 作用&#xff1a;同時緩存原始圖片&#xff08;原圖數據&#xff09;和經過變換&#xff08;…

如何將PDF文檔進行高效編輯處理!

PDF文件可以在任何設備上以相同的格式查看&#xff0c;無論操作系統或軟件環境如何&#xff0c;可以確保修改后的文檔仍然保持原有的布局和格式。它完全免費&#xff0c;下載后雙擊即可運行&#xff0c;無需安裝&#xff0c;使用非常方便。它具備出色的文本編輯功能&#xff0c…

應用層模擬面試題

模擬面試-C第一題(開發音視頻處理模塊)在開發音視頻處理模塊時&#xff0c;FFmpeg資源&#xff08;AVFrame*&#xff09;需要自動釋放。如何用unique_ptr定制刪除器替代手動av_frame_free()&#xff1f;寫出代碼并解釋RAII優勢。參考答案&#xff1a;auto frame_deleter[](AVFr…

分享一款基于STC8H8K32U-45I-LQFP48單片機的4路數字量輸入輸出模塊

4路數字量輸入輸出模塊產品說明產品特性輸入部分&#xff1a; 4路光耦隔離數字量輸入通道支持NPN和PNP兩種輸入方式&#xff0c;可通過撥碼開關切換輸入電壓范圍&#xff1a;10-30VDC典型應用&#xff1a;可連接按鈕開關、接近開關、光電傳感器等數字信號設備輸出部分&#xff…

redis常見的性能問題

Redis 的性能問題通常源于配置不當、數據結構誤用、資源瓶頸或架構缺陷。以下是 Redis 常見的性能問題及優化方案&#xff0c;結合線上經驗整理&#xff1a;&#x1f9e0; ?一、內存相關問題??1. 內存不足&#xff08;OOM&#xff09;???現象?&#xff1a;OOM errors、響…

Blender 基礎操作

基礎操作 一、視角控制 ①旋轉視角 &#xff1a; 拖動鼠標中鍵 ②平移視角 &#xff1a; shift 鼠標中鍵 ③放大\縮小 &#xff1a;鼠標滾輪 二、物體控制 1、重要 ① 移動物體 : G ② 旋轉物體 : R ③ 縮放物體 : S 2、不重要 ④ 新建物體 : shift A ⑤ 復制物體 : shift D…

Go 語言三大核心數據結構深度解析:數組、切片(Slice)與映射(Map)

&#x1f680;Go 語言三大核心數據結構深度解析&#xff1a;數組、切片&#xff08;Slice&#xff09;與映射&#xff08;Map&#xff09; 在 Go 語言的開發領域&#xff0c;數組、切片與映射 這三大核心數據結構猶如構建程序的基石&#xff0c;支撐著各類數據的存儲與處理。它…

《Webpack與Vite熱模塊替換機制深度剖析與策略抉擇》

從早期簡單的文件合并工具,到如今功能強大、高度自動化的Webpack和Vite,它們重塑了前端開發的流程與效率。而熱模塊替換(HMR, Hot Module Replacement)機制,作為其中關鍵的一環,更是成為開發者提升開發體驗、加速項目迭代的秘密武器。Webpack,作為前端構建領域的先驅者,…

虛擬樂隊“天鵝絨落日”:AI生成音樂引發的行業風暴

引言近日&#xff0c;音樂行業掀起了一陣關于一支名為“The Velvet Sundown”&#xff08;天鵝絨落日&#xff09;樂隊的新聞熱潮。原因何在&#xff1f;這支樂隊很可能并非真正的樂隊&#xff0c;其音樂也或許是由人工智能生成的。事實上&#xff0c;越來越多的共識認為&#…

c++ final override 關鍵字

1.finalfinal 防止子類繼承&#xff0c;用于類或虛函數&#xff0c;限制繼承或重寫class Base final {}; // Base類不能被繼承class Base { public:virtual void foo() final; // 禁止子類重寫foo() };2.overrideoverride 子類中重寫父類中函數&#xff0c;&#xff0c;僅用于…

劍橋大學最新研究:基于大語言模型(LLM)的分子動力學模擬框架,是MD的GPT時刻還是概念包裝?

近期&#xff0c;劍橋大學 Michele Vendruscolo 團隊在預印本平臺上發布了一項最新研究&#xff0c;提出了一個名為 MD-LLM 的框架&#xff0c;旨在為高效研究蛋白質動態提供一種全新的思路。簡單來說&#xff0c;他們希望借助大語言模型&#xff08;LLM&#xff09;&#xff0…

MySQL梳理:其他

MySQL數據庫技術知識合集&#xff0c;涵蓋InnoDB存儲引擎的區管理機制、緩沖池機制等核心技術要點。本文檔將持續補充MySQL相關的重要技術知識點。 &#x1f4cb; 目錄 模塊一&#xff1a;InnoDB區狀態管理機制 1.1 核心設計思想1.2 四種區狀態詳解1.3 漸進式空間分配策略1.4…

影刀 —— 飛書電子表格

以獲取列上第一個可用行為例我們需要獲取的分別是 憑證 和 表格唯一標識首先來看如何獲取憑證在飛書開發者后臺創建應用然后添加權限發版拿App ID 和 App Secret下面來創建電子表格&#xff01;&#xff01;&#xff01;注意這個表格一定不要創建到知識庫里面如果創建到知識庫里…

1.二維圖像處理(完整版)

目錄 1.變換矩陣 2.在矩陣的基礎上添加各種變換形式 3.開始變換 4.計算變換矩陣參數 新算子 二、閾值分割 新算子 三、blob分析案例 1.焊點 2.石頭 3.木材 4.車牌 5.骰子 新算子 四、傅里葉變換頻域分析 問題一 五、濾波處理 1.均值濾波 2.中值濾波 3.高斯…

【linux基礎】Linux 文本處理核心命令指南

Linux 文本處理核心命令指南 文本處理是 Linux 系統管理的核心能力&#xff0c;約 80% 的配置文件操作都依賴于文本處理技術。本指南詳細講解 echo、重定向、cat、grep、wc 和 vim 等關鍵命令&#xff0c;涵蓋從基礎操作到高級技巧的完整知識體系&#xff0c;并配有實用案例演示…

基于深度學習YOLOv12的草莓成熟度檢測系統(YOLOv12+YOLO數據集+UI界面+登錄注冊界面+Python項目源碼+模型)https://www.bilibili.com/video/BV1

一、項目介紹 本項目構建了一套基于深度學習 YOLOv12 的草莓成熟度識別檢測系統&#xff0c;旨在實現對草莓在不同成熟階段的高精度、實時檢測與分類。系統采用 YOLO 格式數據集&#xff0c;將草莓分為 3 個類別&#xff1a;生&#xff08;raw&#xff09;、半熟&#xff08;tu…

深入理解Android Kotlin Flow:響應式編程的現代實踐

引言在現代Android開發中&#xff0c;處理異步數據流是一個核心需求。Kotlin Flow作為協程庫的一部分&#xff0c;提供了一種聲明式的、可組合的異步數據流處理方式。本文將深入探討Flow的設計理念、核心組件、高級用法以及在實際項目中的最佳實踐。一、Flow基礎概念1.1 什么是…

功能測試詳解

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 一、測試項目啟動與研讀需求文檔&#xff08;一&#xff09; 組建測試團隊1、測試團隊中的角色2、測試團隊的基本責任盡早地發現軟件程序、系統或產品中所有的問題…