Java 算法之三色旗

本文通过解析经典算法问题‘三色旗’,探讨如何使用最少步骤完成旗子的排序。作者分享了自己的解题思路,包括理解题目、分析问题、优化算法过程,并提供了具体的Java代码实现。通过调整旗子移动策略,减少了不必要的交换次数,从而减少了总步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


最近看到这道经典的算法题,开始也没怎么想,就看了看别人写的,但是当我把他们的结果,用自己的想法画出来过后,就发现了不对,然后我看了看其它写的都是类似的写法,千篇一律的相同,然后我就自己重新写了一个算法,这也是我第一次写文章,如果有什么写的不好或者在这道算法理解上有什么问题,希望大神多多指教。

算法描述:

历史:三色旗的问题最早由E.W.Dijkstra(荷兰人)所提出,他所使用的用语为Dutch Nation Flag(荷兰国家国旗),而多数的作者则使用Three-Color Flag(三色国旗)来称之。

说明:假设有一条绳子,上面有红、白、蓝三种颜色的旗子,起初绳子上的旗子颜色并没有顺序,您希望将之分类,并排列为蓝、白、红的顺序,要如何移动次数才会最少,注意您只能在绳子上进行这个动作,而且一次只能调换两个旗子。


提示:是不是刚读完一点想法都没有,就想去csdn一下,没错是我刚开始看到这题的想法了。这时候我们在读一次题,整理一下思路。

一、解题步骤

我们先不要着急忙慌的直接就上手去看代码,先和我一起分析。

1、解读题目

从题目中我们可以大致理解到,就是对顺序不一样的三种颜色的旗子,进行分类,每一次只能交换两面旗子(可以是相邻的也可以是不相邻),我们可以把旗子理解成对1,2,3三个数的归类,把相同的数放到对应的区间。如果还是不理解我们可以画个图。

原数据:2 1 1 3 1 2
分类后:1 1 1 2 2 3

上面就是我们分类后最终达到的效果

2、解题思路

是不是感觉也还好呀,仔细一读 so easy 呐,不要忘了还有一个我们没有提到的条件,要如何移动次数才会最少.

原数据:2 1 1 3 1 2
第一次:1 2 1 3 1 2
第二次:1 1 2 3 1 2
第三次:1 1 1 3 2 2
第四次:1 1 1 2 3 2
第五次:1 1 1 2 2 3

这种是我们最常想到的但是这是步骤最少吗?显然不是。
我们想一想三个区间如果第一个区间和第三个区间排好了,不就排序完成了

原数据:2 1 1 3 1 2
第一次:1 2 1 3 1 2
第二次:1 1 2 3 1 2
第三次:1 1 1 3 2 2
第四次:1 1 1 2 2 3

很多人到这里估计就感觉最少了,但真的这样吗?显然也不是。

原数据:2 1 1 3 1 2
第一次:1 1 1 3 2 2
第二次:1 1 1 2 2 3

在这里插入图片描述
是不是突然步骤一下就减少了。
为什么会减少这么多?这也是我解决这道算法的核心,诸位别急,我慢慢道来。

既然我们都想到了,第一个区间和第三个区间排序完成就达到目的了,我们不妨在深入一点,置换的时候如果是第一区间的数我们就跳过,不是再去置换,第三区间也一样。
在这里插入图片描述

可以看到我们置换的是第二区间的 1 ,而没有去改变本来已经在第一区间的 1,这样就减少了在 本区间置换产生的"无用"步骤。
在这里插入图片描述
这样我们就可以清楚的对比,本来一步就可以达到的目的却用了三步,这样写出来的代码部分情况,可以达到步骤最少了,但是我们在细一点,如果刚好第一区间存在第三区间的值,第三区间存在第一区间的值,我们是不是可以先对这两个区间进行替换,再去第二区间查找,这样不就又可能减少一些步骤了。

原数据:3 2 1 1
第一次:1 2 3 1
第二次:1 1 3 2
第三次:1 1 2 3
这是我们刚刚算法的思路排序

原数据:3 2 1 1
第一次:1 2 1 3
第二次:1 1 2 3
这是后面的细节操作

