数据结构复习&&刷题&&java

利姆(Prim)算法

1. 最小生成树

概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树。最小生成树属于一种树形结构(树形结构是一种特殊的图),或者说是直链型结构,因为当n个点相连,且路径和最短,那么将它们相连的路一定是n-1条。

可以利用参考一个问题理解最小生成树,有n个村庄,每个村庄之间距离不同,要求村庄之间修路,每一个村庄必须与任意一个村庄联通,如何修路最省钱(修的最短)

2. 普利姆算法介绍

求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图

具体过程如下:

(1)设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合 

(2)若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1

(3)若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1

(4)重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边

#include <stdio.h>
#include <stdlib.h>
#define n 20
#define MaxNum 10000  /*定义一个最大整数*/
  
/*定义邻接矩阵类型*/
typedef int adjmatrix[n + 1][n + 1];   
typedef struct {
    int fromvex, tovex;                 //生成树的起点和终点
    int weight;                         //边的权重
} Edge;
typedef Edge *EdgeNode;                 //定义生成树的别名
int arcnum;     /*边的个数*/
  
/*建立图的邻接矩阵*/
void CreatMatrix(adjmatrix GA) {
    int i, j, k, e;
    printf("=============================\n");
    printf("图中有%d个顶点\n", n);
    for(i=1; i<=n; i++) {
        for(j=1; j<=n; j++) {
            if(i==j) {
                GA[i][j]=0;         /*对角线的值置为0*/
            } else {
                GA[i][j]=MaxNum;    /*其他位置的值置初始化为一个最大整数*/
            }
        }
    }
    printf("请输入边的个数:\n");
    scanf("%d", &arcnum);
    printf("请输入边的信息,依照起点,终点,权值的形式输入:\n");
    for(k=1; k<=arcnum; k++) {
        scanf("%d,%d,%d",&i,&j,&e);  /*读入边的信息*/
        GA[i][j]=e;
        GA[j][i]=e;
    }
}
  
/*初始化图的边集数组*/
void InitEdge(EdgeNode GE,int m) {
    int i;
    for(i=1; i<=m; i++) {
        GE[i].weight=0;
    }
}
  
/*依据图的邻接矩阵生成图的边集数组*/
void GetEdgeSet(adjmatrix GA,EdgeNode GE) {
    int i, j, k = 1;
    for(i=1; i<=n; i++) {
        for(j=i+1; j<=n; j++) {
            if(GA[i][j] !=0 && GA[i][j] != MaxNum) {
                GE[k].fromvex = i;
                GE[k].tovex = j;
                GE[k].weight = GA[i][j];
                k++;
            }
        }
    }
}
  
/*按升序排列图的边集数组*/
void SortEdge(EdgeNode GE,int m) {
    int i,j,k;
    Edge temp;
    for(i=1; i<m; i++) {
        k=i;
        for(j=i+1; j<=m; j++) {
            if(GE[k].weight > GE[j].weight) {
                k=j;
            }
        }
        if(k!=i) {
            temp = GE[i];
            GE[i]=GE[k];
            GE[k]=temp;
        }
    }
}
  
/*利用普里姆算法从初始点v出发求邻接矩阵表示的图的最小生成树*/
void Prim(adjmatrix GA,EdgeNode T) {
    int i,j,k,min,u,m,w;
    Edge temp;
    /*给T赋初值。相应为v1依次到其余各顶点的边*/
    k=1;
    for(i=1; i<=n; i++) {
        if(i!=1) {
            T[k].fromvex=1;
            T[k].tovex=i;
            T[k].weight=GA[1][i];
            k++;
        }
    }
    /*进行n-1次循环,每次求出最小生成树中的第k条边*/
    for(k=1; k<n; k++) {
        min=MaxNum;
        m=k;
        for(j=k; j<n; j++) {
            if(T[j].weight<min) {
                min=T[j].weight;
                m=j;
            }
        }
        /*把最短边对调到k-1下标位置*/ 可用swap替换
        temp=T[k];
        T[k]=T[m];
        T[m]=temp;
        /*把新增加最小生成树T中的顶点序号赋给j*/
        j=T[k].tovex;
        /*改动有关边,使T中到T外的每个顶点保持一条到眼下为止最短的边*/
        for(i=k+1; i<n; i++) {
            u=T[i].tovex;
            w=GA[j][u];
            if(w<T[i].weight) {
                T[i].weight=w;
                T[i].fromvex=j;
            }
        }
    }
}
  
/*输出边集数组的每条边*/
void OutEdge(EdgeNode GE,int e) {
    int i;
    printf("依照起点,终点。权值的形式输出的最小生成树为:\n");
    for(i=1; i<=e; i++) {
        printf("%d,%d,%d\n",GE[i].fromvex,GE[i].tovex,GE[i].weight);
    }
    printf("=============================\n");
}
  
int main() {
    adjmatrix GA;
    Edge GE[n*(n-1)/2], T[n];
    CreatMatrix(GA);
    InitEdge(GE,arcnum);
    GetEdgeSet(GA,GE);
    SortEdge(GE,arcnum);
    Prim(GA,T);
    printf("\n");
    OutEdge(T,n-1);
    return 0;
}

