Kruskal算法 数据结构 浙江大学 陈越、何钦铭

使用Visual Studio community 2022运行。

//虽然写起来还是比较慢的,但是,比起从头开始写快很多了。
//kruskal算法就一点点
//所以,我认为,在完成基本要求后,对于一段代码,如果可能的话,那就仔细探究这段代码的通用性,严格检查其错误,
//让这段代码变成可以快速复制到别的地方的代码。
#include <iostream>
#include <limits>
#include <memory>
#include <stdlib.h>
#include <string>
#include <vector>
using namespace std;
//我要建立一个堆结构,这个堆随本程序运行而建立,随本程序结束而消失,(或者把控制权给别的程序)。
// 这个堆不能进行复制,只能进行创建,修改,交权,删除。
// 我觉得,堆是给数字排序的,但是仅仅给数字排序没有意义。 需要给数字和某种数据类型建立联系。
 //所以参与建立堆的数据类型是带有明确的可比较的数(比如正整数)的数据类型。
// 但是我想把程序写成泛型的。 因为只要带有课比较的数的数据类型都可以建立堆。
// 我不会这样写代码。 可能这需要c++ 的泛型技术或者c里面强制更改指针类型
// 我暂时不会那样写代码。我的代码要有通用性,至少做到可以把大段代码复制过去,改改就能用于新的类型。
//是不是在数据结构这门课上写通用代码不合适啊,有专门熟悉一门编程语言的课或者书。
//Task 是通用的数据类型

typedef struct Edge Task;
//Finger的设计目的是作为堆的元素,使堆能控制int类型以外的数据。
//因为Finger是个对象,而教材上给的只是个整形,所以要给Finger加许多操作。
struct Finger
{
Finger(int c1, Task* c2) :P(c1), t(c2) {}
Finger() = default;
Finger(const Finger& f) :P(f.P), t(f.t) {}

int P;
Task* t;
//埋下隐患1 这里只给Finger的P赋值优先级
Finger& operator=(const int& i)
{
P = i;
return *this;
}
Finger& operator=(const Finger& f) {
P = f.P;
t = f.t;
return *this;
}
};
//重载了Finger的六个关系运算符。
bool operator< (const Finger& h1, const Finger& h2)
{
return h1.P < h2.P;
}
bool operator<= (const Finger& h1, const Finger& h2)
{
return h1.P <= h2.P;
}
bool operator> (const Finger& h1, const Finger& h2)
{
return h1.P > h2.P;
}
bool operator>= (const Finger& h1, const Finger& h2)
{
return h1.P >= h2.P;
}
bool operator== (const Finger& h1, const Finger& h2)
{
return h1.P == h2.P;
}
bool operator!= (const Finger& h1, const Finger& h2)
{
return h1.P != h2.P;
}


//struct Edge是Kruskal算法需要使用的类型。Edge将被构造,Finger类型指向Edge,Finger类型是堆的元素。
//Edge最后被放入vector中,作为结果。
typedef int Vertex;
struct Edge {
	Vertex v1;
	Vertex v2;
	int Weight;
	Edge() = default;
	Edge(const Edge& e1) {
		v1 = e1.v1;
		v2 = e1.v2;
		Weight = e1.Weight;
	}
};
Edge* NewEdge(Vertex v1,Vertex v2,int Weight)
{
	//学号不检查,默认为可以为0或负数
	Edge* e = new Edge;
	e->v1 = v1;
	e->v2 = v2;
	e->Weight = Weight;	
	return e;
}
bool operator==(const Edge& e1, const Edge& e2)
{
	if (e1.Weight != e1.Weight) return false;
	if (e1.v1 == e2.v1 && e1.v2 == e2.v2|| e1.v1 == e2.v2 && e2.v1 == e1.v2)return true;
}



