算法之貪心算法

貪心算法

  • 貪心算法
    • 核心思想
    • 常見應用場景
    • 典型案例
      • 案例一:找零問題
      • 案例二:活動選擇問題
      • 案例三:貨倉選址問題
    • 貪心算法的應用詳解
      • 霍夫曼編碼
      • 最小生成樹
      • Dijkstra最短路徑算法
    • 總結

貪心算法

核心思想

貪心算法(Greedy Algorithm)是一種在每一步選擇中都采取在當前狀態下最優的選擇,從而希望以這種方式能夠達到全局最優解的一種算法思想。貪心算法并不總是能得到全局最優解,但在一些問題中,它能產生出足夠好的解,而且相對簡單高效。

貪心算法的基本思想是通過一系列局部最優的選擇,希望最終達到全局最優。在每一步,貪心算法會選擇當前狀態下的最優解,而不考慮當前選擇對以后的影響。這與動態規劃的思想不同,動態規劃考慮到每一步的選擇對于整體解的影響,而做出相應的決策。

貪心算法通常適用于滿足兩個條件的問題:

  • 最優子結構性質: 當一個問題的最優解包含其子問題的最優解時,稱該問題具有最優子結構性質。
  • 貪心選擇性質: 通過局部最優選擇,期望能夠達到全局最優。

一旦一個問題可以通過貪心算法求解,那么貪心算法一般會比其他算法更加高效。然而,需要注意的是,并非所有問題都滿足貪心選擇性質和最優子結構性質。

常見應用場景

  • 霍夫曼編碼:用于數據壓縮,為高頻字符分配短編碼、低頻字符分配長編碼,減少整體數據量。
  • 活動選擇問題:選擇一組互不相容的活動,使得參與的活動數最大。
  • 最小生成樹:如Kruskal和Prim算法,用于在加權連通圖中找到權值之和最小的生成樹。
  • Dijkstra最短路徑算法:求解單源最短路徑問題,從起始點開始,每次遍歷到距離最近且未訪問過的頂點的鄰接節點。
  • 貨倉選址問題:在一維數軸上為多個商店選擇貨倉位置,使得總距離最小。

在使用貪心算法時,必須確保貪心選擇的局部最優解能夠推導出全局最優解,否則可能得不到最優解。

典型案例

案例一:找零問題

問題描述:假設你是柜臺售貨員,需要給客戶找零n元錢。你擁有無限數量的1元、2元、5元、10元、20元、50元面額的硬幣/紙幣。如何用最少數量的硬幣/紙幣來找零?

貪心策略:每次盡量用面額最大的硬幣來找零。

示例

假設要找零的金額為 n = 36 元。

可用的硬幣面額為 1、2、5、10、20 和 50。

  1. 首先,找零最大的面額,即 50 元。但由于 50 元大于 36 元,所以不能使用。
  2. 接下來,盡量使用次大面額的硬幣,即 20 元。36 元中能用一個 20 元,剩下 16 元。
  3. 然后使用次次大面額的硬幣,即 10 元。16 元中能用一個 10 元,剩下 6 元。
  4. 繼續使用 5 元的硬幣,6 元中能用一張 5 元,剩下 1 元。
  5. 最后,用 1 元的硬幣找零 1 元。

這樣,用 20 元、10 元、5 元和 1 元的硬幣一共找零 36 元,共需要 4 枚硬幣,是最少數量的硬幣。

在這個案例中,貪心算法的貪心策略是每次都選擇當前情況下的最優解,即選擇當前可用的最大面額的硬幣。這種策略在這個特定問題中得到了最優解。但需要指出的是,對于不同的面額組合和要求找零的金額,貪心算法并不總是能夠得到最優解。

代碼實現

#include <stdio.h>
void findChange(int amount) {int money[] = { 50, 20, 10, 5, 2, 1 };int num = sizeof(money) / sizeof(money[0]);printf("找零 %d :\n", amount);for (int i = 0; i < num; ++i) {int current = money[i];int numNotes = amount / current;if (numNotes > 0) {printf("%d 張 %d 塊\n", numNotes, current);amount = amount % current;}}
}
int main() {int amount = 46;findChange(amount);return 0;
}

