算法设计与分析之减治法

减治法的主要思想是将一个比较复杂的原问题转为规模更小的子问题,然后求解子问题,最后再由子问题和原问题的关系得出原问题的解。

 

减治法主要分为三类,一类是减一个常量(通常为1),一类是减常因子,一类是减去可变规模。

(一)减一个常量

较为典型的有两个问题,第一个是插入排序,插入排序在使用减治法时是自底向上进行的,首先从第一个元素开始,一个一个元素进行插入,知道所有元素均已排好序为止。

第二个问题是拓扑排序。拓扑排序的计算可以用两种办法,一种是使用深度优先遍历,然后将出栈结果倒着输出即可,还有一种是使用减治法将没有前驱结点的结点和边同时拿掉,知道全部拿走为止,两种办法最后得出的结果可能会根据图的不同而有所不同。

插入排序代码如下:

#include "stdafx.h"
#include<iostream>
using namespace std;

void InsertSort(int r[], int n)
{
	for (int i = 2;i < n;i++)
	{
		r[0] = r[i];
		for (int j = i - 1;r[j] > r[0];j--)
		{
			r[j + 1] = r[j];
			r[j] = r[0];
		}
	}
	for (int m = 1;m < n;m++)
	{
		cout << r[m] << "  ";
	}
}
int main()
{
	int a[10] = { 0,9,8,7,6,5,4,3,2,1 };
	InsertSort(a, 10);
	system("pause");
	return 0;
}

拓扑排序使用减治法的算法较为简单再次就不列出了,给出使用DFS的拓扑排序。此处使用邻接表来实现。

#include "stdafx.h"
#include<iostream>
using namespace std;
#define maxn 100  //最大顶点个数
int n, m;       //顶点数,边数
#define INF 9999
bool visited[maxn];     //标记顶点是否被考察,初始值为false
int parent[maxn];       //parent[]记录某结点的父亲结点,生成树,初始化为-1
int d[maxn], time, f[maxn]; //时间time初始化为0,d[]记录第一次被发现时,f[]记录结束检查时
int topoSort[maxn];
int cnt;

struct arcnode  //边结点
{
	int vertex;     //与表头结点相邻的顶点编号
	arcnode * next; //指向下一相邻接点
	arcnode() {}
	arcnode(int v) :vertex(v), next(NULL) {}
};

struct vernode      //顶点结点,为每一条邻接表的表头结点
{
	int vex;    //当前定点编号
	arcnode * firarc;   //与该顶点相连的第一个顶点组成的边
}Ver[maxn];

void Init()  //建立图的邻接表需要先初始化,建立顶点结点
{
	for (int i = 1; i <= n; i++)
	{
		Ver[i].vex = i;
		Ver[i].firarc = NULL;
	}
}

void Insert(int a, int b)   //插入以a为起点,b为终点,无权的边
{
	arcnode * q = new arcnode(b);
	if (Ver[a].firarc == NULL)
		Ver[a].firarc = q;
	else
	{
		arcnode * p = Ver[a].firarc;
		while (p->next != NULL)
			p = p->next;
		p->next = q;
	}
}


void dfs(int s)         //深度优先搜索(邻接表实现),记录时间戳,寻找最短路径
{
	//cout << s << " ";
	visited[s] = true;
	time++;
	d[s] = time;
	arcnode * p = Ver[s].firarc;
	while (p != NULL)
	{
		if (!visited[p->vertex])
		{
			parent[p->vertex] = s;
			dfs(p->vertex);
		}
		p = p->next;
	}
	time++;
	f[s] = time;
	topoSort[cnt++] = s;

}
void dfs_travel()       //遍历所有顶点,找出所有深度优先生成树,组成森林
{
	for (int i = 1; i <= n; i++)     //初始化
	{
		parent[i] = -1;
		visited[i] = false;
	}
	time = 0;
	for (int i = 1; i <= n; i++)     //遍历
		if (!visited[i])
			dfs(i);
	//cout << endl;
}
void topological_Sort()
{
	cnt = 0;
	dfs_travel();
	for (int i = cnt - 1; i >= 0; i--)
		cout << topoSort[i] << " ";
	cout << endl;
}
int main()
{
	int a, b, w;
	cout << "Enter n and m:";
	cin >> n >> m;
	Init();
	while (m--)
	{
		cin >> a >> b;       //输入起点、终点
		Insert(a, b);        //插入操作
	}
	topological_Sort();
	system("pause");
	return 0;
}

