主頁 >  其他 > 《啊哈!演算法》的筆記

《啊哈!演算法》的筆記

2021-09-23 12:49:17 其他

本文作為《啊哈!演算法》里面的內容,作者整理了部分知識供大家參考,如有侵權聯系洗掉,

一大波數正在靠近——排序

最快最簡單的排序——桶排序

簡單介紹一下下圖的主人公小哼
在這里插入圖片描述

小哼的班上只有5個同學,這5個同學分別考了5分、3分、5分、2分和8分(滿分為10分),按從大到小排序,排序后是 8 5 5 3 2,

在這里插入圖片描述

我們只需申請一個大小為11的陣列 int a[11],現在我們有編號從a[0]到a[10],我們將a[0]~a[10]初始為0,表示這些分數沒人得到,如:a[0]=0表示目前沒人0分,
在這里插入圖片描述

根據小哼班里的成績可得到最終結果如下圖:
在這里插入圖片描述

因此我們只需將出現過的分數列印處理就可以了,
在這里插入圖片描述

簡化版桶排序:有11個桶,編號從0~10,每出現一個數,就在對應編號的桶中放一個小旗子,最后數出每個桶中有幾個小旗子,如下圖:

代碼實作:

#include <stdio.h>
int main() {
	int a[11], i, j, t;
	for(i = 0; i <= 10; i++) {
		a[i] = 0; // 初始化為0
	}
	for(i = 1; i <= 5; i++) {
		scanf("%d", &t); // 讀入5個數到變數
		a[t]++; // 計數
	}
	for(i = 10; i >=0; i--) {
		for(j = 1; j <= a[i]; j++) {
			printf("%d", i);
		}
	}
	getchar();getchar(); // 暫停程式
	return 0;
}

該演算法的時間復雜度是 O ( M + N ) O(M+N) O(M+N).

鄰居好說話——冒泡排序

簡化版的桶排序的缺點:浪費空間!

冒泡排序的基本思想:每次比較兩個相鄰的元素,如果它們順序錯誤就把它們交換過來,

如 12 35 99 18 76 這5個數按從大到小排序,即越小的越靠后,首先比較第1位和第2位的大小,發現 12 比 35 小,交換這兩個數的位置,交換后的順序是 35 12 99 18 76,經過4次比較,得到 35 99 18 76 12,

我們剛剛將5個數中最小的一個歸位了,每個將一個數歸位我們稱其位“一趟”,
在這里插入圖片描述

總結:如果有n個數進行排序,只需將 n-1 個數歸位(即n-1趟),每次都需從第1位開始進行比較,將較小的一個數放后面,比較完后向后挪一位繼續比較下面兩個相鄰數的大小,

代碼實作