力扣刷题:

【本题算法】

链表。
小根堆。
【链表】

对于题目中提到的栈的操作,可以用数组的形式,也可以用链表的形式,各有优缺点。
数组可以快捷定位到某一个下标位置,但需要事先申请好内存空间。链表可以根据实际需求申请空间。本题使用链表的方式,如下图所示,链表的头节点就是栈顶。
从栈顶pop元素时,pop出来的就是头节点里面的数值,然后新的栈顶就变成了head->next。
往栈顶push新元素时,新生成一个链表节点,其next指向原有的head,然后新的head就是这个新生成的节点。


【小根堆】

题目中要求在push时,能快速找到下标最小的可push位置,然后,在这个位置的栈加入新的元素。
在pop时,能快速找到下标最大的可pop位置,然后,在这个位置的栈pop栈顶。如没有,则返回-1。
那么,我们这里使用小根堆、大根堆可以快速实现该功能。
按照题目要求,push可以采用小根堆来存储可push的下标,pop可以采用大根堆来存储可pop的下标。
但这样我得写小根堆、大根堆两种方法。可以只用小根堆,或者只用大根堆吗?答案是可以的,我们可以在存储可push的下标时,直接存储下标本身放到一个小根堆,而存储可pop的下标时,存储“一个超大常数值减去下标”的差值,这样越大的下标对应的这个差值就越小,同样可以存到小根堆里面,在使用的时候,再用这个超大常数值减去差值,就得到下标值了。
如下图所示,小根堆实际上就是一个数组,其每一个下标按照下面公式进行映射,就可以得到一棵完整二叉树,如果每一个节点,数值都小于等于其子节点(如果存在的话),那么,这棵虚拟的二叉树就是一个小根堆了。
FATHER_NODE(x) = (0 == x) ? -1 : ((x - 1) / 2);
LEFT_NODE(x) = 2 * x + 1;
RIGHT_NODE(x) = 2 * x + 2;

#define MOST_INDEX          180000
#define INVALID_VALUE       -1
#define FATHER_NODE(x)      ((0 == (x)) ? INVALID_VALUE : ((x) - 1 >> 1))
#define LEFT_NODE(x)        (((x) << 1) + 1)
#define RIGHT_NODE(x)       (((x) << 1) + 2)

/* 链表节点定义。 */
typedef struct ValListNode_s
{
    int val;
    struct ValListNode_s *next;
}
ValListNode;

/* 最小堆定义。 */
typedef struct
{
    int *array;
    int arraySize;
}
HeapNode;

/* 对象定义。 */
typedef struct
{
    int capacity;
    int maxIndex;
    int *number;
    bool *flag;
    ValListNode **head;
    HeapNode pushable;
    HeapNode popable;
}
DinnerPlates;

/* 自定义的小根堆push、pop操作函数,具体实现见下。 */
extern void heapPush(HeapNode *heap, int index);
extern void heapPop(HeapNode *heap);

/* 创建对象。 */
DinnerPlates *dinnerPlatesCreate(int capacity)
{
    DinnerPlates *obj = (DinnerPlates *)malloc(sizeof(DinnerPlates));
    obj->capacity = capacity;
    obj->maxIndex = INVALID_VALUE;
    obj->number = (int *)malloc(sizeof(int) * MOST_INDEX);
    obj->flag = (bool *)malloc(sizeof(bool) * MOST_INDEX);
    obj->head = (ValListNode **)malloc(sizeof(ValListNode *) * MOST_INDEX);
    obj->pushable.array = (int *)malloc(sizeof(int) * MOST_INDEX);
    obj->pushable.arraySize = 0;
    obj->popable.array = (int *)malloc(sizeof(int) * MOST_INDEX);
    obj->popable.arraySize = 0;
    return obj;
}

/* push操作。 */
void dinnerPlatesPush(DinnerPlates *obj, int val)
{
    int index = 0;
    ValListNode *node = NULL;
    /* 如果当前pushable为空(可能是首次使用也可能是前面几个栈已满),则开辟新索引。 */
    if(0 == obj->pushable.arraySize)
    {
        index = obj->popable.arraySize;
        /* 这里类似于游戏中往边界移动时,开辟新地图时的初始化操作。
        再未超出maxIndex的情况下,这些flag[index]、number[index]、head[index]在前面必然已经操作过了。 */
        if(obj->maxIndex < index)
        {
            obj->maxIndex = index;
            obj->flag[index] = false;
            obj->number[index] = 0;
            obj->head[index] = NULL;
        }
        /* 新获取的index加入到pushable堆中。 */
        heapPush(&obj->pushable, index);
    }
    /* 堆顶就是最小的可push下标,在此处的栈加入新元素。 */
    index = obj->pushable.array[0];
    node = (ValListNode *)malloc(sizeof(ValListNode));
    node->val = val;
    node->next = obj->head[index];
    obj->head[index] = node;
    obj->number[index]++;
    /* 新出现的栈,且在之前这个下标并未在popable中,则加入到popable中。 */
    if(1 == obj->number[index] && false == obj->flag[index])
    {
        /* 标记这个flag,并且注意,加入popable的实际是“超大常数值减去index”的差值。 */
        obj->flag[index] = true;
        heapPush(&obj->popable, MOST_INDEX - index);
    }
    /* 如果该栈已满,则后续不能再往里面push了,就从pushable中移除。 */
    if(obj->capacity == obj->number[index])
    {
        heapPop(&obj->pushable);
    }
    return;
}

