《啊哈算法》笔记(二)

1 克鲁斯卡尔算法 -图的最小生成树:任意两点之间都有一条线路可以相通
2 普里姆算法(优化) -图的最小生成树
3 匈牙利算法 -二分图最大分配
4 主元素问题 -寻找数量超过50%的元素

1 克鲁斯卡尔算法

解决图的最小生成树问题,即任意两点之间都有一条线路可以相通
//按照边的权值进行从小到大排序,选择权值较小的,两个顶点不在同一集合内的边(即不会产生回路的边),加入到树中,直到加入了n-1条边为止
//时间复杂度为O(nlogn)

//用一个结构体存储边的关系
struct edge {
    int u;
    int v;
    int w;
};
struct edge e[10];
int n=7,m=7;
int f[7]={0},sum=0,count=0;
//快速排序
void realKuaiSu(int left,int right){
    int i,j;
    struct edge t;
    if (left > right)
    {
        return;
    }
    i = left;
    j = right;
    while (i != j)
    {
        //先从右边开始找
        while (e[j].w >= e[left].w && i<j)
        {
            j--;
        }
        //从左边开始找
        while (e[i].w >= e[left].w && i<j) {
            i++;
        }
        if (i<j)
        {
            t = e[i];
            e[i] = e[j];
            e[j] = t;
        }
    }
    //将基准数归位,将left和i互换
    t = e[left];
    e[left] = e[i];
    e[i] = t;
    //继续处理左边的
    realKuaiSu(left, i-1);
    //继续处理右边的
    realKuaiSu(i+1, right);
    return;
}
//并查集寻找祖先的函数
//擒贼先擒王原则  递归函数,不停地找根树
int get(int v){
    if (f[v] == v)
    {
        return v;
    }
    else
    {
        //路径压缩  每次在函数返回的时候,把路上遇到的树的根改为最后找到的根树,可以提高其他树找到根树的速度
        f[v] = get(f[v]);
        return f[v];
    }
}
//合并两子集合的函数
int merge(int v,int u){
    int t1,t2;
    t1 = get(v);
    t2 = get(u);
    //判断两个结点是否在同一个集合中,即是否为同一个祖先
    if (t1 != t2)
    {
        //靠左原则,左边变成右边的根数,把右边集合作为左边集合的子集合
        f[t2] = t1;
    }
    return 0;
}
//克鲁斯卡尔算法
void Kruskal(){
    int i;
    //读入顶点与边数
    scanf("%d,%d",&n,&m);
    for (i = 1; i<=m; i++)
    {
        scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
    }
    //按照权值对边进行快速排序
    realKuaiSu(1, m);
    //并查集初始化
    for (i=1; i<=n; i++)
    {
        f[i] = i;
    }
    //克鲁斯卡尔核心部分
    //先枚举每一条边
    for (i=1; i<=m; i++)
    {
        //判断一条边的两个顶点是否已经连通,即判断是否已在同一个集合中
        if (merge(e[i].u, e[i].v))
        {
            count++;
            sum = sum + e[i].w;
        }
        //选用了n-1条边之后退出循环
        if (count == n-1)
        {
            break;
        }
    }
    //sum
}

2 普里姆算法

//从点开始,在没有线连接的点之间寻找最短的线
//时间复杂度是O(n²)

void Prim(){
    //边
    int e[7][7] = {0};
    //用book来标记一个顶点是否已经加入生成树
    int book[7]={0},min=0,i=0,j=0,k=0;
    //初始化dis数组,顶点到各个顶点的距离的数组
    int dis[7] = {1,2,3,4,5,6,7};
    book[1] = 1;
    count++;
    while (count < n)
    {
        min = 999999;
        for (i = 1; i <= n; i++)
        {
            if (book[i]==0 && dis[i]<min)
            {
                min = dis[i];
                j = i;
            }
        }
        book[j] = 1;
        count++;
        sum = sum + dis[j];
        //扫描当前顶点j所有的边,以j为中间点,更新生成树到每一个非树顶点的距离
        for (k=1; k<=n; k++)
        {
            if (book[k]==0 && dis[k]>e[j][k])
            {
                dis[k] = e[j][k];
            }
        }
    }
    //sum
}

