算法設計與分析之減治法

減治法的主要思想是將一個比較複雜的原問題轉爲規模更小的子問題,然後求解子問題,最後再由子問題和原問題的關係得出原問題的解。

 

減治法主要分爲三類,一類是減一個常量(通常爲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;
}

 

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