日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

回溯法基本思想_LeetCode--回溯法心得

發(fā)布時(shí)間:2023/12/15 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 回溯法基本思想_LeetCode--回溯法心得 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這兩天在刷LeetCode37題解數(shù)獨(dú)時(shí),被這個(gè)回溯法折騰的不要不要的,于是我疼定思疼發(fā)誓一定要找個(gè)能解決這類回溯法的套路出來,方便以后快速解決此類題目。于是我在網(wǎng)上找了兩個(gè)很經(jīng)典的回溯法題目--八皇后問題和迷宮問題,認(rèn)真總結(jié)了一番,發(fā)現(xiàn)其中還真的有一些共同之處,我會(huì)在下面好好講述。

首先,回溯法所用到的核心思想就是遞歸法,雖然其過程邏輯很清楚,而且執(zhí)行效率很高。但缺點(diǎn)也是與之對應(yīng)的,邏輯清楚,必然其抽象性很高,所以有時(shí)候就是這樣的情況:看它的解題過程很容易看懂,但要是讓你自己動(dòng)手寫這個(gè)遞歸過程,發(fā)現(xiàn)很難下筆。當(dāng)時(shí)我就是這樣的情況,于是我想在網(wǎng)上找找看看能不能有哪位大佬分享一些解題心得供我們這些算法渣渣思考學(xué)習(xí)的,然而網(wǎng)上的一些csdn博客(這兒不是刻意貶低csdn的哈)寫的東西都是千篇一律,都是在把一些舊的不能再舊的東西炒來炒去,沒有一點(diǎn)營養(yǎng)!別人靠不住,那就只能靠自己唄!于是我自己找例子,自己琢磨,終于是有些小收獲,本著慈悲為懷的精神,故想拿出來供大家參考,避免大家少趟坑,保證不打馬虎眼,不炒現(xiàn)飯。當(dāng)然了,若有不當(dāng)之處,請各位筆友指出,面的誤人子弟。

1 八皇后問題

問題描述:

該問題是國際西洋棋棋手馬克斯·貝瑟爾于1848年提出:在8×8格的國際象棋上擺放八個(gè)皇后,使其不能互相攻擊,即任意兩個(gè)皇后都不能處于同一行、同一列或同一斜線上,問有多少種擺法。

看完問題描述后,大家之前要是熟悉此題目的也可以先動(dòng)手做做,看看還能不能解出來。再正式回答此題目之前我還是先把我寫的答案貼出來,讓大家對整個(gè)處理過程有個(gè)大致的印象。

八皇后問題代碼如下:

# 檢測皇后之間的位置關(guān)系 def conflict(queen_str, current_queen):""":param queen_str: str-->指代當(dāng)前皇后存放之前的所有皇后的集合:param current_queen: int-->指代當(dāng)前皇后想要存放的位置:return:Flag: boolean-->指代當(dāng)前位置的皇后是否與之前所有位置的皇后有沖突"""# 此處的queen_length既是之前保存的queen_list集合的長度,也可以理解為當(dāng)前current_queen皇后的行下標(biāo)queen_length = len(queen_str)# 定義是否有位置沖突的標(biāo)簽Flag = Falsefor index in range(queen_length):# queen_length - index主要是控制相鄰兩行的皇后不能處于對角線上,其他的就沒要求if abs(current_queen-int(queen_str[index])) in(0, queen_length-index):Flag = Truebreakreturn Flag# 定義執(zhí)行皇后問題的主函數(shù) def queens(nums=8, queen_str=""):""":param nums: int-->指代整個(gè)棋盤中想要存放皇后的個(gè)數(shù):param queen_str: str-->指代當(dāng)前皇后存放之前的所有皇后的集合:return:final_queens: List[int]-->指代最后符合要求的皇后的位置"""final_queens = []# 定義遞歸函數(shù),獲取所有八皇后的值def back(queen_str):# 出口條件if len(queen_str) == nums:final_queens.append(queen_str)returnfor index in range(nums):Flag = conflict(queen_str, index)# 如果當(dāng)前位置的皇后是否與之前所有位置的皇后沒有沖突,則執(zhí)行下述代碼if Flag is False:back(queen_str+str(index))back(queen_str)return final_queensif __name__ == "__main__":final_queens = queens()print(final_queens)print(len(final_queens))

寫的應(yīng)該還是比較清楚的,大家也可以再看看官方給的回溯法的描述

描述:

