基础的几种算法总结

首先是两种排序方法,归并排序和快速排序。

归并排序的思想就是分治,分而治之,分的策略是:将一个数组从中间切开,左右两部分继续对半分,直到分到只包含一个元素即可。

合的策略是:将两个各自排好序的数组合并为一个新的排好序的数组。为什么说两个数组是各自排好序的呢?从最小的单元--一个元素看起,显然是有序的。只要从一到二的过程保证有序,那么从二到四.....自然都是有序数组之间的合并。

代码如下:

#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循环改一下即可。

猜你喜欢

转载自www.cnblogs.com/blog-lmk/p/13406744.html