復雜度分析

  • 時間復雜度:O(1),因為面額種類有限。
  • 空間復雜度:O(1)。

注意:對于某些面額組合,貪心算法不一定能得到最優解。

案例二:活動選擇問題

問題描述:有n個活動,每個活動有開始時間和結束時間,要求選擇最多數量的互不重疊活動。

貪心策略:每次選擇結束時間最早且不與已選活動沖突的活動。

偽代碼

1. 按活動結束時間從小到大排序
2. 依次選擇與已選活動不沖突的下一個活動

代碼實現

#include <stdio.h>
#include <stdlib.h>// 定義活動結構體,包含開始時間和結束時間
typedef struct {int start;  // 活動開始時間int end;    // 活動結束時間int index;  // 活動編號(可選,用于標識活動)
} Activity;// 比較函數,用于按活動結束時間排序
int cmp(const void *a, const void *b) {return ((Activity*)a)->end - ((Activity*)b)->end;
}// 貪心算法選擇活動函數
void selectActivities(Activity acts[], int n) {// 按活動結束時間從小到大排序qsort(acts, n, sizeof(Activity), cmp);// 選擇第一個活動(結束時間最早的活動)int count = 0;  // 記錄選擇的活動數量int lastEnd = -1;  // 上一個選擇的活動的結束時間,初始為-1printf("選擇的活動:\n");for (int i = 0; i < n; ++i) {// 如果當前活動的開始時間大于等于上一個選擇的活動的結束時間,則選擇該活動if (acts[i].start >= lastEnd) {count++;printf("活動%d: [%d, %d]\n", acts[i].index, acts[i].start, acts[i].end);lastEnd = acts[i].end;  // 更新最后選擇的活動的結束時間}}printf("共選擇了 %d 個活動\n", count);
}// 主函數,演示活動選擇問題
int main() {// 示例活動集合,每個活動有開始時間、結束時間和編號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};int n = sizeof(activities) / sizeof(activities[0]);printf("共有 %d 個活動\n\n", n);// 調用貪心算法選擇活動selectActivities(activities, n);return 0;
}

復雜度分析

  • 時間復雜度:O(nlogn),主要是排序的時間復雜度。
  • 空間復雜度:O(1),除了存儲活動的數組外,只需要常數級的額外空間。

貪心策略正確性證明

活動選擇問題的貪心策略是選擇結束時間最早的活動,這一策略的正確性可以通過反證法證明:

假設最優解集合為S,包含k個活動,按結束時間排序后為{a?, a?, …, a?}。如果a?不是所有活動中結束時間最早的活動,那么存在活動b,其結束時間早于a?。

我們可以用b替換S中的a?,得到新的解集合S’ = {b, a?, …, a?}。由于b的結束時間早于a?,b不會與a?, …, a?沖突,因此S’也是一個可行解,且|S’| = |S|。這說明選擇結束時間最早的活動不會導致最優解的丟失。

通過歸納法,可以證明每次選擇當前結束時間最早的、與已選活動不沖突的活動,最終能得到最優解。

應用場景

活動選擇問題在實際中有廣泛應用,例如:

  • 會議室安排:在有限的會議室中安排盡可能多的會議
  • 課程表設計:在固定教室中安排盡可能多的課程
  • 任務調度:在單處理器上安排盡可能多的任務

案例三:貨倉選址問題

問題描述:在一條數軸上有N家商店,它們的坐標分別為A1~AN。現在需要在數軸上建立一家貨倉,每天清晨,從貨倉到每家商店都要運送一車商品。為了提高效率,求把貨倉建在何處,可以使得貨倉到每家商店的距離之和最小。

貪心策略:將貨倉建在所有商店坐標的中位數位置。

代碼實現

