经典常用算法/常用算法思维---附伪代码以及实现

本篇文章旨在分享一些常用算法的伪代码以及部分算法的具体实现,后面也会更新我在刷算法题中学到的或者从别的地方看到的经典算法思维

本博客并不提供算法说明,算法证明,算法分析,算法测试等内容,只提供算法的伪代码表示,在理解算法的前提下,很容易通过此文章了解并实现算法。

类别一:Sorting Algorithms(排序算法)

Bucket Sort(桶排序)

BUCKET_SORT(A)
n = A.length
let B[0...n-1] be a new array
for i = 0 to n-1
	make B[i] an empty list
for i = 1 to n
	insert A[i] to B[A[i]]
for i = 0 to n
	sort list B[i] with insertion sort
concatenate the lists B[0]...B[n] together in order

COUNTING_SORT(计数排序)

COUNTING_SORT(A,B,k)
n = A.length
let C[0...k] be a new array
for i = 0 to k
	C[i] = 0
for i = 1 to n
	C[A[i]] = C[A[i]]+1
for i = 1 to k
	C[i] = C[i-1]+C[i]
for i = n down to 1
	B[C[A[i]]] = A[i]
	C[A[i]] = C[A[i]]-1

RADIX_SORT(基数排序)

RADIX_SORT(A,d)
for i = 1 to b
	user a stable sort to sort array A on digit i

example use COUNTING_SORT:

COUNTING_SORT(A,exp)
n = A.length
let B[1...n] C[0...k] be new arrays
for i = 0 to k
	C[i] = 0
for i = 1 to n
	C[(A[i]/exp)%10] = C[(A[i]/exp)%10] +1
for i = 1 to k
	C[i] = C[i-1] + C[i]
for i = n down to 1
	B[C[(A[i])/exp]%10] = A[i]
	C[(A[i])/exp]%10] = C[(A[i])/exp]%10]-1
for i = n down to 1
	A[i] = B[i]

RADIX_SORT(A,k)
exp = 1
while (k/exp)%10!=0
	COUNTING_SORT(A,k)
	exp = exp*10

INSERT_SORT(直接插入排序)

INSERT_SORT(A)
for i = 2 to n
	key = A[i]
	j = i-1
	while j > 0 and A[j]>key
		A[j+1] = A[j]
		j = j -1
	A[j+1] = key 

C语言实现

void insert_sort(int A[], int n) {
	int i = 0, j = 0;
	for (i = 1; i < n; i++)
	{
		int key = A[i];
		for (j = i - 1; j >=0 && A[j] > key; j--)
		{
			A[j + 1] = A[j];
		}
		A[j + 1] = key;
	}
}

MERGE_SORT(归并排序)

MERGE_SORT(A,low,high)
if low<high
	mid = (low+high)/2
	MERGE_SORT(A,low,mid-1)
	MERGE_SORT(A,mid,high)
	MERGE(A,low,high,mid)

MERGE(A,low,high,mid)
let LA[1..mid-low] RA[1...high-mid+1] be new arrays
for i = 1 to mid-low
	LA[i]  = A[i+low]
for j = 1 to high-mid+1
	RA[i] = A[i+mid-1]
l = 1
r = 1
k = low
while l<=i and r <= j
	if(LA[l]>RA[r])
		A[k++] = LA[l++]
	else A[k++]  = RA[r++]
while l<=i
	A[k++] = LA[l++]
while r<=j
	A[k++] = RA[r++]

C语言实现

void merge(int A[], int low, int mid, int high)
{
	int i = 0, j = 0, k = 0;
	for (i = low; i <= high; i++)
	{
		B[i] = A[i];
	}
	for (i = k = low, j = mid+1; i <= mid && j <= high; k++)
	{
		if (B[i] < B[j]) A[k] = B[i++];
		else A[k] = B[j++];
	}
	while (j<=high)
	{
		A[k++] = B[j++];
	}
	while (i <= mid)
	{
		A[k++] = B[i++];
	}
}

void merge_sort(int A[], int low, int high)
{
	if (low < high)
	{
		int mid = (low + high) / 2;
		merge_sort(A, low, mid);
		merge_sort(A, mid + 1, high);
		merge(A, low, mid, high);
	}
}