在写的时候发现了一个很好的代码,大家可以参考一下。https://github.com/destiny1020/algorithm_playground/tree/master/src/main/java/chap4

(二)减一个常量因子

主要有折半查找、两个序列中位数、假币问题这些。减一个常量因子这类问题较为简单,推荐使用递归来实现,容易想也容易实现。

折半查找代码如下:

#include"stdafx.h"
#include<iostream>
using namespace std;
int r[11] = { 10,20,66,70,80,90,100,101,108,200,500 };

int half(int low,int high)
{
	int mid;
	if (low>high)
		return -1;
	else
	{
		mid = (high + low) / 2;
		if (r[mid] == 66)
		{
			return mid;
		}
		else if (r[mid] > 66)
		{
			return half(low, mid-1);
		}
		else
		{
			return half(mid+1, high);
		}
	}
}
int main()
{
	
	int x=half(0, 10);
	if (x != -1)
		cout << "数66在序列中的位置为" << x << endl;
	else
		cout << "数66不存在" << endl;
	system("pause");
	return 0;
}

求两个序列中位数代码如下:

#include"stdafx.h"
#include<iostream>
using namespace std;
int x[5] = { 11,13,15,17,19 };
int y[5] = { 2,4,10,15,20 };
int me(int a, int b)
{
	int m, n;
	if ((b-a) % 2 == 0)
	{
		m = (b - a) / 2;
		return m+a;
	}
	else
	{
		m = (b - a) / 2 + 1;
		return m+a;
	}
}
int median(int a, int b,int c,int d)
{
	int m, n;
	m = me(a, b);
	n = me(c, d);
	if (x[m] == y[n])
		return x[m];
	else if (x[m] < y[n])
	{
		 return median(m, b, c, n);
	}
	else
	{
		return median(a, m, n, d);
	}
}
int main()
{
	int x = median(0, 4, 0, 4);
	cout << "两个序列的中位数为:" << x << endl;
	system("pause");
	return 0;
}

(三)减去规模可变的

主要有欧几里得算法,二叉查找树,选择问题这些。这一类问题每一次减去的规模都不是确定的,根据具体情况的不同减治法也会变得复杂或者简单一点。比如二叉查找树,二叉查找树不一定就是平衡二叉树,如果只有一个分支而你 要找的数恰好在叶子结点上,则会变得复杂得多。

欧几里得算法和二叉查找树较为简单不做介绍了。

选择问题主要是如何划分,此处划分的思想和快速排序类似,划分结束后根据所求元素的位置和轴值的位置进行比较如果小於则在轴值的左半部分再进行划分,否则在右边进行划分,直到找到为止。

此处给出快速排序的代码,选择问题只需在快排的基础上对位置加以比较即可。

#include"stdafx.h"
#include<iostream>
using namespace std;
int arr[5] = { 5,7,1,8,4 };

void quicksort(int arr[], int left0, int right0)
{
	int temp = 0;
	int left = left0;
	int right = right0;
	if (left <= right)
	{
		temp = arr[left];
		while (left!=right)
		{
			while (right > left&&arr[right] >= temp)
				right--;
			arr[left] = arr[right];
			while (left < right&&arr[left] <= temp)
				left++;
			arr[right] = arr[left];
		}
		arr[right] = temp;
		quicksort(arr, left0, left - 1);
		quicksort(arr, right + 1, right0);
	}
	
}
int main()
{
	
	quicksort(arr, 0, 4);
	for (int i = 0;i <= 4;i++)
	{
		cout << " " << arr[i];
	}
	system("pause");
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章