/* pop操作。 */
int dinnerPlatesPop(DinnerPlates *obj)
{
    /* 返回值先初始化为无效值-1。 */
    int index = 0, val = INVALID_VALUE;
    ValListNode *node = NULL;
    /* 在popAtStack操作时,可能有空栈但对应的index未出堆的,就先借此时机出堆。 */
    while(0 < obj->popable.arraySize && 0 == obj->number[MOST_INDEX - obj->popable.array[0]])
    {
        obj->flag[MOST_INDEX - obj->popable.array[0]] = false;
        heapPop(&obj->popable);
    }
    /* 如果经过上面这一步,popable仍然非空,则可以选取其中的最大下标了。 */
    if(0 < obj->popable.arraySize)
    {
        /* 注意,用这个超大常数值减去堆顶最小值,才是真正的最大可pop下标。 */
        index = MOST_INDEX - obj->popable.array[0];
        node = obj->head[index];
        val = node->val;
        obj->head[index] = node->next;
        free(node);
        obj->number[index]--;
        /* 如果原本是满栈,现在变成了非满栈,则可以把index加入到pushable堆中了。 */
        if(obj->capacity - 1 == obj->number[index])
        {
            heapPush(&obj->pushable, index);
        }
        /* 如果变为空栈,则从popable中移除。 */
        if(0 == obj->number[index])
        {
            obj->flag[index] = false;
            heapPop(&obj->popable);
        }
    }
    return val;
}

/* popAtStack操作。 */
int dinnerPlatesPopAtStack(DinnerPlates *obj, int index)
{
    /* 返回值先初始化为无效值-1。 */
    int val = INVALID_VALUE;
    ValListNode *node = NULL;
    /* 没有超出已知地图边界,且该处非空栈的时候,可以取该处栈顶元素。 */
    if(obj->maxIndex >= index && 0 < obj->number[index])
    {
        node = obj->head[index];
        val = node->val;
        obj->head[index] = node->next;
        free(node);
        obj->number[index]--;
        /* 如果原本是满栈,现在变成了非满栈,则可以把index加入到pushable堆中了。 */
        if(obj->capacity - 1 == obj->number[index])
        {
            heapPush(&obj->pushable, index);
        }
        /* 如果变为空栈,且下标(对应差值)正好是popable的堆顶元素时,则从popable中移除。
        如果变成空栈,但下标(对应差值)不是popable堆顶元素,则先留着,后面pop操作时会找时机移除。 */
        if(0 == obj->number[index] && index == MOST_INDEX - obj->popable.array[0])
        {
            obj->flag[index] = false;
            heapPop(&obj->popable);
        }
    }
    return val;
}

/* 删除对象。 */
void dinnerPlatesFree(DinnerPlates *obj)
{
    int x = 0;
    ValListNode *node = NULL, *next = NULL;
    /* 移除剩余的栈中元素。 */
    while(obj->popable.arraySize > x)
    {
        node = obj->head[MOST_INDEX - obj->popable.array[x]];
        while(NULL != node)
        {
            next = node->next;
            free(node);
            node = next;
        }
        x++;
    }
    /* 释放对象中的各个数组空间,和对象本身。 */
    free(obj->number);
    free(obj->flag);
    free(obj->head);
    free(obj->pushable.array);
    free(obj->popable.array);
    free(obj);
    return;
}

/* 自定义的小根堆push操作。 */
void heapPush(HeapNode *heap, int index)
{
    int son = heap->arraySize, father = FATHER_NODE(son);
    /* 堆size加一。新加入的index默认放到堆尾,然后根据节点和父节点的大小关系,进行位置调整。 */
    heap->arraySize++;
    while(INVALID_VALUE != father && heap->array[father] > index)
    {
        heap->array[son] = heap->array[father];
        son = father;
        father = FATHER_NODE(son);
    }
    heap->array[son] = index;
    return;
}

/* 自定义的小根堆pop操作。 */
void heapPop(HeapNode *heap)
{
    int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0, index = 0;
    /* 堆size减一。堆顶移除后的空缺默认由原堆尾元素填补,然后根据新的节点与左右子节点的大小关系,进行位置调整。 */
    heap->arraySize--;
    index = heap->array[heap->arraySize];
    while((heap->arraySize > left && heap->array[left] < index)
        || (heap->arraySize > right && heap->array[right] < index))
    {
        son = (heap->arraySize > right && heap->array[right] < heap->array[left]) ? right : left;
        heap->array[father] = heap->array[son];
        father = son;
        left = LEFT_NODE(father);
        right = RIGHT_NODE(father);
    }
    heap->array[father] = index;
    return;
}

 java输入与输出

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

苏生十一_Nojambot

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

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

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

打赏作者

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

抵扣说明:

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

余额充值