Python 實現貪心演算法

来源:https://www.cnblogs.com/liuzhongkun/archive/2022/07/24/16515759.html
-Advertisement-
Play Games

貪心演算法 一、 演算法概述 1、 簡介 貪心演算法,又稱貪婪演算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的演算法。[1]比如在旅行推銷員問題中,如果旅行員每次都選擇最近的城市,那這就是一種貪心演算法。 貪心演算法在有最優子結構的問題中尤為有效。最優子結 ...


目錄

貪心演算法

一、 演算法概述

1、 簡介

貪心演算法,又稱貪婪演算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的演算法。[1]比如在旅行推銷員問題中,如果旅行員每次都選擇最近的城市,那這就是一種貪心演算法。

貪心演算法在有最優子結構的問題中尤為有效。最優子結構的意思是局部最優解能決定全局最優解。簡單地說,問題能夠分解成子問題來解決,子問題的最優解能遞推到最終問題的最優解。

貪心演算法與動態規劃的不同在於它對每個子問題的解決方案都做出選擇,不能回退。動態規劃則會保存以前的運算結果,並根據以前的結果對當前進行選擇,有回退功能。

貪心法可以解決一些最優化問題,如:求中的最小生成樹、求哈夫曼編碼……對於其他問題,貪心法一般不能得到我們所要求的答案。一旦一個問題可以通過貪心法來解決,那麼貪心法一般是解決這個問題的最好辦法。由於貪心法的高效性以及其所求得的答案比較接近最優結果,貪心法也可以用作輔助演算法或者直接解決一些要求結果不特別精確的問題。在不同情況,選擇最優的解,可能會導致辛普森悖論,不一定出現最優的解。

2、 基本步驟

  1. 從某個初始解出發;
  2. 採用迭代的過程,當可以向目標前進一步時,就根據局部最優策略,得到一部分解,縮小問題規模;
  3. 將所有解綜合起來。

二、 基本實現

1、 實例

這裡,我們先使用一個找零錢的例子來模擬這個演算法

找零錢問題假設你開了間小店,不能電子支付,錢櫃里的貨幣只有 25 分、10 分、5 分和 1 分四種硬幣,如果你是售貨員且要找給客戶 41 分錢的硬幣,如何安排才能找給客人的錢既正確且硬幣的個數又最少?這裡需要明確的幾個點:1.貨幣只有 25 分、10 分、5 分和 1 分四種硬幣;2.找給客戶 41 分錢的硬幣;3.硬幣最少化思考,能使用我們今天學到的貪婪演算法嗎?怎麼做?

2、 分析步驟

  1. 初始解,我們可以從想找給顧客小於41中最大的硬幣
  2. 使用迭代,不斷的給客戶找儘可能大的硬幣,這樣才能實現個數少的要求
  3. 將所有解綜合起來

3、 代碼實現

這裡我們採用python來實現這個演算法

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "test.py"
__time__ = "2022/7/24 12:16"

coin = [25, 10, 1, 5]  # 所有類型的硬幣
coin.sort()  # 先對硬幣的面值進行排序,後面採用出棧的形式來獲取數據
# 定一個字典,存儲使用了多少硬幣
dic = {25: 0, 10: 0, 5: 0, 1: 0}  # 初始化字典
# 確定初始解
value = 41 - coin[-1]  # 初始條件為減去一個最大的值
dic[coin[-1]] += 1  # 這個也要自加1
while value > 0 and coin:  # 如果我們把錢找完,並且可以把
    if value - coin[-1] >= 0:
        value -= coin[-1]  # 對面值進行相減
        dic[coin[-1]] += 1  # 計數中自加1
    else:
        coin.pop()  # 如果不能再相減的話,就把這個硬幣給彈出,不在考慮

print(dic)  # 最後我們輸出結果

三、 數模實戰

1、 題目展示

這裡,我們使用的題目是2005年的數學建模大賽B,第二問:

表2中列出了網站手上100種DVD的現有張數和當前需要處理的1000位會員的線上訂單(表2的數據格式示例如下表2,具體數據請從http://mcm.edu.cn/mcm05/problems2005c.asp下載),如何對這些DVD進行分配,才能使會員獲得最大的滿意度?請具體列出前30位會員(即C0001~C0030)分別獲得哪些DVD。

數據請自己從網上下載哦!

表2:

DVD編號 D001 D002 D003 D004
DVD現有數量 10 40 15 20
C0001 6 0 0 0
C0002 0 0 0 0
C0003 0 0 0 3
C0004 0 0 0 0

2、 題目分析

  1. 對於庫存的100種光碟,首先滿足所有對它偏愛順序為1的會員的需要,即將每種光碟分配給所有對其偏愛順序為1的會員,如果該光碟的數目偏少無法完成此次分配,則先分配給其中編號較小 的那些會員
  2. 對於剩餘光碟,再優先滿足對它偏愛順序為2的會員需要,同樣地,如果該光碟的數 目偏少無法完成此次分配,則先分配給其中編號較小的那些會員
  3. 依此類推分配下去,在以後分配時,已經擁有3張光碟的會員不參加分配
  4. 如果還有剩下的光碟,隨機分配給尚未分滿的會員,分配結束

3、 代碼實現

這裡,我展示一下,我對這個貪心演算法的理解,展示一下我的代碼,有問題請多多指正:

3.1 初始化數據

這裡,我們要先把Excel中的數據清洗出來,這樣我們更好處理:

def init_data(
        io: str = "./B2005Table2.xls"  # 傳入Excel表格的路徑
) -> (defaultdict, dict):  # 初始化數據的函數
    """讀取Excel中的數據,並且對數據進行清洗"""
    data_num = defaultdict(dict)  # 存存儲成員的對所有數據的喜愛程度

    df = pd.read_excel(io)  # 讀取Excel數據
    data = df[0:].iloc[:, 1:]  # 獲取到關於所有的用戶願意觀看的程度,以及所有的用戶信息

    # 初始化DVD的全部信息
    col_index = data.columns[1:].tolist()  # 獲取到每一種DVD
    capacity = data.loc[0, col_index].tolist()  # 獲取到每一種DVD有多少張
    dic_goods = dict(zip(col_index, capacity))  # 轉換為映射關係,更好訪問
    # 初始化會員的全部信息
    for col_index_ in col_index:  # 得到每一列
        for row_index in data.index[1:]:  # 遍歷每一列
            temp = dict()  # 設置臨時變數
            name = data.loc[row_index, "Unnamed: 1"]  # 獲取名字
            satisfaction = 40 - int(data.loc[row_index, col_index_]) if data.loc[
                row_index, col_index_] else 0.0  # 設置滿意度
            if data_num.get(name):  # 判斷是否存在name,如果存在
                temp = data_num[name]  # 把原來的數據拷貝出來
            temp[col_index_] = satisfaction  # 把新的滿意度存入
            data_num.update({
                name: temp
            })  # 更新數據
    return data_num, dic_goods  # 返回獲取到的滿意度映射表,以及商品餘量映射表

3.2 分發DVD

def get_max(
        dic: dict,  # 滿意度映射表
):
    """獲取最大值"""
    max_ = list(dic.keys())[0]  # 預設第一個鍵值對為最大值
    for key, value in dic.items():
        if value > dic[max_]:
            max_ = key

    return max_  # 返回最大值


def distribution(
        data_num: defaultdict,  # 滿意度映射表
        dic_goods: dict  # 商品餘量映射表
):
    """滿足每一個會員的優先需求"""
    for name, dic in data_num.items():  # 遍歷每一個字典
        temp_dic = dic.copy()  # 防止其修改原來字典的值
        max_ = get_max(temp_dic)  # 獲取最大值
        value = temp_dic[max_]  # 進行對while迴圈的判斷
        while temp_dic and value:  # 如果最終沒貨的話,就不用管了,同時,如果把每個人的感興趣的東西選完,就沒必要去選了,這個while迴圈就可以對成員對的多個滿意度進行迭代,然後進行光碟的分配
            max_ = get_max(temp_dic)  # 獲取最大值、
            value = temp_dic[max_]  # 進行對while迴圈的判斷
            temp_dic.pop(max_)  # 獲取裡面最大的字典,並把這個鍵值對刪除,但是不會刪除主字典裡面的值
            if len(selected_con[name]) < 3:  # 最基本的條件
                if dic_goods[max_] > 0:
                    # 如果DVD有貨,並且用戶沒有獲得3個DVD
                    selected_con[name].append(max_)  # 把光碟添加到主容器中
                    dic_goods[max_] -= 1  # 把商品數據去除
            else:  # 如果滿了的話,就不用考慮了
                break

3.3 分配餘量

剩餘的沒有分配到DVD的會員,我們需要重新對其進行隨機分配,因為剩餘的會員大部分都是沒有獲取到喜好的類型的DVD,對剩餘DVD的興趣為0,我們在這裡可以隨機對其進行分配,直到每個人都擁有DVD

def validation(
        dic_goods: dict  # 商品餘量映射表
):
    """對沒有分滿的會員進行隨機分配"""
    unselected = []  # 存儲沒有分滿的會員
    for name, lis in selected_con.items():  # 獲取出沒有不滿的人
        if len(lis) < 3:
            unselected.append({
                "name": name,  # 存儲沒有補滿的會員名單
                "count": 3 - len(lis)  # 保存剩餘的商品數量
            })
    # 對沒有分滿的人進行隨機補全
    for dic in unselected:  # 對每個會員進行添加數據
        for index in range(dic["count"]):  # 補全剩餘的商品數量
            while True:
                # 添加的數據進行隨機化
                add_name = random.choice(list(dic_goods.keys()))  # 隨機獲取要添加的人
                if dic_goods[add_name] > 0:
                    selected_con[dic["name"]].append(add_name)  # 添加數據
                    dic_goods[add_name] -= 1  # 自減1
                    break

3.4 數據存儲

運算完後,我們需要將數據寫入Excel文件中,進行分析和存儲:

def write_to_excel(
        satisfaction: dict,  # 滿意度映射表
        io_selected: str = "./selected.xlsx",  # 存儲路徑
):
    """將得到DVD的成員寫入Excel中"""

    book = Workbook()
    sheet = book.active
    sheet.title = "已分配到DVD的成員"
    sheet.cell(1, 1).value = '序號'
    sheet.cell(1, 2).value = '名字'
    sheet.cell(1, 3).value = 'DVD_1'
    sheet.cell(1, 4).value = 'DVD_2'
    sheet.cell(1, 5).value = 'DVD_3'
    row_index = 1
    index = 0
    for name, lis in selected_con.items():
        sheet.cell(row_index + 1, 1).value = index + 1
        sheet.cell(row_index + 1, 2).value = name
        lis_0 = f"{lis[0]} / {satisfaction[name][lis[0]]}"  # 類型 / 滿意度
        sheet.cell(row_index + 1, 3).value = lis_0
        lis_1 = f"{lis[1]} / {satisfaction[name][lis[1]]}"
        sheet.cell(row_index + 1, 4).value = lis_1
        lis_2 = f"{lis[2]} / {satisfaction[name][lis[2]]}"
        sheet.cell(row_index + 1, 5).value = lis_2

        get_sum = satisfaction[name][lis[0]] + satisfaction[name][lis[1]] + satisfaction[name][lis[2]]

        row_index += 1
        index += 1
    book.save(io_selected)

4、 總代碼

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
__author__ = "A.L.Kun"
__file__ = "demo2.py"
__time__ = "2022/7/24 16:45"

import pandas as pd
from collections import defaultdict
from openpyxl import Workbook
import random

selected_con = defaultdict(list)  # 存儲篩選出來的用戶

def init_data(
        io: str = "./B2005Table2.xls"  # 傳入Excel表格的路徑
) -> (defaultdict, dict):  # 初始化數據的函數
    """讀取Excel中的數據,並且對數據進行清洗"""
    data_num = defaultdict(dict)  # 存存儲成員的對所有數據的喜愛程度

    df = pd.read_excel(io)  # 讀取Excel數據
    data = df[0:].iloc[:, 1:]  # 獲取到關於所有的用戶願意觀看的程度,以及所有的用戶信息

    # 初始化DVD的全部信息
    col_index = data.columns[1:].tolist()  # 獲取到每一種DVD
    capacity = data.loc[0, col_index].tolist()  # 獲取到每一種DVD有多少張
    dic_goods = dict(zip(col_index, capacity))  # 轉換為映射關係,更好訪問
    # 初始化會員的全部信息
    for col_index_ in col_index:  # 得到每一列
        for row_index in data.index[1:]:  # 遍歷每一列
            temp = dict()  # 設置臨時變數
            name = data.loc[row_index, "Unnamed: 1"]  # 獲取名字
            satisfaction = 40 - int(data.loc[row_index, col_index_]) if data.loc[
                row_index, col_index_] else 0.0  # 設置滿意度
            if data_num.get(name):  # 判斷是否存在name,如果存在
                temp = data_num[name]  # 把原來的數據拷貝出來
            temp[col_index_] = satisfaction  # 把新的滿意度存入
            data_num.update({
                name: temp
            })  # 更新數據
    return data_num, dic_goods  # 返回獲取到的滿意度映射表,以及商品餘量映射表


def get_max(
        dic: dict,  # 滿意度映射表
):
    """獲取最大值"""
    max_ = list(dic.keys())[0]  # 預設第一個鍵值對為最大值
    for key, value in dic.items():
        if value > dic[max_]:
            max_ = key

    return max_  # 返回最大值


def distribution(
        data_num: defaultdict,  # 滿意度映射表
        dic_goods: dict  # 商品餘量映射表
):
    """滿足每一個會員的優先需求"""
    for name, dic in data_num.items():  # 遍歷每一個字典
        temp_dic = dic.copy()  # 防止其修改原來字典的值
        max_ = get_max(temp_dic)  # 獲取最大值
        value = temp_dic[max_]  # 進行對while迴圈的判斷
        while temp_dic and value:  # 如果最終沒貨的話,就不用管了,同時,如果把每個人的感興趣的東西選完,就沒必要去選了
            max_ = get_max(temp_dic)  # 獲取最大值、
            value = temp_dic[max_]  # 進行對while迴圈的判斷
            temp_dic.pop(max_)  # 獲取裡面最大的字典,並把這個鍵值對刪除,但是不會刪除主字典裡面的值
            if len(selected_con[name]) < 3:  # 最基本的條件
                if dic_goods[max_] > 0:
                    # 如果DVD有貨,並且用戶沒有獲得3個DVD
                    selected_con[name].append(max_)  # 把光碟添加到主容器中
                    dic_goods[max_] -= 1  # 把商品數據去除
            else:  # 如果滿了的話,就不用考慮了
                break


def write_to_excel(
        satisfaction: dict,  # 滿意度映射表
        io_selected: str = "./selected.xlsx",  # 存儲路徑
):
    """將得到DVD的成員寫入Excel中"""

    book = Workbook()
    sheet = book.active
    sheet.title = "已分配到DVD的成員"
    sheet.cell(1, 1).value = '序號'
    sheet.cell(1, 2).value = '名字'
    sheet.cell(1, 3).value = 'DVD_1'
    sheet.cell(1, 4).value = 'DVD_2'
    sheet.cell(1, 5).value = 'DVD_3'
    row_index = 1
    index = 0
    for name, lis in selected_con.items():
        sheet.cell(row_index + 1, 1).value = index + 1
        sheet.cell(row_index + 1, 2).value = name
        lis_0 = f"{lis[0]} / {satisfaction[name][lis[0]]}"  # 類型 / 滿意度
        sheet.cell(row_index + 1, 3).value = lis_0
        lis_1 = f"{lis[1]} / {satisfaction[name][lis[1]]}"
        sheet.cell(row_index + 1, 4).value = lis_1
        lis_2 = f"{lis[2]} / {satisfaction[name][lis[2]]}"
        sheet.cell(row_index + 1, 5).value = lis_2

        get_sum = satisfaction[name][lis[0]] + satisfaction[name][lis[1]] + satisfaction[name][lis[2]]

        row_index += 1
        index += 1
    book.save(io_selected)


def validation(
        dic_goods: dict  # 商品餘量映射表
):
    """對沒有分滿的會員進行隨機分配"""
    unselected = []  # 存儲沒有分滿的會員
    for name, lis in selected_con.items():  # 獲取出沒有不滿的人
        if len(lis) < 3:
            unselected.append({
                "name": name,  # 存儲沒有補滿的會員名單
                "count": 3 - len(lis)  # 保存剩餘的商品數量
            })
    # 對沒有分滿的人進行隨機補全
    for dic in unselected:  # 對每個會員進行添加數據
        for index in range(dic["count"]):  # 補全剩餘的商品數量
            while True:
                # 添加的數據進行隨機化
                add_name = random.choice(list(dic_goods.keys()))  # 隨機獲取要添加的人
                if dic_goods[add_name] > 0:
                    selected_con[dic["name"]].append(add_name)  # 添加數據
                    dic_goods[add_name] -= 1  # 自減1
                    break


def main():
    data_num, dic_goods = init_data()  # 初始化數據
    distribution(data_num, dic_goods)  # 初始化數據,同時獲取結果
    validation(dic_goods)  # 那麼剩餘的都是一些沒有分配到喜歡的DVD的對象了
    write_to_excel(data_num)  # 寫入Excel表中


if __name__ == '__main__':
    main()

本文來自博客園,作者:A-L-Kun,轉載請註明原文鏈接:https://www.cnblogs.com/liuzhongkun/p/16515759.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android高仿網易雲音樂-首頁複雜發現界面佈局和功能,效果圖依次為發現界面頂部,包含首頁輪播圖,水平滾動的按鈕,推薦歌單;然後是發現界面推薦單曲,點擊單曲就是直接進入播放界面。 ...
  • 1.下載 下載地址:Download Visual Studio Code VS code,全稱Visual Studio Code,是Microsoft(微軟)在2015年4月30日發佈的,編寫現代web和跨平臺源代碼編輯器。比如說,可以用來寫一個網頁的html+css代碼等。 VS code 優 ...
  • 一.href="#+id名" 可以快速跳轉 如: href="#two" id="two"快速跳 二. div大塊 自動換行 span小塊 不自動換行 三. 註釋標簽 ctrl+/ &lt &gt <和> 四.表格 表格形式 <table align="center" border=1 cellsp ...
  • 自上月從上海結束工作回來 在家閑來無事 想寫點東西打發時間 也順便學習學習新的技術。偶然發現了 pinia 據說比vuex好用些 所以便搭了個demo嘗試著用了下 感覺確實不錯,於是便有了這篇隨筆。 那麼廢話不多說 直接開始吧。(附pinia官網地址:https://pinia.web3doc.to ...
  • 樹形數據的一些相關處理方法 以下方法待補充優化😁... // 用於測試的樹形數據 const treeData = [ { id: '1', name: '測試1', pId: '0', children: [ { id: '11', name: '測試11', pId: '1', childre ...
  • 1.常用標簽 1.基礎標簽 <!DOCTYPE> 定義文檔類型。 <html> 定義 HTML 文檔。 <head> 定義關於文檔的信息。 <title> 定義文檔的標題。 <body> 定義文檔的主體。 <h1> to <h6> 定義 HTML 標題。 <p> 定義段落。 <br> 定義簡單的折行 ...
  • JavaScript進階內容——jQuery 我們在前面的文章中已經掌握了JavaScript的全部內容,現在讓我們瞭解一下JavaScript庫 這篇文章主要是為了為大家大致講解JavaScript庫以及使用方法,本篇不會完全講解jQuery的全部語法 如果希望完全掌握,可以參考網站jQuery ...
  • 概述 ​ SpringBoot中集成官方的第三方組件是通過在POM文件中添加組件的starter的Maven依賴來完成的。添加相關的Maven依賴之後,會引入具體的jar包,在SpringBoot啟動的時候會根據預設自動裝配的配置類的註入條件判斷是否註入該自動配置類到Spring容器中。自動配置類中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...