高階資料結構詳解

gzr發表於2020-08-16

//本文同步發表於簡書,若想食用更佳,點選檢視原文:https://www.jianshu.com/p/978859c65c1b

前言

洛谷簽到

高階資料結構難點很多,而且小編接近一年沒有碰過程式碼了,簡書一天能釋出的文章數目有限?,所以今天決定爆肝一個晚上來一個超長的部落格。
但小編能力有限,只會講解下列幾個部分:

  • 樹、圖遍歷的基礎——搜尋
  • 佇列
  • 樹的基本知識
  • 二叉樹
  • 二叉排序樹
  • 平衡樹Treap
  • 紅黑樹(待更中……)
  • 樹狀陣列
  • 線段樹
  • 圖論(待更中……)

實際上這都是我從網上找來的一大堆看似很高階,實則很高階的東西,但是小編會非常詳細的記錄經驗和講解知識,以及一些實際的例題。
不過圖論內容較多,圖論將會另更一篇(不過如果想立刻學的話可以看一下我昔日的部落格:傳送門),屆時在章末補上鍊接。

一、樹、圖遍歷的基礎——搜尋

在此部分,我們先講解深度優先搜尋(dfs),廣度優先搜尋就放到佇列裡面講。
搜尋是至關重要的,因為樹,圖在遍歷時,使用的就是搜尋演算法,遍歷是它們的基本操作之一。

1)前置基礎

務必學會遞迴

2)核心思想

如果你現在正身處一個迷宮中,你想走出迷宮,那麼方法主要有兩種

  • 1>一條路走到黑,不撞南牆不回頭,只要認定一個方向,就一直走到邊界才掉頭
    DFS

  • 2>環顧四周,不停向外擴充套件,先看離自己近的是不是出口,再看遠的
    BFS
    第一種辦法靠碰運氣,說不準第一次方向就蒙對了,但也有可能運氣差到遍歷整個地圖才能找到。
    第二種情況也靠碰運氣,說不準終點就在起點旁邊,但是也可能運氣差到終點在邊界處。
    我們現在將要講解的就是第一種辦法。

3)談談怎麼程式碼實現吧

整個過程是這樣的:

void dfs(一些需要傳遞的資訊,來唯一識別當前的狀態,例如當前走的次數,當前的位置等)
{
  if(判斷當前狀態是否是目標狀態) 如果是就存下這個答案,返回
  for(逐個列舉可能的狀態)
  {
    擴充套件新狀態;
    if(新狀態可以使用)
    {
      有時需要做標記;
      dfs(新狀態);
      回溯,即撤回標記,防止下次不能再到達這個狀態(有時不需要回溯)
    }
  }
}

具體一點,對於迷宮,要求輸出最小步數,可以修改成這樣:

void dfs(橫座標,縱座標,當前步數)
{
  if(橫座標==終點橫座標&&縱座標==終點縱座標) 
  {
    ans=min(ans,當前步數);
    return;
  }
  for(列舉四個方向,即上下左右)
  {
    擴充套件出下一步的橫座標,縱座標;
    if(橫座標、縱座標沒有超出地圖範圍且這個新地方沒有走過)
    {
      標記這個點走過了;
      dfs(新橫座標,新縱座標,步數+1);
      標記這個點沒有走過;
    }
  }
}

例題

例1

對於上面的迷宮,正好小編最近刷了一道規規矩矩的迷宮題。
題目連結:https://www.luogu.com.cn/problem/P1605
對於不想抬起寶貴的手點選連結(因為這樣手移動會做功消耗動能,與空氣摩擦還要生熱)的同志,直接看題吧:

P1605 迷宮

題目背景
給定一個N*M方格的迷宮,迷宮裡有T處障礙,障礙處不可通過。給定起點座標和終點座標,問: 每個方格最多經過1次,有多少種從起點座標到終點座標的方案。在迷宮中移動有上下左右四種方式,每次只能移動一個方格。資料保證起點上沒有障礙。

題目描述

輸入格式
第一行N、M和T,N為行,M為列,T為障礙總數。第二行起點座標SX,SY,終點座標FX,FY。接下來T行,每行為障礙點的座標。

輸出格式
給定起點座標和終點座標,問每個方格最多經過1次,從起點座標到終點座標的方案總數。

輸入輸出樣例
輸入 #1
2 2 1
1 1 2 2
1 2
輸出 #1
1
說明/提示
【資料規模】

1≤N,M≤5

AC程式碼

#include<bits/stdc++.h>
using namespace std;
int n,m,t,ans,sx,sy,fx,fy,a,b;
int edge[100][100],mapp[100][100];
int next[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//下一步怎麼走
void dfs(int x,int y)
{
	if(x==fx&&y==fy)  //判斷目標狀態
	{
		ans++;//解的個數增加
		return;
	}
	for(int i=0;i<4;i++)
	{
		int tx=x+next[i][0];
		int ty=y+next[i][1];//擴充套件出下一步的位置
		if(tx<1||ty<1||tx>n||ty>m||mapp[tx][ty]==1||edge[tx][ty]==1) continue; //判斷下一步走這個點是否成立
		mapp[tx][ty]=1;//標記走過了,防止同一路徑重複走造成死迴圈
		dfs(tx,ty);
		mapp[tx][ty]=0;//標記回沒走過,因為可能另一條路徑需要經過這個點
	}
}
int main()
{
	cin>>n>>m>>t;
	cin>>sx>>sy>>fx>>fy;
	for(int i=1;i<=t;i++)
	{
		cin>>a>>b;
		edge[a][b]=1;//標記障礙物
	}
	mapp[sx][sy]=1;
	dfs(sx,sy);
	cout<<ans;
	return 0;
}

例2

題目連結:https://www.luogu.com.cn/problem/P1135

P1135 奇怪的電梯

預覽結束,請點此進入原文:https://www.jianshu.com/p/978859c65c1b

相關文章