回溯法(探索與回溯法)是一種選優(yōu)搜索法,又稱為試探法,按選優(yōu)條件向前搜索,以達(dá)到目標(biāo)。但當(dāng)探索到某一步時(shí),發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo),就退回一步重新選擇,這種走不通就退回再走的技術(shù)為回溯法,而滿足回溯條件的某個(gè)狀態(tài)的點(diǎn)稱為“回溯點(diǎn)”。

其實(shí)我總結(jié)起來就3點(diǎn):

1 出口。一個(gè)遞歸算法一定要有出口,否則就是一個(gè)死循環(huán)了。出口語句一般都挺好寫的,但 是出口語句該放在哪兒了,這個(gè)就是關(guān)鍵了,這兒容許我先賣個(gè)關(guān)子。

2 遞歸函數(shù)的參數(shù)。一般情況下,遞歸函數(shù)是要帶參數(shù)的,因?yàn)檫f歸操作都是用來處理下一次的過程,如果沒有參數(shù)的話,那么就很難從下一次的操作回溯到當(dāng)前操作了。這么說,可能會(huì)有點(diǎn)迷糊,別急,后面我會(huì)舉例子,這兒還是賣個(gè)關(guān)子。

3 遞歸函數(shù)的處理過程。這個(gè)自不必多說,重中之重,需要好好理解其過程

上面3點(diǎn)就是我總結(jié)的關(guān)于回溯法的關(guān)鍵點(diǎn)了,我覺得只要真正的把這3步吃透,一般的回溯法題目是ok的(這可不是我吹牛哈)下面我就這3點(diǎn)仔細(xì)講講,大家可要豎起耳朵通清楚了哈。

1 出口

關(guān)于這個(gè)出口條件,就像我上面說的,它的關(guān)鍵是出口語句放置的位置,因?yàn)檫@個(gè)語句其實(shí)挺好寫的,一般也就2-3行代碼,大多數(shù)人都能想出來。但我覺得大多數(shù)人苦惱的就是不知道該把它放在哪兒,我剛開始也是這樣,后面總結(jié)了2-3題之后,我發(fā)現(xiàn)了一個(gè)萬能規(guī)律,就是把出口語句放在遞歸函數(shù)的第一行就行,大家可以看看八皇后問題的遞歸函數(shù)back()以及迷宮問題的遞歸函數(shù)back(),我這兒就直接貼出來。

八皇后問題的遞歸函數(shù)back()

# 定義遞歸函數(shù),獲取所有八皇后的值def back(queen_str):# 出口條件if len(queen_str) == nums:final_queens.append(queen_str)returnfor index in range(nums):Flag = conflict(queen_str, index)# 如果當(dāng)前位置的皇后是否與之前所有位置的皇后沒有沖突,則執(zhí)行下述代碼if Flag is False:back(queen_str+str(index))

迷宮問題的遞歸函數(shù)back()

def back(position=start, pos_list=[start]):# 該遞歸函數(shù)的出口if position == final_position:route.append(pos_list)print("successful")returnpos_x = position[0]pos_y = position[1]for direction in walk_route:next_position = [pos_x+direction[0], pos_y+direction[1]]if isValid(nums, next_position):# 記住,這兒一定要用另一個(gè)list集合保存當(dāng)前路線pos_list以及該路線下一個(gè)位置,方便回溯找到pos_list# 如果直接對pos_list添加next_position,則不能回溯找到之前的pos_listpos_list_copy = []pos_list_copy.extend(pos_list)pos_list_copy.append(next_position)nums[pos_x, pos_y] = 0back(next_position, pos_list_copy)# 如果沒有找到出口,則將當(dāng)前上一個(gè)位置0重置為1,回溯nums[pos_x, pos_y] = 1

大家一對比就很清楚的看到,出口語句都是寫在最前面的,其實(shí)最主要的就是不能把出口語句放在for和while循環(huán)語句里面,因?yàn)槌隹谡Z句一定要方便整個(gè)函數(shù)退出,大家聽不懂的強(qiáng)行記住沒問題的,要是出了問題,也別來找我,啊哈哈哈哈哈。

2 遞歸函數(shù)的參數(shù)

這個(gè)遞歸函數(shù)的參數(shù)的設(shè)置也是有很大門道的,設(shè)置的好就很容易得到答案,否則弄大半天可能還是沒有一點(diǎn)反應(yīng)。大家一定要記住一點(diǎn):這個(gè)參數(shù)是隨著每一次的遞歸操作而發(fā)生改變的。而回溯法很關(guān)鍵的一點(diǎn)就是:如果當(dāng)前操作行不通,如何回溯到上一步操作。大家繼續(xù)看上面貼的兩個(gè)遞歸函數(shù)的參數(shù),會(huì)發(fā)現(xiàn)其參數(shù)都是要改變的,既然參數(shù)會(huì)發(fā)生改變,那么我們要如何保存其上一步操作的值呢?大家可以再細(xì)細(xì)看看上述兩個(gè)函數(shù)的傳值操作。