#include <stdio.h>
#include <stdlib.h>// 比較函數,用于排序
int compare(const void* a, const void* b) {return (*(int*)a - *(int*)b);
}// 計算距離之和的函數
int sum(int warehouse, int* shops, int n) {int totalDistance = 0;for (int i = 0; i < n; ++i) {totalDistance += abs(warehouse - shops[i]);}return totalDistance;
}// 找到貨倉位置的函數
int find(int* shops, int n) {// 將商店坐標排序qsort(shops, n, sizeof(int), compare);// 中位數位置int warehousePosition = shops[n / 2];return warehousePosition;
}int main() {int n;printf("輸入商店的數量:");scanf("%d", &n);int* shops = (int*)malloc(n * sizeof(int));printf("輸入商店的坐標:");for (int i = 0; i < n; ++i) {scanf("%d", &shops[i]);}int pos = find(shops, n);int total = sum(pos, shops, n);printf("把貨倉建在坐標 %d 處,使得貨倉到每家商店的距離之和最小,為:%d\n", pos, total);free(shops);return 0;
}

復雜度分析

  • 時間復雜度:O(nlogn),主要是排序的時間復雜度。
  • 空間復雜度:O(n),需要存儲n個商店的坐標。

注意:這個問題的貪心策略是選擇中位數位置,這是因為在一維數軸上,到各點距離之和最小的位置就是這些點的中位數。

貪心算法的應用詳解

霍夫曼編碼

霍夫曼編碼是一種變長編碼方式,它根據字符出現的頻率來設計編碼長度,頻率高的字符使用較短的編碼,頻率低的字符使用較長的編碼,從而達到壓縮數據的目的。

貪心策略:每次選擇兩個頻率最低的節點合并,構建一棵哈夫曼樹,然后根據從根到葉子的路徑確定編碼。

最小生成樹

最小生成樹是連通無向圖的一個生成樹,其權值之和最小。常用的算法有:

Prim算法

  1. 從圖中任選一個頂點作為起始點,加入到最小生成樹中
  2. 在所有與當前最小生成樹中頂點相鄰的邊中,選擇權值最小的邊,將其連接的未訪問頂點加入到最小生成樹中
  3. 重復步驟2,直到所有頂點都加入到最小生成樹中

Kruskal算法

  1. 將圖中所有邊按權值從小到大排序
  2. 按照權值從小到大的順序選擇邊,如果該邊不會與已選擇的邊構成環路,則將其加入到最小生成樹中
  3. 重復步驟2,直到選擇了n-1條邊(n為頂點數)

Dijkstra最短路徑算法

Dijkstra算法用于求解單源最短路徑問題,即從一個頂點到其余各頂點的最短路徑。

貪心策略:每次選擇當前未訪問的距離起點最近的頂點,然后更新與該頂點相鄰的其他頂點的距離。

總結

貪心算法通過每一步的局部最優選擇,期望獲得全局最優解。適用于滿足最優子結構和貪心選擇性質的問題。常見應用有找零、活動選擇、最小生成樹、最短路徑、霍夫曼編碼等。實際應用時需驗證貪心策略的正確性,因為貪心算法并不總是能得到全局最優解。

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

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

相關文章

英碼科技與泊川軟件,攜手加速AI與嵌入式系統融合創新

2025年4月15日&#xff0c;廣州英碼信息科技有限公司&#xff08;以下簡稱“英碼科技”&#xff09;與廣州泊川軟件技術有限公司&#xff08;以下簡稱“泊川軟件”&#xff09; 正式簽署戰略合作框架協議。此次合作將充分發揮雙方在AI計算硬件與嵌入式操作系統領域的技術優勢&a…

Flowable7.x學習筆記(九)部署 BPMN XML 流程

前言 到本篇為止&#xff0c;我們已經完成了流程定義以及其 BPMN XML 本身的查詢和新增功能&#xff0c;那我們有有了XML之后就可以開始著手研究實現 Flowable7對流程的各種操作了&#xff0c;比如部署&#xff0c;掛起&#xff0c;發起等等。 首先第一步&#xff0c;我們本篇文…