QUICK_SORT(快速排序)

QUICK_SORT(A,p,r)
	if p<r
		q = PARTITION(A,p,r)
		QUICK_SORT(A,p,q-1)
		QUICK_SORT(A,q+1,r)

PARTITION(A,p,r)
	i = p-1
	for j = p to r-1
		if A[j]<A[r]
			i = i+1
			exchange A[j] with A[i]
	exchange A[i+1] with A[r]
	return i+1

C语言实现

int partition(int A[], int low, int high)
{
	int pivot = A[low];
	while (low < high)
	{
		while (low < high && A[high] > pivot) high--;
		A[low] = A[high];
		while (low < high && A[low] < pivot) low++;
		A[high] = A[low];
	}
	A[low] = pivot;
	return low;
}

void quick_sort(int A[], int low, int high)
{
	if (low < high)
	{
		int pivot = partition(A, low, high);
		quick_sort(A, low, pivot - 1);
		quick_sort(A, pivot + 1, high);
	}
}

HEAP_SORT(堆排序)

HEAP_SORT(A)
	BUILD_MAX_HEAP(A)
	for i = [A.length]/2 down to 1
		MAX_HEAPIFY(A,i)

MAX_HEAPIFY(A,i)
l = LEFT(i)
r = RIGHT(i)
if l <=A.heap-size and A[l]>A[i]
	largest = l
else largest  = i
if r <= A.heap-size and A[r]> A[i]
	largest = r
if largest != i
	exchange A[i] with A[largest]
	MAX_HEAPITY(A,largest)

BUILD_MAX_HEAP(A)
A.heap-size  = A.length
for i = A.heap-size/2 down to 1
	MAX_HEAPITY(A,i) 

C语言实现