#include <stdio.h>
int main() {
	int a[100], i, j, t, n;
	scanf("%d", &a[i]);
	for (i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for (i = 1; i <= n - i; j++) {
		for (j = 1; j <= n - i; j++) {
			if (a[j] < a[j + 1]) {
				t = a[j];
				a[j] = a[j + 1];
				a[j + 1] = t;
			}
		}
	}
	for (i = 1; i <= n; i++) {
		printf("%d", a[i]);
	}
	getchar(); getchar();
	return 0;
}

冒泡排序核心是雙重嵌套回圈,時間復雜度是 O ( N 2 ) O(N^2) O(N2)

最常用的排序——快速排序

我們對 6 1 2 7 9 3 4 5 10 8 這10個數進行排序,首先在這個序列中找一個數作基準數(用來做參照的數),我們讓6作基準數,將這個序列中所以大于基準數放6的右邊,小于基準數的放6的左邊,

方法:先從右往左找一個小于6的數,再從左往右找一個大于6的數然后交換,所以我們使用兩個變數i 和j,分別指向序列最左邊和最右邊,我們為這兩個變數取個名字“哨兵i"和”哨兵j“,剛開始讓哨兵i指向序列最左邊(i=1),指向數字6,讓哨兵j指向序列最右邊(j=10),指向數字8.
在這里插入圖片描述

首先哨兵j開始出動,哨兵j向左移動(j–)直到找到一個小于6的數才停下來,接下來哨兵i向右移動(i++),直到找到一個大于6的數才停下來,最后哨兵j停在數字5,哨兵i停在數字7,
在這里插入圖片描述

交換哨兵i和哨兵j所指向的元素的值
在這里插入圖片描述

第一次交換結束,哨兵j繼續向左移動,到數字4停下,哨兵i繼續向右移動,到9停下,再次進行交換
在這里插入圖片描述

第二次交換結束,哨兵j繼續向左移動,到3停下哨兵i繼續向右移動,哨兵i走到數字3與哨兵j相遇了
在這里插入圖片描述

此時探測結束,我們將基準數6和3進行交換
在這里插入圖片描述

整個演算法的處理程序圖
在這里插入圖片描述

快速排序相比于冒泡排序,每次交換是跳躍式的,基于二分的思想,每次排序的時候設定一個基準點,將小于等于基準點的數放基準點左邊,將大于等于基準點的數放基準點右邊,

最壞的情況是相鄰的兩數進行交換,此時快速排序的時間復雜度是 O ( N 2 ) O(N^2) O(N2)(最差時間復雜度),平均時間復雜度是 O ( N l o g N ) O(Nlog_N) O(NlogN?)

#include <stdio.h>
int a[101], n;
void quicksort(int left, int right) {
	int i, j, t, temp;
	if (left > right) {
		return;
	}
	temp = a[left];
	i = left;
	j = right;
	while (i != j) {
		while (a[j] >= temp && i < j) {
			j--;
		}
		while (a[i] <= temp && i < j) {
			i++;
		}
		if (i < j) {
			t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
		a[left] = a[i];
		a[i] = temp;
		quicksort(left, i - 1);
		quicksort(i + 1, right);
	}
}
int main() {
	int i, j, t;
	scanf("%d", &n);
	for (i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	quicksort(1, n);
	for (i = 1; i <= n; i++) {
		printf("%d", a[i]);
	}
	getchar(); getchar();
	return 0;
}

小哼買書

小哼的學校要建一個圖書角,老師派小哼去找同學做調查,每本書有唯一的ISBN號,小哼需要完成去重和排序的作業,
在這里插入圖片描述
輸入2行,第1行表示有多少個同學參加調查(1 ~ 100),第2行表示圖書的ISBN號(1 ~ 1000),

輸出2行,第1行表示需要買多少書,第2行表示從小到大已排序好的圖書的ISBN號,

方法一:去重后排序

#include <stdio.h>
int main() {
	int a[1001], n, i, t;
	for (i = 1; i <= 1000; i++) {
		a[i] = 0; // 初始化
	}
	scanf("%d", &n); // 讀入n
	for (i = 1; i <= n; i++) {
		scanf("%d", &t); 
		a[t] = 1; // 標記
	}
	for (i = 1; i <= 1000; i++) {
		if (a[i] == 1) {
			printf("%d", i);
		}
	}
	getchar(); getchar();
	return 0;
}

桶排序的時間復雜度: O ( N + M ) O(N+M) O(N+M)

方法二:排序后去重

#include <stdio.h>
int main() {
	int a[1001], n, i, t;
	for (i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for (i = 1; i <= n - 1; i++) {
		for (j = 1; j <= n - i; j++) {
			if (a[j] > a[j + 1]) {
				t = a[j];
				a[j] = a[j + 1];
				a[j + 1] = t;
			}
		}
	}
	printf("%d", a[i]);
	for (i = 2; i <= n; i++) {
		if (a[i] != a[i - 1]) {
			printf("%d", a[i]);
		}
	}
	getchar(); getchar();
	return 0;
}

時間復雜度: O ( N 2 ) O(N^2) O(N2)

堆疊、佇列、鏈表

解密QQ號——佇列

小哼向新同桌小哈詢問QQ號,小哈給小哼一串加密過的數字6 3 1 7 5 8 9 2 4,解密規則是:首先將第1個數洗掉,接著將第2個數放到這串數的末尾,直到剩下最后一個數,將最后一個數也洗掉,按剛才洗掉的順序,把這些洗掉的數連在一起就是小哈的QQ號了,

在這里插入圖片描述

如何在陣列中洗掉一個數?

最簡單的方法:將后面的數都往前移動一位,
在這里插入圖片描述

我們引入兩個整型變數head和tail,head用來記錄佇列的隊首(第一位),tail用來記錄佇列的隊尾(最后一位)的下一個位置,我們這里規定隊首和隊尾重合時,佇列為空,
我們將這9個數全部放入佇列,head=1;tail=10;在隊首洗掉一個數的操作是head++
在這里插入圖片描述

在隊尾增加一個數x的操作是q[tail]=x;tail++
在這里插入圖片描述

整個解密程序圖
在這里插入圖片描述

#include <stdio.h>
int main() {
	int q[102] = { 0, 6, 3, 1, 7, 5, 8, 9, 2,4 }, head, tail;
	int i;
	head = 1;
	tail = 10;
	while (head < tail) { // 佇列不為空
		printf("%d", q[head]); // 列印隊首并出隊
		head++;
		q[tail] = q[head]; // 將隊首貼加到隊尾
		tail++;
		head++; // 再將隊首出隊
	}
	getchar(); getchar();
	return 0;
}

佇列:一種特殊的線性結構,只允許在佇列的首部(head)進行洗掉操作,稱為出隊,佇列的尾部(tail)進行插入操作,稱為入隊,當佇列中沒有元素是(head=tail)佇列為空佇列,

佇列是我們今后學習廣度優先搜索以及佇列優化的Bellman-Ford 最短路演算法的核心資料結構,我們將佇列的三個基本元素(一個陣列,兩個變數)封裝為一個結構體型別,

struct queue {
	int data[100]; // 佇列的主體
	int head; // 隊首
	int tail; // 隊尾
};

在這里插入圖片描述

定義結構體變數:

struct queue q;

訪問結構體變數的內部成員

q.head = 1;
q.tail = 1;
scanf("%d", &q.data[q.tail]);

結構體實作的佇列操作

#include <stdio.h>
struct queue {
	int data[100]; // 佇列的主體
	int head; // 隊首
	int tail; // 隊尾
};
int main() {
	struct queue q;
	int i;
	q.head = 1;
	q.tail = 1;
	for (i = 1; i <= 9; i++) {
		scanf("%d", &q.data[q.tail]);
	}
	while (head < tail) { // 佇列不為空
		printf("%d", q[head]); // 列印隊首并出隊
		head++;
		q[tail] = q[head]; // 將隊首貼加到隊尾
		tail++;
		head++; // 再將隊首出隊
	}
	getchar(); getchar();
	return 0;
	
}

解密回文——堆疊

后進后出的資料結構——堆疊

堆疊限定為只能在一端進行插入和洗掉操作,如:有一個小桶直徑只能放一個小球,我們依次放入2、1、3號小球,如果你想要拿出2號小球,必須先將3號小球拿出,再拿出1號小球,最后才能將2號小球拿出,

堆疊的實作:一個一維陣列和一個指向堆疊頂的變數top,我們通過top來對堆疊進行插入和洗掉操作,
如:xyzyx 是一個回文字串,所謂回文字符是正讀反讀均相同的字符序列,
在這里插入圖片描述

讀取這行字串,求出這個字串的長度

char a[101];
int len;
gets(a);
len = strlen(a);

如果一個字串是回文字符,那么它必須是中間對稱的,所以我們求中點

mid = len / 2 - 1;

我們先將mid之前的字符全部入堆疊,char s[101]; ,初始化堆疊 top=0;,入堆疊的操作是top++;s[top]=x;(入堆疊的字符暫存字符變數x中),可簡寫s[++top]=x;,

for(i = 0; i <= mid; i++) {
	s[++top] = x;
}

代碼實作

#include <stdio.h>
#include <string.h>
int main() {
	char a[101], s[101];
	int i, len, mid, next, top;
	gets(a); // 讀入一行字符
	len = strlen(a); // 求字串長度
	mid = len / 2 - 1; // 求字串中點
	top = 0; // 堆疊的初始化
	for (i = 0; i <= mid; i++) {
		s[++top] = a[i];
	}
	if (len % 2 == 0) { // 判斷字串長度是奇數還是偶數
		next = mid + 1;
	}
	else {
		next = mid + 2;
	}
	for (i = next; i <= len - 1; i++) {
		if (a[i] != s[top]) {
			break;
		}
		top--;
	}
	if (top == 0) { // top為0,表示堆疊內所以字符都被一一匹配
		printf("YES");
	}
	else {
		printf("NO");
	}
	getchar(); getchar();
	return 0;
}

鏈表

如果需要在8前面插入一個6,無需像陣列一樣將8及后面的數都依次往后移一位,只需像下圖一樣
在這里插入圖片描述
C語言中可以使用指標和動態分配記憶體函式 malloc來實作,

int *p; // 定義一個整型指標變數

指標作用:存盤一個地址,存盤一個記憶體空間的地址

p = &a; // &取地址符,整型指標p指向了整型變數a

通過指標p來輸出變數a的值

#include <stdio.h>
int main() {
	int a = 10;
	int *p;
	p = &a;
	printf("%d", *p); // *在printf里做間接運算子,取得指標p所指向的記憶體中的值
	getchar();getchar();
}

運行結果

10

動態存盤方法

malloc函式:從記憶體中申請分配指定位元組大小的記憶體空間

malloc(4);

sizeof(int)獲取int型別所占用的位元組數

malloc(sizeof(int));

存盤這個空間的首地址

int *p;
p = (int *)malloc(sizeof(int));

malloc函式的回傳型別是void型別,void表示未確定型別的指標,在C和C++中,void*型別可以強制轉換為任何其他型別的指標,
指標就是用來存盤記憶體地址的,為什么要分不同型別的指標呢?因為變數存盤的是一個記憶體空間的首地址(第一個位元組的地址),但這個空間占用了多少個位元組,用來存盤什么型別的數,則是由指標的型別來標明的,這樣系統才知道去多少個連續記憶體作為一個資料,

#include <stdio.h>
#include <stdlib.h>
int main() {
	int *p; // 定義一個指標p
	p = (int *)malloc(sizeof(int)); // 指標p獲取動態分配的記憶體空間地址
	*p = 10;
	printf("%d", *p); // 輸出指標p所指向的記憶體中的值
}

運行結果

10

陣列模擬鏈表

在這里插入圖片描述
每個結點都由兩個部分組成,左邊部分用來存盤具體的數值,可以用一個整型變數;右邊部分存盤下一個結點的地址,可以用指標實作(稱為后繼指標),

定義一個結構體來存盤這個結點

struct node {
	int data;
	struct node *next;
};

在這里插入圖片描述
我們定義一個叫做 node 的結構體型別,第一個成員整型data,用來存盤具體的數值;第二個成員是一個指標,用來存盤下一個節點的地址,

如何建立鏈表?

我們需要一個頭指標 head 指向鏈表的最開始,當鏈表還沒建立時頭指標 head 為空(指向空指標),

struct node *head;
head = NULL; // 頭指標初始為空

創建一個結點,并用臨時指標p指向這個結點

struct node *p;
p = (struct node *)malloc(sizeof(struct node));

設定新創建的這個結點的左半部分和右半部分

scanf("%d", &a);
p->data = a; 
p->next = NULL;

在這里插入圖片描述
->結構體指標運算子,用來訪問結構內部成員的,因為p是個指標,所以不能使用.號訪問內部成員,

設定頭指標并設定新創建結點的*next指向空,頭指標的作用是方便以后從頭遍歷整個鏈表,

if(head == NULL) {
	head = p; // 頭指標指向這個結點
}
else {
	q->next = p; // 上一個結點的后續指標指向當前結點
}

如果這是第一個創建的結點,則將頭指標指向這個結點
在這里插入圖片描述
如果不是第一個創建的結點,則將上一個結點的后續指標指向當前結點
在這里插入圖片描述
最后將指標q也指向當前結點,因為臨時指標p將會指向新創建的結點

q = p; // 指標q指向當前結點
#include <stdio.h>
#include <stdlib.h>
// 創建一個結構體表示鏈表的結點型別
struct node
{
	int data;
	struct node *next;
};
int main() 
{
	struct node *head, *p, *q, *t;
	int i, n, a;
	scanf("%d", &n);
	head = NULL; // 頭指標初始為空
	for(i = 1; i <= n; i++)
	{
		scanf("%d", &a);
		p = (struct node *)malloc(sizeof(struct node));
		p->data = a; // 將資料存盤到當前結點的data域中
		p->next = NULL; // 設定當前結點的后續指標指向空,也就是當前結點的下一個結點為空
		if(head == NULL)
			head = p; // 如果是第一個創建的結點,則將頭指標指向這個結點
		else
			q->next = p; // 如果不是第一個創建的結點,則將上一個結點的后續指標指向當前結點
		q = p; // 指標q也指向當前結點
	}
	t = head;
	while(t != NULL)
	{
		printf("%d", t->data);
		t = t->next; // 繼續下一個結點
	}
	getchar();getchar();
	return 0;
}

注意:記得用釋放動態申請的空間
在這里插入圖片描述

scanf("%d", &a); // 讀入待插入的數
while(t != NULL)
{
	if(t->next->data > a)
	{
		p = (struct node*)malloc(sizeof(struct node)); // 動態空間
		p->data = a;
		p->next = t->next; // 新增結點的后續指標指向當前結點的后續指標所指向的結點
		t->next = p; // 當前結點的后續指標指向新增結點
		break; // 插入完后退出
	}
	t = t->next; // 繼續下個結點
}

模擬鏈表

利用data陣列來存盤序列中的每個數,利用right來存盤序列中每個數右邊的數在陣列data中的位置,
在這里插入圖片描述
如:right[0]表示當前序列中9號元素的右邊沒有元素

現要在8前面插入一個6,只需將6直接存放在陣列末尾(data[10]=6),接下來將right[3]=10表示新序列3號元素右邊的元素存放在data[10]中,再將right[10]=4,表示新序列中10號元素右邊的元素存放在data[4]中,這樣就完成了8前面插入一個6,
在這里插入圖片描述

#include <stdio.h>
int main()
{
	int data[101], right[101];
	int i, n, t, len;
	// 讀入數字
	scanf("%d", &n);
	for(i = 1; i <=n; i++)
		scanf("%d", &data[i]);
	len = n;
	// 初始化陣列right
	for(i = 1; i <= n; i++)
	{
		if(i != n)
			right[i] = i + 1;
		else
			right[i] = 0;
	}
	// 陣列data末尾增加一個數
	len++;
	scanf("%d", &data[len]);
	// 鏈表頭部開始遍歷
	t = 1;
	while(t != 0)
	{
		if(data[right][t] > data[len]) // 如果當前結點的下個結點的值大于待插入數,將數插入中間 
		{
			right[len] = right[t]; // 新插入數的下一個結點編號等于當前結點的下一個結點編號
			right[t] = len; // 當前結點的下一個節點編號就是新插入數的編號
			break; // 插入完成結束回圈
		}
		t = right[t];
	}
	// 輸出鏈表的數
	t = 1;
	while(t != 0)
	{
		printf("%d", data[t]);
		t = right[t];
	}
	getchar();getchar();
	return 0;
}

列舉!很暴力

坑爹的奧數

列舉演算法(窮舉演算法):有序地去嘗試每一種可能

小哼在數學課上遇到一道奧數題:在這里插入圖片描述,填入相同的數字使得等式成立,

for (int i = 1; i <= 9; i++) {
		if ((i * 10 + 3) * 6528 == (30 + i) * 8256)
			printf("%d", i);
}

數的全排列

123 的全排列是 123、132、213、312、312、321,

#include <iostream>
int main() {
    for(int a = 1; a <= 3; a++) {
        for(int b = 1; b <= 3; b++) {
            for(int c = 1; c <= 3; c++) {
                if(a != b && a != c && b != c) {
                    printf("%d%d%d\n", a, b, c);
                }
            }
        }
    }
}

運行結果
在這里插入圖片描述

萬能的搜索

不撞南墻不回頭——深度優先搜索

有編號為1、2、3的3張撲克牌和編號為1、2、3的3個盒子,現需將這3張撲克牌分別放到3個盒子里,且每個盒子只能放一張撲克牌,那么有多少種不同放法?

在這里插入圖片描述
小哼走到盒子面前,每到一個新盒子按照1、2、3號撲克牌的順序來放,于是小哼將1號放入1號盒子,

在這里插入圖片描述
現手上剩下2號和3號撲克牌,于是小哼將2號撲克牌放入了2號盒子中,
在這里插入圖片描述
小哼走到3號盒子面前時,手中只有3號撲克牌了,于是只能往3號盒子里放3號撲克牌,
在這里插入圖片描述
這是產生了一種排序1 2 3,于是小哼需取回放在3號盒子里的撲克牌,看看能否產生新的排序,當取回3號盒子的撲克牌時,發現手中只有3號撲克牌,于是小哼得往回退一步,識訓2號撲克牌后,小哼手中有2號和3號撲克牌了,按規定小哼需往2號盒子放3號撲克牌,放好后來到3號盒子面前放入僅剩的2號撲克牌,這是產生了新的排序1 3 2,

#include <stdio.h>
int a[10], book[10], n;
void dfs(int step) { // step 表示現在站在第幾個盒子面前
    int i;
    if(step == n + 1) { // 站在n+1盒子前表示n個盒子已經放好撲克牌
        for(i = 1; i <= n; i++) { // 輸出排列
            printf("%d", a[i]);
        }
        printf("\n");
        return;
    }
    for(i = 1; i <= n; i++) {
        if(book[i] == 0) { // 判斷撲克牌是否在手中
            a[step] = i; // 將i號撲克牌放入第step個盒子
            book[i] = 1; // i號撲克牌已經不在手中
            dfs(step + 1); // 函式呼叫
            book[i] = 0; // 識訓撲克牌
        }
    }
    return;
}
int main() {
    scanf("%d", &n);
    dfs(1); // 站在1號盒子面前
    getchar();getchar();
    return 0;
}

總結:深度優先搜索的關鍵在于解決 “ 當下該如何做”,

深度優先搜索的基本模型

void dfs(int step) {
	判斷邊界
	嘗試每種可能 for(i = 1; i <= n; i++) {
		繼續下一步 dfs(step + 1);
	}
	回傳
}

層層遞進——廣度優先搜索

廣度優先搜索(Breadth First Search,BFS),也稱寬度優先搜索,
在這里插入圖片描述
我們使用一個二維陣列來存盤一個迷宮,最開始小哼在迷宮(1,1)處,可以往下或往右走,

深度優先搜索:先讓小哼往右走,然后一直嘗試下去,直至走不通的時候再回傳,

我們通過 ”一層一層“擴展的方法來找到終點,擴展時每發現一個點就將這個點加入佇列直到到達終點,

小哼站在(1,1)只能通過(1,2)和(2,1)兩點往下走,當小哼走到(1,2)時它接下來能到達(2,2);當小哼走到(2,1)時它接下來能到達(2,2)和(3,1),兩個都可以到達(2,2)這個點,
在這里插入圖片描述
于是當小哼在(2,2)或(3,1)時又可以往哪里走,通過(2,2)可以到(3,2)和(2,3)兩點,通過(3,1)可以到(3,2)和(4,1)兩點,于是繼續重復直到到達終點,
在這里插入圖片描述
我們用一個結構體來實作佇列來模擬這個程序,

struct note {
	int x; // 橫坐標
	int y; // 縱坐標
	int s; // 步數
};
struct note que[2501]; 
int head, tail;
int a[51][51] = {0};
int book[51][51] = {0};
head = 1;
tail = 1;
// 將(1,1)加入佇列,標記(1,1)走過
que[tail].x = 1;
que[tail].y = 1;
que[tail].s = 0;
tail++;
book[1][1] = 1;

在這里插入圖片描述
往右走到達(1,2)

tx = que[head].x;
ty = que[head].y + 1;

判斷(1,2)是否越界

if(tx < 1 || tx > n || ty < 1 || ty > m)
	continue;

判斷(1,2)是否為障礙物或已經在路徑中

if(a[tx][ty] == 0 && book[tx][ty] == 0) {

}

滿足條件(1,2)入隊,標記該店已走過

book[tx][ty] = 1;
que[tail].x = tx;
que[tail].y = ty;
que[tail].s = que[head].s + 1;
tail++;

在這里插入圖片描述
按右、下、左、上順序嘗試,將(2,1)加入佇列
在這里插入圖片描述

擴展完后將(1,1)出隊

head++;

出隊后,head指向(1,2)這個點,通過這個點可到達(2,2),于是將(2,2)加入佇列

在這里插入圖片描述
(1,2)出隊后,head指向(2,1),通過(2,1)可到達(2,2)和(3,1),于是將(3,1)入隊,
在這里插入圖片描述
于是繼續重復,直至走到終點,演算法結束,

圖的遍歷

深度和廣度優先究竟指啥

深度和廣度是針對圖的遍歷而言的,
在這里插入圖片描述
圖:一些小圓點(頂點)和連接小圓點的直線(邊)組成,
如:上圖是由(1、2、3、4、5)和5條邊(1-2、1-3、1-5、2-4、3-5)組成
我們從1號頂點開始遍歷這個圖,遍歷就是把圖的每個頂點都訪問一次,

使用深度優先會得到如下的結果

在這里插入圖片描述
深度優先搜索思想:首先以一個未被訪問過的頂點作為起始頂點,沿當前頂點的邊走到未訪問過的頂點;當沒有未訪問過的頂點時,則回到上一個頂點,繼續訪問別的頂點,直到所以的頂點都被訪問過,即沿著圖的某一條分支遍歷直到末端,然后回溯,再沿另一條繼續遍歷,直到所以頂點都被訪問過,

廣度優先搜索來遍歷這個圖
在這里插入圖片描述
廣度優先搜索遍歷圖的程序:首先以一個未被訪問過的頂點作為起始頂點,如:以1號頂點為起點,將1號頂點放入佇列中,然后將1號頂點相鄰的未訪問過的頂點(2號、3號、5號)依次放入佇列,

在這里插入圖片描述
再將2號頂點相鄰的未訪問過的頂點4放入佇列中,此時遍歷結束,
在這里插入圖片描述
廣度優先遍歷的思想:首先以一個未被訪問過的頂點作為起始頂點,訪問其所有頂點,然后對每個相鄰的頂點,訪問它們相鄰的未被訪問的頂點,直到所有頂點都被訪問過,遍歷結束,

最短路徑

只有五行的演算法——Floyd-Warshall

在這里插入圖片描述
小哼準備去城市旅游,為節省經費和方便計劃旅程,小哼希望在出發之前知道兩個城市之間的最短路程,
在這里插入圖片描述
我們用二維陣列來存盤, ∞ \infty 代表無法到達,
在這里插入圖片描述
當只允許經過1號頂點時,我們需要判斷e[i][1]+e[1][j]是否小于e[i][j],
注意:e[i][[j]代表從i號頂點到j號頂點之間的路程,

for(i = 1; i <= n; i++) {
	for(j = 1; j <= n; j++) {
		if(e[i][j] > e[i][1] + e[1][j])
			e[i][j] = e[i][1] + e[1][j];
	}
}

當只允許經過1號頂點時,任意兩點之間的最短路徑更新為:
在這里插入圖片描述
只經過1和2號頂點的情況下任意兩點之間的最短路

// 1號頂點
for(i = 1; i <= n; i++) {
	for(j = 1; j <= n; j++) {
		if(e[i][j] > e[i][1] + e[1][j])
			e[i][j] = e[i][1] + e[1][j];
	}
}
// 2號頂點
for(i = 1; i <= n; i++) {
	for(j = 1; j <= n; j++) {
		if(e[i][j] > e[i][2] + e[2][j])
			e[i][j] = e[i][2] + e[2][j];
	}
}

當允許經過1、2號頂點時,任意兩點之間的最短路程更新為:
在這里插入圖片描述
接著經過1、2、3號頂點進行中轉,任意兩點之間的最短路程更新為:
在這里插入圖片描述
允許所有頂點作為中轉,任意兩點最終的最短路程為:
在這里插入圖片描述
核心代碼

for(k = 1; k <= n; k++)
	for(i = 1; i <= n; i++)
		for(j = 1; j <= n; j++)
			if(e[i][j] > e[i][k] + e[k][j])
				e[i][j] = e[i][k] + e[k][j];

注意:Floyd-Warshall演算法不能解決帶有“負權回路”(“負權環”)的圖,因為它沒有最短路徑,如下圖

在這里插入圖片描述

Dijkstra演算法——通過邊實作松弛

指定一個點(源點)到其他各個頂點的最短路徑(單源最短路徑)
在這里插入圖片描述
二維陣列存盤頂點之間邊的關系
在這里插入圖片描述
一維陣列存盤1號頂點到其他頂點的路程
在這里插入圖片描述
此時dis陣列中的值稱為最短路程的“估計值”,當求1號頂點到其他各個頂點的最短路程,那就先找離1號頂點最近的頂點2號頂點,于是dis[2]的值從“估計值”變為了“確定值”,即1號頂點到2號頂點的最短路程就是dis[2]的值,
選擇了2號頂點后,發現有2-3和2-4這兩條邊,通過2-3這條邊時判斷dis[3]和dis[2]+dis[2][3]的大小,因為12>10所有dis[3]要更新為10,這個程序叫做“松弛”,即Dijkstra演算法的主要思想:通過“邊”來松弛1號頂點到其他各個頂點的路程,通過2-4,dis[4]的值松弛為4,
在這里插入圖片描述
最終效果
在這里插入圖片描述
這個演算法的基本思想:每次找到離源點(1號頂點)最近的一個頂點,然后以該頂點進行擴展,最終得到源點到其余所有點的最短路徑,

Bellman-Ford——解決負權邊

我們使用dis陣列記錄源點到其它各個頂點的最短路徑,通過判斷1號頂點到v[i]號頂點的距離是否小于1號頂點到u[i]號頂點加上u[i]-v[i]這條邊的值(權值w[i])來實作松弛,

for(k = 1; k <= n - 1; k++)
	for(i = 1; i <= m; i++)
		if(dis[v[i]] > dis[u[i]] + w[i])
			dis[v[i]] = dis[u[i]] + w[i]

1號頂點到其它各個頂點的最短路徑
在這里插入圖片描述
用dis陣列存盤
在這里插入圖片描述
判斷dis[3]是否大于dis[2]+2,發現松弛失敗,繼續判斷dis[2]是否大于dis[1]+(-3),發現松弛成功,繼續處理,松弛一遍后效果如下

在這里插入圖片描述
第2輪松弛
在這里插入圖片描述
小哼提出了如下的問題
在這里插入圖片描述
思考:最多只能包含n-1條邊嗎?為什么最短路徑中沒有回路?

我們知道回路分為正權回路(回路權值之和為正)和負權回路,當包含正權回路是,去掉這個回路,可以得到更短的路徑;當包含負權回路時,肯定沒有最短路徑,因此最短路徑肯定是個不包含回路的簡單路徑,

Bellman-Ford的佇列優化

每次僅對最短路程發生變化了的點的相鄰邊執行松弛操作,

在這里插入圖片描述
我們用dis陣列存盤1號頂點到其它各個頂點的最短路徑,初始值dis[1]=0,其它為 ∞ \infty ,1號頂點入隊,用陣列que及佇列頭head和尾tail來實作佇列,

在這里插入圖片描述
判斷dis[2]和dis[1]+(1$\to$2)的大小,然后將2號頂點入隊且dis[2]的值更新為2,在這里插入圖片描述
對1號頂點剩余邊進行處理

在這里插入圖片描述
1號頂點處理完畢后,將1號頂點出隊(head++),再對新隊首2號頂點進行處理時,上如dis[5]的值更新為9,但5號頂點已經在佇列中了,因此不能入隊,

在這里插入圖片描述
最終效果
在這里插入圖片描述

最短路徑演算法對比分析

在這里插入圖片描述

神奇的樹

開啟“樹”之旅

在這里插入圖片描述
上圖是一個樹,我們將其變換一下
在這里插入圖片描述
樹和圖的區別?

樹不包含回路的連通無向圖,

在這里插入圖片描述
可以看出左邊是個樹,右邊是個圖,因為左邊沒有回路,而右邊有回路,
樹的特性:

  1. 一棵樹中的任意兩個結點有且僅有唯一的一條路徑連通,
  2. 一棵樹如果有n個結點,那么它一定恰好有n-1條邊
  3. 在一棵樹中加一條邊將會構成一個回路

樹的用途:如目錄結構

在這里插入圖片描述
在這里插入圖片描述
我們將樹中的每個點稱為結點,上圖1、3結點是樹的樹根(即根結點),一棵樹只有一個根結點,父親結點稱為父結點,兒子結點稱為子結點,如果一個結點沒有子結點,那么稱為葉結點;沒有父結點,那么稱為根結點,如果一個結點既不是根結點也不是葉結點,則稱為內部結點,結點的深度是指從根到這個結點的層數(根為第一層),

在這里插入圖片描述

二叉樹

二叉樹是一種特殊的樹,二叉樹的特點是每個結點最多有兩個兒子,左邊的叫做左兒子,右邊的叫做右兒子(即每個結點最多右兩棵子樹),更加嚴格的遞回定義:二叉樹要么為空,要么右根結點、左子樹和右子樹組成,而左子樹和右子樹分別是一棵二叉樹,
在這里插入圖片描述
二叉樹中特殊的兩種二叉樹:滿二叉樹和完全二叉樹,
滿二叉樹:二叉樹中每個內部結點都有兩個兒子(滿二叉樹所有的葉結點都有相同的深度),嚴格定義:一棵深度為n且有 2 n ? 1 2^n-1 2n?1個結點的二叉樹,
在這里插入圖片描述
完全二叉樹:一棵二叉樹除了最右邊位置上有一個或幾個葉結點缺少外,其他都是豐滿的,嚴格定義:若設二叉樹的高度為n,出第n層外,其他各層(1~n-1)的結點數都達到最大個數,第n層從右向左連續缺若干結點(即如果一個結點有右子結點,那么它一定也有左子結點,

在這里插入圖片描述
在這里插入圖片描述
上圖我們發現如果完全二叉樹的一個父結點編號為k,那么左兒子編號為2k,右兒子編號為2k+1,如果知道兒子(左兒子或右兒子)的編號是x,那么它的父結點編號就是x/2(取整數),

堆——神奇的優先佇列

堆是一種特殊的完全二叉樹,如下圖

在這里插入圖片描述
上圖的二叉樹的父結點都比子結點要小,這樣的完全二叉樹稱為最小堆,反之,稱為最大堆,

假如有14個數,分別是99、5、36、7、22、17、46、12、2、19、25、28、1和92,請找出這14個數中最小的數,

for(i = 1; i <= 14; i++) {
	if(a[i] < min)
		min = a[i];
}

我們想洗掉其中最小的數,并增加一個新數23,再求這14個數中最小的一個數,
我們把14個數按最小堆的要求放入一棵完全二叉樹,如下圖,

在這里插入圖片描述
我們設存盤這個堆的陣列為h,最小數就是h[1],我們將堆頂的數洗掉,將增加的數23放到堆頂,然后我們進行調整,

在這里插入圖片描述
我們將23與2和5進行比較,選擇較小的與之交換
在這里插入圖片描述
發現還是不符合最小堆的特性,繼續向下調整
在這里插入圖片描述
最終效果
在這里插入圖片描述
我們發現我們比較了3次,就恢復了最小堆的特性,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302233.html

標籤:其他

上一篇:Dockerfile詳解與鏡像發布

下一篇:?還敢宕機,佛祖教你做人(11副圖+內附原始碼+效果展示)?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more