typedef struct HNode* Heap; /* 堆的类型定义 */
typedef Finger ElementType;/*Finger是堆的元素,该元素能指向其他类型*/
struct HNode {
ElementType* Data; /* 存储元素的数组 */
int Size; /* 堆中当前元素个数 */
int Capacity; /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */
#define MAXDATA 10000 /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */
#define MINDATA -10000
MinHeap CreateHeap(int MaxSize)
{ /* 创建容量为MaxSize的空的最小堆 */

MinHeap H = (MinHeap)malloc(sizeof(struct HNode));
H->Data = (ElementType*)malloc((MaxSize + 1) * sizeof(ElementType));
H->Size = 0;
H->Capacity = MaxSize;
H->Data[0] =ElementType(-10000,NULL); /* 定义"哨兵"为x小于堆中所有可能元素的值*/
// 我 我为这个操作重载了=
return H;
}
bool IsFull(MinHeap H)
{
return (H->Size == H->Capacity);
}
bool IsEmptyH(MinHeap H) {
    return H->Size == 0;
}
bool Insert(MinHeap H, ElementType X)
{ /* 将元素X插入最小堆H,其中H->Data[0]已经定义为哨兵 */
int i;

if (IsFull(H)) {
printf("最小堆已满");
return false;
}
i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
for (; H->Data[i / 2] > X; i /= 2)
H->Data[i] = H->Data[i / 2]; /* 上滤X */
H->Data[i] = X; /* 将X插入 */
return true;
}
ElementType DelRoot(MinHeap H)
{
    //这个操作不管没有第0项的bug;
    if (IsEmptyH(H)) {
        printf("MinHeap is Empty\n");
        return H->Data[0];
    }
    ElementType anwser = H->Data[1];
    /* 用最xiao堆中最后一个元素从根结点开始向上过滤下层结点 */
    ElementType X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */
    int Parent, Child;
    for (Parent = 1; Parent * 2 <= H->Size; Parent = Child) {
        Child = Parent * 2;
        if ((Child != H->Size) && (H->Data[Child].P > H->Data[Child + 1].P))
            Child++;  /* Child指向左右子结点的较xiao者 */
        if (X.P <= H->Data[Child].P) break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;

    return anwser;
}






//20210820我要做的是简单的并查集合,只有合并和查找操作。
//用数组实现,
//数组的元素应该是个结构啊,老师给的代码怎么没有呢
//看不懂老师给的代码
//她可能给的是那个最简表示法,不要数据,既然不要数据,
//那就不应该有ElementType
//我很难看懂老师的通用化操作,以及她为了教学而简化的各种细节,
// 实际上 复杂一点反而更容易懂
//我首先不要直接追求通用化,我首先应该最求把数据结构和算法写出来,
//直接用int写最好。
/*20210906写非最简版本
*/
//我要更改Element为我需要的类型Edge
//我弄错了,并查集要存的是顶点,而不是边。两个顶点都入了集,就回路了。
/*这个并查集的Node元素的设计似乎没必要。它的Element类型没什么运用。它的功能过于简单,如果要追究它的元素应该倒过来
* 让别的数据指向这个并查集的元素。
*/
struct Node
{
    int Root = -1;
    int Element = 0;
    Node() = default;
    Node(int i1, int i2) :Root(i1), Element(i2) {}
};
int GoToRoot(int X, const vector<Node>& s1)
{
    //检查该元素是否在该集合内,因为是0~size-1与实体对应的集合,所以比较两个
    //特殊数据就可以了
    if (X > -1 && X < s1.size())
    {
        if (s1[X].Root < 0) return X;
        else
            return GoToRoot(s1[X].Root, s1);
    }
    else
        return -1;
}
int FindElement(int X, const vector<Node>& s1)
{
    //函数名不好,实际上是找到该元素的根的位置。
    for (int i = 0; i != s1.size(); ++i)
        if (s1[i].Element == X)
            return GoToRoot(i, s1);
    return -1;
}
void UnionRoot(vector<Node>& S, int Root1, int Root2)
{ /* 这里默认Root1和Root2是不同集合的根结点 */
    /* 保证小集合并入大集合 */
    if (S[Root2].Root < S[Root1].Root) { /* 如果集合2比较大 */
        S[Root2].Root += S[Root1].Root;     /* 集合1并入集合2  */
        S[Root1].Root = Root2;
    }
    else {                         /* 如果集合1比较大 */
        S[Root1].Root += S[Root2].Root;     /* 集合2并入集合1  */
        S[Root2].Root = Root1;
    }
}
//bool Union(vector<Node>& S, int X1, int X2)
//{
//    auto r1 = FindElement(X1, S);
//    auto r2 = FindElement(X2, S);
//    auto Same = r1 == r2;
//    if (!Same)
//        UnionRoot(S, r1, r2);
//    return Same;
//}
bool Union2(vector<Node>& S, int X1, int X2)
{
    auto r1 = GoToRoot(X1, S);
    auto r2 = GoToRoot(X2, S);
    auto Same = r1 == r2;
    if (!Same)
        UnionRoot(S, r1, r2);
    return Same;
}




//用邻接表存储 you权有向图。
//所以要复习一遍邻接表的构造。
//如果你要用到邻接表,你又会发现,你好像只能用动态内存来存 出 或 入 的数组。
// 我的邻接链表是出度和入度都有的。
//当你要表示有权图的时候,是不是要把权重写道listnode里面呢?有没有别的办法?
//这样权重被存储了两次。出一次,入一次。
//更新:本次要用于kruskal算法,这个算法对无向图才有效,所以,要在buildgraph上建立有向图。
//如果要提高效率,那么不应该区分出度和入度,只用一个度就够了。否则就太浪费了。
//所以,在构建Edge时只用一半就行了。

struct ListNode
{
    int vertex;
    int weight;
    struct ListNode* next;
};
struct Adjacency_List {
    int vertex_N;
    struct ListNode** point_me;
    struct ListNode** point_other;
};
typedef int Vertex;
struct Adjacency_List* set_vertex_N(struct Adjacency_List* pa, int n)
{
    //无检查,默认你输入的全部正确;
    //你可以检查一下,whether pa所指向的Adjacency_List had been initialized or whether n more than 0;
    //I just ignore it.
    //这个函数就是分配两个数组,数组里存指针,指针是链表头,
    pa->vertex_N = n;
    pa->point_me = (struct ListNode**)malloc(sizeof(struct ListNode*) * n);
    pa->point_other = (struct ListNode**)malloc(sizeof(struct ListNode*) * n);

    //因为是初始化,所以,我要清理一下,让上述数组的每个元素都是NULL
    for (int i = 0; i != n; ++i)
    {
        pa->point_me[i] = NULL;
        pa->point_other[i] = NULL;
    }
    return pa;
}
struct Adjacency_List* insert_edge(struct Adjacency_List* const pa, int vertex1, int vertex2, int weight) {
    //vertex1----> vertex2
    struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
    temp->vertex = vertex1;
    temp->next = pa->point_me[vertex2];
    temp->weight = weight;
    pa->point_me[vertex2] = temp;

    temp = (struct ListNode*)malloc(sizeof(struct ListNode));
    temp->vertex = vertex2;
    temp->next = pa->point_other[vertex1];
    temp->weight = weight;
    pa->point_other[vertex1] = temp;
    return pa;
}

void build_graph_AC(struct Adjacency_List* pa)
{
    /*这是一个无向图版本,实际上它还是有图,只不过,正反各insert一次*/
	int vertexN;
	puts("输入顶点数");
	scanf_s("%d", &vertexN);
	set_vertex_N(pa, vertexN);
	puts("输入vertex1 <----> vertex2,  权重,\n示例:0 2 100\n输入三个负数结束\n示例:-1 -1 -1");
	int vertex1, vertex2, weight;
	scanf_s("%d %d %d", &vertex1, &vertex2, &weight);
	while (vertex1 >= 0) {
		insert_edge(pa, vertex1, vertex2, weight);
		insert_edge(pa, vertex2, vertex1, weight);
		scanf_s("%d %d %d", &vertex1, &vertex2, &weight);
	}
}

//struct TwoIntP {
//    /*用于保存结果*/
//    int* p1;
//    int  size1;
//    int* p2;
//    int  size2;
//};

//void show_distance_or_path(int d[], int n) {
//    for (int i = 0; i < n; ++i) {
//        printf("%d  ", d[i]);
//    }
//    printf("\n");
//}

vector<Edge> Kruskal(const struct Adjacency_List& g)
{
    MinHeap H=CreateHeap(g.vertex_N*(g.vertex_N-1)/2);
    for (int i = 0; i != g.vertex_N; ++i)
    {
        auto* po = g.point_other[i];
        while (po)
        {
            auto v2 = po->vertex;
            if (v2>i)/*避免Graph的来回指向的冗余*/
            {
                Edge* ep = NewEdge(i, v2, po->weight);
                Insert(H, Finger(po->weight, ep));
            }
            po = po->next;
        }
    }
    vector<Node> vn;
    vn.resize(g.vertex_N);
    vector<Edge> ve;

    do
    {
        Edge e1=*(DelRoot(H).t);
        if (GoToRoot(e1.v1, vn) != GoToRoot(e1.v2, vn))
        {
            ve.push_back(e1);
            Union2(vn, e1.v1, e1.v2);
        }
    } while (ve.size() < static_cast<unsigned long long>(g.vertex_N) - 1 && !IsEmptyH(H));

    if (ve.size() < g.vertex_N - 1)
    {
        cerr << "ERROR:这个图不连通!" << endl;
    }
    return ve;
}




int main()
{
	struct Adjacency_List* pa = new Adjacency_List;
	build_graph_AC(pa);
    vector<Edge> ve = Kruskal(*pa);
    for (const auto& c : ve)
    {
        cout << "(" << c.v1 << " , " << c.v2 << ")" << "  ______路程: " << c.Weight << endl;
    }

}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值