基础的几种算法总结
首先是两种排序方法,归并排序和快速排序。
归并排序的思想就是分治,分而治之,分的策略是:将一个数组从中间切开,左右两部分继续对半分,直到分到只包含一个元素即可。
合的策略是:将两个各自排好序的数组合并为一个新的排好序的数组。为什么说两个数组是各自排好序的呢?从最小的单元--一个元素看起,显然是有序的。只要从一到二的过程保证有序,那么从二到四.....自然都是有序数组之间的合并。
代码如下:
#include "iostream"
using namespace std;
//[start, mid]是第一个区间;
//[start+1, end]是第二个区间
void *MergeGroups(int *a, int start, int mid, int end)
{
int *temp = new int[end - start + 1];
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end)
{
if (a[i] <= a[j])
{
temp[k++] = a[i];
i++;
}
else
{
temp[k++] = a[j];
j++;
}
}
while (i <= mid)
{
temp[k++] = a[i];
i++;
}
while (j <= end)
{
temp[k++] = a[j];
j++;
}
for (int i = 0; i < k; i++)
{
a[start + i] = temp[i];
}
delete[] temp;
}
//start 和 end均指下标,长度为n的数组,end为n-1
void MergeSort(int *a, int start, int end)
{
if (a == NULL || start >= end)
{
return;
}
int mid = (start + end) / 2;
MergeSort(a, start, mid);
MergeSort(a, mid + 1, end);
MergeGroups(a, start, mid, end);
}
int main(){
int a[] = {80,30,60,40,20,10,50,70};
int ilen = (sizeof(a)) / (sizeof(a[0]));
MergeSort(a,0,ilen-1);
for(int i=0;i<ilen;i++){
cout<<a[i]<<endl;
}
}
重点在合并这一部分,要考虑一种特殊情况,最后数组a所有的元素全被加到了结果数组上,数组b还剩下一截,这种情况怎么办?
第二个算法是快速排序。
在我看来,快速排序的思想就是冒泡排序加上归并排序。
为什么这么说呢?在选取基准点,调整基准点的位置,使其前面的数字全小于基准值,后面的数字全大于基准值。这是和冒泡排序的思想相似的,通过比较交换数字的位置,不过它的想法比较巧妙,从两个方向遍历,最终到达的效果是基准值在数组中间。
第二点,这里面又有分治的思想,就是基准点左右两部分各自看作一个新的数组继续执行前面的操作(找基准点,调整基准点的位置)
代码如下:
#include "iostream"
using namespace std;
//快速排序遍历数组,有两个方向,从左向右遍历,执行++
//操作,找比基准值大的值;从右向左遍历,执行--操作,
//找比基准值小的值。默认基准值为第一个数。
void QuickSort(int *a, int start, int end)
{
if (start >= end || a == NULL)
{
return;
}
int i = start;
int j = end;
int level = a[i];
bool right = true;
while (i < j)
{
if (right)
{
if (a[j] < level)
{
int temp = a[j];
a[j] = a[i];
a[i] = temp;
right = false;
}
else
{
j--;
}
}
else
{
if (a[i] > level)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
right = true;
}
else
{
i++;
}
}
// cout<<"level value is: "<<level<<endl;
// for (int i = 0; i < 8; i++)
// {
// cout << a[i] <<" ";
// }
// cout<<endl;
}
QuickSort(a, start, i);
QuickSort(a, i + 1, end);
}
int main()
{
int a[] = {80, 30, 60, 40, 20, 10, 50, 70};
int ilen = (sizeof(a)) / (sizeof(a[0]));
cout << ilen << endl;
QuickSort(a, 0, ilen - 1);
for (int i = 0; i < ilen; i++)
{
cout << a[i] << endl;
}
}
这里面重点是有一个bool值right来控制当前遍历的方向,right为true,代表自右向左,反之,为自左向右。第二个重点是QuickSort()代码中,一定需要判断start是否大于等于end,如果不判断的话,直接执行下面的代码,会导致内存溢出的!
接下来就是最小生成树的两个算法,kruskal算法和prim算法。
先介绍prim算法吧。prim算法是这样想的。一个图想找出其中的最小生成树。最小生成树起码是一个连通图。那么我们从一个点入手,除了这个点是我们已知的外,其他点都是一片黑暗,那么我们怎么使这个点和其他的点连通呢?我们知道从这个点到其他点有几条边。我们可以通过这几条边来联系其他的顶点。但我们的要求又是最小生成树。所有我们采用这些边里面最小的那条边。通过这个步骤,我们成功的连通了一个点。那么接下来,还是一样的想法,这两个点延伸出边连接未知的点。我们根据最小生成树的原则,继续选取最小值的边,如此往复,直到没有什么需要连通的点,我们选取的边就是最小生成树的边集。
代码如下:
#include "iostream"
using namespace std;
const int maxN = 20;
const int INF = 1 << 20;
int N = 6,M;
int edge[maxN][maxN];
int lowcost[maxN];
bool visited[maxN];
void init(){
edge[1][1]=INF; edge[1][2]=6; edge[1][3]=1; edge[1][4]=5; edge[1][5]=INF; edge[1][6]=INF;
edge[2][1]=6; edge[2][2]=INF; edge[2][3]=5; edge[2][4]=INF; edge[2][5]=3; edge[2][6]=INF;
edge[3][1]=1; edge[3][2]=5; edge[3][3]=INF; edge[3][4]=5; edge[3][5]=6; edge[3][6]=4;
edge[4][1]=5; edge[4][2]=INF; edge[4][3]=5; edge[4][4]=INF; edge[4][5]=INF; edge[4][6]=2;
edge[5][1]=INF; edge[5][2]=3; edge[5][3]=6; edge[5][4]=INF; edge[5][5]=INF; edge[5][6]=6;
edge[6][1]=INF; edge[6][2]=INF; edge[6][3]=4; edge[6][4]=2; edge[6][5]=6; edge[6][6]=INF;
}
void prim(){
int first = 1;
for(int i=1;i<=N;i++){
lowcost[i] = edge[first][i];
visited[i] = false;
}
visited[1] = true;
int sum = 0;
for(int i=1;i<N;i++){
int min = INF;
int k = 0;
for(int j=1;j<=N;j++){
if(!visited[j]&&lowcost[j]<min){
min = lowcost[j];
k = j;
}
}
visited[k] = true;
sum += min;
for(int j=1;j<=N;j++){
if(!visited[j]&&lowcost[j]>edge[k][j]){
lowcost[j] = edge[k][j];
}
}
}
cout<<sum<<endl;
}
int main(){
init();
prim();
}
这里面涉及到了一个图使用邻接矩阵来表示的知识点。
第二个最小生成树的算法就是kruskal算法。这个算法使用到了一个性质,n个顶点的最小生成树有n-1条边。结合最小生成树的原则,那么我们将图中所有的边按权值从大到小排列,取前面的n-1条边即可。这就是kruskal的思想,但是还要解决一个问题,万一前面的边形成环怎么办?我们知道树是无圈图,那么取前n-1条边就不是一个正确的选择了。那么该怎么做呢?很简单我们在从边的有序列表中遍历的时候,每取一条边,就需要判断加入这条边后会不会形成圈,如果会的话,则舍弃这条边。反之取用。这个方法就是并查集。这个数据结构有find和merge两个方法。我们只需要find方法即可。
代码如下:
#include "iostream"
using namespace std;
typedef struct Edge{
int start;
int end;
int weight;
};
void Sort_Edge(Edge* e,int start,int end){
if(e == NULL || start >= end){
return;
}
int i = start;
int j = end;
int level = e[start].weight;
bool right = true;
while(i < j){
if(right){
if(e[j].weight < level){
Edge temp = e[i];
e[i] = e[j];
e[j] = temp;
right = false;
}else{
j--;
}
}else{
if(e[i].weight > level){
Edge temp = e[j];
e[j] = e[i];
e[i] = temp;
right = true;
}else{
i++;
}
}
}
Sort_Edge(e,start,i);
Sort_Edge(e,i+1,end);
}
int parent[100];
//此处x为下标。
int Find(int* parent,int x){
while(parent[x]>=0){
x = parent[x];
}
return x;
}
void kruskal(Edge* e,int p_num,int e_num){
Sort_Edge(e,0,e_num-1);
int en = 0;
for(int i = 0;i<e_num;i++){
int start_find = Find(parent,e[i].start);
int end_find = Find(parent,e[i].end);
if(start_find != end_find){
en++;
cout<<e[i].start<<" "<<e[i].end<<" "<<e[i].weight<<endl;
if(start_find > end_find){
parent[start_find] = end_find;
}else{
parent[end_find] = start_find;
}
}
if(en>=p_num-1){
break;
}
}
}
int main(){
for(int i=0;i<100;i++){
parent[i] = -1;
}
cout<<"请输入顶点的数量和边的数量"<<endl;
int p_num,e_num;
cin>>p_num>>e_num;
cout<<"请输入边,一条边一行,格式为start end weight"<<endl;
Edge* e = new Edge[e_num];
for(int i=0;i<e_num;i++){
cin>>e[i].start>>e[i].end>>e[i].weight;
}
kruskal(e,p_num,e_num);
}
这里面表示一个图用的是边的数组。
最后一种算法是求从一定点到图其他点的最短路径。也就是dijkstra算法。
这个算法的思想和prim的想法是一样的。我们从一定点开始,通过从这一定点发射出的几条边,我们可以知道这一点到这几条边连通的点的最短距离(此问题的前提是非负边,所以直达的边就是最短路径。)其他的点,我们暂且不清楚,可以记作无穷大,而我们继续寻找最小边,每寻到一个点,这个点又存在几条向外发射的边。连接其他的点。我们可以使用这条边来更新最短路径的列表。如此,知道所有的点都加入了集合。
代码如下:
#include "iostream"
using namespace std;
const int maxN = 20;
const int INF = 1 << 20;
int N = 6, M;
int edge[maxN][maxN];
int lowcost[maxN];
bool visited[maxN];
void init()
{
edge[1][1] = INF;
edge[1][2] = 6;
edge[1][3] = 1;
edge[1][4] = 5;
edge[1][5] = INF;
edge[1][6] = INF;
edge[2][1] = 6;
edge[2][2] = INF;
edge[2][3] = 5;
edge[2][4] = INF;
edge[2][5] = 3;
edge[2][6] = INF;
edge[3][1] = 1;
edge[3][2] = 5;
edge[3][3] = INF;
edge[3][4] = 5;
edge[3][5] = 6;
edge[3][6] = 4;
edge[4][1] = 5;
edge[4][2] = INF;
edge[4][3] = 5;
edge[4][4] = INF;
edge[4][5] = INF;
edge[4][6] = 2;
edge[5][1] = INF;
edge[5][2] = 3;
edge[5][3] = 6;
edge[5][4] = INF;
edge[5][5] = INF;
edge[5][6] = 6;
edge[6][1] = INF;
edge[6][2] = INF;
edge[6][3] = 4;
edge[6][4] = 2;
edge[6][5] = 6;
edge[6][6] = INF;
}
void prim()
{
int first = 1;
for (int i = 1; i <= N; i++)
{
lowcost[i] = edge[first][i];
visited[i] = false;
}
visited[1] = true;
int sum = 0;
for (int i = 1; i < N; i++)
{
int min = INF;
int k = 0;
//此处有异议,j可否从i开始?
//不可以,此处循环是在寻找最小临界边,我们不能保证j之前的点都在新集合中。
for (int j = 1; j <= N; j++)
{
if (!visited[j] && lowcost[j] < min)
{
min = lowcost[j];
k = j;
}
}
visited[k] = true;
for (int j = 1; j <= N; j++)
{
if (!visited[j] && lowcost[k] + edge[k][j] < lowcost[j])
{
lowcost[j] = lowcost[k] + edge[k][j];
}
}
}
for(int i=1;i<=N;i++){
cout<<lowcost[i]<<" ";
}
cout<<endl;
}
int main()
{
init();
prim();
}
这里面只需要将prim的代码,中更新lowcost[j]的那个For循环改一下即可。
浙公网安备 33010602011771号