八皇后問題的傳值操作

for index in range(nums):Flag = conflict(queen_str, index)# 如果當(dāng)前位置的皇后是否與之前所有位置的皇后沒有沖突,則執(zhí)行下述代碼if Flag is False:back(queen_str+str(index))

大家可以看到back(queen_str+str(index))這一步,其傳的參數(shù)就是queen_str+str(index) 其實(shí)想法就是不破壞當(dāng)前參數(shù)的值,直接把當(dāng)前值加上一個(gè)值(大家可以理解為定義了另一個(gè)非queen_str當(dāng)前值的值給傳到下一次函數(shù)),只要不破壞當(dāng)前值,函數(shù)就能回溯。這一步很關(guān)鍵,大家可以好好品味。

for index in range(nums):Flag = conflict(queen_str, index)# 如果當(dāng)前位置的皇后是否與之前所有位置的皇后沒有沖突,則執(zhí)行下述代碼if Flag is False:queen_str = queen_str+str(index)back(queen_str )

如果大家還有些疑惑的話,可以再把傳值操作改成這樣試試,你會(huì)發(fā)現(xiàn)結(jié)果會(huì)大相徑庭的,這里就是破壞了當(dāng)前值。

迷宮問題的傳值操作

if isValid(nums, next_position):# 記住,這兒一定要用另一個(gè)list集合保存當(dāng)前路線pos_list以及該路線下一個(gè)位置,方便回溯找到pos_list# 如果直接對pos_list添加next_position,則不能回溯找到之前的pos_listpos_list_copy = []pos_list_copy.extend(pos_list)pos_list_copy.append(next_position)nums[pos_x, pos_y] = 0back(next_position, pos_list_copy)# 如果沒有找到出口,則將當(dāng)前上一個(gè)位置0重置為1,回溯nums[pos_x, pos_y] = 1

大家再可以參考迷宮操作的傳值操作理解。

關(guān)于參數(shù),我還有一點(diǎn)就是強(qiáng)調(diào):就是結(jié)果一定是要有一個(gè)全局參數(shù)來保存,這個(gè)全局參數(shù)不會(huì)隨著每一次的遞歸操作而隨時(shí)改變,它只是用來保存每一次遞歸操作成功時(shí)的結(jié)果,其它的不關(guān)它的事。你仔細(xì)看看這兩個(gè)程序也會(huì)發(fā)現(xiàn):它們在一開始就定義了一個(gè)List空列表。大家也可以照搬的,凡是結(jié)果需要保存的題目90%以上就是要預(yù)先定義一個(gè)List空列表(不要問我這個(gè)90%數(shù)據(jù)是怎么得來的哈,問了我也不知道,哈哈哈哈哈)

八皇后問題的List空列表

# 定義執(zhí)行皇后問題的主函數(shù) def queens(nums=8, queen_str=""):""":param nums: int-->指代整個(gè)棋盤中想要存放皇后的個(gè)數(shù):param queen_str: str-->指代當(dāng)前皇后存放之前的所有皇后的集合:return:final_queens: List[int]-->指代最后符合要求的皇后的位置"""# 定義一個(gè)保存結(jié)果的List列表final_queens = []

迷宮問題的List空列表

""" 迷宮問題,使用回溯法 """ def maze(nums, start):""":param nums: List[List[int]]-->指代所給的迷宮:param start: List[int X, Y]-->指代起始點(diǎn)位置:return: route: List[]"""# 定義最終路線的集合route = []

3 遞歸函數(shù)的處理過程

這個(gè)過程是最關(guān)鍵的了,但是也很少有人能把它說清楚,當(dāng)然也包括我。我想來想去,總結(jié)起來一句話就是:如果當(dāng)前遞歸過程的處理參數(shù)符合要求,則執(zhí)行相關(guān)賦值或其它操作,然后轉(zhuǎn)入下一次遞歸,如果下一次遞歸不能找到出口,則把之前相關(guān)賦值或其它操作重置為初始狀態(tài)。說的有些抽象,但我目前確實(shí)是說的這么樣了,還需要自己好好看幾個(gè)題目,好好做幾道題目才能理解這層意思。大家也可以好好看看下述迷宮問題的處理過程。

迷宮問題的處理過程

nums[pos_x, pos_y] = 0 back(next_position, pos_list_copy) # 如果沒有找到出口,則將當(dāng)前上一個(gè)位置0重置為1,回溯 nums[pos_x, pos_y] = 1