electron 渲染進程按鈕創建新window,報BrowserWindow is not a constructor錯誤;

在 Electron 中&#xff0c;有主進程和渲染進程 主進程&#xff1a;在Node.js環境中運行—意味著能夠使用require模塊并使用所有Node.js API 渲染進程&#xff1a;每個electron應用都會為每個打開的BrowserWindow&#xff08;與每個網頁嵌入&#xff09;生成一個單獨的渲染器進…

深入規劃 Elasticsearch 索引:策略與實踐

一、Elasticsearch 索引概述 &#xff08;一&#xff09;索引基本概念 Elasticsearch 是一個分布式、高性能的全文搜索引擎&#xff0c;其核心概念之一便是索引。索引本質上是一個存儲文檔的邏輯容器&#xff0c;它使得數據能夠在高效的檢索機制下被查詢到。當我們對文檔進行…

llamafactory的包安裝

cuda版本12.1&#xff0c;python版本3.10&#xff0c;torch版本2.4.0&#xff0c;幾個關鍵包版本如下&#xff1a; torch2.4.0cu121 transformers4.48.3 triton3.0.0 flash-attn2.7.1.post4 xformers0.0.27.post2 vllm0.6.3.post1 vllm-flash-attn2.6.1 unsloth2025.3.18 unsl…

Redis專題

前言 Redis的各種思想跟機組Cache和操作系統對進程的管理非常類似&#xff01; 一&#xff1a;看到你的簡歷上寫了你的項目里面用到了redis&#xff0c;為啥用redis&#xff1f; 因為傳統的關系型數據庫如Mysql,已經不能適用所有的場景&#xff0c;比如秒殺的庫存扣減&#xff…

【Rust 精進之路之第7篇-函數之道】定義、調用與參數傳遞:構建代碼的基本單元

系列: Rust 精進之路:構建可靠、高效軟件的底層邏輯 作者: 碼覺客 發布日期: 2025-04-20 引言:封裝邏輯,代碼復用的基石 在之前的文章中,我們已經探索了 Rust 如何處理數據(變量、標量類型、復合類型)以及如何控制程序的執行流程(if/else、循環)。這些構成了編寫簡…

文件有幾十個T,需要做rag,用ragFlow能否快速落地呢?

一、RAGFlow的優勢 1、RAGFlow處理大規模數據性能&#xff1a; &#xff08;1&#xff09;、RAGFlow支持分布式索引構建&#xff0c;采用分片技術&#xff0c;能夠處理TB級數據。 &#xff08;2&#xff09;、它結合向量搜索和關鍵詞搜索&#xff0c;提高檢索效率。 &#xf…

安卓的桌面 launcher是什么

安卓的桌面Launcher是一種安卓應用程序&#xff0c;它主要負責管理和展示手機主屏幕的界面以及相關功能&#xff0c;為用戶提供與設備交互的主要入口。以下是其詳細介紹&#xff1a; 功能 主屏幕管理&#xff1a;用戶可以在主屏幕上添加、刪除和排列各種應用程序圖標、小部件…

【學習筆記】計算機網絡(九)—— 無線網絡和移動網絡

第9章 無線網絡和移動網絡 文章目錄 第9章 無線網絡和移動網絡9.1 無線局域網WLAN9.1.1 無線局域網的組成9.1.2 802.11局域網的物理層9.1.3 802.11局域網的MAC層協議CSMA 協議CSMA/CD 協議 - 總線型 - 半雙工CSMA/CA 協議 9.1.4 802.11局域網的MAC幀 9.2 無線個人區域網WPAN9.3…

無線網絡入侵檢測系統實戰 | 基于React+Python的可視化安全平臺開發詳解

隨著無線網絡的普及&#xff0c;網絡攻擊風險也日益嚴峻。本項目旨在構建一個實時監測、智能識別、高效防護的無線網絡安全平臺&#xff0c;通過結合前后端技術與安全算法&#xff0c;實現對常見攻擊行為的有效監控和防御。 一、項目簡介與功能目的 本系統是一款基于 React 前…