使用堆来优化

//使用堆来优化
//book数组用来记录哪些顶点已经放入生成树中
int dis[7],book[7]={0};
//h用来保存堆,pos用来存储每个顶点在堆中的位置,size为堆的大小
int h[7],pos[7],size;
//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y){
    int t = h[x];
    h[x] = h[y];
    h[y] = t;
    //更新到pos
    t = pos[h[x]];
    pos[h[x]] = pos[h[y]];
    pos[h[y]] = t;
}
//堆 向下调整函数
void down(int i){
    //flag用来标记是否需要继续向下调整
    int t,flag=0;
    while (i*2<=size && flag==0)
    {
        //比较i和它左儿子i*2在dis中的值,并用t记录值较小的结点编号
        if (dis[h[i]] > dis[h[i*2]])
        {
            t = i * 2;
        }
        else
        {
            t = i;
        }
        //如果它有右儿子,再对右儿子进行讨论
        if (i*2+1 <= size)
        {
            //如果右儿子的值更小,更新较小的结点编号
            if (dis[h[t]] > dis[h[i*2+1]])
            {
                t = i*2+1;
            }
        }
        //如果发现最小的结点编号不是自己,说明子结点中又比父结点更小的
        if (t != i)
        {
            swap(t, i);
            //更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整
            i = t;
        }
        else
        {
            //否则说明当前的父结点已经比两个子结点都要小,不需要再进行调整
            flag = 1;
        }
    }
}

//传入一个需要向上调整的结点编号i
void up(int i){
    //用来标记是否需要继续向上调整
    int flag=0;
    if (i==1)
    {
        return;
    }
    //不在堆顶,并且当前结点i的值比父结点小的时候继续向上调整
    while (i!=1 && flag==0)
    {
        //判断是否比父结点小
        if (dis[h[i]] < dis[h[i/2]])
        {
            //交换它和它父结点的位置
            swap(i, i/2);
        }
        else
        {
            //表示已经不需要调整了,当前结点的值比父结点的值要大
            flag = 1;
        }
        //更新编号i为它父结点的编号,便于下一次继续向上调整
        i = i/2;
    }
}

//从堆顶取一个元素
int pop(){
    int t = h[1];
    //将堆的最后一个点赋值到堆顶
    h[1] = h[size];
    pos[h[1]] = 1;
    //堆的元素减少1
    size--;
    //向下调整
    down(1);
    //返回堆顶点
    return t;
}
//普里姆算法
void PrimDui(){
    int n=7,m=7,i,j,k;
    //uvw要比2m的最大值大1,因为此图是无向图
    //first要比n的最大值大1,比2*m的最大值大1
    int u[19],v[19],w[19],first[7],next[19];
    //count记录生成树中顶点的个数,sum用来存储路径之和
    int count=0,sum=0;
    //用邻接表存储边
    for (i = 1; i <= n; i++)
    {
        first[i] = -1;
    }
    for (i = 1; i <= 2*m; i++)
    {
        next[i] = first[u[i]];
        first[u[i]] = i;
    }
    
    //Prim核心部分开始
    //将1号顶点加入生成树
    //用book来标记一个顶点已经加入生成树
    book[1] = 1;
    count++;
    //初始化dis数组,这里是1号顶点到其余各个顶点的初始距离
    dis[1] = 0;
    for (i = 2; i <= n; i++)
    {
        dis[i] = 999999;
    }
    k = first[1];
    while (k!=-1)
    {
        dis[v[k]] = w[k];
        k = next[k];
    }
    //初始化堆
    size = n;
    for (i = 1; i <= size; i++)
    {
        h[i] = i;
        pos[i] = i;
    }
    for (i=size/2; i>=1; i--)
    {
        down(i);
    }
    //先弹出一个堆顶元素,即1号顶点
    pop();
    while (count < n)
    {
        j = pop();
        book[j] = 1;
        count++;
        sum = sum+dis[j];
        //扫描当前顶点j所有的边,再以j为中间结点,进行松弛
        k = first[j];
        while (k != -1)
        {
            if (book[v[k]] == 0 && dis[v[k]] > w[k])
            {
                //更新距离
                dis[v[k]] = w[k];
                //对该点在堆中进行向上调整
                //存储的是顶点v[k]在堆中的位置
                up(pos[v[k]]);
            }
            k = next[k];
        }
    }
    //sum
}