2 迷宮問題

問題描述:

定義一個(gè)二維數(shù)組:

int maze[5][5] = {0, 1, 0, 0, 0,0, 1, 0, 1, 0,0, 0, 0, 0, 0,0, 1, 1, 1, 0,0, 0, 0, 1, 0,};

它表示一個(gè)迷宮,其中的1表示墻壁,0表示可以走的路,只能橫著走或豎著走,不能斜著走,要求編程序找出從左上角到右下角的最短路線。

解題思路:

本題我才用的方法也是很常規(guī)的廣度優(yōu)先搜索(BFS)也就是定義四個(gè)方向,即上下左右,對迷宮內(nèi)每個(gè)可以通過的點(diǎn)執(zhí)行該四個(gè)方向的操作。具體的操作我這兒就不說了,直接貼代碼吧!

迷宮問題代碼如下:

mport numpy as np# 檢查當(dāng)前位置是否有效 # 如果當(dāng)前位置為0,則表示不能通過; # 如果當(dāng)前位置表示為1,則表示可以繼續(xù)通過 def isValid(nums, current_position):''':param nums: List[List[int]]-->指代所給的迷宮:param current_position: List[int X, Y]-->指代當(dāng)前坐標(biāo)點(diǎn)位置:return: boolean-->指代當(dāng)前位置是否有效'''pos_x = current_position[0]pos_y = current_position[1]if pos_x in range(len(nums)) and pos_y in range(len(nums)) and nums[pos_x, pos_y] == 1:return Trueelse:return False""" 迷宮問題,使用回溯法 """ def maze(nums, start):""":param nums: List[List[int]]-->指代所給的迷宮:param start: List[int X, Y]-->指代起始點(diǎn)位置:return: route: List[]"""# 定義最終路線的集合route = []# 定義當(dāng)前點(diǎn)上下左右移動(dòng)方向的集合walk_route = [[-1, 0], [0, -1], [1, 0], [0, 1]]# 獲取迷宮的終點(diǎn)nums_length = len(nums)final_position = [nums_length-1, nums_length-1]def back(position=start, pos_list=[start]):# 該遞歸函數(shù)的出口if position == final_position:route.append(pos_list)print("successful")returnpos_x = position[0]pos_y = position[1]for direction in walk_route:next_position = [pos_x+direction[0], pos_y+direction[1]]if isValid(nums, next_position):# 記住,這兒一定要用另一個(gè)list集合保存當(dāng)前路線pos_list以及該路線下一個(gè)位置,方便回溯找到pos_list# 如果直接對pos_list添加next_position,則不能回溯找到之前的pos_listpos_list_copy = []pos_list_copy.extend(pos_list)pos_list_copy.append(next_position)nums[pos_x, pos_y] = 0back(next_position, pos_list_copy)# 如果沒有找到出口,則將當(dāng)前上一個(gè)位置0重置為1,回溯nums[pos_x, pos_y] = 1back()return routeif __name__ == "__main__":nums = [[1, 0, 0, 1, 0, 1], [1, 1, 1, 0, 1, 0], [0, 0, 1, 0, 1, 0], [0, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 1],[1, 0, 0, 0, 1, 1]]nums = np.array(nums)print(nums)current_position = [0, 0]print(maze(nums, current_position))

該講的我在八皇后問題那兒都講到了,本題大家就用作檢驗(yàn)方法和思考作用吧。

3 解數(shù)獨(dú)

問題描述

編寫一個(gè)程序,通過已填充的空格來解決數(shù)獨(dú)問題。