速通FlinkCDC3.0

1.FlinkCDC概述 1.1FlinkCDC是什么&#xff1f; FlinkCDC&#xff08;Flink Change Data Capture&#xff09;是一個用于實時捕獲數據庫變更日志的工具&#xff0c;它可以將數據庫的變更實時同步到ApacheFlink系統中。 1.2 FlinkCDC的三個版本&#xff1f; 1.x 這個版本的Fli…

B+樹節點與插入操作

B樹節點與插入操作 設計B樹節點 在設計B樹的數據結構時&#xff0c;我們首先需要定義節點的格式&#xff0c;這將幫助我們理解如何進行插入、刪除以及分裂和合并操作。以下是對B樹節點設計的詳細說明。 節點格式概述 所有的B樹節點大小相同&#xff0c;這是為了后續使用自由…

C# 檢查字符串是否包含在另一個字符串中

string shopList "我是大浪,你的小狼"; this.ShopId"你的小狼"; bool existsShopId false; if (!string.IsNullOrEmpty(shopList)) {existsShopId shopList.Split(,).Any(part > part.Trim() this.ShopId); }檢查 goodsIdSet 中的每個元素是否都在 …

珈和科技遙感賦能農業保險創新 入選省級衛星應用示范標桿

為促進空天信息與數字經濟深度融合&#xff0c;拓展衛星數據應用場景價值&#xff0c;提升衛星數據應用效能和用戶體驗&#xff0c;加速衛星遙感技術向民生領域轉化應用&#xff0c;近日&#xff0c;湖北省國防科工辦組織開展了2024年湖北省衛星應用示范項目遴選工作。 經多渠…

深入理解 React 組件的生命周期:從創建到銷毀的全過程

React 作為當今最流行的前端框架之一&#xff0c;其組件生命周期是每個 React 開發者必須掌握的核心概念。本文將全面剖析 React 組件的生命周期&#xff0c;包括類組件的各個生命周期方法和函數組件如何使用 Hooks 模擬生命周期行為&#xff0c;幫助開發者編寫更高效、更健壯的…

緩存 --- Redis性能瓶頸和大Key問題

緩存 --- Redis性能瓶頸和大Key問題 內存瓶頸網絡瓶頸CPU 瓶頸持久化瓶頸大key問題優化方案 Redis 是一個高性能的內存數據庫&#xff0c;但在實際使用中&#xff0c;可能會在內存、網絡、CPU、持久化、大鍵值對等方面遇到性能瓶頸。下面從這些方面詳細分析 Redis 的性能瓶頸&a…

Python爬蟲與代理IP:高效抓取數據的實戰指南

目錄 一、基礎概念解析 1.1 爬蟲的工作原理 1.2 代理IP的作用 二、環境搭建與工具選擇 2.1 Python庫準備 2.2 代理IP選擇技巧 三、實戰步驟分解 3.1 基礎版&#xff1a;單線程免費代理 3.2 進階版&#xff1a;多線程付費代理池 3.3 終極版&#xff1a;Scrapy框架自動…

Nginx HTTP 414 與“大面積”式洪水攻擊聯合防御實戰

一、引言 在大規模分布式應用中&#xff0c;Nginx 常作為前端負載均衡和反向代理服務器。攻擊者若結合超長 URI/頭部攻擊&#xff08;觸發 HTTP 414&#xff09;與海量洪水攻擊&#xff0c;可在網絡層與應用層形成雙重打擊&#xff1a;一方面耗盡緩沖區和內存&#xff0c;另一…

【上位機——MFC】運行時類信息機制

運行時類信息機制的使用 類必須派生自CObject類內必須添加聲明宏DECLARE_DYNAMIC(theClass)3.類外必須添加實現宏 IMPLEMENT_DYNAMIC(theClass,baseClass) 具備上述三個條件后&#xff0c;CObject::IsKindOf函數就可以正確判斷對象是否屬于某個類。 代碼示例 #include <…