void adjust_heap(int A[], int k, int len)
{
	A[0] = A[k];
	for (int i = 2*k; i <= len; i *= 2)
	{
		if (i < len && A[i] < A[i + 1])
		{
			i++;
		}
		if (A[0] >= A[i])
		{
			break;
		}
		else
		{
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
}

void build_max_heap(int A[], int len)
{
	for (int i = len / 2; i > 0; i--)
	{
		adjust_heap(A, i, len);
	}
}

void heap_sort(int A[], int len)
{
	build_max_heap(A, len);
	for (int i = len; i > 0; i--)
	{
		swap(&A[1], &A[i]);
		adjust_heap(A, 1, i - 1);
	}
}

类别二:BINARY TREE ALGORITHMS(二叉树算法)

TREE_SEARCH(二叉树搜索)

TREE_SEARCH(x,k)
while x!=NIL and k! = x.key
	if k<x.key
		x = x.left
	else x = x.right
if x!=NIL
	return x

TREE_MINIMUM(查找BST最小值)

TREE_MINIMUM(x,k)
	whiel x!= NIL and k!=x.key
		if k<x.key
			x = x.left
		else x= x.right
return x

TREE_MAXIMUM(查找BST最大值)

TREE_MAXIMUM(x)
	while x.right != NIL
		x = x.right
return x

TREE_INSERT(BST插入)

TREE_INSERT(T,z)
y = NIL
x = T.root
while x!=NIL
	y  = x
	if z.key < x.key
		x = x.left
	else x = x.right
z.p = y
if(y==NIL)
	T.root  = z
else if z.key > y.key
	y.right = z
else y.left = z

TREE_DELETE(BST删除)

TREE_DELETE(T,z)
if z.left == NIL
	TRANSPLANT(T,z,z.right)
else if z.right == NIL
	TRANSPLANT(T,z,z.left)
else y = TREE_MINIMUM(z.right)
	if y.p!=z
		TRANSPLANT(T,y,y.right)
		y.right = z.right
		y.right.p = y
	TRANSPLANT(T,z,y)
	y.left = z.left
	y.left.p = y

TRANSPLANT(T,u,v)
if u.p == NIL
	T.root = v
else if u == u.p.left
	u.p.left = v
else u.p.right = v
if v!=NIL
	v.p = u.p

Minimum spanning tree(最小生成树)

GENETRIC_MST(A,W)
A =while A doesn't form a spanning tree
	find an edge(u,v) that is safe for A
	A = A∪(u,v)
return A

MST_KRUSKAL(Kruskal生成树算法)

MST_KRUSKAL(G,w)
A =for each vertex v ∈ G.V
	MAKE SET(v)
sort the edges of G.E into nondecreasing order by weight w
for each edge (u,v) ∈ G.V taken in nondecreasing order by weight
	if FIND-SET(u) != FIND-SET(v)
		A = A∪(u,v)
		UNION(u,v)
return A

MST_PRIM(Prim生成树算法)

MST_PRIM(G,w,r)
for each u ∈ G.V
	u.key = ∞
	u.π = NIL
r.key = 0
Q = G.V
while Q != ∅
	u = EXTRACT-MIN(Q)
	for each v∈G.Adj(u)
		if v∈Q and w(u,v)<v.key
			v.π = u
			v.key = w(u,v)

类别三:Graph Algorithms(图类算法)

BFS(广度优先搜索)

BFS(G,s)
for each vertex u∈G.V-{s}
	u.color = WHITE
	u.d = ∞
	u.π = NIL
s.color = GRAY
s.d = 0
s.π = NIL
Q=ENQUEUE(Q,s)
while Q!=∅
	u = DEQUEUE(Q)
	for each v ∈ G.Adj(u)
		v.color = GRAY
		v.d = u.d + 1
		v.π = u
		ENQUEUE(Q,v)
	u.color = BLACK

DFS(深度优先搜索)

DFS(G)
for each vertex u ∈ G.V
	u.color = WHITE
	u.π  = NIL
time = 0
for each vertex u ∈ G.V
	if u.color = WHITE
		DFS-VISIT(G,u)

DFS-VISIT(G,u)
time = time +1
u.d = time
u.color = GRAY
for each v ∈ G.Adj(u)
	if v.color = WHITE
		DFS-VISIT(G, v)
u.color = BLACK
time = time +1
u.f = time

DIJKSTRA(迪杰斯特拉算法)

DIJKSTRA(G,w,s)
S =  ∅
Q = G.V
while Q!= ∅
 	u = EXTRACT-MIN(Q)
 	S = S ∪ {u};
 	for each v ∈ Q.Adj(u)
 		RELAX(u, v, w)
 		

RELAX(u,v,w)
if v.d > u.d + w(u,v)
	v.d = u.d + w(u,v)
	v.π  = u

类别四:模式匹配算法

朴素字符串匹配算法

NAIVE-STRING-MATCHER(T, P)
	n = T.length
	m = P.length
	for s = 0 to n-m
		if P[1...m] == T[s+1...s+m]
			print "Pattern occurs with shift"s

预处理时间:0
时间复杂度:O((n-m+1)*m)

Rabin-Karp算法

RABIN-KARP-MATCHER(T, P)
	n = T.length
	m = p.length
	h = d^m-1 mod q
	p = 0
	t0 = 0
	//预处理
	for i = 1 to m
		p = (d*p + P[i]) mod q
		t_0 = (d*t_0 + T[i]) mod q
	//匹配
	for s = 0 to n-m
		if p == t_s
			if P[1..m] == T[s+1..s+m]
				print "Pattern occurs with shift"s
		if s < n-m
			t_(s+1) = (d*(t_s - T[s+1]*h) + T[s+m+1])mod q

预处理时间O(m)
最坏时间复杂度O((n-m+1)*m)
一般时间复杂度O(n) + O(m(v + n/q))

基于有限状态机的字符串匹配算法

FINITE-AUTOMATION-MATCHER(T, δ, m)
	//δ为状态转移函数
	n = T.length
	q = 0
	for i = 1 to n
		q = δ(q, T[i])
		if q == m
			print "Pattern occurs with shift"s
COMPUTE-TRANSITION-FUNCTION(P,)
	//∑为字符串的字母表
	m = P.length
	for q = 0 to m
		for each character a ∈ ∑
			k = min(m+1, q+2)
			repeat
				k = k-1
			until P_k 是 P_qa的后缀
			δ(q,a) = k
	return δ

KMP 算法

KMP-MATCHER(T,P)
	n = T.length
	m = P.length
	//Π是后缀函数,Π(q) = max{k| k<q and P_k是P_q的后缀}
	Π = COMPUTE-PREFIX-FUNCTION(P)
	q = 0
	for i=1 to n
		while q > 0 and P[q+1]!=T[i]
			q = Π[q]
		if P[q+1] == T[i]
			q = q+1
		if q == m
			print "Pattern occurs with shift"s
			q = Π[q]
COMPUTE-PERFIX-FUNCTION(P)
	m = P.length
	let Π[1..n] be a new array
	Π[1] = 0
	k = 0
	for q = 2 to m
		while k > 0 and P[k+1] != P[q]
			k = Π[k]
		if P[k+1] == P[q]
			k = k+1
		Π[q] = k
	return Π

类别五:常用算法思维

递归

void function(规模){
	if base_state
		return base_result
	else function(子规模)
}

分治算法

所谓分治算法就是将整个问题分解为子问题求解,再将子问题的解合并为原问题的解
不同于动态规划算法和贪心算法,分支算法的子问题是不包含重叠子问题的,子问题的形式求解方法与原问题是一致的,只是规模不同

void devide_conquer(问题)
	if 问题规模 < 某一规模
		conquer(问题)
	子问题集合 = divide(问题)
	for each 子问题∈子问题集合
		divide_conquer(子问题)

动态规划

使用动态规划之前,首先要确定问题是否具有最优子结构性质以及无后效应,总来的说就是是否符合最优性原则

最优子结构性质指的是整体问题的最优解包含其子问题的最优解

无后效应是指从现在所在的状态往后所做的所有决策只于当前状态有关,而与当前状态之前的状态或决策无关,并不关心当前状态是怎么来的

最优性原则就是说无论初始状态或者初始决策是什么,以后的决策一定相对于初始状态构成一个最优决策序列

既然动态规划解决问题,首先要根据问题画出FSM(有限状态机),搞清楚每个状态的前一个状态是什么,什么操作会导致什么状态变化,然后根据FSM可以写出状态转移方程,用代码表示出状态转移方程即可。

dp[基本状态] = 初始值
void dynamic_programming()
	for each 状态∈状态列表
		dp[状态] = 择优(前一个状态做选择1,前一个状态做选择2....) 

贪心算法

贪心算法和动态规划算法有点像,首先都具有最优子结构性质,只不过贪心算法区别于动态规划算法的一点就是贪心算法求解的问题需要具有贪心选择性质

贪心选择性质:整体的最优解可以通过一系列局部最优选择,也就是贪心选择来获取

最优解 = 输入序列[1…n]的一个子集

void recursive_greedy(问题,选择列表, 解集)
	for each 选择∈选择列表
		if 选择满足局部最优
			解集 = 解集 ∪ 选择
			recursive_greedy(子问题, 选择列表, 解集)
		

回溯算法

void backtrack(解集, 选择序列, 选择列表)
	if 满足结束条件
		解集 = 解集 ∪ 选择序列
	for each 选择∈选择列表
		//做选择
		选择序列 = 选择序列 ∪ 选择
		//下一步
		backtrack(解集, 选择序列, 选择列表)
		//撤销选择
		选择序列 = 选择序列 - 选择

双指针

滑动窗口

void slidWindow(arr)
	init window
	int left, right
	for ;right < arr.size(); right++:
		add arr[right] to window
		while window should shrink:
			left++
			...

最小覆盖子串

string minWindow(string s, string t) {
    int n = s.length(), m = t.length();
    int left = 0, start = 0, len = n + 1, cnt = 0;
    map<char, int> needed;
    for(auto &c : t) needed[c]++;
    for(int right = 0; right < n; right++) {
        if(--needed[s[right]] >= 0) cnt++;
        while(cnt == m) {
            if(right - left + 1 < len) {
                len = right - left + 1;
                start = left;
            }
            if(++needed[s[left++]] > 0) cnt--;
        }
    }
    return len == n + 1 ? "" : s.substr(start, len);
}
  • 2
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hack Rabbit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值