//割点:在一个无向连通图中,如果删除某个顶点后,图不再连通(任意两点之间不能互相到达),称这样的顶点为割点
//割边:在一个无向连通图中,如果删除某条边后,图不再连通...
//使用邻接表来存储,时间复杂度为O(m+n)
//一个算法要选用合适的数据结构

//二分图最大分配
//二分图:如果一个图的所有顶点可以被分为x和y两个集合,并且所有边的两个顶点恰好一个属于集合x,另一个属于集合y,每个集合内的顶点没有边相连,那么就是二分图
//增加配对数称为增广路

3 匈牙利算法

求二分图的最大匹配
时间复杂度是O(nm)

int book[101];
int match[101];
int e[101][101];
int m,n;

int dfs(int u){
    for (int i=1; i<=n; i++)
    {
        if (book[i]==0 && e[u][i]==1)
        {
            //标记i已访问过
            book[i] = 1;
            //如果点未被配对或者找到了新的配对
            if (match[i]==0 || dfs(match[i]))
            {
                //更新配对关系
                match[i] = u;
                match[u] = i;
                return 1;
            }
        }
    }
    return 0;
}


void xiongYaLi(){
    //n个点,m条边
    n=10,m=10;
    //用二维数组存储
    int t1,t2;
    //配对数
    int sum = 0;
    for (int i=1; i<=m; i++)
    {
        scanf("%d%d", &t1,&t2);
        e[t1][t2] = 1;
        //无向图,反向存储一遍
        e[t2][t1] = 1;
    }
    for (int i=1; i<=n; i++)
    {
        match[i] = 0;
    }
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=n; j++)
        {
            //清空上次搜索时的标记
            book[j] = 0;
            //寻找增广路,如果找到,配对数加1
            if (dfs(i))
            {
                sum++;
            }
        }
    }
    //sum
}

4 主元素问题

//现在有一个序列,其中一个数出现的次数超过了50%,请找出这个数
//投票系统中如果有一个人的票数超过50%,这个人立即当选
//寻找多数元素,主元素问题
//在原序列中去除两个不同的元素后,那么在原序列中的多数元素在新的序列中还是多数元素。抵消法,直至剩下的元素都一样。

int candidate(int a[], int m, int n)
{
    int j = m, c = a[m], count = 1;
    
    while (j < n && count > 0)
    {
        ++ j;
        if (a[j] == c)
            ++ count;
        else
            -- count;
    }
    
    if (j == n)
        return c;
    else
        return candidate(a, j+1, n);
};
//第一遍扫描完成以后,count不等于1,所以把A[1]舍去
//如果所有的元素都已经扫描完毕并且计数器大于0,那么返回c作为多数元素的候选者
// a[1...n]
int Majority(int a[], int n)
{
    int c = candidate(a, 1, 10);
    int count = 0;
    int majority;
    
    for (int i = 1; i <= n; ++ i)
        if (a[i] == c)
            ++ count;
    
    if (n%2 == 0)
        majority = n/2 + 1;
    else
        majority = n/2;
    if (count > majority)
        return c;
    else
        return -1;
};

void duoYuanSu()
{
    int a[11];
    
    for (int i = 1; i < 11; ++ i)
        scanf("%d",a+i);
    
    printf("%d\n",Majority(a, 10));
    getchar();
    getchar();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,771评论 0 19
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,102评论 0 12
  • 课程介绍 先修课:概率统计,程序设计实习,集合论与图论 后续课:算法分析与设计,编译原理,操作系统,数据库概论,人...
    ShellyWhen阅读 2,286评论 0 3
  • 归去来兮。 1.1 说明 本篇为《挑战程序设计竞赛(第2版)》[http://www.ituring.com.cn...
    尤汐Yogy阅读 14,334评论 0 160
  • 通过流 sheet 合并单元格 普通写入string(还有write_blank, write_number等等)...
    蒋狗阅读 1,134评论 0 0