可以看出来又少了一些步骤,这就是我们的解题思路,看到这的可以先写一写,把思路转为代码。

二、代码

代码不难,一行一行看下去就可以明白。

public class Test {

    public static void main(String[] args) {

        //三色旗
        //有一条绳子上面挂有白、红、蓝三种颜色的多面旗子,这些旗子的排列是无序的。
        //现在要将绳子上的旗子按蓝、白、红三种颜色进行归类排列,
        //但是只能在绳子上进行旗子的移动,并且每次只能调换两个旗子。
        //问如何采用最少的步骤来完成三色旗的排列呢?
        int []a = {3,2,1,1,2,2,1,2,1,3,1,2,3};
        //来记录调换次数
        int sum = 0;
        // todo 1就往前交换  3就往后交换 解题的最基本的想法
        //输出原始数据
        System.out.print("原始数据:");
        for(int i = 0 ; i < a.length ; i ++){
            System.out.print(a[i] + " ");
        }
        System.out.println();

        //记录每个数出现的次数,方便划分区间
        int one = 0 , two = 0 , three = 0;
        for(int i = 0 ; i < a.length ; i++){

            if(a[i] == 1){
                one ++;
            }else if(a[i] == 2){
                two ++;
            }else {
                three ++;
            }
        }
        System.out.println("出现1的次数:"+ one + " 出现2的次数:" + two + " 出现三的次数:" + three);
        // todo 思路
        //  划分出三个的区间界限 只需要排好第一个区间 和 最后一个区间 就可以完成排序
        //  第一个区间排序
        //  循环 one 如果不等于 1 先判断是否属于第三区间的数,如果是在判断第三区间有没有第一区间的值,有进行置换
        for(int i = 0 ; i < one ; i++){
            //循环前五个数看是否为 1 不为1就调换
            if(a[i] != 1){
                //判断是否等于3  如果等于先从第三区间找 是否有等于  这样又可以减少步骤
                if(a[i] == 3){
                    for(int k = one + two ; k < a.length ; k++){
                        int temp;
                        if(a[k] == 1){
                            temp = a[i];
                            a[i] = a[k];
                            a[k] = temp;
                            break;
                        }
                    }
                }else {
                    for (int j = one; j < a.length ; j++) {
                        int temp;
                        if (a[j] == 1) {
                            temp = a[i];
                            a[i] = a[j];
                            a[j] = temp;
                            break;
                        }
                    }
                }
                //输出每一行调换后的结果
                for(int k = 0 ; k < a.length; k++){
                    System.out.print(a[k] + " ");
                }
                System.out.println();
                //成功调换一次就记录一次
                sum ++;
            }
        }
        // 给最后一个区间进行排序
        for(int i = a.length - 1 ; i >= a.length - three ; i --){

            if(a[i] != 3){
                //方便理解选择从后面往前找,也可以选择从 1 区间结束开始查找到 3 区间之前
                for(int k = a.length - three - 1 ; k > a.length - three - two -1 ; k --){
                    int temp;
                    if(a[k] == 3){
                        temp = a[i];
                        a[i] = a[k];
                        a[k] = temp;
                        break;
                    }
                }
                for(int j = 0 ; j < a.length; j++){
                    System.out.print(a[j] + " ");
                }
                System.out.println();
                sum ++;
            }
        }
        System.out.println("进行调换的总次数: " + sum);
    }
}

结果

原始数据:3 2 1 1 2 2 1 2 1 3 1 2 3 
出现1的次数:5 出现2的次数:5 出现三的次数:3
1 2 1 1 2 2 1 2 1 3 3 2 3 
1 1 1 1 2 2 2 2 1 3 3 2 3 
1 1 1 1 1 2 2 2 2 3 3 2 3 
1 1 1 1 1 2 2 2 2 2 3 3 3 
进行调换的总次数: 4

小结

刚开始写文章,估计有很多不足的地方,欢迎大家留言或者私信我,我们共同进步,最后以上算法为作者自己的想法,可能哪里有问题或者bug,可以提出来我们大家一起优化。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值