一個(gè)數(shù)獨(dú)的解法需遵循如下規(guī)則:

  • 數(shù)字 1-9 在每一行只能出現(xiàn)一次。
  • 數(shù)字 1-9 在每一列只能出現(xiàn)一次。
  • 數(shù)字 1-9 在每一個(gè)以粗實(shí)線分隔的 3x3 宮內(nèi)只能出現(xiàn)一次。
  • 空白格用 '.' 表示。

    一個(gè)數(shù)獨(dú)。

    答案被標(biāo)成紅色。

    Note:

    • 給定的數(shù)獨(dú)序列只包含數(shù)字 1-9 和字符 '.' 。
    • 你可以假設(shè)給定的數(shù)獨(dú)只有唯一解。
    • 給定數(shù)獨(dú)永遠(yuǎn)是 9x9 形式的。

    思路:

    這一題是LeetCode上面的第37題,確實(shí)是挺惡心的,當(dāng)時(shí)做了兩三天。但如果你把它解答出來了,我覺得你對應(yīng)回溯法的理解應(yīng)該是差不多了。下面我還是直接貼代碼了,相關(guān)注解我都在代碼里寫的很清楚了,大家應(yīng)該很容易看得懂!

    代碼如下:

    import numpy as npclass Solution(object):# 本題采用回溯法解決# 當(dāng)時(shí)在area3x3檢查時(shí)栽了跟頭def solveSudoku(self, board):""":type board: List[List[str]]:rtype: void Do not return anything, modify board in-place instead."""# board = np.array(board)def back(board, position=[0, 0]):# 如果當(dāng)前數(shù)獨(dú)中沒有空白元素'.',則說明查找成功了if position == [-1, -1]:print("successful")return True# 獲取當(dāng)前位置的橫縱坐標(biāo)pos_x = position[0]pos_y = position[1]# 獲取當(dāng)前位置的值pos_value = board[pos_x][pos_y]if pos_value == '.':for index in range(1, 10):value = str(index)if self.isValid(board, position, value) is True:board[pos_x][pos_y] = valuenext_position = self.getNextPosition(board, position)if back(board, next_position) is True:return Trueelse:board[pos_x][pos_y] = '.'else:next_pos = self.getNextPosition(board, position)back(board, next_pos)return Falseback(board)return board# 獲取下一個(gè)有效點(diǎn)的坐標(biāo)位置def getNextPosition(self, board, position):next_x = position[0]next_y = position[1]while board[next_x][next_y] != '.':next_y += 1if next_y >= len(board):next_x += 1next_y = 0if next_x not in range(len(board)) or next_y not in range(len(board)):return [-1, -1]return [next_x, next_y]# 判斷當(dāng)前位置是否有效def isValid(self, board, position, value):""":param board: array[[]]-->指代所給的數(shù)獨(dú)列表:param position: List[int x, y]-->指代所給的當(dāng)前位置:param value: str-->指代當(dāng)前位置的值:return: boolean-->若返回為True,則表示當(dāng)前位置有效;反之,則無效"""board = np.array(board)# 獲取當(dāng)前位置的橫縱坐標(biāo)pos_x = position[0]pos_y = position[1]# 獲取當(dāng)前位置橫縱坐標(biāo)所對應(yīng)的每一行每一列元素pos_row = board[pos_x]pos_col = board[:, pos_y]# 如果當(dāng)前位置的值value與其所在的每一行或者每一列的值重復(fù),則表示當(dāng)前值無效,返回Falseif value in pos_row or value in pos_col:return False# 獲取當(dāng)前位置點(diǎn)所在的3x3區(qū)域的位置area3x3_x = pos_x//3*3area3x3_y = pos_y//3*3area3x3_batch = board[area3x3_x:area3x3_x+3, area3x3_y:area3x3_y+3]# 如果當(dāng)前位置的值value與其所在的3x3區(qū)域的值重復(fù),則表示當(dāng)前值無效,返回Falseif value in area3x3_batch:return Falsereturn Trueif __name__ == "__main__":board = [['5', '3', '.', '.', '7', '.', '.', '.', '.'],['6', '.', '.', '1', '9', '5', '.', '.', '.'],['.', '9', '8', '.', '.', '.', '.', '6', '.'],['8', '.', '.', '.', '6', '.', '.', '.', '3'],['4', '.', '.', '8', '.', '3', '.', '.', '1'],['7', '.', '.', '.', '2', '.', '.', '.', '6'],['.', '6', '.', '.', '.', '.', '2', '8', '.'],['.', '.', '.', '4', '1', '9', '.', '.', '5'],['.', '.', '.', '.', '8', '.', '.', '7', '9']]result = Solution().solveSudoku(board)print(np.array(result))

    暫時(shí)就補(bǔ)充這么多了,如果有更好的想法,我也會(huì)及時(shí)補(bǔ)充的,當(dāng)然了,各位讀者如果有更nice的解題套路也希望大家積極分享!!!


    2019/3/19 19:13補(bǔ)充

    昨天有位朋友給我出了道題,也是涉及回溯法的,我覺得很有意思,所以想分享出來。

    題目如下:

    思路:

    因?yàn)檫@個(gè)方格是不規(guī)則的方格,所以我首先想到的是將它填充成一個(gè)規(guī)則方格,便于我們插值。 如圖所示:

    之所以這樣,是因?yàn)槲覀兙涂梢灾苯訉γ總€(gè)方格內(nèi)的元素的相鄰方格內(nèi)元素作比較了。

    如圖所示:

    總結(jié)

    以上是生活随笔為你收集整理的回溯法基本思想_LeetCode--回溯法心得的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。